3. 玩家物理
2026/4/14大约 7 分钟
3. 超级玛丽奥——玩家物理
简介
玩家物理系统决定了玛丽奥移动和跳跃时的"手感"——跑起来顺不顺滑、跳起来高不高、落地时稳不稳。
这就像汽车的悬挂系统——好的悬挂让车开起来舒适,差的悬挂让你觉得颠簸。游戏物理也一样:好的物理参数让角色操控起来"手感好",差的物理参数让角色像在冰上走路一样不受控制。
核心物理概念
在开始写代码之前,先理解几个物理概念。别担心,我们用生活中的例子来解释:
加速度
想象你在推一辆静止的购物车——一开始推,车不会立刻达到最大速度,而是慢慢变快。这个"从慢到快"的过程就是加速。
不按方向键 → 速度为0
按住方向键 → 速度逐渐增加 → 达到最大速度摩擦力
松开方向键后,玛丽奥不会立刻停下,而是会滑行一小段距离后慢慢停下来。这个"阻止滑动"的力就是摩擦力。就像在冰面上滑冰——松开脚后还会往前滑一段。
松开方向键 → 速度逐渐减小 → 速度为0(停下)重力
玛丽奥跳起来后,会被一股看不见的力量拉回地面。这就是重力。就像你扔一个球,球不会一直往上飞,而是飞到一定高度后掉回来。
跳跃中 → 速度逐渐减小 → 到达最高点 → 速度变为向下 → 加速下落 → 落地可变高度跳跃
这是平台跳跃游戏最重要的机制:按跳跃键的时间长短决定跳的高度。
- 轻点跳跃键:玛丽奥只跳一个小高度(比如刚好跳过一个矮的敌人)
- 长按跳跃键:玛丽奥跳到最大高度(比如跳到高处的平台)
原理:当你松开跳跃键时,如果玛丽奥还在往上飞,就立刻把向上的速度"砍掉",让玛丽奥开始下落。
轻点跳跃键: 跳 → 立刻松开 → 速度被砍掉 → 快速下落(低跳)
长按跳跃键: 跳 → 持续按住 → 速度自然减小 → 到达顶点 → 下落(高跳)物理参数配置
| 参数 | 值 | 说明 |
|---|---|---|
| 最大跑步速度 | 200 像素/秒 | 正常跑步的最大速度 |
| 最大奔跑速度 | 320 像素/秒 | 按住Shift时的最大速度 |
| 加速度 | 1200 像素/秒^2 | 从静止加速到最大速度的快慢 |
| 摩擦力 | 600 像素/秒^2 | 松开方向键后减速的快慢 |
| 跳跃力 | -400 像素/秒 | 跳跃时初始向上的速度 |
| 重力 | 980 像素/秒^2 | 向下拉的力量 |
| 可变跳跃截止速度 | -100 像素/秒 | 松开跳跃键时的速度上限 |
玩家物理脚本
// Player.cs - 玩家物理和移动
using Godot;
public partial class Player : CharacterBody2D
{
// ========== 物理常量 ==========
[ExportGroup("Movement")]
[Export] public float MaxRunSpeed { get; set; } = 200.0f;
[Export] public float MaxSprintSpeed { get; set; } = 320.0f;
[Export] public float Acceleration { get; set; } = 1200.0f;
[Export] public float Friction { get; set; } = 600.0f;
[ExportGroup("Jump")]
[Export] public float JumpVelocity { get; set; } = -400.0f;
[Export] public float VariableJumpCutoff { get; set; } = -100.0f;
[ExportGroup("Gravity")]
[Export] public float Gravity { get; set; } = 980.0f;
// ========== 状态变量 ==========
private bool _isOnFloor = false;
private bool _wasOnFloor = false;
private bool _isJumping = false;
private bool _isRunning = false;
// ========== 节点引用 ==========
private AnimatedSprite2D _animatedSprite;
private CollisionShape2D _collisionShape;
private Vector2 _originalScale;
// ========== 生命周期 ==========
public override void _Ready()
{
_animatedSprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
_originalScale = Scale;
// 设置初始碰撞形状
UpdateCollisionShape();
}
// ========== 物理帧更新 ==========
public override void _PhysicsProcess(double delta)
{
if (GameManager.Instance.CurrentState != GameManager.GameState.Playing)
return;
float dt = (float)delta;
// 记住上一帧是否在地面
_wasOnFloor = _isOnFloor;
// 1. 应用重力
ApplyGravity(dt);
// 2. 处理水平移动(加速度和摩擦力)
HandleHorizontalMovement(dt);
// 3. 处理跳跃
HandleJump();
// 4. 应用移动
_isOnFloor = MoveAndSlide();
// 5. 更新动画
UpdateAnimation();
// 6. 检测落地
if (_isOnFloor && !_wasOnFloor)
{
OnLanded();
}
}
// ========== 重力 ==========
private void ApplyGravity(float delta)
{
if (!IsOnFloor())
{
// 在空中时,持续向下加速
Velocity += new Vector2(0, Gravity * delta);
}
}
// ========== 水平移动 ==========
private void HandleHorizontalMovement(float delta)
{
// 获取输入方向(-1=左, 0=不动, 1=右)
float inputDirection = Input.GetAxis("move_left", "move_right");
// 检查是否按住奔跑键
_isRunning = Input.IsActionPressed("run");
// 确定当前最大速度
float maxSpeed = _isRunning ? MaxSprintSpeed : MaxRunSpeed;
if (inputDirection != 0)
{
// 有输入:向输入方向加速
Velocity = new Vector2(
Mathf.MoveToward(Velocity.X, inputDirection * maxSpeed, Acceleration * delta),
Velocity.Y
);
// 朝向移动方向
_animatedSprite.FlipH = (inputDirection < 0);
}
else
{
// 没有输入:应用摩擦力减速
Velocity = new Vector2(
Mathf.MoveToward(Velocity.X, 0, Friction * delta),
Velocity.Y
);
}
}
// ========== 跳跃 ==========
private void HandleJump()
{
// 按下跳跃键
if (Input.IsActionJustPressed("jump") && IsOnFloor())
{
// 开始跳跃
Velocity = new Vector2(Velocity.X, JumpVelocity);
_isJumping = true;
}
// 松开跳跃键(可变高度跳跃的核心)
if (Input.IsActionJustReleased("jump") && _isJumping)
{
// 如果当前向上的速度超过了截止速度,就截断它
if (Velocity.Y < VariableJumpCutoff)
{
Velocity = new Vector2(Velocity.X, VariableJumpCutoff);
}
_isJumping = false;
}
}
// ========== 动画更新 ==========
private void UpdateAnimation()
{
if (!IsOnFloor())
{
// 在空中
if (Velocity.Y < 0)
{
_animatedSprite.Play("jump"); // 上升
}
else
{
_animatedSprite.Play("fall"); // 下落
}
}
else if (Mathf.Abs(Velocity.X) > 10)
{
// 在地面且在移动
if (_isRunning)
{
_animatedSprite.Play("run"); // 奔跑
}
else
{
_animatedSprite.Play("walk"); // 走路
}
}
else
{
// 静止
_animatedSprite.Play("idle"); // 站立
}
}
// ========== 落地事件 ==========
private void OnLanded()
{
_isJumping = false;
// 可以在这里播放落地音效或粒子效果
}
// ========== 碰撞形状更新(变大/变小用) ==========
private void UpdateCollisionShape()
{
// 小玛丽奥的碰撞形状
// 大玛丽奥的碰撞形状会更高
}
}# player.gd - 玩家物理和移动
extends CharacterBody2D
# ========== 物理常量 ==========
@export_group("Movement")
@export var max_run_speed: float = 200.0
@export var max_sprint_speed: float = 320.0
@export var acceleration: float = 1200.0
@export var friction: float = 600.0
@export_group("Jump")
@export var jump_velocity: float = -400.0
@export var variable_jump_cutoff: float = -100.0
@export_group("Gravity")
@export var gravity: float = 980.0
# ========== 状态变量 ==========
var is_on_floor_flag: bool = false
var was_on_floor: bool = false
var is_jumping: bool = false
var is_running: bool = false
# ========== 节点引用 ==========
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var collision_shape: CollisionShape2D = $CollisionShape2D
# ========== 生命周期 ==========
func _ready() -> void:
update_collision_shape()
# ========== 物理帧更新 ==========
func _physics_process(delta: float) -> void:
if GameManager.current_state != GameManager.GameState.PLAYING:
return
# 记住上一帧是否在地面
was_on_floor = is_on_floor_flag
# 1. 应用重力
apply_gravity(delta)
# 2. 处理水平移动(加速度和摩擦力)
handle_horizontal_movement(delta)
# 3. 处理跳跃
handle_jump()
# 4. 应用移动
is_on_floor_flag = move_and_slide()
# 5. 更新动画
update_animation()
# 6. 检测落地
if is_on_floor_flag and not was_on_floor:
on_landed()
# ========== 重力 ==========
func apply_gravity(delta: float) -> void:
if not is_on_floor():
# 在空中时,持续向下加速
velocity += Vector2(0, gravity * delta)
# ========== 水平移动 ==========
func handle_horizontal_movement(delta: float) -> void:
# 获取输入方向(-1=左, 0=不动, 1=右)
var input_direction: float = Input.get_axis("move_left", "move_right")
# 检查是否按住奔跑键
is_running = Input.is_action_pressed("run")
# 确定当前最大速度
var max_speed: float = max_sprint_speed if is_running else max_run_speed
if input_direction != 0:
# 有输入:向输入方向加速
velocity = Vector2(
move_toward(velocity.x, input_direction * max_speed, acceleration * delta),
velocity.y
)
# 朝向移动方向
animated_sprite.flip_h = (input_direction < 0)
else:
# 没有输入:应用摩擦力减速
velocity = Vector2(
move_toward(velocity.x, 0, friction * delta),
velocity.y
)
# ========== 跳跃 ==========
func handle_jump() -> void:
# 按下跳跃键
if Input.is_action_just_pressed("jump") and is_on_floor():
# 开始跳跃
velocity = Vector2(velocity.x, jump_velocity)
is_jumping = true
# 松开跳跃键(可变高度跳跃的核心)
if Input.is_action_just_released("jump") and is_jumping:
# 如果当前向上的速度超过了截止速度,就截断它
if velocity.y < variable_jump_cutoff:
velocity = Vector2(velocity.x, variable_jump_cutoff)
is_jumping = false
# ========== 动画更新 ==========
func update_animation() -> void:
if not is_on_floor():
# 在空中
if velocity.y < 0:
animated_sprite.play("jump") # 上升
else:
animated_sprite.play("fall") # 下落
elif absf(velocity.x) > 10:
# 在地面且在移动
if is_running:
animated_sprite.play("run") # 奔跑
else:
animated_sprite.play("walk") # 走路
else:
# 静止
animated_sprite.play("idle") # 站立
# ========== 落地事件 ==========
func on_landed() -> void:
is_jumping = false
# ========== 碰撞形状更新 ==========
func update_collision_shape() -> void:
# 小玛丽奥的碰撞形状
# 大玛丽奥的碰撞形状会更高
pass可变高度跳跃原理详解
这是最重要的物理机制,让我们详细解释:
时间轴:
────────────────────────────────────────→ 时间
按住跳跃键:
速度 ↑
| /\
| / \
|/ \ ← 速度自然减小到0,然后变为向下
| \
| \ ← 重力让下落速度越来越快
─────┴────────┴──────────→
A B C D
A点:按下跳跃键,速度瞬间变为 -400(向上)
B点:速度减到 -200(还在上升)
C点:速度减到 0(到达最高点)
D点:速度变为正值(开始下落)
轻点跳跃键:
速度 ↑
| /\
| / \ ← 在B点松开跳跃键
|/ X ← 速度被截断为 -100
| \
| \ ← 立刻开始下落
─────┴────────┴──────────→
A B X
松开跳跃键时:
如果当前速度 < -100(还在快速上升),强制设为 -100
如果当前速度 >= -100(已经接近最高点),不改变玩家场景配置
在 Godot 中创建 scenes/player.tscn:
Player (CharacterBody2D) # 使用 CharacterBody2D 处理物理
├── AnimatedSprite2D # 动画精灵
│ └── SpriteFrames # 精灵帧
│ ├── idle(站立) # 1帧
│ ├── walk(走路) # 3帧循环
│ ├── run(奔跑) # 3帧循环(速度更快)
│ ├── jump(跳跃) # 1帧
│ └── fall(下落) # 1帧
│
├── CollisionShape2D # 碰撞形状
│ └── RectangleShape2D # 矩形(16x16 或 16x32)
│
└── Camera2D # 摄像机(跟随玩家)
├── Position Smoothing: true
└── Smoothing Speed: 5碰撞层设置
| 节点 | Layer | Mask | 说明 |
|---|---|---|---|
| Player | 第2层 (Player) | 第1, 3, 6, 7层 | 检测地面、敌人、砖块、边界 |
| Camera2D | - | - | 不需要碰撞 |
下一章预告
玩家物理系统完成了!玛丽奥可以跑、跳、在空中控制高度。下一章我们将实现敌人交互——蘑菇怪和乌龟的AI,以及最重要的"踩踏判定"。
