前言

  为了便于测试,我们会先使用PC端的键盘和鼠标输入来控制人物的移动,等到功能测试完成之后,再将PC端的键盘和鼠标输入换成移动端的虚拟摇杆和按钮输入。这里,我们首先使用PC端的键盘和鼠标输入来实现控制角色进行移动的功能。


为角色添加Collider和Rigidbody

  为了让角色具有物理属性,我们需要为角色添加Collider和Rigidbody。此外,为了避免角色出现翻滚的问题,让角色一直保持直立,因此我们需要在Rigidbody2DConstraints属性里设置勾选Freeze Rotation Z,不让角色在进行物理模拟时,绕Z轴进行旋转。

角色添加的组件信息
角色添加的组件信息

控制角色移动

  接下来,我们开始编写脚本来实现让怪物在场景中移动的功能。我们在Assets\Scripts文件夹下创建一个名为Player的文件夹用于保存和角色相关的脚本。创建完毕后,我们在Player文件夹下创建一个名为PlayerController的C#脚本,然后添加以下代码:

PlayerController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour {
[Tooltip("角色初始朝向是否朝向右边")]
public bool FacingRight = true;
[Tooltip("移动时角色加速的力大小")]
public float MoveForce = 365f;
[Tooltip("角色移动的最大速度")]
public float MaxSpeed = 5f;
[Tooltip("跳跃时向上加速的力大小")]
public float JumpForce = 1000f;
[Tooltip("检测角色是否落地")]
public Transform GroundCheck;

// 记录角色当前是否处于准备跳跃状态
private bool m_IsReadyToJump;
// 记录角色当前是否正处于跳跃状态
private bool m_IsJumping;
// 记录角色当前是否处于着地状态
private bool m_GroundedStatus;

// 组件引用变量
private Rigidbody2D m_Rigidbody2D;

private void Awake() {
// 获取组件引用
m_Rigidbody2D = GetComponent<Rigidbody2D>();
}

private void Start() {
// 监测变量是否正确赋值
if(GroundCheck == null) {
Debug.LogError("请先设置GroundCheck");
}

// 初始化变量
m_IsReadyToJump = false;
m_IsJumping = false;
m_GroundedStatus = false;
}

private void Update() {
// 通过检测角色和groundCheck之间是否存在Ground层的物体来判断当前是否落地
m_GroundedStatus = Physics2D.Linecast(
transform.position,
GroundCheck.position,
LayerMask.GetMask("Obstacle")
);

// 着地时,如果当前不处于跳跃状态且按下了跳跃键,进入准备跳跃状态
if(m_GroundedStatus && !m_IsJumping && Input.GetButtonDown("Jump")) {
m_IsReadyToJump = true;
}

// 刚刚落地,退出跳跃状态
if(m_GroundedStatus && m_IsJumping) {
m_IsJumping = false;
}
}

private void FixedUpdate() {
//获取水平输入
float h = Input.GetAxis("Horizontal");

// 若h * m_Rigidbody2D.velocity.x为正数且小于MaxSpeed,表示需要继续加速
// 若h * m_Rigidbody2D.velocity.x为负数,则表示需要反向加速
if(h * m_Rigidbody2D.velocity.x < MaxSpeed) {
m_Rigidbody2D.AddForce(Vector2.right * h * MoveForce);
}

//设置物体速度的阈值
if(Mathf.Abs(m_Rigidbody2D.velocity.x) > MaxSpeed) {
m_Rigidbody2D.velocity = new Vector2(
Mathf.Sign(m_Rigidbody2D.velocity.x) * MaxSpeed,
m_Rigidbody2D.velocity.y
);
}

//判断当前是否需要转向
if(h > 0 && !FacingRight) {
Flip();
}else if(h < 0 && FacingRight) {
Flip();
}

// 跳跃
if(m_IsReadyToJump) {
Jump();
}
}

private void Jump() {
// 进入跳跃状态
m_IsJumping = true;

// 设置一个竖直向上的力
m_Rigidbody2D.AddForce(new Vector2(0f, JumpForce));

// 退出准备跳跃状态,避免重复跳跃
m_IsReadyToJump = false;
}

private void Flip() {
// 修改当前朝向
FacingRight = !FacingRight;

// 修改scale的x分量实现转向
this.transform.localScale = Vector3.Scale(
new Vector3(-1, 1, 1),
this.transform.localScale
);
}
}

代码说明:

  • Input.GetAxis(“Horizontal”):Unity提供的默认输入项,能获取键盘上A\D键和<-\->方向键的输入
  • Input.GetButtonDown(“Jump”):Unity提供的默认输入项,能获取键盘上空格键的输入
  • Physics2D.Linecast:来获取场景里两个点之间属于某个Layer的所有Collider
  • 因为和角色物理模拟有关的操作都应该在FixedUpdate里执行,所以Jump函数需要在FixedUpdate里调用
  • 我们为了避免出现多次按跳跃键导致Jump函数被多次调用的问题,我们使用变量m_IsJumping来判断当前是否处于跳跃状态

添加Physics Material

  添加完成后,将PlayerController.cs添加到Hierarchy窗口的Player上,运行游戏,我们发现角色在运动时,会出现很明显的滑动现象。这是因为我们没有为角色和平台上的Collider设置Physics Material,导致角色和平台之间的摩擦力为0。

添加Physics Material的步骤

  1. Assets目录下新建一个名为Physics Material的文件夹
  2. Physics Material的文件夹创建两个Physics Material 2D,并将它们分别命名为PlatformPlayer
  3. PlatformPlayer这两个Physics Material 2DFriction属性都设置为1
  4. PlatformPlayer这两个Physics Material 2D分别设置到所有角色可移动的平台角色的Rigidbody2D

  因为平台数量较多,我们可以批量操作。按住shift键选择多个GameObject,右侧Inspector就会显示它们同时都具备的组件,我们可以同时对它们同时都具备的组件进行编辑。

批量操作
批量操作

  此外,角色在跳跃时,我们发现角色跳跃的高度很高,且下落的速度很慢,游戏体验较差。为了提高游戏体验,我们需要增大游戏角色的重力加速度。将PlayerRigidbody2D组件的Gravity Scale设置为3.1,表示将角色的重力加大至原有的3.1倍。

设置Gravity Scale
设置Gravity Scale

  从上面的图片可以看到,我将Player这一Physics Material 2D设置到角色的Rigidbody2D上,这表示角色的所有没设置Physics Material 2D的Collider都会使用Player这一Physics Material 2D

  设置完成之后,运行游戏,可以看到之前的滑动和下落缓慢的问题都消失了,但是出现了一个新的问题,那就是当我们按住方向键不动的时候,角色会贴在平台边缘。之所以会出现这个问题,是因为角色和平台之间有摩擦力,为了避免这一情况,我们需要创建一个名为WallPhysics Material 2D,将其Friction属性都设置为0,然后在各个平台的末端处创建一个使用Wall这一Physics Material 2D的Collider。

Foreground添加Collider的情况如下:

  • env_TowerFull和env_TowerFull (1):
    • 不添加新的Collider
    • 设置原有Collider的Physics Material 2DWall
  • env_PlatformBridge和env_PlatformBridge (1):
    • 新添加一个BoxCollider2D
    • Material:Wall
    • Offset:(0.8, 0.8)
    • Size: (15.5, 0.6)
  • env_PlatformTop和env_PlatformTop (1):
    • 新添加一个BoxCollider2D
    • Material:Wall
    • Offset:(4.7, 0.12)
    • Size: (0.5, 2.6)
  • env_PlatformUfo:
    • 新添加两个CircleCollider2D
    • Material:都为Wall
    • Offset:(-15.3, -0.65)和(15.3, -0.65)
    • Radius: 都为0.5

  添加完毕之后,再次运行游戏,可以看到不再出现角色贴在平台边缘的问题。


添加音效

  接着,我们需要为角色添加音效。首先,在PlayerController.cs内加入以下代码,其中...表示原有的代码:

PlayerController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
[RequireComponent(typeof(AudioSource))]
public class PlayerController : MonoBehaviour {
...
[Tooltip("跳跃音效")]
public AudioClip[] JumpClips;

...
private AudioSource m_AudioSource;

...

private void Awake() {
...
m_AudioSource = GetComponent<AudioSource>();
}

private void Jump() {
...

//随机在角色当前所处的位置播放一个跳跃的音频
if(JumpClips.Length > 0) {
int i = Random.Range(0, JumpClips.Length);
AudioSource.PlayClipAtPoint(JumpClips[i], transform.position);
}
}
}

  添加完毕之后,我们给Player添加AudioSource组件,并将Assets\Audio\Player\Jumps添加到JumpClips属性上。接着运行游戏,可以听到角色在跳跃的时候已经有了音效。最后,将我们在Player上做的修改Apply到Prefab上,并保存场景产生的修改即可。

设置音效
设置音效

PlayerController.cs完整代码

  此时,PlayerController.cs完整代码如下:

PlayerController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(AudioSource))]
public class PlayerController : MonoBehaviour {
[Tooltip("角色初始朝向是否朝向右边")]
public bool FacingRight = true;
[Tooltip("移动时角色加速的力大小")]
public float MoveForce = 365f;
[Tooltip("角色移动的最大速度")]
public float MaxSpeed = 5f;
[Tooltip("跳跃时向上加速的力大小")]
public float JumpForce = 1000f;
[Tooltip("检测角色是否落地")]
public Transform GroundCheck;

[Tooltip("跳跃音效")]
public AudioClip[] JumpClips;

// 记录角色当前是否处于准备跳跃状态
private bool m_IsReadyToJump;
// 记录角色当前是否正处于跳跃状态
private bool m_IsJumping;
// 记录角色当前是否处于着地状态
private bool m_GroundedStatus;

// 组件引用变量
private Rigidbody2D m_Rigidbody2D;
private AudioSource m_AudioSource;

private void Awake() {
// 获取组件引用
m_Rigidbody2D = GetComponent<Rigidbody2D>();
m_AudioSource = GetComponent<AudioSource>();
}

private void Start() {
// 监测变量是否正确赋值
if(GroundCheck == null) {
Debug.LogError("请先设置GroundCheck");
}

// 初始化变量
m_IsReadyToJump = false;
m_IsJumping = false;
m_GroundedStatus = false;
}

private void Update() {
// 通过检测角色和groundCheck之间是否存在Ground层的物体来判断当前是否落地
m_GroundedStatus = Physics2D.Linecast(
transform.position,
GroundCheck.position,
LayerMask.GetMask("Obstacle")
);

// 着地时,如果当前不处于跳跃状态且按下了跳跃键,进入准备跳跃状态
if(m_GroundedStatus && !m_IsJumping && Input.GetButtonDown("Jump")) {
m_IsReadyToJump = true;
}

// 刚刚落地,退出跳跃状态
if(m_GroundedStatus && m_IsJumping) {
m_IsJumping = false;
}
}

private void FixedUpdate() {
//获取水平输入
float h = Input.GetAxis("Horizontal");

// 若h * m_Rigidbody2D.velocity.x为正数且小于MaxSpeed,表示需要继续加速
// 若h * m_Rigidbody2D.velocity.x为负数,则表示需要反向加速
if(h * m_Rigidbody2D.velocity.x < MaxSpeed) {
m_Rigidbody2D.AddForce(Vector2.right * h * MoveForce);
}

//设置物体速度的阈值
if(Mathf.Abs(m_Rigidbody2D.velocity.x) > MaxSpeed) {
m_Rigidbody2D.velocity = new Vector2(
Mathf.Sign(m_Rigidbody2D.velocity.x) * MaxSpeed,
m_Rigidbody2D.velocity.y
);
}

//判断当前是否需要转向
if(h > 0 && !FacingRight) {
Flip();
}else if(h < 0 && FacingRight) {
Flip();
}

// 跳跃
if(m_IsReadyToJump) {
Jump();
}
}

private void Jump() {
// 进入跳跃状态
m_IsJumping = true;

// 设置一个竖直向上的力
m_Rigidbody2D.AddForce(new Vector2(0f, JumpForce));

// 退出准备跳跃状态,避免重复跳跃
m_IsReadyToJump = false;

//随机在角色当前所处的位置播放一个跳跃的音频
if(JumpClips.Length > 0) {
int i = Random.Range(0, JumpClips.Length);
AudioSource.PlayClipAtPoint(JumpClips[i], transform.position);
}
}

private void Flip() {
// 修改当前朝向
FacingRight = !FacingRight;

// 修改scale的x分量实现转向
this.transform.localScale = Vector3.Scale(
new Vector3(-1, 1, 1),
this.transform.localScale
);
}
}


后言

  可以看到,目前角色在运动的时候还没有动画。因为给角色加上动画设计到动画状态机的制作,较为复杂,在下一篇文章将会介绍如何给角色添加动画。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay5分支下看到,读者可以clone这个仓库到本地进行查看。


参考链接

  1. Gravity Scale
  2. Conventional Game Input
  3. Physics Material 2D