How can I create an atmosphere/oxygen system in my 3d space game.

Hi,

I’m currently working on a space game in unity, the game is a mix of ftl and pulsar. I want to have oxygen systems that play a role in gameplay for each room, I am still trying to decide on if the ships are random shapes and sizes or created on a tile-based grid. each ship will have multiple rooms all with their own air supply, this supply increases with an oxygen generator and decreases if there is a crew member or fire etc in the room.
_
The issue I’m having is that I am struggling to think of how to make a system (almost like space engineers and ftl) where the rooms know their pressure, fill with oxygen and if doors are open it will move around freely between them as well as draining out if it is open to the vacuum of space. I know this isn’t the best-worded question so any queries about what I mean or the system I’m looking for let me know.

Thank you for your help.

You can make this work as trigger volumes with dedicated component to represent sealed spaces. And script those changes in the environment (doors opening, fires etc) as changes to the oxygenLevel for given volumes.

Be pragmatic and make some gameplay first. Don’t make this a voxelized fluid (gas) simulation project.

preview

AtmosphericVolume.cs

using UnityEngine;

[RequireComponent( typeof(BoxCollider) )]
public class AtmosphericVolume : MonoBehaviour
{
    [Range(0,1)] public float oxygenLevel = 1;

    #if UNITY_EDITOR
    void OnValidate ()
    {
        var collider = GetComponent<BoxCollider>();
        if( !collider.isTrigger )
            Debug.LogWarning("make me a trigger human!", gameObject);
    }
    void OnDrawGizmos ()
    {
        var collider = GetComponent<BoxCollider>();
        if( collider!=null )
        {
            Gizmos.matrix = transform.localToWorldMatrix;
            Gizmos.color = oxygenLevel<1 ? Color.black : Color.white;
            Gizmos.DrawWireCube( collider.center , collider.size );
            Gizmos.color = new Color{ r=1f , g=1f , b=1f , a=0.05f };
            Gizmos.DrawCube( collider.center , collider.size );
        }
    }
    #endif
}

BreathingPawn.cs

using System.Collections.Generic;
using UnityEngine;
[RequireComponent( typeof(CapsuleCollider) )]
[RequireComponent( typeof(Rigidbody) )]
public class BreathingPawn : MonoBehaviour
{
    // note: not a List but HashSet because it prevents duplication
    HashSet<AtmosphericVolume> _oxygenSources = new HashSet<AtmosphericVolume>();
    
    [SerializeField][Range(0,1)] float _oxygen = 1f;
    [SerializeField][Min(0)] float _oxygenReplenishment = 0.05f;
    [SerializeField][Min(0)] float _oxygenConsumption = 0.05f;
    [SerializeField] AnimationCurve _oxygenLevelViability = new AnimationCurve( new Keyframe(0,0,0,0) , new Keyframe(1,1,3.3f,0) );
    const float k_breathing_tick_rate = 1f / 3f;// 3 times per second
    void OnEnable ()
    {
        // This will create a custom Update-like function that will be called repeatedly.
        // I'm not using Update because breathing don't have to be calculated every time a frame is drawn (just wasteful)
        InvokeRepeating( nameof(Tick_Breathing) , time:k_breathing_tick_rate , repeatRate:k_breathing_tick_rate );
    }
    void OnTriggerEnter ( Collider other )
    {
        var comp = other.GetComponent<AtmosphericVolume>();
        if( comp!=null )
            _oxygenSources.Add(comp);
    }
    void OnTriggerExit ( Collider other )
    {
        var comp = other.GetComponent<AtmosphericVolume>();
        if( comp!=null )
            _oxygenSources.Remove(comp);
    }
    #if UNITY_EDITOR
    void OnDrawGizmos ()
    {
        Gizmos.color = Color.white;
        Vector3 pos = transform.position;
        foreach( var source in _oxygenSources )
            Gizmos.DrawLine( pos , source.transform.position );
    }
    #endif
    void Tick_Breathing ()
    {
        // in cases when atmo volumes overlap - pick the better one:
        float oxygenAround = 0f;
        foreach( var volume in _oxygenSources )
            oxygenAround = Mathf.Max( oxygenAround , volume.oxygenLevel );
        
        // breathing:
        float oxygenIntake = _oxygenReplenishment * k_breathing_tick_rate * _oxygenLevelViability.Evaluate(oxygenAround);
        _oxygen = Mathf.Clamp01( _oxygen + oxygenIntake );

        // oxygen consumption:
        float oxygenConsumed = _oxygenConsumption * k_breathing_tick_rate * ( 1f - oxygenAround );
        _oxygen = Mathf.Clamp01( _oxygen - oxygenConsumed );
        
        if( !(_oxygen>0) )
            OnAsphyxiation();
    }
    void OnAsphyxiation ()
    {
        Debug.Log($"{gameObject.name} is x__x (asphyxiation)",gameObject);
        gameObject.SetActive( false );
    }
}