Godot Version
Godot 4.3 Stable
Question
Soo i’ve been working on a 64x64 tile atmospheric system heavily inspired by the one seem in space station 13, i made it in only a few hours but i would like tips and feedback on my code to see how i can optimize it, currently i ran into the issue that the gas after a few ticks of spreading the game crashes crashes due to the crazy amount of loops
you can see the gas spreading, theres currently functions to: create gas, remove gas, move gas, spread gas, convert position to nearest “index” (basically snappedi with 64), and stuff, but the issue is that crashes
i would like help with optimizing this code as when i finish it i plan on releasing it completely for free, because its such an interesting and easy concept that ive never seen another game do except for space station 13, and i would love to see other games using the mechanic itself, it brings life and freedom to the game
anyways yapping aside, here is the project if anyone would like to test it and help me improve it
and heres the script if lazy to download and do all the stuff:
edit 1: fixed the formatting and changed to the new code
extends Node
var atmos = {
cfg = {
snap = 64,
tickrate = 1.0,
tickupdate = false,
maxgas = 10000
},
gas = {
},
gas_properties = {
"Air" = {
spread = 0.5
}
}
}
@onready var gases = $Gas
@onready var solid = $Solid
@onready var unittexture = $Unit
func _ready() -> void:
create_gas(Vector2(0,0),"Air",5000.0)
await get_tree().create_timer(atmos.cfg.tickrate).timeout
#spread_all_gas("Air",0.5)
#await get_tree().create_timer(atmos.cfg.tickrate).timeout
#spread_all_gas("Air",0.5)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if atmos.cfg.tickupdate == false:
atmos.cfg.tickupdate = true
await get_tree().create_timer(atmos.cfg.tickrate).timeout
atmos.cfg.tickupdate = false
atmostick()
func atmostick():
var agpdupe = atmos.gas_properties.duplicate(true)
for gas_prop in agpdupe:
spread_all_gas(gas_prop,atmos.gas_properties[gas_prop].spread)
agpdupe = null
atmosupdate()
func atmosupdate():
pass
for unit in gases.get_children():
unit.modulate.a = (gas_total_count(unit.name)/atmos.cfg.maxgas)
func create_gas(pos: Vector2, type: String, amount: float):
if amount <= 0:
return
var newgasindex = pos_to_nearest_index(pos)
var totalcount = gas_total_count(newgasindex)
var foundgas = find_gas(newgasindex, type)
if atmos.gas.has(newgasindex):
if atmos.gas[newgasindex].content.has(type):
if totalcount + amount > atmos.cfg.maxgas:
var subbedamount = (totalcount + amount) - atmos.cfg.maxgas
foundgas[type] = clamp(foundgas[type] + subbedamount,0,atmos.cfg.maxgas)
else:
foundgas[type] = clamp(foundgas[type] + amount,0,atmos.cfg.maxgas)
else:
foundgas[type] = clamp(amount,0,atmos.cfg.maxgas)
else:
atmos.gas[newgasindex] = {
content = {
}
}
atmos.gas[newgasindex].content[type] = amount
var newunit = unittexture.duplicate()
newunit.name = newgasindex
newunit.show()
newunit.position = pos_to_nearest_pos(pos)
newunit.modulate.a = (gas_total_count(newgasindex)/atmos.cfg.maxgas)
gases.add_child(newunit)
func spread_all_gas(type: String, multi: float):
if multi <= 0:
return
var directions = [
# up down left right
Vector2(0,64),
Vector2(0,-64),
Vector2(64,0),
Vector2(-64,0),
# diagonal
Vector2(64,64),
Vector2(-64,-64),
Vector2(-64,64),
Vector2(64,-64)
]
var ttdir = directions.size()
var atmosgas_dupe = atmos.gas.duplicate(true)
for unitindex in atmosgas_dupe:
var foundgas = find_gas(unitindex, type)
if foundgas != null:
var splitamount = (foundgas[type]/ttdir)*multi
var cpos = index_to_pos(unitindex)
for dir in directions:
#print(cpos)
var dirgas = find_gas(pos_to_nearest_index(cpos + dir), type)
if dirgas != null:
if dirgas[type] < foundgas[type]:
move_gas(cpos,cpos + dir,type,splitamount)
atmosgas_dupe = null
#print(atmos)
func move_gas(oldpos: Vector2, newpos: Vector2, type: String, amount: float):
if amount <= 0:
return
var gasindex = pos_to_nearest_index(oldpos)
var foundgas = find_gas(gasindex, type)
if foundgas != null:
var foundgasamount = foundgas[type]
if foundgasamount <= amount:
destroy_gas(oldpos, type, foundgasamount)
create_gas(newpos, type, foundgasamount)
else:
destroy_gas(oldpos, type, amount)
create_gas(newpos, type, amount)
#print(atmos)
func destroy_gas(pos: Vector2, type: String, amount: float):
if amount <= 0:
return
var gasindex = pos_to_nearest_index(pos)
var foundgas = find_gas(gasindex, type)
if foundgas != null:
foundgas[type] = clamp(foundgas[type] - amount,0,atmos.cfg.maxgas)
var findnode = gases.get_node(gasindex)
if findnode != null:
if foundgas[type] <= 0:
findnode.queue_free()
else:
findnode.modulate.a = (gas_total_count(gasindex)/atmos.cfg.maxgas)
#print(atmos)
func gas_total_count(gasindex: String):
var total_count = 0
if atmos.gas.has(gasindex):
for unit in atmos.gas[gasindex].content:
total_count += atmos.gas[gasindex].content[unit]
return total_count
func find_gas(gasindex: String, type: String):
if atmos.gas.has(gasindex):
if atmos.gas[gasindex].content.has(type):
return atmos.gas[gasindex].content
else:
return null
else:
return null
func pos_to_nearest_pos(pos: Vector2):
pos.x = snappedi(pos.x,atmos.cfg.snap)
pos.y = snappedi(pos.y,atmos.cfg.snap)
return pos
func pos_to_nearest_index(pos: Vector2):
pos.x = snappedi(pos.x,atmos.cfg.snap)
pos.y = snappedi(pos.y,atmos.cfg.snap)
return pos_to_idxformat(pos)
func pos_to_idxformat(pos: Vector2):
return "x" + str(pos.x) + " y" + str(pos.y)
func index_to_pos(index: String):
var posplit = index.split(" ", false, 2)
var newpos_x = int(posplit[0])
var newpos_y = int(posplit[1])
var newpos = Vector2(newpos_x, newpos_y)
#print(newpos)
return newpos