You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 05:34:54