clean up stale test_level files (project uses LevelRuntime now)
This commit is contained in:
@@ -1,116 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
class_name TestLevelBuilder
|
|
||||||
## Builds the test level ONLY from code — no .tscn SubResource references.
|
|
||||||
## One-click playtest: attach to TestLevel root, F5.
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
build()
|
|
||||||
|
|
||||||
func build() -> void:
|
|
||||||
_build_floor()
|
|
||||||
_build_walls()
|
|
||||||
_build_lighting()
|
|
||||||
_build_player()
|
|
||||||
_build_camera()
|
|
||||||
_build_ui()
|
|
||||||
|
|
||||||
|
|
||||||
func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
|
|
||||||
var body := StaticBody3D.new()
|
|
||||||
body.name = "Static_" + str(pos)
|
|
||||||
body.position = pos
|
|
||||||
add_child(body)
|
|
||||||
var shape := CollisionShape3D.new()
|
|
||||||
shape.shape = BoxShape3D.new()
|
|
||||||
shape.shape.size = size
|
|
||||||
body.add_child(shape)
|
|
||||||
var mesh := MeshInstance3D.new()
|
|
||||||
mesh.mesh = BoxMesh.new()
|
|
||||||
mesh.mesh.size = size
|
|
||||||
var mat := StandardMaterial3D.new()
|
|
||||||
mat.albedo_color = color
|
|
||||||
mesh.mesh.surface_set_material(0, mat)
|
|
||||||
body.add_child(mesh)
|
|
||||||
return body
|
|
||||||
|
|
||||||
|
|
||||||
func _build_floor() -> void:
|
|
||||||
_box_static(Vector3(0, -1.1, 0), Vector3(30, 0.4, 30), Color(0.35, 0.25, 0.15))
|
|
||||||
|
|
||||||
|
|
||||||
func _build_walls() -> void:
|
|
||||||
_box_static(Vector3(0, 3, -15), Vector3(30, 6, 0.4), Color(0.55, 0.45, 0.35))
|
|
||||||
_box_static(Vector3(15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
|
|
||||||
_box_static(Vector3(-15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
|
|
||||||
|
|
||||||
|
|
||||||
func _build_lighting() -> void:
|
|
||||||
var sun := DirectionalLight3D.new()
|
|
||||||
sun.name = "Sun"
|
|
||||||
sun.position = Vector3(0, 10, 0)
|
|
||||||
sun.rotation_degrees = Vector3(50, 0, 0)
|
|
||||||
sun.light_color = Color(1, 0.95, 0.85)
|
|
||||||
sun.light_energy = 1.1
|
|
||||||
sun.shadow_enabled = true
|
|
||||||
add_child(sun)
|
|
||||||
|
|
||||||
|
|
||||||
func _build_player() -> void:
|
|
||||||
var player := CharacterBody3D.new()
|
|
||||||
player.name = "Player"
|
|
||||||
player.position = Vector3(0, 1.5, 8)
|
|
||||||
add_child(player)
|
|
||||||
var shape := CollisionShape3D.new()
|
|
||||||
shape.shape = CapsuleShape3D.new()
|
|
||||||
shape.shape.radius = 0.4
|
|
||||||
shape.shape.height = 1.8
|
|
||||||
player.add_child(shape)
|
|
||||||
var sm := Node.new()
|
|
||||||
sm.name = "MovementStateMachine"
|
|
||||||
player.add_child(sm)
|
|
||||||
for state_name in ["ground", "air", "wall_run", "wall_cling", "slide", "dash"]:
|
|
||||||
var st := Node.new()
|
|
||||||
st.name = "state_" + state_name
|
|
||||||
sm.add_child(st)
|
|
||||||
var mover_script = load("res://movement/player_movement_controller.gd")
|
|
||||||
if mover_script:
|
|
||||||
player.set_script(mover_script)
|
|
||||||
# set_script after node is in tree does not auto-call _ready()
|
|
||||||
# Manually initialize the controller
|
|
||||||
if player.has_method("_ready"):
|
|
||||||
player._ready()
|
|
||||||
|
|
||||||
|
|
||||||
func _build_camera() -> void:
|
|
||||||
var cam := Camera3D.new()
|
|
||||||
cam.name = "FpsCamera"
|
|
||||||
cam.current = true
|
|
||||||
cam.position = Vector3(0, 1.7, 0)
|
|
||||||
cam.fov = 90.0
|
|
||||||
var player_node = get_node_or_null("Player")
|
|
||||||
if player_node:
|
|
||||||
player_node.add_child(cam)
|
|
||||||
else:
|
|
||||||
add_child(cam)
|
|
||||||
|
|
||||||
|
|
||||||
func _build_ui() -> void:
|
|
||||||
var canvas := CanvasLayer.new()
|
|
||||||
canvas.name = "UI"
|
|
||||||
add_child(canvas)
|
|
||||||
var speed := Label.new()
|
|
||||||
speed.name = "SpeedLabel"
|
|
||||||
speed.offset_left = 16
|
|
||||||
speed.offset_top = 16
|
|
||||||
speed.offset_right = 520
|
|
||||||
speed.offset_bottom = 48
|
|
||||||
speed.text = "Speed: 0.0 m/s | State: --- | Chain: +0%"
|
|
||||||
canvas.add_child(speed)
|
|
||||||
var help := Label.new()
|
|
||||||
help.name = "HelpLabel"
|
|
||||||
help.offset_left = 16
|
|
||||||
help.offset_top = 52
|
|
||||||
help.offset_right = 720
|
|
||||||
help.offset_bottom = 108
|
|
||||||
help.text = "WASD Move | Sprint=Shift | Slide=Ctrl+S | Double-Jump=Space twice | Dash=LShift | Wall-Jump=Jump at wall"
|
|
||||||
canvas.add_child(help)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[map]
|
|
||||||
name="Test Level"
|
|
||||||
scene_path="res://scenes/maps/test_level/test_level.tscn"
|
|
||||||
color1=Color(0.2, 0.2, 0.25, 1.0)
|
|
||||||
color2=Color(0.3, 0.5, 0.6, 1.0)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[gd_scene format=3 uid="uid://c5k51gbegvxss"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c7ltcn37gfd71" path="res://debug/test_level_builder.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="TestLevel" type="Node3D" unique_id=130984349]
|
|
||||||
script = ExtResource("1")
|
|
||||||
+11
-33
@@ -1,5 +1,5 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import os
|
import math
|
||||||
|
|
||||||
# Clear scene
|
# Clear scene
|
||||||
bpy.ops.object.select_all(action='SELECT')
|
bpy.ops.object.select_all(action='SELECT')
|
||||||
@@ -19,18 +19,13 @@ if not armatures:
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
armature = armatures[0]
|
armature = armatures[0]
|
||||||
print(f"ARMature: {armature.name}, bones: {len(armature.data.bones)}")
|
print(f"Armature: {armature.name}, bones: {len(armature.data.bones)}")
|
||||||
|
|
||||||
# The key: we need to bake the animation with HIGH FPS and LARGE motion
|
|
||||||
# so the optimizer can't strip it.
|
|
||||||
|
|
||||||
|
# Switch to pose mode
|
||||||
bpy.context.view_layer.objects.active = armature
|
bpy.context.view_layer.objects.active = armature
|
||||||
bpy.ops.object.mode_set(mode='POSE')
|
bpy.ops.object.mode_set(mode='POSE')
|
||||||
|
|
||||||
# Function to create an animation with PER-FRAME keyframes (can't be optimized away)
|
|
||||||
def create_anim_dense(name, bone_rotations_func, length_frames=60):
|
def create_anim_dense(name, bone_rotations_func, length_frames=60):
|
||||||
"""bone_rotations_func: callable(frame) -> dict of bone_name -> euler_tuple
|
|
||||||
Creates keyframes on EVERY frame to prevent Blender from optimizing."""
|
|
||||||
action = bpy.data.actions.new(name=name)
|
action = bpy.data.actions.new(name=name)
|
||||||
armature.animation_data.action = action
|
armature.animation_data.action = action
|
||||||
action.use_fake_user = True
|
action.use_fake_user = True
|
||||||
@@ -47,8 +42,6 @@ def create_anim_dense(name, bone_rotations_func, length_frames=60):
|
|||||||
|
|
||||||
return action
|
return action
|
||||||
|
|
||||||
import math
|
|
||||||
|
|
||||||
# WALK: Per-frame keyframes (60 frames, 1 second)
|
# WALK: Per-frame keyframes (60 frames, 1 second)
|
||||||
def walk_fn(frame, length):
|
def walk_fn(frame, length):
|
||||||
t = frame / length * math.pi * 2
|
t = frame / length * math.pi * 2
|
||||||
@@ -61,7 +54,7 @@ def walk_fn(frame, length):
|
|||||||
}
|
}
|
||||||
|
|
||||||
create_anim_dense("Walk", walk_fn, 60)
|
create_anim_dense("Walk", walk_fn, 60)
|
||||||
print("Walk created (dense per-frame keyframes)")
|
print("Walk created")
|
||||||
|
|
||||||
# RUN: Faster (40 frames)
|
# RUN: Faster (40 frames)
|
||||||
def run_fn(frame, length):
|
def run_fn(frame, length):
|
||||||
@@ -80,14 +73,14 @@ print("Run created")
|
|||||||
# JUMP: Single jump cycle (40 frames)
|
# JUMP: Single jump cycle (40 frames)
|
||||||
def jump_fn(frame, length):
|
def jump_fn(frame, length):
|
||||||
t = frame / length
|
t = frame / length
|
||||||
peak = max(0.0, 1.0 - abs(t - 0.5) * 3.0) # Sharp peak
|
peak = max(0.0, 1.0 - abs(t - 0.5) * 3.0)
|
||||||
return {
|
return {
|
||||||
"LeftUpperArm": (-2.5 * peak, 0, -0.3 * peak),
|
"LeftUpperArm": (-2.5 * peak, 0, -0.3 * peak),
|
||||||
"RightUpperArm": (-2.5 * peak, 0, 0.3 * peak),
|
"RightUpperArm": (-2.5 * peak, 0, 0.3 * peak),
|
||||||
"LeftUpperLeg": (-0.5 * peak, 0, 0),
|
"LeftUpperLeg": (-0.5 * peak, 0, 0),
|
||||||
"RightUpperLeg": (-0.5 * peak, 0, 0),
|
"RightUpperLeg": (-0.5 * peak, 0, 0),
|
||||||
"Spine": (0.15 * peak, 0, 0),
|
"Spine": (0.15 * peak, 0, 0),
|
||||||
"Hips": (0, 0.3 * peak, 0), # Bounce up
|
"Hips": (0, 0.3 * peak, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
create_anim_dense("Jump", jump_fn, 40)
|
create_anim_dense("Jump", jump_fn, 40)
|
||||||
@@ -107,10 +100,10 @@ def idle_fn(frame, length):
|
|||||||
create_anim_dense("Idle", idle_fn, 60)
|
create_anim_dense("Idle", idle_fn, 60)
|
||||||
print("Idle created")
|
print("Idle created")
|
||||||
|
|
||||||
# Remove all C_001 duplicates and original stripped actions
|
# Remove original stripped actions, keep only our custom ones
|
||||||
for action in list(bpy.data.actions):
|
for action in list(bpy.data.actions):
|
||||||
if action.use_fake_user:
|
if action.use_fake_user:
|
||||||
# Rename to clean names
|
# Rename Blender's auto-suffixed names
|
||||||
if action.name == "Walk.001":
|
if action.name == "Walk.001":
|
||||||
action.name = "Walk"
|
action.name = "Walk"
|
||||||
elif action.name == "Run.001":
|
elif action.name == "Run.001":
|
||||||
@@ -122,10 +115,11 @@ for action in list(bpy.data.actions):
|
|||||||
else:
|
else:
|
||||||
bpy.data.actions.remove(action)
|
bpy.data.actions.remove(action)
|
||||||
|
|
||||||
print(f"Actions: {[a.name for a in bpy.data.actions if a.use_fake_user]}")
|
print(f"Final actions: {[a.name for a in bpy.data.actions if a.use_fake_user]}")
|
||||||
|
|
||||||
# CRITICAL: Export with animation sampling at high rate to prevent optimization
|
# Export
|
||||||
bpy.context.scene.render.fps = 60
|
bpy.context.scene.render.fps = 60
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
bpy.ops.export_scene.gltf(
|
bpy.ops.export_scene.gltf(
|
||||||
filepath=output_path,
|
filepath=output_path,
|
||||||
@@ -139,20 +133,4 @@ bpy.ops.export_scene.gltf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
print(f"\nExported to {output_path}")
|
print(f"\nExported to {output_path}")
|
||||||
|
|
||||||
# Verify
|
|
||||||
bpy.ops.import_scene.gltf(filepath=output_path)
|
|
||||||
new_armatures = [obj for obj in bpy.data.objects if obj.type == 'ARMATURE']
|
|
||||||
if new_armatures:
|
|
||||||
new_ap = new_armatures[0]
|
|
||||||
new_ap.animation_data_create()
|
|
||||||
for action in bpy.data.actions:
|
|
||||||
if action.use_fake_user:
|
|
||||||
new_ap.animation_data.action = action
|
|
||||||
# Check motion
|
|
||||||
max_delta = max((action.frame_range[1] - action.frame_range[0]), 1)
|
|
||||||
# Check track count
|
|
||||||
track_count = len(action.fcurves) if hasattr(action, 'fcurves') else 0
|
|
||||||
print(f" {action.name}: {track_count} fcurves, {max_delta:.0f} frames")
|
|
||||||
|
|
||||||
print("Done!")
|
print("Done!")
|
||||||
|
|||||||
Reference in New Issue
Block a user