Godot引擎角色物理控制:从CharacterBody2D到移动、碰撞与手感优化

发布时间:2026/7/12 12:49:12

Godot引擎角色物理控制:从CharacterBody2D到移动、碰撞与手感优化 1. 项目概述从标题拆解核心诉求“Godot引擎开发角色移动与物理碰撞_案例分析实现具有物理碰撞的角色控制系统”这个标题信息量其实非常大。它直接点出了游戏开发中一个最基础、也最核心的交互模块让角色动起来并且能正确地与游戏世界发生碰撞。乍一看这似乎是个简单的需求——“不就是让角色走两步别穿墙吗”但做过实际项目的老手都知道这里面藏着无数个“坑”。角色的移动是平滑的还是卡顿的上坡下坡时速度是否一致碰到墙角会不会鬼畜抖动从斜坡滑落时感觉是否自然这些细节直接决定了玩家对游戏操作手感的第一印象也是区分“玩具Demo”和“可玩产品”的关键门槛。这个标题隐含了几个递进式的核心诉求移动角色需要响应玩家的输入键盘、手柄、触摸按照预期方向进行位移。碰撞位移不能无视环境角色需要与墙壁、地面、障碍物等发生交互被“阻挡”或产生其他物理反应。控制系统这不仅仅是移动和碰撞的简单叠加而是一个有机的整体。它要求移动逻辑能智能地处理碰撞结果比如沿墙滑动碰撞检测能准确地反馈给移动逻辑比如判断是否在地面最终形成一个稳定、可靠、手感舒适的操作体验。在Godot引擎的语境下实现这个“控制系统”的核心就是围绕CharacterBody2D对于2D项目或CharacterBody3D对于3D项目节点展开的。它被设计为专门处理这类“受玩家控制且需要复杂碰撞响应”的物理对象。与RigidBody刚体完全由物理引擎模拟和StaticBody静态体不动如山不同CharacterBody将移动的控制权交给了开发者同时提供了强大的工具来处理碰撞后的响应。接下来的内容我将以一个典型的2D平台游戏角色为例带你从零开始一步步拆解这个“角色控制系统”的构建过程。我们会涵盖从节点结构设计、输入处理、移动逻辑编写到碰撞响应优化、常见问题排查等全流程。目标是让你不仅能做出一个能跑能跳的角色更能理解其背后的每一个决策和原理从而具备解决实际开发中各种疑难杂症的能力。2. 核心节点解析为什么是CharacterBody2D在Godot中处理物理和碰撞的节点有好几种选择CharacterBody2D作为角色控制的核心绝非偶然。我们需要先理解它与其他物理节点的本质区别才能用好它。2.1 物理体三兄弟Static, Rigid, CharacterStaticBody2D静态体这是场景中固定不动的部分比如地面、墙壁、固定的平台。它参与碰撞检测但自身不会被物理引擎推动。你可以把它理解为物理世界的“背景”或“规则制定者”。角色、子弹等动态物体会与它发生碰撞。RigidBody2D刚体这是完全由物理引擎模拟的物体。你给它一个力重力、推力物理引擎就会根据质量、摩擦力、弹性等属性计算出它的运动轨迹。它适合模拟箱子、球、破碎的物体等。你不应该直接设置它的position而是通过施加力或冲量来影响它。对于需要精确控制的玩家角色来说这太“自由”了容易失控。CharacterBody2D角色体这是我们的主角。它介于两者之间它检测碰撞但不受物理引擎的动力学模拟。这意味着重力不会自动作用在它身上碰撞也不会自动让它弹开。它的每一次移动都必须由你通过代码来驱动通常是move_and_slide()或move_and_collide()方法。同时它会详细地报告碰撞信息让你可以基于这些信息编写复杂的响应逻辑比如“在地面上才能跳跃”、“沿着墙壁滑动”、“爬上斜坡”。简单来说CharacterBody2D把“是否移动”和“如何移动”的决定权交给了你而把“是否发生碰撞”和“碰撞的详细信息”提供给你。这种“半手动”模式正是实现灵活、可靠角色控制所需要的。2.2 CharacterBody2D的关键属性与方法在动手写代码前先熟悉一下这个节点的几个核心“武器”velocity向量这是角色的速度向量。在_physics_process(delta)函数中我们主要就是通过修改这个向量然后调用移动方法来驱动角色。例如velocity.x控制左右速度velocity.y控制垂直速度下落或跳跃。move_and_slide()方法这是最常用、功能最强大的移动方法。它会根据当前的velocity尝试移动角色并自动处理与场景中其他PhysicsBody2D和Area2D的碰撞。碰撞发生后它会自动调整角色的最终位置并更新velocity向量例如撞墙后水平速度归零。它还提供了一系列便捷的属性用于查询状态。move_and_collide()方法这个方法更“原始”一些。它沿着给定的向量移动角色一旦发生碰撞就立即停止并返回一个KinematicCollision2D对象。这个对象包含了碰撞点、法线、碰撞体等信息。你需要手动处理碰撞后的逻辑比如计算反弹。它适合用于子弹、射线等简单的一次性碰撞检测。is_on_floor()/is_on_wall()/is_on_ceiling()方法这些是move_and_slide()提供的状态查询方法非常关键。它们告诉你角色当前是否接触着地面、墙壁或天花板。这是实现跳跃、爬墙、蹬墙跳等动作的基础。get_slide_collision_count()和get_slide_collision()方法当一次move_and_slide()调用中发生多次碰撞时比如同时撞到墙角和地面可以用这些方法遍历所有碰撞信息进行更精细的处理。up_direction向量默认是Vector2.UP(0, -1)。它定义了什么是角色的“上方”。is_on_floor()等方法就是根据碰撞面法线与这个方向的夹角来判断的。如果你在做太空游戏或可以爬墙的游戏可能需要修改这个值。floor_max_angle浮点数默认约 0.785 弧度45度。它定义了多大的斜坡角色仍然可以视为“地面”并站在上面。大于这个角度的斜坡角色会滑下来。理解了这些我们就有了搭建角色控制系统的“原材料”。接下来我们开始组装。3. 构建角色控制系统从节点树到核心脚本让我们从一个具体的平台跳跃角色案例开始。我们的目标是用键盘A/D或左右箭头控制左右移动空格键跳跃角色能稳定地站在地面上能走上斜坡从平台边缘会自然下落。3.1 场景节点结构设计一个健壮的角色场景节点结构应该清晰且有扩展性。我推荐以下结构Character (CharacterBody2D) ├── Sprite2D (或 AnimatedSprite2D) │ └── 角色的视觉表现 ├── CollisionShape2D (或 CollisionPolygon2D) │ └── 角色的物理碰撞形状如矩形、胶囊形 ├── Camera2D (可选) │ └── 角色的跟随相机 └── 其他辅助节点如粒子效果、音效播放器为什么这么设计CharacterBody2D 作为根节点所有物理和移动逻辑都挂载在这个节点上。它的position就是角色在世界中的位置。视觉Sprite和碰撞CollisionShape分离这是非常重要的原则。碰撞形状应该紧密贴合角色的运动逻辑而不是完全贴合美术资源的外形。对于一个平台跳跃角色一个简单的矩形或胶囊形CapsuleShape2D通常比精确的多边形更稳定能有效减少卡在微小缝隙或墙角抖动的问题。Camera2D 作为子节点这样相机会自动跟随角色移动实现简单的跟随效果。你可以在 Camera2D 上设置平滑、边界等属性。实操心得碰撞形状的选择对于2D平台角色CapsuleShape2D胶囊形通常是比RectangleShape2D矩形更好的选择。因为胶囊形的两端是半圆在从平台边缘下落或走上斜坡时能提供更平滑的过渡减少“卡顿”感。在Godot中创建CapsuleShape2D后记得在检查器中调整height和radius属性来匹配你的精灵图。3.2 编写核心移动脚本现在我们将脚本附加到CharacterBody2D根节点上。以下是完整的、带有详细注释的GDScript代码我会逐段解释extends CharacterBody2D # 1. 定义可调参数导出变量方便在编辑器中实时调整 export var max_speed: float 300.0 # 最大水平移动速度像素/秒 export var acceleration: float 1500.0 # 水平加速度像素/秒^2 export var friction: float 1200.0 # 地面摩擦减速度像素/秒^2 export var air_friction: float 300.0 # 空中水平阻力像素/秒^2 export var jump_velocity: float -400.0 # 跳跃初速度向上为负 export var gravity: float 980.0 # 重力加速度像素/秒^2 export var double_jump_velocity: float -350.0 # 二段跳速度可选 # 2. 状态变量 var has_double_jumped: bool false var is_jumping: bool false func _physics_process(delta: float) - void: # 3. 应用重力无论是否在地面持续作用 velocity.y gravity * delta # 4. 获取玩家输入 var input_direction : Input.get_axis(move_left, move_right) # 5. 处理水平移动逻辑 _handle_horizontal_movement(input_direction, delta) # 6. 处理跳跃逻辑 _handle_jump() # 7. 执行移动与碰撞检测 move_and_slide() # 8. 移动后更新状态特别是接地状态 _update_state_after_move() # --- 辅助函数 --- func _handle_horizontal_movement(input_dir: float, delta: float) - void: if input_dir ! 0: # 有输入时向目标速度加速 var target_speed input_dir * max_speed # 计算加速所需的力。sign()确保力的方向正确。 var speed_diff target_speed - velocity.x var accel acceleration if is_on_floor() else air_friction velocity.x speed_diff * accel * delta # 使用 clamp 防止速度轻微超出目标值导致的抖动 velocity.x clamp(velocity.x, -max_speed, max_speed) else: # 无输入时应用摩擦减速 var decel friction if is_on_floor() else air_friction # 逐渐将速度减至0避免瞬间停止的生硬感 velocity.x move_toward(velocity.x, 0, decel * delta) func _handle_jump() - void: # 在地面时重置跳跃状态 if is_on_floor(): has_double_jumped false is_jumping false # 检测跳跃按键刚刚按下 if Input.is_action_just_pressed(jump): if is_on_floor(): # 地面跳跃 velocity.y jump_velocity is_jumping true elif not has_double_jumped: # 空中二段跳 velocity.y double_jump_velocity has_double_jumped true is_jumping true # 跳跃手感优化短按跳得低长按跳得高 if is_jumping and Input.is_action_just_released(jump) and velocity.y 0: # 松开跳跃键时如果还在上升则削减Y轴速度实现“可变高度跳跃” velocity.y * 0.6 func _update_state_after_move() - void: # 移动后如果正在下落且碰到地面则结束跳跃状态 if is_on_floor() and velocity.y 0: is_jumping false代码深度解析导出变量使用export将关键参数暴露在编辑器的检查器中。这太有用了你可以在游戏运行时动态调整这些值实时感受不同重力、跳跃力度对手感的影响快速找到最佳参数。重力应用注意重力是持续累加到velocity.y上的。这模拟了现实中的重力加速度。即使角色在地面上我们也在计算重力但move_and_slide()会通过碰撞阻止角色穿透地面并将垂直速度适当归零取决于floor_stop_on_slope等参数。输入处理Input.get_axis(“move_left”, “move_right”)返回一个介于 -1 到 1 之间的浮点数。这比分别检查两个按键更优雅天然支持手柄摇杆并且自动处理了同时按下左右键相互抵消结果为0的情况。你需要在项目设置 - 输入映射中预先定义好 “move_left”, “move_right”, “jump” 这些动作。移动逻辑这是手感的核心。我们没有简单地将速度设置为input_direction * max_speed而是采用了加速度和摩擦模型。加速当玩家按下方向键我们计算当前速度与目标速度的差值然后乘以加速度和delta逐步逼近目标速度。这会产生一个平滑的启动过程而不是瞬间达到全速。摩擦当玩家松开按键我们使用move_toward()函数将水平速度逐渐向0衰减。地面摩擦 (friction) 通常比空中阻力 (air_friction) 大得多这样角色在空中时会有一些惯性落地后能快速停止。跳跃逻辑is_action_just_pressed()确保只在按键按下的那一帧触发跳跃防止按住空格键连续跳。通过is_on_floor()判断是否允许起跳这是平台游戏的基础。可变高度跳跃通过检测跳跃键的释放并削减上升速度实现了“按得久跳得高按得短跳得低”的经典手感。velocity.y * 0.6这个系数可以调整值越小松开键后速度削减得越厉害。move_and_slide()这是所有计算的集大成者。它接收我们计算好的velocity向量尝试移动角色。过程中发生的所有碰撞都会被处理撞到墙velocity.x会被置零落到地面velocity.y会被限制通常置零或一个很小的正值碰到斜坡它会自动沿着斜坡表面调整移动方向。调用完毕后is_on_floor()等状态会被更新为我们下一帧的逻辑提供依据。3.3 配置输入映射与碰撞层输入映射Project Settings - Input Map添加move_left动作并分配按键A和Left Arrow。添加move_right动作并分配按键D和Right Arrow。添加jump动作并分配按键Space和Up Arrow根据习惯。碰撞层与遮罩Collision Layer/Mask 这是确保角色只与应该碰撞的物体交互的关键。在CharacterBody2D的检查器中Layer层将角色放在一个独立的层例如第2层。这表示“我是谁”。Mask遮罩勾选地面、墙壁等环境物体所在的层例如第1层。这表示“我会与谁碰撞”。不要勾选角色自身的层除非你需要角色之间相互碰撞比如对战游戏。同样你的地面、墙壁等StaticBody2D或TileMap它也有碰撞层属性其Mask需要勾选角色所在的层第2层。这样双方才能“看见”彼此发生碰撞。4. 高级碰撞处理与手感打磨基础移动跑起来后我们会遇到各种边缘情况。下面是一些提升操作手感和稳定性的高级技巧。4.1 斜坡与楼梯处理默认情况下move_and_slide()已经能处理斜坡。关键在于floor_max_angle和floor_stop_on_slope这两个属性。floor_max_angle设为45度约0.785弧度左右意味着小于45度的斜坡角色可以走上去大于45度的会滑下来。floor_stop_on_slope建议设为true。这能防止角色在轻微斜坡上因重力产生微小的向下滑动使其能稳定站立。但是对于楼梯或网格状平台角色可能会被“卡住”因为碰撞形状的边缘会撞到台阶的垂直面。解决方案是使用RayCast2D或ShapeCast2D进行预处理。使用 RayCast2D 实现台阶检测 在角色节点下添加一个向下的RayCast2D节点。将其target_position设置为一个较小的值比如Vector2(0, 20)并启用。在跳跃前或移动前检查func _handle_jump() - void: # ... 原有的地面检查 ... if Input.is_action_just_pressed(jump): # 先检查标准地面 if is_on_floor(): velocity.y jump_velocity # 再检查如果没在地面但射线检测到可站立物如下一级台阶也允许跳跃 elif $StepRayCast.is_colliding(): # 可以将角色瞬间“拉”到碰撞点上方实现自动上台阶 # 或者直接允许跳跃 velocity.y jump_velocityShapeCast2D更强大它投射的是整个形状可以检测前方是否有空间让角色通过常用于处理狭窄通道或悬挂边缘。4.2 墙体跳跃与爬墙实现墙体跳跃Wall Jump和爬墙Wall Slide能极大丰富游戏玩法。核心是检测侧向碰撞并区分左右墙。检测墙体move_and_slide()后使用is_on_wall()判断。但为了区分左右我们需要检查碰撞详情。获取碰撞法线在_physics_process中move_and_slide()之后# 在 move_and_slide() 之后 for i in get_slide_collision_count(): var collision get_slide_collision(i) var normal collision.get_normal() # 如果碰撞法线大致水平说明撞到的是墙 if abs(normal.x) 0.7: # 0.7是个经验值对应约45度 if normal.x 0: print(撞到右墙) # 可以在这里设置一个状态比如 is_on_right_wall true else: print(撞到左墙) # is_on_left_wall true # 墙体跳跃如果撞墙且按下跳跃键可以给一个反向的力 if Input.is_action_just_pressed(jump): velocity.y jump_velocity * 0.8 # 跳跃高度稍低 velocity.x -normal.x * 300 # 给一个远离墙的水平速度 break # 跳出循环避免多次响应爬墙在检测到墙体后可以修改重力或摩擦系数让角色下落变慢实现“贴墙滑落”的效果。var wall_gravity gravity * 0.3 # 爬墙时重力减小 var normal_gravity gravity func _physics_process(delta): if is_on_wall() and velocity.y 0: # 在墙上且正在下落 velocity.y wall_gravity * delta # 还可以额外加一个向墙的微小速度让贴墙感更强 # velocity.x move_toward(velocity.x, 0, air_friction * delta) # 取消水平惯性 else: velocity.y normal_gravity * delta4.3 coyote time 与 jump buffer这是两个能极大提升操作宽容度、让游戏感觉更“顺滑”的技巧几乎所有现代平台游戏都在用。Coyote Time土狼时间指角色离开平台边缘后短时间内仍然允许跳跃。这解决了玩家因为按跳跃键时机稍晚实际上角色已离地几个像素而导致的失败感。var coyote_time 0.1 # 100毫秒 var coyote_timer 0.0 var was_on_floor false func _physics_process(delta): # 记录上一帧是否在地面 was_on_floor is_on_floor() # ... 执行移动 move_and_slide() ... # 移动后如果刚离开地面启动计时器 if was_on_floor and not is_on_floor() and velocity.y 0: coyote_timer coyote_time # 更新计时器 if coyote_timer 0: coyote_timer - delta # 在跳跃判断中加入土狼时间条件 if Input.is_action_just_pressed(jump): if is_on_floor() or coyote_timer 0: velocity.y jump_velocity coyote_timer 0.0 # 跳跃后重置Jump Buffer跳跃缓冲指在角色落地前的短时间内如果玩家按下了跳跃键这个指令会被“缓存”起来并在角色落地时自动执行。这解决了玩家因为按跳跃键时机稍早角色还在空中而导致的失败感。var jump_buffer_time 0.1 # 100毫秒 var jump_buffer_timer 0.0 func _physics_process(delta): # 检测跳跃按键按下即开始缓冲 if Input.is_action_just_pressed(jump): jump_buffer_timer jump_buffer_time # 更新缓冲计时器 if jump_buffer_timer 0: jump_buffer_timer - delta # 如果缓冲期内角色落地则执行跳跃 if is_on_floor(): velocity.y jump_velocity jump_buffer_timer 0.0 # ... 原有的跳跃逻辑处理空中按跳跃键的情况...将两者结合角色的跳跃操作会变得非常跟手玩家会感觉游戏响应灵敏容错率高。5. 常见问题排查与性能优化即使逻辑正确在实际运行中也可能遇到各种奇怪的问题。这里有一个我多年踩坑总结出来的排查清单。5.1 问题排查速查表现象可能原因解决方案角色抖动或卡进地面/墙壁1. 碰撞形状与精灵图不匹配太大或太小。2. 每帧移动距离过大穿透了薄物体。3.move_and_slide()的max_slides参数太小默认4复杂碰撞下可能不够。1. 在编辑器中可视化碰撞形状调试 - 可见碰撞形状确保其大小和位置合理。2. 确保velocity * delta不会过大。对于高速物体考虑使用move_and_collide()或增加物理帧率。3. 适当增加max_slides如改为8但会略微增加计算量。角色在斜坡上滑落或不稳定1.floor_max_angle设置过小。2.floor_stop_on_slope为false。3. 斜坡碰撞面不平滑如由多个小矩形瓦片组成。1. 增大floor_max_angle例如到50度。2. 确保floor_stop_on_slope true。3. 对于瓦片地图考虑使用TileMap的自动生成碰撞多边形功能或手动创建更平滑的StaticBody2D碰撞体。is_on_floor()偶尔检测失败1. 角色移动速度过快单帧越过了地面。2. 地面是Area2D而非PhysicsBody2D。3. 角色与地面的碰撞层/遮罩未正确设置。1. 如前所述检查速度。可考虑增加物理帧率项目设置 - 物理 - 公共 - 物理FPS。2.is_on_floor()只检测与PhysicsBody2D的碰撞。确保地面是StaticBody2D或TileMap其物理层。3. 双击检查器中的层/遮罩图标确保双方互相“可见”。角色移动有延迟或粘滞感1. 输入处理放在了_process()而非_physics_process()中。2. 加速度/摩擦值设置过大导致速度变化过于“生硬”。3. 使用了get_action_strength()等函数但未正确配置手柄死区。1.所有物理相关操作尤其是读取输入驱动CharacterBody2D必须放在_physics_process(delta)中2. 调小acceleration和friction让加速和减速过程更平滑。3. 在项目设置的输入映射中为手柄摇杆配置死区Deadzone。高速移动时穿过薄墙这是经典的“子弹穿过纸张”问题。单帧移动距离超过了墙的厚度。1. 对于角色确保max_speed * delta不会太大。如果必须高速考虑增加物理FPS。2. 对于子弹等高速物不要用move_and_slide。改用move_and_collide()并使用safe_margin参数Godot 4.1或使用RayCast2D进行连续碰撞检测CCD。5.2 性能优化要点碰撞形状尽量简单圆形、矩形、胶囊形的性能远优于复杂的凹多边形ConcavePolygonShape2D。对于角色一个胶囊形或矩形通常就够了。合理使用碰撞层不要让你的角色去检测它根本不需要碰撞的层。例如背景装饰物、远处的敌人等应该放在不同的层并在角色的碰撞遮罩中取消勾选。避免在_physics_process中进行复杂计算或频繁的资源操作这里是性能敏感区。如果需要加载资源、进行复杂AI计算尽量放在_process()中或者分帧处理。利用PhysicsServer2D进行自定义查询如果你需要大量进行射线检测或形状检测比如敌人的视野检测直接使用PhysicsServer2D的API如intersect_ray可能比创建很多RayCast2D节点更高效。调试可视化Godot的调试菜单调试 - 可见碰撞形状、调试 - 可见导航是你的好朋友。在开发阶段打开它们可以直观地看到碰撞体的实际位置和大小快速定位问题。5.3 一个更健壮的移动函数封装最后分享一个我常用的、经过打磨的移动函数封装。它整合了加速度模型、空中控制、以及更精确的落地检测解决某些边缘情况下is_on_floor()不可靠的问题。extends CharacterBody2D export var max_speed: float 300.0 export var acceleration: float 1500.0 export var deceleration: float 1200.0 export var air_control: float 0.5 # 空中控制力系数 (0-1) export var jump_velocity: float -400.0 export var gravity: float 980.0 var coyote_time_window: float 0.1 var coyote_timer: float 0.0 var jump_buffer_window: float 0.1 var jump_buffer_timer: float 0.0 var was_on_floor: bool false func _physics_process(delta: float) - void: # 1. 应用重力 velocity.y gravity * delta # 2. 记录上一帧地面状态用于土狼时间 was_on_floor is_on_floor() # 3. 处理输入和水平移动 var input_dir Input.get_axis(move_left, move_right) _apply_horizontal_movement(input_dir, delta) # 4. 处理跳跃缓冲 if Input.is_action_just_pressed(jump): jump_buffer_timer jump_buffer_window # 5. 执行移动 move_and_slide() # 6. 移动后更新土狼时间计时器 _update_coyote_timer(delta) # 7. 处理跳跃整合土狼时间和缓冲 _handle_jump_with_buffers() func _apply_horizontal_movement(input_dir: float, delta: float) - void: var target_speed input_dir * max_speed var current_speed velocity.x var speed_diff target_speed - current_speed # 选择加速度值地面用acceleration空中用衰减值 var accel_rate: float if is_on_floor(): accel_rate acceleration if abs(speed_diff) 1 else deceleration else: # 空中控制允许部分转向但不如地面灵活 accel_rate acceleration * air_control # 应用加速度 velocity.x move_toward(current_speed, target_speed, accel_rate * delta) func _update_coyote_timer(delta: float) - void: # 如果刚从地面离开且不是向上跳启动土狼时间 if was_on_floor and not is_on_floor() and velocity.y 0: coyote_timer coyote_time_window elif is_on_floor(): coyote_timer 0.0 # 在地面重置 else: # 在空中递减计时器 coyote_timer max(coyote_timer - delta, 0.0) func _handle_jump_with_buffers() - void: var can_jump is_on_floor() or coyote_timer 0 var wants_to_jump jump_buffer_timer 0 if wants_to_jump and can_jump: velocity.y jump_velocity coyote_timer 0.0 jump_buffer_timer 0.0 # 这里可以触发跳跃动画或音效 # 更新跳跃缓冲计时器 if jump_buffer_timer 0: jump_buffer_timer max(jump_buffer_timer - get_physics_process_delta_time(), 0.0)这个版本将移动、跳跃、状态管理分离得更清晰加入了空中控制系数 (air_control)并更稳健地处理了土狼时间和跳跃缓冲的逻辑。你可以把它作为一个可靠的起点根据你的具体游戏机制如冲刺、下砸、爬墙等进行扩展。构建一个手感出色的角色控制系统是一个不断测试、调整、再测试的过程。没有一套参数能通吃所有游戏。多玩一些你喜欢的游戏感受它们的操作手感然后回到Godot里通过调整那些export变量不断逼近你想要的感觉。记住参数微调带来的手感提升有时比增加一个新功能更重要。

相关新闻