tutorial_kinematic_char - seraph526/godot-se GitHub Wiki
运动角色 (Kinematic Character (2D))
介绍 (Introduction)
是的,名字有些奇怪。"运动角色"到底是什么?原因是当物理引擎出现时,它们被叫做"动力学"引擎(因为它们主要是用来处碰撞检测)。使用动力学引擎创建角色控制器做过很多尝试,但看起来并不容易。Godot拥有你可以找到的最好的动力人物控制器之一(你可以看下2d/platformer demo),但是要使用它,你要具有一定的技巧,并理解物理引擎(或者非常非常的耐心去尝试和各种错误).一些物理引擎,如Havok,是动态角色控制器的好的选择,另外一些(PhysX)则倾向于运动角色控制器。 所以,到底有什么不同?:
- **动态角色控制器 (ynamic character controller)**使用刚体无限惯性检测。基本上,它是一个不能旋转的刚体。物体引擎通常允许物体间发生碰撞,然后一起解决碰撞问题。这使得动态角色控制器可以与其他的物理物体无缝地发生交互(查看platformer demo),但是,这种交互不是一直都精确,碰撞要花费不只一帧的时间来解决,一些碰撞可能会有一些小的偏差。这些问题可以解决,但需要一些小技巧。
- **运动角色控制器(dynamic character controller)**假定是处于没有碰撞的状态,并且通常转换为无碰撞状态。如果它开始处于碰撞状态,它会尝试释放它自己(就像刚体那样),但是也有例外,这不是规定。这使得它的控制和动作非常精确,,并且易于用程序控制。但是,如下面所说,它们不能直接和其他的物理对象发生交互(除非手动编写代码).
这个小教程关注于运动角色控制器。基本上,是守旧的处理碰撞的方式(这不一定是最简单的方式,但封装起来也可以作为一个好用的简单的API)
固定进程 (Fixed Process)
要管理一个运动对象或角色的逻辑,通常建议使用固定进程(Fixed Process),即每秒运行次数。这使得物理和行为计算比使用普通的进程精确,普通的进程在帧数太高或太低时会变得不精确。
extends KinematicBody2D
func _fixed_process(delta):
pass
func _ready():
set_fixed_process(true)
场景设置 (Scene Setup)
要准备一些测试用的素材,请下载scene from the tilemap tutorial。我们为角色创建一个新的场景。使用robot精灵,创建场景如下:
添加一个圆形的碰撞区域到碰撞体,在CollisionShape2D的shape属性中创建CircleShape2D,设置半径为30:
现在为角色创建脚本,上面的例子可以作为基础。 最后,在timlemap场景中实例化character场景,设置map场景为为主场景,以便在我们点击播放时可以查看效果。
移动运动角色 (Moving the Kinematic Character)
回到character场景,打开脚本,奇迹开始了! 默认情况下,运动角色不会有任何动作,但它有一个非常有用的函数move(motion_vector:Vector2).这个函数使用一个Vector2作为参数,尝试应用行为到运动角色上。如果碰撞发生,它会停在碰撞发生的地方。
所以,让我们移动精灵,直接它碰到地面为止:
extends KinematicBody2D
func _fixed_process(delta):
move( Vector2(0,1) ) #move down 1 pixel per physics frame
func _ready():
set_fixed_process(true)
结果,角色会移动,直到它碰到地面为止。非常酷哈? 接下来要增加重力混合,这样,它更像是一直真实的游戏角色:
extends KinematicBody2D
const GRAVITY = 200.0
var velocity = Vector2()
func _fixed_process(delta):
velocity.y += delta * GRAVITY
var motion = velocity * delta
move( motion )
func _ready():
set_fixed_process(true)
现在,角色平滑下落。现在我们让角色在按下方向键时左右走。记住,使用的值(如speed)是以pixels/second为单位的。 下面添加按下left和right时的走路代码:
extends KinematicBody2D
const GRAVITY = 200.0
const WALK_SPEED = 200
var velocity = Vector2()
func _fixed_process(delta):
velocity.y += delta * GRAVITY
if (Input.is_action_pressed("ui_left")):
velocity.x = -WALK_SPEED
elif (Input.is_action_pressed("ui_right")):
velocity.x = WALK_SPEED
else:
velocity.x = 0
var motion = velocity * delta
move( motion )
func _ready():
set_fixed_process(true)
再试一次。
问题? (Problem?)
但,上面的代码并不是工作的很好。如果你走到左边的墙,它会会卡在那里,除非你释放方向键。一旦它处于地面上,也会卡住,不会再左右移动,为什么?
答案是,看起来很简单,但实际上并不简单。如果这个行为不完成,那么角色不会再移动。就这么简单。下图会描述到底发生了什么:
基本上,期望的行为不会发生,因为它在行为轨迹中太早的碰撞了地面或墙,这使得它停在那里。记住,即使角色在地面上,仍然存在向下的重力。
解决! (Solution!)
如何解决?这个问题通过碰撞法线的"滑行"(sliding)来解决。KinematicBody2D提供了两个有用的函数:
当碰撞时,函数move()返回行为向量的余数(remainder)。就是说,如果行为向量是40 pixels,但是碰撞发生在10 pixels,相同的向量,但返回值为30 pixels. 正确的解决行为的方法是通过法线滑动:
func _fixed_process(delta):
velocity.y += delta * GRAVITY
if (Input.is_action_pressed("ui_left")):
velocity.x = - WALK_SPEED
elif (Input.is_action_pressed("ui_right")):
velocity.x = WALK_SPEED
else:
velocity.x = 0
var motion = velocity * delta
motion = move( motion )
if (is_colliding()):
var n = get_collision_normal()
motion = n.slide( motion )
velocity = n.slide( velocity )
move( motion )
func _ready():
set_fixed_process(true)
注意,不光行为向量被修改了,被修改的还有速率。这是有意义的,它保证了新的向量方向。
法线也可以通过检测角度的方法来检测角色是否在地面上。如果法线指向(或者至少,在一定的阈值内),角色可以决定是否在那里。 完成度更高的demo可以在引擎的demo压缩包中找到或者在 demo folder。