feat(studio,stacking,animations,collections): M16 ModBison Studio, object stacking, model animations, and poly collections for MVP 2
checkpoint / checkpoint (push) Successful in 6m32s

This commit is contained in:
2026-09-22 18:30:57 +00:00
parent 654c1ec6bf
commit 721dbe5ae7
37 changed files with 5202 additions and 17 deletions
+150 -4
View File
@@ -16,21 +16,136 @@ extends Node3D
const ScriptBehaviorClass := preload("res://scripts/scripting/script_behavior.gd")
const ScriptInterpreterNodeClass := preload("res://scripts/scripting/script_interpreter.gd")
const ObjectMotionComponentClass := preload("res://scripts/components/object_motion_component.gd")
## The world object data this node represents.
var world_object: WorldObject = null
## The loaded mesh instance (or placeholder).
var _mesh_instance: MeshInstance3D = null
var _anim_player: AnimationPlayer = null
var _motion_comp: Node = null
func _ready() -> void:
if world_object == null:
return
_load_mesh()
_setup_animation_and_motion()
_setup_behavior()
func _setup_animation_and_motion() -> void:
if world_object == null or not world_object.properties is Dictionary:
return
# 1. Animation playback setup
if _anim_player != null:
var anim_cfg = world_object.properties.get("animation", {})
var clip: String = ""
var autoplay: bool = true
var loop: bool = true
var speed: float = 1.0
if anim_cfg is Dictionary and not anim_cfg.is_empty():
clip = str(anim_cfg.get("clip", anim_cfg.get("name", "")))
autoplay = bool(anim_cfg.get("autoplay", true))
loop = bool(anim_cfg.get("loop", true))
speed = float(anim_cfg.get("speed", 1.0))
elif world_object.properties.has("active_animation"):
clip = str(world_object.properties["active_animation"])
if clip == "" and not _anim_player.get_animation_list().is_empty():
clip = _anim_player.get_animation_list()[0]
if clip != "" and _anim_player.has_animation(clip):
var anim := _anim_player.get_animation(clip)
if anim != null:
anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
_anim_player.speed_scale = speed
if autoplay:
_anim_player.play(clip)
# 2. Motion Component setup
var motion_cfg = world_object.properties.get("motion", null)
if motion_cfg == null and world_object.properties.has("motion_type"):
motion_cfg = {
"motion_type": world_object.properties.get("motion_type"),
"trigger_mode": world_object.properties.get("trigger_mode", "always"),
"rotation_speed": world_object.properties.get("rotation_speed", 45.0),
"bob_amplitude": world_object.properties.get("bob_amplitude", 0.5),
"bob_frequency": world_object.properties.get("bob_frequency", 1.0),
"waypoints": world_object.properties.get("waypoints", []),
"move_speed": world_object.properties.get("move_speed", 2.0),
}
if motion_cfg is Dictionary and not motion_cfg.is_empty() and str(motion_cfg.get("motion_type", "none")).to_lower() != "none":
if _motion_comp == null:
_motion_comp = ObjectMotionComponentClass.new()
_motion_comp.name = "ObjectMotionComponent"
add_child(_motion_comp)
_motion_comp.setup(self, motion_cfg)
elif _motion_comp != null:
_motion_comp.queue_free()
_motion_comp = null
func get_animation_player() -> AnimationPlayer:
return _anim_player
func get_available_animations() -> Array[String]:
if _anim_player != null:
var res: Array[String] = []
for anim in _anim_player.get_animation_list():
res.append(str(anim))
return res
return []
func play_animation(clip_name: String, speed: float = 1.0, loop: bool = true) -> void:
if _anim_player != null and _anim_player.has_animation(clip_name):
var anim := _anim_player.get_animation(clip_name)
if anim != null:
anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
_anim_player.speed_scale = speed
_anim_player.play(clip_name)
func stop_animation() -> void:
if _anim_player != null:
_anim_player.stop()
func get_motion_component() -> Node:
return _motion_comp
func update_animation(anim_config: Dictionary) -> void:
if world_object != null:
world_object.properties["animation"] = anim_config
_setup_animation_and_motion()
func update_motion(motion_config: Dictionary) -> void:
if world_object != null:
world_object.properties["motion"] = motion_config
_setup_animation_and_motion()
func trigger_motion(action: String = "toggle") -> void:
if _motion_comp == null:
return
match action:
"start":
_motion_comp.start_motion()
"stop":
_motion_comp.stop_motion()
"toggle":
_motion_comp.toggle_motion()
"interact":
_motion_comp.on_interact()
func _setup_behavior() -> void:
if world_object == null or not world_object.properties is Dictionary:
return
@@ -63,11 +178,42 @@ func _load_mesh() -> void:
_mesh_instance = asset_node as MeshInstance3D
else:
_mesh_instance = asset_node.find_child("*", true, false) as MeshInstance3D
return
else:
# 2. Build a stylized, recognizable 3D representation based on catalog ID / asset ID
var cat_id := _get_catalog_id()
_build_procedural_object(cat_id)
# 2. Build a stylized, recognizable 3D representation based on catalog ID / asset ID
var cat_id := _get_catalog_id()
_build_procedural_object(cat_id)
# Scan for AnimationPlayer
_anim_player = find_child("*", true, false) as AnimationPlayer
if _anim_player == null:
var players = find_children("*", "AnimationPlayer", true, false)
if not players.is_empty():
_anim_player = players[0] as AnimationPlayer
if _anim_player != null and world_object != null and world_object.properties is Dictionary:
world_object.properties["available_animations"] = Array(_anim_player.get_animation_list())
# Ensure collision body exists for physics / platform ride
_ensure_collision()
func _ensure_collision() -> void:
var sb := find_child("collision", true, false) as StaticBody3D
if sb == null:
sb = find_child("*", true, false) as StaticBody3D
if sb == null:
sb = _create_static_collision()
var col_shape := CollisionShape3D.new()
var box := BoxShape3D.new()
if _mesh_instance != null and _mesh_instance.mesh != null:
var aabb := _mesh_instance.mesh.get_aabb()
box.size = Vector3(maxf(0.2, aabb.size.x), maxf(0.2, aabb.size.y), maxf(0.2, aabb.size.z))
col_shape.position = aabb.get_center()
else:
box.size = Vector3(1.0, 1.0, 1.0)
col_shape.position = Vector3(0, 0.5, 0)
col_shape.shape = box
sb.add_child(col_shape)
add_child(sb)
func _get_catalog_id() -> String: