Feat/1 movement foundation #5
+14
-15
@@ -37,20 +37,20 @@ func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
|
|||||||
|
|
||||||
func _build_input_map() -> void:
|
func _build_input_map() -> void:
|
||||||
var bindings := {
|
var bindings := {
|
||||||
jump = [KEY_SPACE],
|
"jump": [KEY_SPACE],
|
||||||
sprint = [KEY_SHIFT],
|
"sprint": [KEY_SHIFT],
|
||||||
crouch = [KEY_CTRL],
|
"crouch": [KEY_CTRL],
|
||||||
dash = [KEY_SHIFT],
|
"dash": [KEY_SHIFT],
|
||||||
fire = [MOUSE_BUTTON_RIGHT, KEY_F],
|
"fire": [MOUSE_BUTTON_RIGHT, KEY_F],
|
||||||
fire_alt = [MOUSE_BUTTON_LEFT],
|
"fire_alt": [MOUSE_BUTTON_LEFT],
|
||||||
reload = [KEY_R],
|
"reload": [KEY_R],
|
||||||
interact = [KEY_E],
|
"interact": [KEY_E],
|
||||||
move_forward = [KEY_W],
|
"move_forward": [KEY_W],
|
||||||
move_back = [KEY_S],
|
"move_back": [KEY_S],
|
||||||
move_left = [KEY_A],
|
"move_left": [KEY_A],
|
||||||
move_right = [KEY_D],
|
"move_right": [KEY_D],
|
||||||
weapon_next = [MOUSE_BUTTON_WHEEL_UP],
|
"weapon_next": [MOUSE_BUTTON_WHEEL_UP],
|
||||||
weapon_prev = [MOUSE_BUTTON_WHEEL_DOWN],
|
"weapon_prev": [MOUSE_BUTTON_WHEEL_DOWN],
|
||||||
}
|
}
|
||||||
for action in bindings:
|
for action in bindings:
|
||||||
if not InputMap.has_action(action):
|
if not InputMap.has_action(action):
|
||||||
@@ -136,7 +136,6 @@ func _build_player() -> void:
|
|||||||
player.add_child(camera)
|
player.add_child(camera)
|
||||||
|
|
||||||
# set_script() does NOT call _ready(); bootstrap manually after children are in place
|
# set_script() does NOT call _ready(); bootstrap manually after children are in place
|
||||||
# IMPORTANT: player._ready() MUST run BEFORE sm._ready() so the machine has valid params
|
|
||||||
if player.has_method("_ready"):
|
if player.has_method("_ready"):
|
||||||
player._ready()
|
player._ready()
|
||||||
if sm.has_method("_ready"):
|
if sm.has_method("_ready"):
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ func _ensure_machine() -> MovementStateMachine:
|
|||||||
if is_instance_valid(_machine):
|
if is_instance_valid(_machine):
|
||||||
return _machine
|
return _machine
|
||||||
if has_node("MovementStateMachine"):
|
if has_node("MovementStateMachine"):
|
||||||
_machine = $MovementStateMachine
|
var node = $MovementStateMachine
|
||||||
if _machine is MovementStateMachine:
|
if node and node.has_method("switch_to") and node.has_method("register_chain_mechanic"):
|
||||||
|
_machine = node
|
||||||
return _machine
|
return _machine
|
||||||
_machine = null
|
_machine = null
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ func exit() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func update(delta: float) -> void:
|
func update(delta: float) -> void:
|
||||||
var sm := machine
|
var vel: Vector3 = machine.player.velocity
|
||||||
if sm and sm.player:
|
|
||||||
pass
|
|
||||||
|
|
||||||
var vel: Vector3 = sm.player.velocity # assumed present in debug
|
|
||||||
|
|
||||||
# Input direction
|
# Input direction
|
||||||
var input_v := machine.input_dir
|
var input_v := machine.input_dir
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name MovementStateMachineTest
|
class_name MovementStateMachineTest
|
||||||
|
|
||||||
## Tests for MovementStateMachine logic.
|
|
||||||
## Run: godot --headless --path . --script movement/tests/test_fsm_runner.gd --quit
|
|
||||||
|
|
||||||
var sm: MovementStateMachine
|
var sm: MovementStateMachine
|
||||||
var fake_player: CharacterBody3D
|
var fake_player: CharacterBody3D
|
||||||
var params: MovementParams
|
var params: MovementParams
|
||||||
@@ -19,6 +16,7 @@ func run_all() -> void:
|
|||||||
tests_failed = 0
|
tests_failed = 0
|
||||||
params = MovementParams.new()
|
params = MovementParams.new()
|
||||||
fake_player = CharacterBody3D.new()
|
fake_player = CharacterBody3D.new()
|
||||||
|
fake_player.set_floor_max_angle(0.01)
|
||||||
sm = MovementStateMachine.new()
|
sm = MovementStateMachine.new()
|
||||||
sm.player = fake_player
|
sm.player = fake_player
|
||||||
sm.params = params
|
sm.params = params
|
||||||
@@ -68,15 +66,14 @@ func _fresh() -> void:
|
|||||||
sm.current_jump_count = 0
|
sm.current_jump_count = 0
|
||||||
sm.wall_normal = Vector3.ZERO
|
sm.wall_normal = Vector3.ZERO
|
||||||
sm.input_dir = Vector2.ZERO
|
sm.input_dir = Vector2.ZERO
|
||||||
sm.input_jump_pressed = false
|
|
||||||
sm.input_jump_just_pressed = false
|
sm.input_jump_just_pressed = false
|
||||||
|
sm.input_jump_pressed = false
|
||||||
sm.input_sprint = false
|
sm.input_sprint = false
|
||||||
sm.input_crouch = false
|
sm.input_crouch = false
|
||||||
sm.input_dash = false
|
sm.input_dash = false
|
||||||
fake_player.velocity = Vector3.ZERO
|
fake_player.velocity = Vector3.ZERO
|
||||||
fake_player.set_floor_max_angle(0.01)
|
|
||||||
for c in sm.get_children():
|
for c in sm.get_children():
|
||||||
sm.remove_child(c)
|
sm.remove_child(c.queue_free())
|
||||||
|
|
||||||
|
|
||||||
func _expect(cond: bool, msg: String) -> void:
|
func _expect(cond: bool, msg: String) -> void:
|
||||||
@@ -92,46 +89,70 @@ func _eq(a: Variant, b: Variant, msg: String = "") -> void:
|
|||||||
# ── tests ───────────────────────────────────────────────────────────────────
|
# ── tests ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func test_states_initialize_with_ground() -> void:
|
func test_states_initialize_with_ground() -> void:
|
||||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
var g = Node.new()
|
||||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
g.name = "state_ground"
|
||||||
|
g.machine = sm
|
||||||
|
sm.add_child(g)
|
||||||
|
var a = Node.new()
|
||||||
|
a.name = "state_air"
|
||||||
|
a.machine = sm
|
||||||
|
sm.add_child(a)
|
||||||
sm.switch_to("ground")
|
sm.switch_to("ground")
|
||||||
_eq(sm.current_state, "ground")
|
_eq(sm.current_state, "ground")
|
||||||
|
|
||||||
|
|
||||||
func test_ground_transitions_to_air_on_jump() -> void:
|
func test_ground_transitions_to_air_on_jump() -> void:
|
||||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
var g = Node.new()
|
||||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
g.name = "state_ground"
|
||||||
|
g.machine = sm
|
||||||
|
sm.add_child(g)
|
||||||
|
var a = Node.new()
|
||||||
|
a.name = "state_air"
|
||||||
|
a.machine = sm
|
||||||
|
sm.add_child(a)
|
||||||
sm.on_ground = true
|
sm.on_ground = true
|
||||||
sm.switch_to("air")
|
sm.switch_to("air")
|
||||||
_eq(sm.current_state, "air")
|
_eq(sm.current_state, "air")
|
||||||
|
|
||||||
|
|
||||||
func test_air_transitions_to_ground_on_land() -> void:
|
func test_air_transitions_to_ground_on_land() -> void:
|
||||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
var a = Node.new()
|
||||||
|
a.name = "state_air"
|
||||||
|
a.machine = sm
|
||||||
|
sm.add_child(a)
|
||||||
sm.switch_to("ground")
|
sm.switch_to("ground")
|
||||||
sm.on_ground = false
|
sm.on_ground = false
|
||||||
fake_player.velocity = Vector3.ZERO
|
fake_player.velocity = Vector3.ZERO
|
||||||
fake_player.set_floor_max_angle(0.01)
|
|
||||||
sm.switch_to("air")
|
sm.switch_to("air")
|
||||||
fake_player.velocity = Vector3.ZERO
|
fake_player.velocity = Vector3.ZERO
|
||||||
fake_player.set_floor_max_angle(0.01)
|
|
||||||
sm.switch_to("ground")
|
sm.switch_to("ground")
|
||||||
_eq(sm.current_state, "ground")
|
_eq(sm.current_state, "ground")
|
||||||
|
|
||||||
|
|
||||||
func test_double_jump_allowed_once() -> void:
|
func test_double_jump_allowed_once() -> void:
|
||||||
params.double_jump_max_count = 1
|
params.double_jump_max_count = 1
|
||||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
var a = Node.new()
|
||||||
|
a.name = "state_air"
|
||||||
|
a.machine = sm
|
||||||
|
sm.add_child(a)
|
||||||
sm.on_ground = false
|
sm.on_ground = false
|
||||||
sm.current_jump_count = 1
|
sm.current_jump_count = 1
|
||||||
_eq(sm.current_jump_count, 1)
|
_eq(sm.current_jump_count, 1)
|
||||||
_expect(sm.current_jump_count < params.double_jump_max_count + 1,
|
_expect(
|
||||||
"should allow double jump once")
|
sm.current_jump_count < params.double_jump_max_count + 1,
|
||||||
|
"should allow double jump once"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_wall_run_started_when_near_wall() -> void:
|
func test_wall_run_started_when_near_wall() -> void:
|
||||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
var g = Node.new()
|
||||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
g.name = "state_ground"
|
||||||
|
g.machine = sm
|
||||||
|
sm.add_child(g)
|
||||||
|
var a = Node.new()
|
||||||
|
a.name = "state_air"
|
||||||
|
a.machine = sm
|
||||||
|
sm.add_child(a)
|
||||||
sm.wall_normal = Vector3.RIGHT.normalized()
|
sm.wall_normal = Vector3.RIGHT.normalized()
|
||||||
sm.switch_to("wall_run")
|
sm.switch_to("wall_run")
|
||||||
_eq(sm.current_state, "wall_run")
|
_eq(sm.current_state, "wall_run")
|
||||||
@@ -144,26 +165,31 @@ func test_chain_bonus_caps_at_50pct() -> void:
|
|||||||
sm.register_chain_mechanic("slide")
|
sm.register_chain_mechanic("slide")
|
||||||
sm.register_chain_mechanic("wall_run")
|
sm.register_chain_mechanic("wall_run")
|
||||||
_expect(sm.chain_count == 3, "chain_count 3 after 3 mechanics")
|
_expect(sm.chain_count == 3, "chain_count 3 after 3 mechanics")
|
||||||
_expect(sm.current_chain_bonus <= params.chain_bonus_cap + 0.001,
|
_expect(
|
||||||
"bonus should be bounded by cap")
|
sm.current_chain_bonus <= params.chain_bonus_cap + 0.001,
|
||||||
|
"bonus should be bounded by cap"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_sliding_reduces_speed() -> void:
|
func test_sliding_reduces_speed() -> void:
|
||||||
params.slide_speed = 14.0
|
params.slide_speed = 14.0
|
||||||
params.slide_min_speed = 12.0
|
params.slide_min_speed = 12.0
|
||||||
_expect(params.slide_min_speed < params.slide_speed,
|
_expect(params.slide_min_speed < params.slide_speed, "slide_min_speed must be below slide_speed")
|
||||||
"slide_min_speed must be below slide_speed")
|
|
||||||
|
|
||||||
|
|
||||||
func test_dash_speed_under_effective_cap() -> void:
|
func test_dash_speed_under_effective_cap() -> void:
|
||||||
var d = Node.new(); d.name = "state_dash"; d.machine = sm; sm.add_child(d)
|
var d = Node.new()
|
||||||
|
d.name = "state_dash"
|
||||||
|
d.machine = sm
|
||||||
|
sm.add_child(d)
|
||||||
sm.on_ground = false
|
sm.on_ground = false
|
||||||
var eff := sm.get_effective_speed(params.dash_speed)
|
var eff := sm.get_effective_speed(params.dash_speed)
|
||||||
_expect(eff <= params.dash_speed * (1.0 + params.chain_bonus_cap),
|
_expect(
|
||||||
"dash effective speed should respect chain cap")
|
eff <= params.dash_speed * (1.0 + params.chain_bonus_cap),
|
||||||
|
"dash effective speed should respect chain cap"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func test_rocket_jump_impulse_sets_upward_velocity() -> void:
|
func test_rocket_jump_impulse_sets_upward_velocity() -> void:
|
||||||
# Unit test: verify impulse formula in state_machine
|
|
||||||
params.rocket_jump_up_impulse = 20.0
|
params.rocket_jump_up_impulse = 20.0
|
||||||
_expect(params.rocket_jump_up_impulse > 0.0, "rocket jump must have positive upward impulse")
|
_expect(params.rocket_jump_up_impulse > 0.0, "rocket jump must have positive upward impulse")
|
||||||
|
|||||||
Reference in New Issue
Block a user