更新于 

2D Movement in Unity

Character Controller

原视频资源

作者提供的脚本代码:

将Character-Controller加入控制角色Player的脚本中:

地面检测

地面检测的两个关键问题:

  • 什么是地面?
    • What is Ground属性:除了Player自身以外的Everything
  • 检测到地面的条件是什么?
    • Ground Check:脚部坐标点,用于检测和地面之间的碰撞
    • Ceiling Check:头部坐标点,检测和头顶物体的碰撞
Ceiling Check
Ceiling Check
Ground Check
Ground Check
碰撞检测
Rigidbody 2D

2点需要注意:

  • Mass比其他物体稍大,跳跃下落的效果更好
  • Collision Detection设置为Continuous,不然在玩家下落时会出现短暂的地面卡顿
  • Z旋转轴锁死,因为会出现下面的情况:
Tilemap 碰撞检测

Tilemap这里划分成3个模块:

  • 前景:不被遮挡无碰撞元素
  • 后景:被遮挡的无碰撞元素
  • 平台:碰撞元素

这里为需要进行碰撞检测的Tilemap添加3个脚本:

  • Rigidbody 2D:开启Static类型
  • Tilemap Collider 2D
  • Composite Collider 2D
Multi Collider 混合碰撞检测

Tips1: 在Edit Collider的时候,按住alt键可以进行对称编辑
Tips2: 鼠标拖动调整属性时,按住alt可以进行细调

角色如果只用一个Box Collider或者Circle Collider进行碰撞检测,
效果都会有缺点,仅使用Box Collider的时候,斜坡地段会出现卡顿的情况
这里作者建议角色上半身用Box Collider,
下半身用Circle Collider:

Movement

CharacterController2D

Brackeys提供的CharacterController2D类,
其中提供移动的主要方法就是Move,
Move需要3个参数:

  • float move:水平移动距离
  • bool crouch:是否下蹲
  • bool jump:是否跳跃
Horizontal Movement 水平移动

为Player添加一个新的脚本PlayerMovement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PlayerMovement : MonoBehaviour
{
public CharacterController2D controller2D; // 关联的控制器

public float moveSpeed = 50f; // 移动速度

float horizontalMove = 0f; // 水平移动距离

// Update is called once per frame
void Update()
{
// Input.GetAxisRow("Horizontal") 获取水平方向的移动
// 左移:A键 Input.GetAxisRow("Horizontal") = -1
// 右移:D键 Input.GetAxisRow("Horizontal") = 1
horizontalMove = Input.GetAxisRaw("Horizontal") * moveSpeed;
}

void FixedUpdate() {
// Time.fixedDeltaTime 时间增量
controller2D.Move(horizontalMove * Time.fixedDeltaTime, false, false);
}
}

并将之前为玩家添加的Character Controller 2D脚本与controller2D属性关联。

unity input setting

Unity提供按键检测的方法:

1
2
Input.GetButtonDown("Jump"); // 检测按键按下
Input.GetButtonUp("Crouch"); // 检测按键松开

这样只要我们按下W键,这个方法就会返回true,
W键-Jump的对应关系在Unity内可以修改:

Edit - Project Settings - Input Manager

Jump 跳跃

在PlayerMovement中添加进行跳跃检测的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PlayerMovement : MonoBehaviour
{
// ...
bool jump = false;

void Update()
{
// ...
if (Input.GetButtonDown("Jump")) {
jump = true;
}
}

void FixedUpdate() {
controller2D.Move(horizontalMove * Time.fixedDeltaTime, false, jump);
jump = false; // 跳跃后重置
}
}

这样做是可以进行跳跃,但是可能会出现角色卡在平台边缘的情况,
这是因为角色上半身的方形碰撞盒和平台碰撞盒之间存在摩擦力,

因此可以为角色的碰撞盒添加新的Physics Material:

  • Fiction = 0
  • Bounciness = 0

这样在平台边缘,角色就会因为缺少摩擦力自动滑下来

Crouch 下蹲

在PlayerMovement中对下蹲进行检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PlayerMovement : MonoBehaviour
{
// ...
bool crouch = false; // 下蹲检测

void Update()
{
// ...
// 下蹲检测
if (Input.GetButtonDown("Crouch"))
{
crouch = true;
}else if (Input.GetButtonUp("Crouch"))
{
crouch = false;
}
}

void FixedUpdate() {
controller2D.Move(horizontalMove * Time.fixedDeltaTime, crouch, jump);
jump = false;
}
}

在为Player添加碰撞体时,分别为上半身和下半身添加了一个碰撞体积,
下蹲实际实现的是使上半身的碰撞盒失效,
这一点需要在Character Controller 2D中对Crouch Disable Collider进行配置

在按下下蹲键时,上半身的碰撞盒就会失效:

onLanding闪电触发Bug

问题描述

角色跳跃时,本应只在角色落地时触发,
但现在在角色跳起之前也被触发一次,
于是角色动画就因为跳起之前的这一次被关闭了:

解决方式

查看CharacterController2D代码,发现地面检测是在FixedUpdate函数中被调用的,
因此应当是因为跳跃之后的第一帧,角色还没有离地,CharacterController2D的FixedUpdate函数就被调用了,
于是就判定跳跃结束,
因此这里在FixedUpdate函数中使用Invoke进行延时调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   // 地面检测函数
private void CollideCheck()
{
bool wasGrounded = m_Grounded;
m_Grounded = false;

Collider2D[] colliders = Physics2D.OverlapCircleAll(m_GroundCheck.position, k_GroundedRadius, m_WhatIsGround);
for (int i = 0; i < colliders.Length; i++)
{
if (colliders[i].gameObject != gameObject)
{
m_Grounded = true;
if (!wasGrounded)
OnLandEvent.Invoke();
}
}
}

// 延时调用0.01s
private void FixedUpdate()
{
Invoke("CollideCheck", 0.01f);
}