fix: 优化跳一跳运行态与地块资源
This commit is contained in:
@@ -6,7 +6,9 @@ use crate::{
|
||||
};
|
||||
|
||||
const JUMP_HOP_PLATFORM_SIZE_MULTIPLIER: f32 = 2.0;
|
||||
const JUMP_HOP_CHARGE_TO_DISTANCE_RATIO: f32 = 0.008;
|
||||
const JUMP_HOP_CHARGE_TO_DISTANCE_RATIO: f32 = 0.004;
|
||||
const JUMP_HOP_TOP_FACE_HITBOX_WIDTH_RATIO: f32 = 0.72;
|
||||
const JUMP_HOP_TOP_FACE_HITBOX_HEIGHT_RATIO: f32 = 0.52;
|
||||
|
||||
pub fn generate_jump_hop_path(seed: &str, difficulty: JumpHopDifficulty) -> JumpHopPath {
|
||||
let config = difficulty_config(difficulty);
|
||||
@@ -62,8 +64,8 @@ pub fn start_run(
|
||||
pub fn apply_jump(
|
||||
run: &JumpHopRunSnapshot,
|
||||
drag_distance: f32,
|
||||
drag_vector_x: Option<f32>,
|
||||
drag_vector_y: Option<f32>,
|
||||
_drag_vector_x: Option<f32>,
|
||||
_drag_vector_y: Option<f32>,
|
||||
jumped_at_ms: u64,
|
||||
) -> Result<JumpHopRunSnapshot, JumpHopError> {
|
||||
if run.status != JumpHopRunStatus::Playing {
|
||||
@@ -86,20 +88,15 @@ pub fn apply_jump(
|
||||
let vector_x = target.x - current.x;
|
||||
let vector_y = target.y - current.y;
|
||||
let target_distance = vector_x.hypot(vector_y).max(0.0001);
|
||||
let (unit_x, unit_y) = normalize_jump_direction(
|
||||
drag_vector_x,
|
||||
drag_vector_y,
|
||||
vector_x / target_distance,
|
||||
vector_y / target_distance,
|
||||
);
|
||||
let unit_x = vector_x / target_distance;
|
||||
let unit_y = vector_y / target_distance;
|
||||
let landed_x = current.x + unit_x * jump_distance;
|
||||
let landed_y = current.y + unit_y * jump_distance;
|
||||
let landing_error = (landed_x - target.x).hypot(landed_y - target.y);
|
||||
let target_landing_radius = target.landing_radius;
|
||||
let landed_on_target = is_landing_inside_platform_footprint(target, landed_x, landed_y);
|
||||
|
||||
let mut next = run.clone();
|
||||
next.path = path;
|
||||
let result = if landing_error <= target_landing_radius {
|
||||
let result = if landed_on_target {
|
||||
JumpHopJumpResultKind::Hit
|
||||
} else {
|
||||
JumpHopJumpResultKind::Miss
|
||||
@@ -128,6 +125,19 @@ pub fn apply_jump(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
fn is_landing_inside_platform_footprint(
|
||||
platform: &JumpHopPlatform,
|
||||
landed_x: f32,
|
||||
landed_y: f32,
|
||||
) -> bool {
|
||||
let half_width = (platform.width * 0.5 * JUMP_HOP_TOP_FACE_HITBOX_WIDTH_RATIO).max(0.0);
|
||||
let half_height = (platform.height * 0.5 * JUMP_HOP_TOP_FACE_HITBOX_HEIGHT_RATIO).max(0.0);
|
||||
let error_x = landed_x - platform.x;
|
||||
let error_y = landed_y - platform.y;
|
||||
|
||||
error_x.abs() <= half_width && error_y.abs() <= half_height
|
||||
}
|
||||
|
||||
pub fn restart_run(
|
||||
run: &JumpHopRunSnapshot,
|
||||
next_run_id: String,
|
||||
@@ -250,30 +260,6 @@ fn extend_jump_hop_path(mut path: JumpHopPath, required_count: usize) -> JumpHop
|
||||
path
|
||||
}
|
||||
|
||||
fn normalize_jump_direction(
|
||||
drag_vector_x: Option<f32>,
|
||||
drag_vector_y: Option<f32>,
|
||||
fallback_x: f32,
|
||||
fallback_y: f32,
|
||||
) -> (f32, f32) {
|
||||
let Some(drag_x) = drag_vector_x.filter(|value| value.is_finite()) else {
|
||||
return (fallback_x, fallback_y);
|
||||
};
|
||||
let Some(drag_y) = drag_vector_y.filter(|value| value.is_finite()) else {
|
||||
return (fallback_x, fallback_y);
|
||||
};
|
||||
// 前端提交的是屏幕拖拽向量:x 轴同向,y 轴向下为正。
|
||||
// 真实起跳需要“反向弹出”,同时把屏幕 y 翻回世界坐标的向上为正。
|
||||
let jump_x = -drag_x;
|
||||
let jump_y = drag_y;
|
||||
let length = jump_x.hypot(jump_y);
|
||||
if length < 0.0001 {
|
||||
(fallback_x, fallback_y)
|
||||
} else {
|
||||
(jump_x / length, jump_y / length)
|
||||
}
|
||||
}
|
||||
|
||||
fn difficulty_config(difficulty: JumpHopDifficulty) -> DifficultyConfig {
|
||||
match difficulty {
|
||||
JumpHopDifficulty::Easy => DifficultyConfig {
|
||||
@@ -353,8 +339,8 @@ impl DeterministicRng {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
JumpHopDifficulty, JumpHopJumpResultKind, JumpHopRunStatus, apply_jump,
|
||||
generate_jump_hop_path, restart_run, start_run,
|
||||
JumpHopDifficulty, JumpHopJumpResultKind, JumpHopPlatform, JumpHopRunStatus,
|
||||
JumpHopTileType, apply_jump, generate_jump_hop_path, restart_run, start_run,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -371,16 +357,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn difficulty_charge_to_distance_ratio_is_doubled() {
|
||||
fn difficulty_charge_to_distance_ratio_is_reduced_for_long_press() {
|
||||
let easy = generate_jump_hop_path("seed-ratio-easy", JumpHopDifficulty::Easy);
|
||||
let standard = generate_jump_hop_path("seed-ratio-standard", JumpHopDifficulty::Standard);
|
||||
let advanced = generate_jump_hop_path("seed-ratio-advanced", JumpHopDifficulty::Advanced);
|
||||
let challenge = generate_jump_hop_path("seed-ratio-challenge", JumpHopDifficulty::Challenge);
|
||||
let challenge =
|
||||
generate_jump_hop_path("seed-ratio-challenge", JumpHopDifficulty::Challenge);
|
||||
|
||||
assert_eq!(easy.scoring.charge_to_distance_ratio, 0.008);
|
||||
assert_eq!(standard.scoring.charge_to_distance_ratio, 0.008);
|
||||
assert_eq!(advanced.scoring.charge_to_distance_ratio, 0.008);
|
||||
assert_eq!(challenge.scoring.charge_to_distance_ratio, 0.008);
|
||||
assert_eq!(easy.scoring.charge_to_distance_ratio, 0.004);
|
||||
assert_eq!(standard.scoring.charge_to_distance_ratio, 0.004);
|
||||
assert_eq!(advanced.scoring.charge_to_distance_ratio, 0.004);
|
||||
assert_eq!(challenge.scoring.charge_to_distance_ratio, 0.004);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -454,7 +441,7 @@ mod tests {
|
||||
None,
|
||||
200,
|
||||
)
|
||||
.expect("jump should resolve");
|
||||
.expect("jump should resolve");
|
||||
assert_eq!(miss.status, JumpHopRunStatus::Failed);
|
||||
assert_eq!(
|
||||
miss.last_jump.as_ref().unwrap().result,
|
||||
@@ -463,7 +450,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_resolution_uses_screen_drag_y_axis_for_forward_jump_direction() {
|
||||
fn jump_resolution_ignores_client_drag_direction_and_targets_next_center() {
|
||||
let path = generate_jump_hop_path("seed-screen-axis", JumpHopDifficulty::Easy);
|
||||
let run = start_run(
|
||||
"run-screen-axis".to_string(),
|
||||
@@ -478,21 +465,49 @@ mod tests {
|
||||
let target_distance = (target.x - current.x).hypot(target.y - current.y);
|
||||
let charge = (target_distance / run.path.scoring.charge_to_distance_ratio).round() as u32;
|
||||
|
||||
let result = apply_jump(
|
||||
&run,
|
||||
charge as f32,
|
||||
Some(-(target.x - current.x)),
|
||||
Some(target.y - current.y),
|
||||
200,
|
||||
)
|
||||
.expect("jump should resolve");
|
||||
let result = apply_jump(&run, charge as f32, Some(-999.0), Some(-999.0), 200)
|
||||
.expect("jump should resolve");
|
||||
|
||||
let last_jump = result.last_jump.as_ref().expect("last jump should exist");
|
||||
assert_eq!(result.status, JumpHopRunStatus::Playing);
|
||||
assert_eq!(
|
||||
result.last_jump.as_ref().unwrap().result,
|
||||
JumpHopJumpResultKind::Hit
|
||||
);
|
||||
assert_eq!(last_jump.result, JumpHopJumpResultKind::Hit);
|
||||
assert_eq!(result.current_platform_index, 1);
|
||||
assert!((last_jump.landed_x - target.x).abs() < target.landing_radius);
|
||||
assert!((last_jump.landed_y - target.y).abs() < target.landing_radius);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_resolution_uses_visual_top_face_footprint_instead_of_landing_radius() {
|
||||
let mut path = generate_jump_hop_path("seed-footprint", JumpHopDifficulty::Easy);
|
||||
path.platforms[0] = test_platform("p0", 0.0, 0.0, 1.2, 1.0);
|
||||
path.platforms[1] = test_platform("p1", 1.0, 0.0, 2.0, 0.6);
|
||||
path.scoring.max_charge_ms = 600;
|
||||
let run = start_run(
|
||||
"run-footprint".to_string(),
|
||||
"user-footprint".to_string(),
|
||||
"profile-footprint".to_string(),
|
||||
path,
|
||||
100,
|
||||
)
|
||||
.expect("run should start");
|
||||
|
||||
let edge_hit_charge = 1.6 / run.path.scoring.charge_to_distance_ratio;
|
||||
let edge_hit =
|
||||
apply_jump(&run, edge_hit_charge, None, None, 200).expect("jump should resolve");
|
||||
let last_hit = edge_hit.last_jump.as_ref().expect("last jump should exist");
|
||||
assert_eq!(edge_hit.status, JumpHopRunStatus::Playing);
|
||||
assert_eq!(last_hit.result, JumpHopJumpResultKind::Hit);
|
||||
assert!(last_hit.landed_x > 1.5);
|
||||
assert!(last_hit.landed_x <= 1.72);
|
||||
|
||||
let outside_charge = 1.8 / run.path.scoring.charge_to_distance_ratio;
|
||||
let outside =
|
||||
apply_jump(&run, outside_charge, None, None, 200).expect("jump should resolve");
|
||||
assert_eq!(outside.status, JumpHopRunStatus::Failed);
|
||||
assert_eq!(
|
||||
outside.last_jump.as_ref().unwrap().result,
|
||||
JumpHopJumpResultKind::Miss
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -551,4 +566,18 @@ mod tests {
|
||||
assert!(run.path.platforms.len() >= 12);
|
||||
assert!(run.finished_at_ms.is_none());
|
||||
}
|
||||
|
||||
fn test_platform(id: &str, x: f32, y: f32, width: f32, height: f32) -> JumpHopPlatform {
|
||||
JumpHopPlatform {
|
||||
platform_id: id.to_string(),
|
||||
tile_type: JumpHopTileType::Normal,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
landing_radius: 0.2,
|
||||
perfect_radius: 0.1,
|
||||
score_value: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user