求助:Godot 4中仿BuilderDash/Supaplex的四向Tile-Based移动优化方案
四向Tile-Based网格移动优化方案(适配Godot 4)
针对你需要的类似BuilderDash/Supaplex的严格网格移动需求,核心要解决移动过程中禁止变向、强制网格对齐两个问题,以下是几种优化实现方案:
核心思路
- 加入移动状态锁:角色正在移动时拒绝新的输入,直到到达目标Tile
- 强制网格对齐:所有移动的起点和终点必须是网格整数倍坐标
- 用线性缓动实现匀速移动,贴合复古游戏的动画风格
方案一:CharacterBody2D实现(推荐)
CharacterBody2D本身适合角色移动,通过状态锁和网格对齐修复之前的问题:
extends CharacterBody2D @export var tile_size: Vector2 = Vector2(32, 32) @export var move_speed: float = 128.0 # 可根据游戏节奏调整 var is_moving: bool = false var target_position: Vector2 = Vector2.ZERO @onready var animated_sprite = $AnimatedSprite2D @onready var shape_cast = $ShapeCast2D # 需提前添加ShapeCast2D子节点,设置与角色相同的碰撞形状 func _ready(): # 初始化时强制对齐网格 snap_to_grid() func snap_to_grid(): position = position.snapped(tile_size) target_position = position func _input(event): # 移动中不响应新输入 if is_moving: return var direction = Vector2.ZERO if event.is_action_pressed("move_left"): direction = Vector2.LEFT animated_sprite.flip_h = false elif event.is_action_pressed("move_right"): direction = Vector2.RIGHT animated_sprite.flip_h = true elif event.is_action_pressed("move_up"): direction = Vector2.UP elif event.is_action_pressed("move_down"): direction = Vector2.DOWN if direction != Vector2.ZERO: target_position = position + direction * tile_size # 检测目标位置是否可通行 if is_position_valid(target_position): is_moving = true animated_sprite.play("running") func is_position_valid(pos: Vector2) -> bool: shape_cast.target_position = pos - position shape_cast.force_shapecast_update() return !shape_cast.is_colliding() func _physics_process(delta): if is_moving: var move_vector = (target_position - position).normalized() * move_speed * delta # 到达目标点时直接对齐网格,结束移动 if position.distance_to(target_position) <= move_vector.length(): position = target_position is_moving = false animated_sprite.stop() animated_sprite.play("idle") else: position += move_vector else: # 非移动状态下保持网格对齐,防止意外偏移 snap_to_grid()
方案优势
- 用
is_moving状态锁彻底解决快速按键导致的中途变向问题 - ShapeCast2D碰撞检测覆盖角色整个碰撞体,比RayCast更准确
- 物理帧驱动移动,适配不同帧率设备
方案二:Area2D+状态锁修复原代码
如果坚持用Area2D,只需添加状态锁并修改输入检测逻辑:
extends Area2D var tile_size = Vector2(32,32) var is_moving: bool = false var target_position: Vector2 = Vector2.ZERO @onready var raycast = $RayCast2D @onready var animated_sprite = $AnimatedSprite2D func _ready(): position = position.snapped(tile_size) target_position = position func _process(delta: float) -> void: if is_moving: var move_speed = 128.0 var move_vector = (target_position - position).normalized() * move_speed * delta # 到达目标点后结束移动 if position.distance_to(target_position) <= move_vector.length(): position = target_position is_moving = false animated_sprite.stop() animated_sprite.play("idle") else: position += move_vector return # 仅在按键按下瞬间响应输入,避免持续触发 if Input.is_action_just_pressed("move_left"): target_position = position + Vector2(-32,0) animated_sprite.play("running") animated_sprite.flip_h = false raycast.target_position = target_position - position raycast.force_raycast_update() if !raycast.is_colliding(): is_moving = true elif Input.is_action_just_pressed("move_right"): target_position = position + Vector2(32,0) animated_sprite.play("running") animated_sprite.flip_h = true raycast.target_position = target_position - position raycast.force_raycast_update() if !raycast.is_colliding(): is_moving = true elif Input.is_action_just_pressed("move_up"): target_position = position + Vector2(0,-32) animated_sprite.play("running") raycast.target_position = target_position - position raycast.force_raycast_update() if !raycast.is_colliding(): is_moving = true elif Input.is_action_just_pressed("move_down"): target_position = position + Vector2(0,32) animated_sprite.play("running") raycast.target_position = target_position - position raycast.force_raycast_update() if !raycast.is_colliding(): is_moving = true else: if !is_moving: animated_sprite.play("idle")
关键修改点
- 把
Input.is_action_pressed改为Input.is_action_just_pressed,避免持续触发移动 - 添加
is_moving状态锁,移动中忽略新输入 - 强制网格对齐初始化和目标位置
Tween动画优化
之前Tween动画不自然,是因为默认缓动曲线不符合复古游戏的匀速风格,改用线性缓动即可:
# 在触发移动时替换成以下代码 var tween = create_tween() tween.tween_property(self, "position", target_position, tile_size.x / move_speed) tween.set_trans(Tween.TRANS_LINEAR) # 线性匀速,贴合复古游戏手感 tween.finished.connect(): is_moving = false animated_sprite.stop() animated_sprite.play("idle")
额外注意事项
- 不要通过降低帧率解决问题,会导致其他动画异常,用状态锁和网格对齐就能彻底解决偏移问题
- 碰撞检测节点(ShapeCast/RayCast)要设置正确的碰撞层,避免检测到角色自身
内容的提问来源于stack exchange,提问作者LEmon
相关产品推荐
相关产品推荐

