Make dashback configurable, fix movement bugs, add double jump forward and back.
This commit is contained in:
@@ -1,37 +1,44 @@
|
|||||||
(
|
(
|
||||||
id: TestFighter,
|
id: TestFighter,
|
||||||
model_path: "fighters/test/test-fighter-normal.glb",
|
model_path: "fighters/test/test-fighter-2.glb",
|
||||||
|
model_scale: "10.0",
|
||||||
baked_animation_path: "fighters/test/test.fighterbake",
|
baked_animation_path: "fighters/test/test.fighterbake",
|
||||||
|
icon: (
|
||||||
|
path: "fighters/test/icon.png",
|
||||||
|
),
|
||||||
attributes: (
|
attributes: (
|
||||||
base_walk_accel: "0.1",
|
base_walk_accel: "0.1",
|
||||||
stick_walk_accel: "0.16",
|
stick_walk_accel: "0.16",
|
||||||
max_walk_vel: "0.7",
|
max_walk_vel: "1.0",
|
||||||
ground_friction: "0.09",
|
ground_friction: "0.1",
|
||||||
dash_acceleration_duration: 13,
|
dash_acceleration_duration: 13,
|
||||||
dash_duration: 24,
|
dash_duration: 24,
|
||||||
base_dash_accel: "0.02",
|
base_dash_accel: "0.1",
|
||||||
stick_dash_accel: "0.065",
|
stick_dash_accel: "0.05",
|
||||||
max_dash_vel: "1.1",
|
max_dash_vel: "2.3",
|
||||||
short_hop_vertical_velocity: "1.05",
|
short_hop_vertical_velocity: "1.35",
|
||||||
full_jump_vertical_velocity: "1.6",
|
full_jump_vertical_velocity: "2.1",
|
||||||
jump_horizontal_velocity: "0.7",
|
jump_horizontal_velocity: "0.7",
|
||||||
air_jump_horizontal_velocity: "0.5",
|
air_jump_horizontal_velocity: "0.96",
|
||||||
air_jump_multiplier: "1.0",
|
air_jump_multiplier: "1.0",
|
||||||
dash_initial_velocity: "1.4",
|
dash_initial_velocity: "1.4",
|
||||||
terminal_velocity: "1.3",
|
terminal_velocity: "1.6",
|
||||||
gravity: "0.064",
|
gravity: "0.084",
|
||||||
|
weight: "100.0",
|
||||||
jumpsquat_duration: 5,
|
jumpsquat_duration: 5,
|
||||||
landing_iasa: 4,
|
landing_iasa: 4,
|
||||||
landing_duration: 30,
|
landing_duration: 30,
|
||||||
|
|
||||||
max_air_horizontal_velocity: "1.35",
|
max_air_horizontal_velocity: "2.5",
|
||||||
air_acceleration_base: "0.19",
|
air_drift_max: "1.0",
|
||||||
air_acceleration_stick: "0.09",
|
air_acceleration_base: "0.03",
|
||||||
air_friction: "0.05",
|
air_acceleration_stick: "0.01",
|
||||||
|
air_friction: "0.01",
|
||||||
|
|
||||||
air_dodge_duration: 49,
|
air_dodge_duration: 49,
|
||||||
air_dodge_velocity: "2.790",
|
air_dodge_velocity: "2.790",
|
||||||
air_dodge_decay: "0.9"
|
air_dodge_decay: "0.9",
|
||||||
|
ecb_vertical_midpoint_offset: "0.0"
|
||||||
),
|
),
|
||||||
camera: (
|
camera: (
|
||||||
vertical_origin_offset: "8.0",
|
vertical_origin_offset: "8.0",
|
||||||
@@ -42,20 +49,166 @@
|
|||||||
visibility_radius: "5.0",
|
visibility_radius: "5.0",
|
||||||
),
|
),
|
||||||
animations: {
|
animations: {
|
||||||
Wait: "PlyPurin5K_Share_ACTION_Wait",
|
Wait: "Idle_Loop_Game",
|
||||||
Walk: "PlyPurin5K_Share_ACTION_WalkFast",
|
Walk: "Walk_Loop_IK_Game",
|
||||||
Run: "PlyPurin5K_Share_ACTION_Run",
|
Run: "Run_Game",
|
||||||
Dash: "PlyPurin5K_Share_ACTION_Dash",
|
Dash: "Dash_Game",
|
||||||
JumpSquat: "PlyPurin5K_Share_ACTION_Landing",
|
JumpSquat: "Jump_Squat_Game",
|
||||||
JumpForward: "PlyPurin5K_Share_ACTION_JumpF",
|
JumpForward: "Jump_Start_IK_Game",
|
||||||
JumpBack: "PlyPurin5K_Share_ACTION_JumpB",
|
JumpBack: "JumpB_Game",
|
||||||
Landing: "PlyPurin5K_Share_ACTION_Landing",
|
LandingSpecial: "SpecialLand_Game",
|
||||||
AirDodge: "PlyPurin5K_Share_ACTION_EscapeAir",
|
Landing: "Jump_Land_Game",
|
||||||
DoubleJump: "PlyPurin5K_Share_ACTION_JumpAerialF1",
|
AirDodge: "Idle_Loop_Game",
|
||||||
Fall: "PlyPurin5K_Share_ACTION_Fall",
|
DoubleJumpForward: "Jump_Start_IK_Game",
|
||||||
Turn: "PlyPurin5K_Share_ACTION_Turn",
|
DoubleJumpBack: "JumpB_Game",
|
||||||
|
Fall: "Fall_Loop_Game",
|
||||||
|
Turn: "Turn_Game",
|
||||||
|
AttackJab1: "Punch_Jab_Game",
|
||||||
|
AttackUpTilt: "Idle_Loop_Game",
|
||||||
|
AttackForwardTilt: "Idle_Loop_Game",
|
||||||
|
AttackDownTilt: "Idle_Loop_Game"
|
||||||
},
|
},
|
||||||
attack_scripts: {
|
attack_scripts: {
|
||||||
NAir: "fighters/test/scripts/fair.attack.rhai"
|
NeutralAir: "fighters/test/scripts/fair.attack.rhai",
|
||||||
}
|
Jab: "fighters/test/scripts/fair.attack.rhai",
|
||||||
|
UpTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
|
ForwardTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
|
DownTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
|
},
|
||||||
|
hurtboxes: [
|
||||||
|
// center one
|
||||||
|
(
|
||||||
|
bone: "spine_01",
|
||||||
|
offset: (x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.05",
|
||||||
|
radius: "0.15"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "spine_03",
|
||||||
|
offset: (x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.05",
|
||||||
|
radius: "0.15"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "Head",
|
||||||
|
offset: (x: "0.0", y: "0.125", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.05",
|
||||||
|
radius: "0.1"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "thigh_r",
|
||||||
|
offset: (x: "0.0", y: "0.15", z: "-0.01"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.2",
|
||||||
|
radius: "0.07"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "thigh_l",
|
||||||
|
offset: (x: "0.0", y: "0.15", z: "-0.01"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.2",
|
||||||
|
radius: "0.07"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "calf_l",
|
||||||
|
offset: (x: "0.0", y: "0.15", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.2",
|
||||||
|
radius: "0.05"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "calf_r",
|
||||||
|
offset: (x: "0.0", y: "0.15", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.2",
|
||||||
|
radius: "0.05"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "foot_r",
|
||||||
|
offset: (x: "0.0", y: "0.1", z: "0.01"),
|
||||||
|
rotation: FGVec3(x: "-25.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.1",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "foot_l",
|
||||||
|
offset: (x: "0.0", y: "0.1", z: "0.01"),
|
||||||
|
rotation: FGVec3(x: "-25.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.1",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "upperarm_l",
|
||||||
|
offset: (x: "0.0", y: "0.09", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.125",
|
||||||
|
radius: "0.05"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "lowerarm_l",
|
||||||
|
offset: (x: "0.0", y: "0.12", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.115",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "hand_l",
|
||||||
|
offset: (x: "0.0", y: "0.05", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.01",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "upperarm_r",
|
||||||
|
offset: (x: "0.0", y: "0.09", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.125",
|
||||||
|
radius: "0.05"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "lowerarm_r",
|
||||||
|
offset: (x: "0.0", y: "0.12", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.115",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "hand_r",
|
||||||
|
offset: (x: "0.0", y: "0.05", z: "-0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
half_length: "0.01",
|
||||||
|
radius: "0.04"
|
||||||
|
),
|
||||||
|
/*(
|
||||||
|
bone: "RFootJA",
|
||||||
|
offset: (x: "0.0", y: "0.5", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "90.0"),
|
||||||
|
half_length: "0.5",
|
||||||
|
radius: "1.0"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "JOBJ_47",
|
||||||
|
offset: (x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "90.0"),
|
||||||
|
half_length: "0.5",
|
||||||
|
radius: "1.0"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "LFootJA",
|
||||||
|
offset: (x: "0.0", y: "0.5", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "90.0"),
|
||||||
|
half_length: "0.5",
|
||||||
|
radius: "1.0"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
bone: "JOBJ_41",
|
||||||
|
offset: (x: "0.0", y: "0.0", z: "0.0"),
|
||||||
|
rotation: FGVec3(x: "0.0", y: "0.0", z: "90.0"),
|
||||||
|
half_length: "0.5",
|
||||||
|
radius: "1.0"
|
||||||
|
),*/
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
(
|
(
|
||||||
|
cursor: (
|
||||||
|
path: "ui/textures/cursor.png",
|
||||||
|
sampling: Nearest
|
||||||
|
),
|
||||||
|
css_token: (
|
||||||
|
path: "ui/textures/fighter_select_token.png",
|
||||||
|
sampling: Linear
|
||||||
|
),
|
||||||
|
player_colors: [
|
||||||
|
Srgba((red: 0.780, green: 0.420, blue: 0.933, alpha: 1.000)),
|
||||||
|
Srgba((red: 0.843, green: 0.227, blue: 0.204, alpha: 1.000)),
|
||||||
|
Srgba((red: 0.333, green: 0.804, blue: 0.078, alpha: 1.000)),
|
||||||
|
Srgba((red: 0.075, green: 0.376, blue: 0.416, alpha: 1.000)),
|
||||||
|
Srgba((red: 0.682, green: 0.878, blue: 0.914, alpha: 1.000))
|
||||||
|
],
|
||||||
button_styles: {
|
button_styles: {
|
||||||
MainMenu: (
|
MainMenu: (
|
||||||
focus_outline_width_px: 4,
|
focus_outline_width_px: 4,
|
||||||
@@ -24,8 +39,29 @@
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
CharacterSelectCharacter: (
|
CharacterSelectCharacter: (
|
||||||
focus_outline_width_px: 4,
|
focus_outline_width_px: 10,
|
||||||
focus_outline_offset_px: 2,
|
focus_outline_offset_px: 5,
|
||||||
|
focus_outline_color: Srgba((red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
normal: (
|
||||||
|
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
box_shadow: (),
|
||||||
|
border_size: 10,
|
||||||
|
),
|
||||||
|
press: (
|
||||||
|
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
box_shadow: (),
|
||||||
|
),
|
||||||
|
hover: (
|
||||||
|
background_color: Srgba((red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
box_shadow: (),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
CharacterSelectStartBanner: (
|
||||||
|
focus_outline_width_px: 10,
|
||||||
|
focus_outline_offset_px: 5,
|
||||||
focus_outline_color: Srgba((red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
focus_outline_color: Srgba((red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
normal: (
|
normal: (
|
||||||
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
@@ -42,6 +78,51 @@
|
|||||||
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
box_shadow: (),
|
box_shadow: (),
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
StageSelectIcon: (
|
||||||
|
focus_outline_width_px: 4,
|
||||||
|
focus_outline_offset_px: 2,
|
||||||
|
focus_outline_color: Srgba((red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
normal: (
|
||||||
|
border_color: Srgba((red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)),
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
border_size: 10,
|
||||||
|
border_radius: 40.0,
|
||||||
|
box_shadow: (
|
||||||
|
spread_radius: 5.0,
|
||||||
|
blur_radius: 5.0,
|
||||||
|
offset_px_y: 10,
|
||||||
|
offset_px_x: 10,
|
||||||
|
color: Srgba((red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
press: (
|
||||||
|
background_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
border_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
border_radius: 40.0,
|
||||||
|
border_size: 10,
|
||||||
|
box_shadow: (
|
||||||
|
spread_radius: 5.0,
|
||||||
|
offset_px_y: 10,
|
||||||
|
offset_px_x: 10,
|
||||||
|
color: Srgba((red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hover: (
|
||||||
|
background_color: Srgba((red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
border_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
border_radius: 40.0,
|
||||||
|
border_size: 10,
|
||||||
|
text_color: Srgba((red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)),
|
||||||
|
box_shadow: (
|
||||||
|
spread_radius: 5.0,
|
||||||
|
offset_px_y: 10,
|
||||||
|
offset_px_x: 10,
|
||||||
|
color: Srgba((red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
smash_input_reset_axis_threshold: "0.25",
|
smash_input_reset_axis_threshold: "0.25",
|
||||||
smash_input_axis_threshold: "0.8",
|
smash_input_axis_threshold: "0.8",
|
||||||
run_stick_threshold: "0.625",
|
run_stick_threshold: "0.625",
|
||||||
smash_input_frame_threshold: 2,
|
smash_input_frame_threshold: 3,
|
||||||
input_buffer_size: 5
|
input_buffer_size: 3,
|
||||||
|
turn_into_dashback_window: 2,
|
||||||
|
|
||||||
),
|
),
|
||||||
fighter_common: (
|
fighter_common: (
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
ground_friction_over_walk_speed_multiplier: "2.0"
|
ground_friction_over_walk_speed_multiplier: "2.0"
|
||||||
),
|
),
|
||||||
stage_manifest_paths: [
|
stage_manifest_paths: [
|
||||||
"stages/test.stage.ron"
|
"stages/battlefield/test.stage.ron"
|
||||||
],
|
],
|
||||||
fighter_manifest_paths: [
|
fighter_manifest_paths: [
|
||||||
"fighters/test/test.fighter.ron"
|
"fighters/test/test.fighter.ron"
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
(
|
||||||
|
id: TestStage,
|
||||||
|
model_path: "stages/battlefield/battlefield.glb#Scene0",
|
||||||
|
icon: (
|
||||||
|
path: "stages/battlefield/icon.png"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Battlefield
|
||||||
|
/*camera: (
|
||||||
|
left: -200.0,
|
||||||
|
right: 200.0,
|
||||||
|
bottom: -59.0,
|
||||||
|
top: 170.0,
|
||||||
|
origin: (0.0, 44.0),
|
||||||
|
vertical_fov_degrees: 30.0,
|
||||||
|
min_depth: 83.0,
|
||||||
|
max_depth: 1000.0,
|
||||||
|
subject_scale: 1.5,
|
||||||
|
fighter_forward_extent_scale: 1.5,
|
||||||
|
tracking_smoothness: 1.8,
|
||||||
|
vertical_pan_degrees: -10.0,
|
||||||
|
horizontal_pan_degrees_per_unit: 0.1,
|
||||||
|
vertical_pan_degrees_per_unit: 0.1,
|
||||||
|
max_horizontal_pan_degrees: 17.5,
|
||||||
|
max_upward_pan_degrees: 5.0,
|
||||||
|
max_downward_pan_degrees: 7.0,
|
||||||
|
),*/
|
||||||
|
// Yoshi's story
|
||||||
|
|
||||||
|
/*camera: (
|
||||||
|
left: -180.0,
|
||||||
|
right: 179.0,
|
||||||
|
bottom: -71.0,
|
||||||
|
top: 169.0,
|
||||||
|
origin: (0.0, 44.0),
|
||||||
|
vertical_fov_degrees: 20.0,
|
||||||
|
min_depth: 100.0,
|
||||||
|
max_depth: 1000.0,
|
||||||
|
subject_scale: 1.0,
|
||||||
|
fighter_forward_extent_scale: 1.0,
|
||||||
|
tracking_smoothness: 1.0,
|
||||||
|
vertical_pan_degrees: -10.0,
|
||||||
|
horizontal_pan_degrees_per_unit: 0.05,
|
||||||
|
vertical_pan_degrees_per_unit: 0.15,
|
||||||
|
max_horizontal_pan_degrees: 17.5,
|
||||||
|
max_upward_pan_degrees: 5.0,
|
||||||
|
max_downward_pan_degrees: 7.0,
|
||||||
|
),*/
|
||||||
|
|
||||||
|
camera: (
|
||||||
|
left: -180.0,
|
||||||
|
right: 179.0,
|
||||||
|
bottom: -71.0,
|
||||||
|
top: 169.0,
|
||||||
|
origin: (0.0, 44.0),
|
||||||
|
vertical_fov_degrees: 20.0,
|
||||||
|
min_depth: 100.0,
|
||||||
|
max_depth: 1000.0,
|
||||||
|
subject_scale: 1.5,
|
||||||
|
fighter_forward_extent_scale: 1.0,
|
||||||
|
tracking_smoothness: 1.0,
|
||||||
|
vertical_pan_degrees: -10.0,
|
||||||
|
horizontal_pan_degrees_per_unit: 0.05,
|
||||||
|
vertical_pan_degrees_per_unit: 0.15,
|
||||||
|
max_horizontal_pan_degrees: 17.5,
|
||||||
|
max_upward_pan_degrees: 5.0,
|
||||||
|
max_downward_pan_degrees: 7.0,
|
||||||
|
),
|
||||||
|
polygons: [
|
||||||
|
(
|
||||||
|
poly_type: Platform,
|
||||||
|
lines: [
|
||||||
|
(
|
||||||
|
(x: "-20.0", y: "47.0"),
|
||||||
|
Floor
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "20.0", y: "47.0"),
|
||||||
|
Floor
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
poly_type: Platform,
|
||||||
|
lines: [
|
||||||
|
(
|
||||||
|
(x: "-65.0", y: "24.0"),
|
||||||
|
Floor
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "-25.0", y: "24.0"),
|
||||||
|
Floor
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
poly_type: Platform,
|
||||||
|
lines: [
|
||||||
|
(
|
||||||
|
(x: "25.0", y: "24.0"),
|
||||||
|
Floor
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "65.0", y: "24.0"),
|
||||||
|
Floor
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
(
|
||||||
|
poly_type: Closed,
|
||||||
|
lines: [
|
||||||
|
(
|
||||||
|
(x: "-68.0", y: "-3.0"),
|
||||||
|
Wall
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "-68.0", y: "0.0"),
|
||||||
|
Floor,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "0.0", y: "0.0"),
|
||||||
|
Floor
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "68", y: "0.0"),
|
||||||
|
Wall
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "68.0", y: "-3.0"),
|
||||||
|
Ceiling
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x: "0.0", y: "-15.0"),
|
||||||
|
Ceiling
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -73,8 +73,10 @@ pub enum AnimKind {
|
|||||||
JumpSquat,
|
JumpSquat,
|
||||||
JumpForward,
|
JumpForward,
|
||||||
JumpBack,
|
JumpBack,
|
||||||
DoubleJump,
|
DoubleJumpForward,
|
||||||
|
DoubleJumpBack,
|
||||||
Landing,
|
Landing,
|
||||||
|
LandingSpecial,
|
||||||
AirDodge,
|
AirDodge,
|
||||||
Fall,
|
Fall,
|
||||||
Turn,
|
Turn,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub struct FighterAttributes {
|
|||||||
pub air_acceleration_base: FGi32,
|
pub air_acceleration_base: FGi32,
|
||||||
pub air_acceleration_stick: FGi32,
|
pub air_acceleration_stick: FGi32,
|
||||||
pub max_air_horizontal_velocity: FGi32,
|
pub max_air_horizontal_velocity: FGi32,
|
||||||
|
pub air_drift_max: FGi32,
|
||||||
|
|
||||||
pub ground_friction: FGi32,
|
pub ground_friction: FGi32,
|
||||||
pub terminal_velocity: FGi32,
|
pub terminal_velocity: FGi32,
|
||||||
|
|||||||
+82
-24
@@ -19,31 +19,23 @@ pub fn apply_air_drift(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let target_velocity =
|
let target_velocity =
|
||||||
input.get_last_frame().movement.x.signum() * attribs.max_air_horizontal_velocity;
|
input.get_last_frame().movement.x * attribs.air_drift_max;
|
||||||
let acceleration =
|
|
||||||
input.get_last_frame().movement.x.signum() * attribs.air_acceleration_base;
|
|
||||||
let mut acceleration =
|
let mut acceleration =
|
||||||
acceleration + input.get_last_frame().movement.x * attribs.air_acceleration_stick;
|
input.get_last_frame().movement.x.signum() * attribs.air_acceleration_base + input.get_last_frame().movement.x * attribs.air_acceleration_stick;
|
||||||
let velocity_x = velocity.x;
|
|
||||||
if !(velocity_x * acceleration < FGi32::ZERO) {
|
// Acceleration opposes velocity
|
||||||
if acceleration > FGi32::ZERO {
|
if acceleration * velocity.x < 0 {
|
||||||
if velocity_x + acceleration > target_velocity {
|
// do nothing
|
||||||
acceleration = -attribs.air_friction;
|
} else {
|
||||||
if velocity_x + acceleration < target_velocity {
|
let predicted_vel = velocity.x + acceleration;
|
||||||
acceleration = target_velocity - velocity_x;
|
let will_go_past = predicted_vel.abs() > target_velocity.abs();
|
||||||
}
|
|
||||||
if velocity_x + acceleration > attribs.max_air_horizontal_velocity {
|
if velocity.x.abs() < target_velocity.abs() && will_go_past {
|
||||||
acceleration = attribs.max_air_horizontal_velocity - velocity_x;
|
// Prevent overshoot
|
||||||
}
|
acceleration = target_velocity - velocity.x
|
||||||
}
|
} else if velocity.x.abs() > target_velocity.abs() {
|
||||||
} else if velocity_x + acceleration < target_velocity {
|
let diff = (velocity.x - target_velocity).abs();
|
||||||
acceleration = attribs.air_friction;
|
acceleration = velocity.x.signum() * (-(attribs.air_friction).min(diff));
|
||||||
if velocity_x + acceleration > target_velocity {
|
|
||||||
acceleration = target_velocity - velocity_x;
|
|
||||||
}
|
|
||||||
if velocity_x + acceleration < -attribs.max_air_horizontal_velocity {
|
|
||||||
acceleration = -attribs.max_air_horizontal_velocity - velocity_x;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,3 +52,69 @@ pub fn apply_air_motion(
|
|||||||
prev_translation.0 = translation.0;
|
prev_translation.0 = translation.0;
|
||||||
translation.0 += velocity.0;
|
translation.0 += velocity.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
input::{FighterInput, FighterInputFrame},
|
||||||
|
math::vec::FGVec2,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_attributes() -> FighterAttributes {
|
||||||
|
FighterAttributes {
|
||||||
|
base_walk_accel: FGi32::ZERO,
|
||||||
|
stick_walk_accel: FGi32::ZERO,
|
||||||
|
max_walk_vel: FGi32::ZERO,
|
||||||
|
dash_acceleration_duration: 0,
|
||||||
|
dash_duration: 0,
|
||||||
|
dash_initial_velocity: FGi32::ZERO,
|
||||||
|
base_dash_accel: FGi32::ZERO,
|
||||||
|
stick_dash_accel: FGi32::ZERO,
|
||||||
|
max_dash_vel: FGi32::ZERO,
|
||||||
|
jumpsquat_duration: 0,
|
||||||
|
short_hop_vertical_velocity: FGi32::ZERO,
|
||||||
|
full_jump_vertical_velocity: FGi32::ZERO,
|
||||||
|
jump_horizontal_velocity: FGi32::ZERO,
|
||||||
|
air_jump_multiplier: FGi32::ZERO,
|
||||||
|
air_jump_horizontal_velocity: FGi32::ZERO,
|
||||||
|
landing_iasa: 0,
|
||||||
|
landing_duration: 0,
|
||||||
|
air_acceleration_base: FGi32::ZERO,
|
||||||
|
air_acceleration_stick: FGi32::ZERO,
|
||||||
|
max_air_horizontal_velocity: FGi32::lit("1.2"),
|
||||||
|
air_drift_max: FGi32::lit("1.2"),
|
||||||
|
ground_friction: FGi32::ZERO,
|
||||||
|
terminal_velocity: FGi32::ZERO,
|
||||||
|
gravity: FGi32::ZERO,
|
||||||
|
weight: FGi32::lit("1"),
|
||||||
|
air_friction: FGi32::lit("0.001"),
|
||||||
|
air_dodge_duration: 0,
|
||||||
|
air_dodge_velocity: FGi32::ZERO,
|
||||||
|
air_dodge_decay: FGi32::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_air_acceleration_is_symmetric() {
|
||||||
|
let attributes = test_attributes();
|
||||||
|
let mut right_input = FighterInput::default();
|
||||||
|
right_input.push_input_frame(FighterInputFrame {
|
||||||
|
movement: FGVec2::new(FGi32::ONE, FGi32::ZERO),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
let mut left_input = FighterInput::default();
|
||||||
|
left_input.push_input_frame(FighterInputFrame {
|
||||||
|
movement: FGVec2::new(-FGi32::ONE, FGi32::ZERO),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut right_velocity = FighterVelocity(FGVec2::ZERO);
|
||||||
|
let mut left_velocity = FighterVelocity(FGVec2::ZERO);
|
||||||
|
apply_air_drift(&mut right_velocity, &attributes, &right_input);
|
||||||
|
apply_air_drift(&mut left_velocity, &attributes, &left_input);
|
||||||
|
|
||||||
|
assert_eq!(right_velocity.x, FGi32::ZERO);
|
||||||
|
assert_eq!(left_velocity.x, FGi32::ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind, collision, state::{
|
||||||
collision,
|
FighterState, FighterStateContext, FighterStateImpl, air, fall::FallState, ground::GroundedStateCommon, land::{LandingState, LandingType},
|
||||||
state::{
|
|
||||||
FighterState, FighterStateContext, FighterStateImpl, air, fall::FallState,
|
|
||||||
ground::GroundedStateCommon, land::LandingState,
|
|
||||||
},
|
},
|
||||||
},
|
}, input, math::vec::FGVec2,
|
||||||
input,
|
|
||||||
math::vec::FGVec2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@@ -47,7 +42,7 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
current_line_id: res.line_id,
|
current_line_id: res.line_id,
|
||||||
};
|
};
|
||||||
Some(FighterState::Land(LandingState { grounded_common }))
|
Some(FighterState::Land(LandingState { grounded_common, landing_type: LandingType::Special }))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::{FighterStateContext, FighterStateImpl};
|
|||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::collision;
|
use crate::fighter::collision;
|
||||||
use crate::fighter::state::ground::GroundedStateCommon;
|
use crate::fighter::state::ground::GroundedStateCommon;
|
||||||
use crate::fighter::state::land::LandingState;
|
use crate::fighter::state::land::{LandingState, LandingType};
|
||||||
use crate::fighter::state::{FighterState, air, jump};
|
use crate::fighter::state::{FighterState, air, jump};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash)]
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
@@ -45,7 +45,7 @@ impl FighterStateImpl for FallState {
|
|||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
current_line_id: res.line_id,
|
current_line_id: res.line_id,
|
||||||
};
|
};
|
||||||
Some(FighterState::Land(LandingState { grounded_common }))
|
Some(FighterState::Land(LandingState::create(grounded_common, LandingType::Normal)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
+218
-39
@@ -9,9 +9,9 @@ use crate::fighter::{
|
|||||||
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::math::int::FGi32;
|
use crate::math::int::{FGWide, FGi32};
|
||||||
use crate::math::vec::FGVec2;
|
use crate::math::vec::FGVec2;
|
||||||
use crate::stage::line::{StageLineID, StagePolyLineSegmentType};
|
use crate::stage::line::{StageLineID, StagePoly, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
@@ -74,6 +74,85 @@ pub enum GroundedMotionResult {
|
|||||||
InAir,
|
InAir,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GroundedPositionResult {
|
||||||
|
InGround {
|
||||||
|
point: FGVec2,
|
||||||
|
segment_idx: usize,
|
||||||
|
blocked: bool,
|
||||||
|
},
|
||||||
|
InAir,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_walkable_floor(segment: &StagePolyLineSegment) -> bool {
|
||||||
|
segment.segment_type == StagePolyLineSegmentType::Floor && segment.normal.y > FGi32::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_grounded_position(
|
||||||
|
stage_poly: &StagePoly,
|
||||||
|
starting_segment_idx: usize,
|
||||||
|
point: FGVec2,
|
||||||
|
can_walk_off: bool,
|
||||||
|
) -> GroundedPositionResult {
|
||||||
|
let mut segment_idx = starting_segment_idx;
|
||||||
|
|
||||||
|
// A single frame can cross several short connected segments. The bound also
|
||||||
|
// prevents malformed closed polygons from cycling forever.
|
||||||
|
for _ in 0..stage_poly.segments.len() {
|
||||||
|
let Some(segment) = stage_poly.get_segment(segment_idx) else {
|
||||||
|
return GroundedPositionResult::InAir;
|
||||||
|
};
|
||||||
|
if !is_walkable_floor(segment) {
|
||||||
|
return GroundedPositionResult::InAir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment_vector = segment.segment.point2() - segment.segment.point1();
|
||||||
|
let projection = segment_vector.dot(point - segment.segment.point1());
|
||||||
|
let length_squared = segment_vector.length_squared();
|
||||||
|
|
||||||
|
let (next_segment_idx, edge_point) = if projection < FGWide::ZERO {
|
||||||
|
(
|
||||||
|
stage_poly.get_prev_segment_index(segment_idx),
|
||||||
|
segment.segment.point1(),
|
||||||
|
)
|
||||||
|
} else if projection > length_squared {
|
||||||
|
(
|
||||||
|
stage_poly.get_next_segment_index(segment_idx),
|
||||||
|
segment.segment.point2(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let projected_point = segment.segment.point1()
|
||||||
|
+ (point - segment.segment.point1()).project_onto(segment.segment.direction());
|
||||||
|
return GroundedPositionResult::InGround {
|
||||||
|
point: projected_point,
|
||||||
|
segment_idx,
|
||||||
|
blocked: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(next_segment_idx) = next_segment_idx
|
||||||
|
&& next_segment_idx != segment_idx
|
||||||
|
&& stage_poly
|
||||||
|
.get_segment(next_segment_idx)
|
||||||
|
.is_some_and(is_walkable_floor)
|
||||||
|
{
|
||||||
|
segment_idx = next_segment_idx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return if can_walk_off {
|
||||||
|
GroundedPositionResult::InAir
|
||||||
|
} else {
|
||||||
|
GroundedPositionResult::InGround {
|
||||||
|
point: edge_point,
|
||||||
|
segment_idx,
|
||||||
|
blocked: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GroundedPositionResult::InAir
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_grounded_motion(
|
pub fn apply_grounded_motion(
|
||||||
velocity: &FighterVelocity,
|
velocity: &FighterVelocity,
|
||||||
translation: &mut FighterTranslation,
|
translation: &mut FighterTranslation,
|
||||||
@@ -87,7 +166,7 @@ pub fn apply_grounded_motion(
|
|||||||
pub fn collide_with_stage_grounded(
|
pub fn collide_with_stage_grounded(
|
||||||
state_context: &mut FighterStateContext,
|
state_context: &mut FighterStateContext,
|
||||||
ground_common: &mut GroundedStateCommon,
|
ground_common: &mut GroundedStateCommon,
|
||||||
_can_walk_off: bool,
|
can_walk_off: bool,
|
||||||
) -> GroundedMotionResult {
|
) -> GroundedMotionResult {
|
||||||
let translation = &mut state_context.translation;
|
let translation = &mut state_context.translation;
|
||||||
let ecb = &state_context.ecb;
|
let ecb = &state_context.ecb;
|
||||||
@@ -99,49 +178,149 @@ pub fn collide_with_stage_grounded(
|
|||||||
|
|
||||||
if let Some(stage_poly) = stage_poly {
|
if let Some(stage_poly) = stage_poly {
|
||||||
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
||||||
let current_segment_idx = ground_common.current_line_id.segment;
|
match resolve_grounded_position(
|
||||||
|
stage_poly,
|
||||||
// First, we check the current segment, if that fails, we check the surrounding ones
|
ground_common.current_line_id.segment,
|
||||||
let grounded_ecb_bottom_pos = [
|
ecb_bottom_pos,
|
||||||
Some(current_segment_idx),
|
can_walk_off,
|
||||||
stage_poly.get_prev_segment_index(current_segment_idx),
|
) {
|
||||||
stage_poly.get_next_segment_index(current_segment_idx),
|
GroundedPositionResult::InGround {
|
||||||
]
|
point,
|
||||||
.into_iter()
|
segment_idx,
|
||||||
.flatten()
|
blocked,
|
||||||
.find_map(|segment_idx| {
|
} => {
|
||||||
let segment = stage_poly.get_segment(segment_idx)?;
|
let current_segment = stage_poly.get_segment(segment_idx).unwrap();
|
||||||
if segment.segment_type != StagePolyLineSegmentType::Floor {
|
translation.0 = point - ecb.get_bottom_point();
|
||||||
return None;
|
state_context.velocity.0 = if blocked {
|
||||||
|
FGVec2::ZERO
|
||||||
|
} else {
|
||||||
|
state_context.velocity.0
|
||||||
|
- state_context
|
||||||
|
.velocity
|
||||||
|
.0
|
||||||
|
.project_onto_normalized(current_segment.normal)
|
||||||
|
};
|
||||||
|
ground_common.current_line_id.segment = segment_idx;
|
||||||
|
GroundedMotionResult::InGround
|
||||||
}
|
}
|
||||||
|
GroundedPositionResult::InAir => GroundedMotionResult::InAir,
|
||||||
let line_dir = segment.segment.direction();
|
|
||||||
let projected_point = segment.segment.point1()
|
|
||||||
+ (ecb_bottom_pos - segment.segment.point1()).project_onto(line_dir);
|
|
||||||
let closest_point = segment.segment.closest_point(ecb_bottom_pos);
|
|
||||||
let distance = closest_point.distance(projected_point);
|
|
||||||
|
|
||||||
if distance < 0.01 {
|
|
||||||
return Some((projected_point, segment_idx, segment.normal));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((bp, new_segment_idx, normal)) = grounded_ecb_bottom_pos {
|
|
||||||
translation.0 = bp - ecb.get_bottom_point();
|
|
||||||
state_context.velocity.0 =
|
|
||||||
state_context.velocity.0 - state_context.velocity.0.project_onto_normalized(normal);
|
|
||||||
ground_common.current_line_id.segment = new_segment_idx;
|
|
||||||
GroundedMotionResult::InGround
|
|
||||||
} else {
|
|
||||||
GroundedMotionResult::InAir
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GroundedMotionResult::InAir
|
GroundedMotionResult::InAir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::stage::line::StagePolyType;
|
||||||
|
|
||||||
|
fn two_segment_floor() -> StagePoly {
|
||||||
|
StagePoly::build(
|
||||||
|
StagePolyType::Platform,
|
||||||
|
&[
|
||||||
|
(FGVec2::lit("0", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
(FGVec2::lit("10", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
(FGVec2::lit("20", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tiny_endpoint_crossing_moves_to_adjacent_floor() {
|
||||||
|
let stage = two_segment_floor();
|
||||||
|
let point = FGVec2::new(FGi32::from_bits(10 * 65_536 + 1), FGi32::ZERO);
|
||||||
|
|
||||||
|
let GroundedPositionResult::InGround {
|
||||||
|
point: resolved,
|
||||||
|
segment_idx,
|
||||||
|
blocked,
|
||||||
|
} = resolve_grounded_position(&stage, 0, point, false)
|
||||||
|
else {
|
||||||
|
panic!("connected floor should remain grounded");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(segment_idx, 1);
|
||||||
|
assert_eq!(resolved, point);
|
||||||
|
assert!(!blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blocked_edge_keeps_floor_and_can_move_inward() {
|
||||||
|
let stage = StagePoly::build(
|
||||||
|
StagePolyType::Platform,
|
||||||
|
&[
|
||||||
|
(FGVec2::lit("0", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
(FGVec2::lit("10", "0"), StagePolyLineSegmentType::Wall),
|
||||||
|
(FGVec2::lit("10", "-2"), StagePolyLineSegmentType::Ceiling),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let just_past_edge = FGVec2::new(FGi32::from_bits(10 * 65_536 + 1), FGi32::ZERO);
|
||||||
|
|
||||||
|
let GroundedPositionResult::InGround {
|
||||||
|
point,
|
||||||
|
segment_idx,
|
||||||
|
blocked,
|
||||||
|
} = resolve_grounded_position(&stage, 0, just_past_edge, false)
|
||||||
|
else {
|
||||||
|
panic!("non-walk-off movement should stop at the edge");
|
||||||
|
};
|
||||||
|
assert_eq!(point, FGVec2::lit("10", "0"));
|
||||||
|
assert_eq!(segment_idx, 0);
|
||||||
|
assert!(blocked);
|
||||||
|
|
||||||
|
let GroundedPositionResult::InGround {
|
||||||
|
point,
|
||||||
|
segment_idx,
|
||||||
|
blocked,
|
||||||
|
} = resolve_grounded_position(
|
||||||
|
&stage,
|
||||||
|
segment_idx,
|
||||||
|
FGVec2::new(FGi32::from_bits(10 * 65_536 - 1), FGi32::ZERO),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
panic!("fighter should be able to move back from the edge");
|
||||||
|
};
|
||||||
|
assert!(point.x < FGi32::lit("10"));
|
||||||
|
assert_eq!(segment_idx, 0);
|
||||||
|
assert!(!blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn walk_off_edge_enters_air() {
|
||||||
|
let stage = StagePoly::build(
|
||||||
|
StagePolyType::Platform,
|
||||||
|
&[
|
||||||
|
(FGVec2::lit("0", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
(FGVec2::lit("10", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let just_past_edge = FGVec2::new(FGi32::from_bits(10 * 65_536 + 1), FGi32::ZERO);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
resolve_grounded_position(&stage, 0, just_past_edge, true),
|
||||||
|
GroundedPositionResult::InAir
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vertical_segment_cannot_hold_grounded_fighter() {
|
||||||
|
let stage = StagePoly::build(
|
||||||
|
StagePolyType::Platform,
|
||||||
|
&[
|
||||||
|
(FGVec2::lit("10", "0"), StagePolyLineSegmentType::Floor),
|
||||||
|
(FGVec2::lit("10", "-2"), StagePolyLineSegmentType::Floor),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
resolve_grounded_position(&stage, 0, FGVec2::lit("10", "0"), false),
|
||||||
|
GroundedPositionResult::InAir
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn grounded_movement_common_interrupts(
|
pub fn grounded_movement_common_interrupts(
|
||||||
state_context: &FighterStateContext,
|
state_context: &FighterStateContext,
|
||||||
ground_common: &GroundedStateCommon,
|
ground_common: &GroundedStateCommon,
|
||||||
|
|||||||
+11
-13
@@ -2,18 +2,10 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind, collision, state::{
|
||||||
collision,
|
FighterState, FighterStateContext, FighterStateImpl, air, air_dodge::{self, AirDodgeState}, fall::FallState, ground::{self, GroundedMotionResult, GroundedStateCommon}, land::{LandingState, LandingType},
|
||||||
state::{
|
|
||||||
FighterState, FighterStateContext, FighterStateImpl, air,
|
|
||||||
air_dodge::{self, AirDodgeState},
|
|
||||||
fall::FallState,
|
|
||||||
ground::{self, GroundedMotionResult, GroundedStateCommon},
|
|
||||||
land::LandingState,
|
|
||||||
},
|
},
|
||||||
},
|
}, input::FighterCommands, math::{int::FGi32, vec::FGVec2},
|
||||||
input::FighterCommands,
|
|
||||||
math::{int::FGi32, vec::FGVec2},
|
|
||||||
};
|
};
|
||||||
#[derive(Debug, Hash, Clone, Copy)]
|
#[derive(Debug, Hash, Clone, Copy)]
|
||||||
pub enum JumpType {
|
pub enum JumpType {
|
||||||
@@ -174,7 +166,13 @@ impl FighterStateImpl for JumpState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
JumpType::DoubleJump => {
|
JumpType::DoubleJump => {
|
||||||
state_context.play_animation(AnimKind::DoubleJump, false);
|
if movement_stick_x.is_zero()
|
||||||
|
|| movement_stick_x.signum() == state_context.facing_direction.to_sign()
|
||||||
|
{
|
||||||
|
state_context.play_animation(AnimKind::DoubleJumpForward, false);
|
||||||
|
} else {
|
||||||
|
state_context.play_animation(AnimKind::DoubleJumpBack, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +231,7 @@ impl FighterStateImpl for JumpState {
|
|||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
current_line_id: res.line_id,
|
current_line_id: res.line_id,
|
||||||
};
|
};
|
||||||
Some(FighterState::Land(LandingState { grounded_common }))
|
Some(FighterState::Land(LandingState::create(grounded_common, LandingType::Normal)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,21 @@ use crate::fighter::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub enum LandingType {
|
||||||
|
Normal,
|
||||||
|
Special
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct LandingState {
|
pub struct LandingState {
|
||||||
pub grounded_common: GroundedStateCommon,
|
pub grounded_common: GroundedStateCommon,
|
||||||
|
pub landing_type: LandingType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LandingState {
|
impl LandingState {
|
||||||
pub fn create(grounded_common: GroundedStateCommon) -> Self {
|
pub fn create(grounded_common: GroundedStateCommon, landing_type: LandingType) -> Self {
|
||||||
Self { grounded_common }
|
Self { grounded_common, landing_type }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +36,7 @@ impl FighterStateImpl for LandingState {
|
|||||||
if state_context.is_current_animation_finished() {
|
if state_context.is_current_animation_finished() {
|
||||||
grounded_movement_standstill_common_interrupts(state_context, &self.grounded_common)
|
grounded_movement_standstill_common_interrupts(state_context, &self.grounded_common)
|
||||||
} else if state_context.get_current_animation_frame()
|
} else if state_context.get_current_animation_frame()
|
||||||
>= state_context.fighter_manifest.attributes.landing_iasa
|
>= state_context.fighter_manifest.attributes.landing_iasa && let LandingType::Normal = self.landing_type
|
||||||
{
|
{
|
||||||
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
||||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||||
@@ -69,7 +76,7 @@ impl FighterStateImpl for LandingState {
|
|||||||
state_context: &mut FighterStateContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, true)
|
||||||
{
|
{
|
||||||
Some(FighterState::Fall(FallState))
|
Some(FighterState::Fall(FallState))
|
||||||
} else {
|
} else {
|
||||||
@@ -78,6 +85,13 @@ impl FighterStateImpl for LandingState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Landing, false);
|
match self.landing_type {
|
||||||
|
LandingType::Normal => {
|
||||||
|
state_context.play_animation(AnimKind::Landing, false);
|
||||||
|
},
|
||||||
|
LandingType::Special => {
|
||||||
|
state_context.play_animation(AnimKind::LandingSpecial, false);
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::fighter::animation::AnimKind;
|
|||||||
use crate::fighter::state::fall::FallState;
|
use crate::fighter::state::fall::FallState;
|
||||||
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
|
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
|
||||||
use crate::fighter::state::{FighterState, dash, jump, wait, walk};
|
use crate::fighter::state::{FighterState, dash, jump, wait, walk};
|
||||||
|
use crate::input::FighterCommands;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct TurnState {
|
pub struct TurnState {
|
||||||
@@ -13,7 +14,13 @@ impl FighterStateImpl for TurnState {
|
|||||||
const NAME: &'static str = "Turn";
|
const NAME: &'static str = "Turn";
|
||||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
// State chain that results in the turn completing
|
// State chain that results in the turn completing
|
||||||
|
if state_context.get_current_animation_frame() <= state_context.game_settings.input_common.turn_into_dashback_window as u32 {
|
||||||
|
if let Some(state) = dash::check_input(state_context, &self.grounded_common) {
|
||||||
|
return Some(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(state) = jump::check_input(state_context, &self.grounded_common) {
|
if let Some(state) = jump::check_input(state_context, &self.grounded_common) {
|
||||||
|
*state_context.facing_direction = state_context.facing_direction.reverse();
|
||||||
Some(state)
|
Some(state)
|
||||||
} else if state_context.is_current_animation_finished() {
|
} else if state_context.is_current_animation_finished() {
|
||||||
if let Some(state) = dash::check_input(state_context, &self.grounded_common) {
|
if let Some(state) = dash::check_input(state_context, &self.grounded_common) {
|
||||||
@@ -78,14 +85,14 @@ pub fn check_input(
|
|||||||
grounded_common: &GroundedStateCommon,
|
grounded_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
let smash_input_frame_threshold = state_context
|
|
||||||
.game_settings
|
let has_smash_input = state_context.input.has_command(FighterCommands::SmashMoveLeft) || state_context.input.has_command(FighterCommands::SmashMoveRight);
|
||||||
.input_common
|
|
||||||
.smash_input_frame_threshold;
|
|
||||||
if !input_frame.movement.x.is_zero()
|
if !input_frame.movement.x.is_zero()
|
||||||
&& input_frame.movement.x.signum() != state_context.facing_direction.to_sign()
|
&& input_frame.movement.x.signum() != state_context.facing_direction.to_sign()
|
||||||
&& state_context.input.get_frames_in_smash_move_deadzone() > smash_input_frame_threshold
|
&& !has_smash_input
|
||||||
{
|
{
|
||||||
|
dbg!(input_frame.movement.x);
|
||||||
return Some(FighterState::Turn(TurnState {
|
return Some(FighterState::Turn(TurnState {
|
||||||
grounded_common: grounded_common.clone(),
|
grounded_common: grounded_common.clone(),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ pub struct WalkState {
|
|||||||
impl FighterStateImpl for WalkState {
|
impl FighterStateImpl for WalkState {
|
||||||
const NAME: &'static str = "Walk";
|
const NAME: &'static str = "Walk";
|
||||||
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if let Some(state) = turn::check_input(state_context, &self.grounded_common) {
|
if let Some(state) =
|
||||||
return Some(state);
|
|
||||||
} else if let Some(state) =
|
|
||||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||||
{
|
{
|
||||||
return Some(state);
|
return Some(state);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct InputSettingsCommon {
|
|||||||
pub run_stick_threshold: FGi32,
|
pub run_stick_threshold: FGi32,
|
||||||
pub smash_input_frame_threshold: u8,
|
pub smash_input_frame_threshold: u8,
|
||||||
pub input_buffer_size: u8,
|
pub input_buffer_size: u8,
|
||||||
|
pub turn_into_dashback_window: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Reflect, Serialize, Deserialize, Resource, Asset, Clone, RonAsset)]
|
#[derive(Reflect, Serialize, Deserialize, Resource, Asset, Clone, RonAsset)]
|
||||||
|
|||||||
@@ -216,19 +216,6 @@ pub fn postprocess_input(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if x_abs >= axis_threshold && input.frames_in_x_flick_deadzone <= frame_threshold {
|
|
||||||
input.clear_command(FighterCommands::SmashMoveLeft);
|
|
||||||
input.clear_command(FighterCommands::SmashMoveRight);
|
|
||||||
|
|
||||||
let command_to_set = if input.current_frame.movement.x > 0.0 {
|
|
||||||
FighterCommands::SmashMoveRight
|
|
||||||
} else {
|
|
||||||
FighterCommands::SmashMoveLeft
|
|
||||||
};
|
|
||||||
|
|
||||||
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut y_flick_detected =
|
let mut y_flick_detected =
|
||||||
y_abs >= axis_threshold && input.frames_in_y_flick_deadzone <= frame_threshold;
|
y_abs >= axis_threshold && input.frames_in_y_flick_deadzone <= frame_threshold;
|
||||||
let mut x_flick_detected =
|
let mut x_flick_detected =
|
||||||
|
|||||||
+1
-4
@@ -272,10 +272,7 @@ fn read_local_inputs(
|
|||||||
Some((_, LocalInputSource::Gamepad(_))) => gamepad
|
Some((_, LocalInputSource::Gamepad(_))) => gamepad
|
||||||
.map(|pad| gamepad::sample_gamepad(pad, &input_map))
|
.map(|pad| gamepad::sample_gamepad(pad, &input_map))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
None => match gamepad {
|
None => FighterInputFrame::default()
|
||||||
Some(pad) => gamepad::sample_gamepad(pad, &input_map),
|
|
||||||
None => keyboard::sample_keyboard(&key, &input_map),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// preprocess frame
|
// preprocess frame
|
||||||
|
|||||||
@@ -141,6 +141,15 @@ impl FGSegment2d {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a value representing the normalized position along the segment.
|
||||||
|
pub fn project_point_normalized(&self, point: FGVec2) -> FGi32 {
|
||||||
|
let segment_vector = self.point2 - self.point1;
|
||||||
|
let offset = point - self.point1;
|
||||||
|
// The signed projection of `offset` onto `segment_vector`, scaled by the length of the segment.
|
||||||
|
let projection_scaled = segment_vector.dot(offset);
|
||||||
|
FGi32::from_num(FGWide::from_num(projection_scaled) / segment_vector.length_squared())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user