feat(studio,stacking,animations,collections): M16 ModBison Studio, object stacking, model animations, and poly collections for MVP 2
checkpoint / checkpoint (push) Successful in 6m32s
checkpoint / checkpoint (push) Successful in 6m32s
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
extends SceneTree
|
||||
## Smoke test for animated .glb model instantiation and clip playback (Task 38d23c9dc0cb).
|
||||
|
||||
var _failures: int = 0
|
||||
var _checks: int = 0
|
||||
|
||||
const StaticObjectNodeClass = preload("res://scripts/static_object_node.gd")
|
||||
const WorldObjectClass = preload("res://scripts/world_object.gd")
|
||||
|
||||
func _init() -> void:
|
||||
print("--- Running Smoke Test: Model Animation Playback ---")
|
||||
test_animation_clip_detection()
|
||||
test_animation_playback_and_looping()
|
||||
test_animation_speed_and_autoplay()
|
||||
test_inspector_animation_properties()
|
||||
|
||||
if _failures == 0:
|
||||
print("--- PASS: All %d model animation checks passed! ---" % _checks)
|
||||
quit(0)
|
||||
else:
|
||||
printerr("--- FAIL: %d failures out of %d checks ---" % [_failures, _checks])
|
||||
quit(1)
|
||||
|
||||
|
||||
func _check(desc: String, cond: bool) -> void:
|
||||
_checks += 1
|
||||
if cond:
|
||||
print(" ✓ %s" % desc)
|
||||
else:
|
||||
_failures += 1
|
||||
printerr(" ✗ FAIL: %s" % desc)
|
||||
|
||||
|
||||
func _create_test_animated_node(asset_id: String, properties: Dictionary = {}) -> StaticObjectNode:
|
||||
var wo := WorldObjectClass.new()
|
||||
wo.id = "test_anim_obj"
|
||||
wo.type = "prop"
|
||||
wo.asset_id = asset_id
|
||||
wo.position = Vector3(0, 0, 0)
|
||||
wo.properties = properties.duplicate(true)
|
||||
|
||||
var node = StaticObjectNodeClass.new()
|
||||
node.world_object = wo
|
||||
root.add_child(node)
|
||||
return node
|
||||
|
||||
|
||||
func test_animation_clip_detection() -> void:
|
||||
print("Testing animation clip discovery...")
|
||||
# Create a dummy node with an AnimationPlayer and animation library
|
||||
var wo := WorldObjectClass.new()
|
||||
wo.id = "mock_anim_obj"
|
||||
wo.type = "prop"
|
||||
wo.asset_id = "props/crate_small.glb"
|
||||
wo.properties = {}
|
||||
|
||||
var node = StaticObjectNodeClass.new()
|
||||
node.world_object = wo
|
||||
root.add_child(node)
|
||||
|
||||
# Manually attach an AnimationPlayer with 2 mock clips to test clip discovery
|
||||
var ap := AnimationPlayer.new()
|
||||
ap.name = "AnimationPlayer"
|
||||
var lib := AnimationLibrary.new()
|
||||
var anim_walk := Animation.new()
|
||||
anim_walk.length = 1.0
|
||||
var anim_idle := Animation.new()
|
||||
anim_idle.length = 2.0
|
||||
lib.add_animation("walk", anim_walk)
|
||||
lib.add_animation("idle", anim_idle)
|
||||
ap.add_animation_library("", lib)
|
||||
node.add_child(ap)
|
||||
|
||||
# Call _setup_animation_and_motion
|
||||
node.call("_load_mesh")
|
||||
node.call("_setup_animation_and_motion")
|
||||
|
||||
var anim_list: Array[String] = node.get_available_animations()
|
||||
_check("Found AnimationPlayer", node.get_animation_player() != null)
|
||||
_check("Discovered 2 animation clips", anim_list.size() >= 2)
|
||||
_check("Contains 'walk' clip", anim_list.has("walk"))
|
||||
_check("Contains 'idle' clip", anim_list.has("idle"))
|
||||
_check("Recorded available_animations in world_object properties",
|
||||
wo.properties.has("available_animations") and wo.properties["available_animations"].has("walk"))
|
||||
|
||||
node.queue_free()
|
||||
|
||||
|
||||
func test_animation_playback_and_looping() -> void:
|
||||
print("Testing animation clip playback & loop modes...")
|
||||
var wo := WorldObjectClass.new()
|
||||
wo.id = "playback_obj"
|
||||
wo.type = "prop"
|
||||
wo.properties = {
|
||||
"animation": {
|
||||
"clip": "spin",
|
||||
"autoplay": true,
|
||||
"loop": true,
|
||||
"speed": 1.5
|
||||
}
|
||||
}
|
||||
|
||||
var node = StaticObjectNodeClass.new()
|
||||
node.world_object = wo
|
||||
root.add_child(node)
|
||||
|
||||
var ap := AnimationPlayer.new()
|
||||
var lib := AnimationLibrary.new()
|
||||
var anim_spin := Animation.new()
|
||||
anim_spin.length = 1.0
|
||||
lib.add_animation("spin", anim_spin)
|
||||
ap.add_animation_library("", lib)
|
||||
node.add_child(ap)
|
||||
|
||||
node.call("_load_mesh")
|
||||
node.call("_setup_animation_and_motion")
|
||||
|
||||
_check("Animation 'spin' is playing automatically", ap.is_playing() and ap.current_animation == "spin")
|
||||
_check("Animation speed scale set to 1.5", is_equal_approx(ap.speed_scale, 1.5))
|
||||
_check("Animation loop mode set to LOOP_LINEAR", anim_spin.loop_mode == Animation.LOOP_LINEAR)
|
||||
|
||||
# Test stopping and manual play
|
||||
node.stop_animation()
|
||||
_check("Animation stopped", not ap.is_playing())
|
||||
|
||||
node.play_animation("spin", 2.0, false)
|
||||
_check("Animation restarted with speed 2.0", ap.is_playing() and is_equal_approx(ap.speed_scale, 2.0))
|
||||
_check("Loop mode changed to LOOP_NONE", anim_spin.loop_mode == Animation.LOOP_NONE)
|
||||
|
||||
node.queue_free()
|
||||
|
||||
|
||||
func test_animation_speed_and_autoplay() -> void:
|
||||
print("Testing autoplay disable and speed settings...")
|
||||
var wo := WorldObjectClass.new()
|
||||
wo.id = "no_autoplay_obj"
|
||||
wo.type = "prop"
|
||||
wo.properties = {
|
||||
"animation": {
|
||||
"clip": "dance",
|
||||
"autoplay": false,
|
||||
"loop": false,
|
||||
"speed": 0.5
|
||||
}
|
||||
}
|
||||
|
||||
var node = StaticObjectNodeClass.new()
|
||||
node.world_object = wo
|
||||
root.add_child(node)
|
||||
|
||||
var ap := AnimationPlayer.new()
|
||||
var lib := AnimationLibrary.new()
|
||||
var anim_dance := Animation.new()
|
||||
anim_dance.length = 2.5
|
||||
lib.add_animation("dance", anim_dance)
|
||||
ap.add_animation_library("", lib)
|
||||
node.add_child(ap)
|
||||
|
||||
node.call("_load_mesh")
|
||||
node.call("_setup_animation_and_motion")
|
||||
|
||||
_check("Animation does not autoplay when autoplay=false", not ap.is_playing())
|
||||
_check("Speed scale configured properly", is_equal_approx(ap.speed_scale, 0.5))
|
||||
|
||||
node.queue_free()
|
||||
|
||||
|
||||
func test_inspector_animation_properties() -> void:
|
||||
print("Testing dynamic property updates on StaticObjectNode...")
|
||||
var wo := WorldObjectClass.new()
|
||||
wo.id = "dynamic_anim_obj"
|
||||
wo.type = "prop"
|
||||
wo.properties = {}
|
||||
|
||||
var node = StaticObjectNodeClass.new()
|
||||
node.world_object = wo
|
||||
root.add_child(node)
|
||||
|
||||
var ap := AnimationPlayer.new()
|
||||
var lib := AnimationLibrary.new()
|
||||
var a1 := Animation.new()
|
||||
a1.length = 1.0
|
||||
var a2 := Animation.new()
|
||||
a2.length = 1.0
|
||||
lib.add_animation("run", a1)
|
||||
lib.add_animation("jump", a2)
|
||||
ap.add_animation_library("", lib)
|
||||
node.add_child(ap)
|
||||
|
||||
node.call("_load_mesh")
|
||||
node.call("_setup_animation_and_motion")
|
||||
|
||||
# Update animation config dynamically as InspectorPanel would
|
||||
node.update_animation({
|
||||
"clip": "jump",
|
||||
"autoplay": true,
|
||||
"loop": true,
|
||||
"speed": 1.25
|
||||
})
|
||||
|
||||
_check("Updated animation is 'jump'", ap.is_playing() and ap.current_animation == "jump")
|
||||
_check("Updated speed is 1.25", is_equal_approx(ap.speed_scale, 1.25))
|
||||
|
||||
node.queue_free()
|
||||
Reference in New Issue
Block a user