Завершить state machine персонажа

This commit is contained in:
2026-08-19 03:20:24 +03:00
parent 0943caa3cc
commit 62ee6877f9
12 changed files with 463 additions and 8 deletions
@@ -0,0 +1,109 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public final class CharacterStateMachineTest {
@Test
public void groundedVelocitySelectsIdleAndRunDeterministically() {
CharacterStateMachine machine = new CharacterStateMachine(PhysicsConfig.release());
machine.reset(true, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.IDLE, machine.state());
machine.observe(1f / 60f, true, true, 370f, 0f, 1);
assertEquals(CharacterStateMachine.State.RUN, machine.state());
machine.observe(1f / 60f, true, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.IDLE, machine.state());
}
@Test
public void verticalMotionSelectsJumpFallAndDeathHasPriority() {
CharacterStateMachine machine = new CharacterStateMachine(PhysicsConfig.release());
machine.reset(true, false, 120f, -300f, 1);
assertEquals(CharacterStateMachine.State.JUMP, machine.state());
machine.observe(1f / 60f, true, false, 120f, 80f, 1);
assertEquals(CharacterStateMachine.State.FALL, machine.state());
machine.observe(1f / 60f, false, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.DEATH, machine.state());
}
@Test
public void changingFacingEmitsOneConfiguredTurnWindow() {
PhysicsConfig physics = PhysicsConfig.release();
CharacterStateMachine machine = new CharacterStateMachine(physics);
machine.reset(true, true, physics.runSpeed(), 0f, 1);
machine.observe(physics.fixedStepSeconds(), true, true,
-physics.runSpeed(), 0f, -1);
assertEquals(CharacterStateMachine.State.TURN, machine.state());
machine.observe(physics.turnSeconds() + physics.fixedStepSeconds(),
true, true, -physics.runSpeed(), 0f, -1);
assertEquals(CharacterStateMachine.State.RUN, machine.state());
}
@Test
public void firstGroundedFrameAfterFallingEmitsConfiguredLandWindow() {
PhysicsConfig physics = PhysicsConfig.release();
CharacterStateMachine machine = new CharacterStateMachine(physics);
machine.reset(true, false, 0f, 300f, 1);
machine.observe(physics.fixedStepSeconds(), true, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.LAND, machine.state());
machine.observe(physics.landSeconds() + physics.fixedStepSeconds(),
true, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.IDLE, machine.state());
}
@Test
public void interactionIsAConfiguredCommandAndDeathCancelsIt() {
PhysicsConfig physics = PhysicsConfig.release();
CharacterStateMachine machine = new CharacterStateMachine(physics);
machine.reset(true, true, 0f, 0f, 1);
machine.beginInteraction();
assertEquals(CharacterStateMachine.State.INTERACT, machine.state());
machine.observe(physics.interactSeconds() * 0.5f,
true, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.INTERACT, machine.state());
machine.observe(physics.fixedStepSeconds(), false, true, 0f, 0f, 1);
assertEquals(CharacterStateMachine.State.DEATH, machine.state());
}
@Test
public void stunOverridesInteractionThenReturnsToObservedMovement() {
PhysicsConfig physics = PhysicsConfig.release();
CharacterStateMachine machine = new CharacterStateMachine(physics);
machine.reset(true, true, physics.runSpeed(), 0f, 1);
machine.beginInteraction();
machine.stun();
assertEquals(CharacterStateMachine.State.STUNNED, machine.state());
machine.observe(physics.stunnedSeconds() * 0.5f,
true, true, physics.runSpeed(), 0f, 1);
assertEquals(CharacterStateMachine.State.STUNNED, machine.state());
machine.observe(physics.stunnedSeconds(),
true, true, physics.runSpeed(), 0f, 1);
assertEquals(CharacterStateMachine.State.RUN, machine.state());
}
@Test
public void interactionCannotDowngradeAnActiveStun() {
CharacterStateMachine machine = new CharacterStateMachine(PhysicsConfig.release());
machine.reset(true, true, 0f, 0f, 1);
machine.stun();
machine.beginInteraction();
assertEquals(CharacterStateMachine.State.STUNNED, machine.state());
}
}
@@ -18,6 +18,10 @@ public final class PhysicsConfigTest {
assertEquals(0.13f, physics.coyoteSeconds(), 0f);
assertEquals(0.14f, physics.jumpBufferSeconds(), 0f);
assertEquals(0.78f, physics.respawnSeconds(), 0f);
assertEquals(0.10f, physics.turnSeconds(), 0f);
assertEquals(0.12f, physics.landSeconds(), 0f);
assertEquals(0.24f, physics.interactSeconds(), 0f);
assertEquals(0.32f, physics.stunnedSeconds(), 0f);
assertTrue(physics.maximumBodies() >= 10);
}
}
@@ -84,4 +84,32 @@ public final class VolunteerRigTest {
assertEquals(VolunteerRig.Motion.DEAD,
VolunteerRig.bodyMotion(false, false, false));
}
@Test
public void everyLivingCharacterStateHasAnExplicitAnimationMotion() {
for (CharacterStateMachine.State state : CharacterStateMachine.State.values()) {
assertEquals(state.name(), VolunteerRig.motionFor(state).name());
}
}
@Test
public void transientCharacterStatesHaveNonColorPoseSignals() {
VolunteerRig.Skin skin = VolunteerRig.Skin.BRASS_SCOUT;
VolunteerRig.Pose idle = VolunteerRig.pose(skin, VolunteerRig.Motion.IDLE, 0.25f, 1);
VolunteerRig.Pose turn = VolunteerRig.pose(skin, VolunteerRig.Motion.TURN, 0.25f, 1);
VolunteerRig.Pose land = VolunteerRig.pose(skin, VolunteerRig.Motion.LAND, 0.25f, 1);
VolunteerRig.Pose interact = VolunteerRig.pose(
skin, VolunteerRig.Motion.INTERACT, 0.25f, 1);
VolunteerRig.Pose stunned = VolunteerRig.pose(
skin, VolunteerRig.Motion.STUNNED, 0.25f, 1);
assertNotEquals(idle.x(VolunteerRig.Joint.LEFT_HAND),
turn.x(VolunteerRig.Joint.LEFT_HAND), 0.001f);
assertNotEquals(idle.y(VolunteerRig.Joint.ROOT),
land.y(VolunteerRig.Joint.ROOT), 0.001f);
assertNotEquals(idle.x(VolunteerRig.Joint.RIGHT_HAND),
interact.x(VolunteerRig.Joint.RIGHT_HAND), 0.001f);
assertNotEquals(idle.y(VolunteerRig.Joint.LEFT_HAND),
stunned.y(VolunteerRig.Joint.LEFT_HAND), 0.001f);
}
}