150 lines
3.8 KiB
GDScript
150 lines
3.8 KiB
GDScript
class_name StudioSimulationManager
|
|
extends Node
|
|
## Manages Play / Run / Stop simulation harness for ModBison Studio.
|
|
## Takes full state snapshot before play and restores on stop.
|
|
|
|
signal simulation_started(mode: String)
|
|
signal simulation_stopped
|
|
|
|
enum SimulationState { EDIT, PLAYING, PAUSED }
|
|
|
|
var state: SimulationState = SimulationState.EDIT
|
|
var _snapshot: Array[Dictionary] = []
|
|
var _spawned_avatar: Node = null
|
|
var _core: EditorCore = null
|
|
var _world_container: Node3D = null
|
|
|
|
|
|
func setup(core: EditorCore, world_container: Node3D = null) -> void:
|
|
_core = core
|
|
_world_container = world_container
|
|
|
|
|
|
func start_play(spawn_player: bool = true) -> void:
|
|
if state == SimulationState.PLAYING:
|
|
return
|
|
|
|
# 1. Capture snapshot of all objects
|
|
_capture_snapshot()
|
|
|
|
state = SimulationState.PLAYING
|
|
|
|
# 2. Spawn test avatar if requested
|
|
if spawn_player and _world_container != null:
|
|
_spawn_test_avatar()
|
|
|
|
# 3. Activate moving objects
|
|
for m in _get_moving_nodes():
|
|
if m.has_method("start_motion"):
|
|
m.start_motion()
|
|
|
|
simulation_started.emit("play" if spawn_player else "run")
|
|
|
|
|
|
func pause_simulation() -> void:
|
|
if state != SimulationState.PLAYING:
|
|
return
|
|
state = SimulationState.PAUSED
|
|
for m in _get_moving_nodes():
|
|
if m.has_method("stop_motion"):
|
|
m.stop_motion()
|
|
|
|
|
|
func resume_simulation() -> void:
|
|
if state != SimulationState.PAUSED:
|
|
return
|
|
state = SimulationState.PLAYING
|
|
for m in _get_moving_nodes():
|
|
if m.has_method("start_motion"):
|
|
m.start_motion()
|
|
|
|
|
|
func stop_simulation() -> void:
|
|
if state == SimulationState.EDIT:
|
|
return
|
|
|
|
# 1. Stop all moving objects
|
|
for m in _get_moving_nodes():
|
|
if m.has_method("stop_motion"):
|
|
m.stop_motion()
|
|
|
|
# 2. Despawn test avatar
|
|
if _spawned_avatar != null and is_instance_valid(_spawned_avatar):
|
|
_spawned_avatar.queue_free()
|
|
_spawned_avatar = null
|
|
|
|
# 3. Restore state snapshot
|
|
_restore_snapshot()
|
|
|
|
state = SimulationState.EDIT
|
|
simulation_stopped.emit()
|
|
|
|
|
|
func _get_moving_nodes() -> Array:
|
|
if is_inside_tree() and get_tree() != null:
|
|
return get_tree().get_nodes_in_group("moving_objects")
|
|
var main_loop = Engine.get_main_loop()
|
|
if main_loop is SceneTree:
|
|
return (main_loop as SceneTree).get_nodes_in_group("moving_objects")
|
|
return []
|
|
|
|
|
|
func _capture_snapshot() -> void:
|
|
_snapshot.clear()
|
|
if _core == null or _core.model == null:
|
|
return
|
|
for obj in _core.model.payload.objects:
|
|
_snapshot.append({
|
|
"id": obj.id,
|
|
"position": obj.position,
|
|
"rotation": obj.rotation,
|
|
"scale": obj.scale,
|
|
"properties": obj.properties.duplicate(true)
|
|
})
|
|
|
|
|
|
func _restore_snapshot() -> void:
|
|
if _core == null or _core.model == null:
|
|
return
|
|
for snap in _snapshot:
|
|
var obj = _core.model.find_object(snap["id"])
|
|
if obj != null:
|
|
obj.position = snap["position"]
|
|
obj.rotation = snap["rotation"]
|
|
obj.scale = snap["scale"]
|
|
obj.properties = snap["properties"].duplicate(true)
|
|
_snapshot.clear()
|
|
|
|
|
|
func _spawn_test_avatar() -> void:
|
|
var player_scene = load("res://scenes/player.tscn")
|
|
if player_scene != null:
|
|
var p = player_scene.instantiate()
|
|
p.name = "TestStudioPlayer"
|
|
p.position = Vector3(0, 2, 0)
|
|
_world_container.add_child(p)
|
|
_spawned_avatar = p
|
|
else:
|
|
var p: CharacterBody3D = CharacterBody3D.new()
|
|
p.name = "TestStudioPlayer"
|
|
p.position = Vector3(0, 2, 0)
|
|
_world_container.add_child(p)
|
|
_spawned_avatar = p
|
|
|
|
|
|
func _physics_process(_delta: float) -> void:
|
|
if state == SimulationState.PLAYING and _spawned_avatar != null and is_instance_valid(_spawned_avatar):
|
|
if _spawned_avatar is Node3D and (_spawned_avatar as Node3D).global_position.y < -30.0:
|
|
(_spawned_avatar as Node3D).global_position = Vector3(0, 1.5, 0)
|
|
if _spawned_avatar is CharacterBody3D:
|
|
(_spawned_avatar as CharacterBody3D).velocity = Vector3.ZERO
|
|
|
|
|
|
func is_playing() -> bool:
|
|
return state == SimulationState.PLAYING
|
|
|
|
|
|
func is_editing() -> bool:
|
|
return state == SimulationState.EDIT
|
|
|