前言
为了便于测试,我们会先使用PC端的键盘和鼠标输入
来控制人物的移动,等到功能测试完成之后,再将PC端的键盘和鼠标输入
换成移动端的虚拟摇杆和按钮输入
。这里,我们首先使用PC端的键盘和鼠标输入
来实现控制角色进行移动的功能。
为角色添加Collider和Rigidbody
为了让角色具有物理属性,我们需要为角色添加Collider和Rigidbody。此外,为了避免角色出现翻滚的问题,让角色一直保持直立,因此我们需要在Rigidbody2D
的Constraints
属性里设置勾选Freeze Rotation Z
,不让角色在进行物理模拟时,绕Z轴进行旋转。
控制角色移动
接下来,我们开始编写脚本来实现让怪物在场景中移动的功能。我们在Assets\Scripts
文件夹下创建一个名为Player
的文件夹用于保存和角色相关的脚本。创建完毕后,我们在Player
文件夹下创建一个名为PlayerController
的C#脚本,然后添加以下代码:
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();
}
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的步骤
- 在
Assets
目录下新建一个名为Physics Material
的文件夹- 在
Physics Material
的文件夹创建两个Physics Material 2D
,并将它们分别命名为Platform
和Player
- 将
Platform
和Player
这两个Physics Material 2D
的Friction
属性都设置为1
- 将
Platform
和Player
这两个Physics Material 2D
分别设置到所有角色可移动的平台
和角色的Rigidbody2D
上
因为平台数量较多,我们可以批量操作。按住shift
键选择多个GameObject,右侧Inspector
就会显示它们同时都具备的组件,我们可以同时对它们同时都具备的组件进行编辑。
此外,角色在跳跃时,我们发现角色跳跃的高度很高,且下落的速度很慢,游戏体验较差。为了提高游戏体验,我们需要增大游戏角色的重力加速度。将Player
下Rigidbody2D
组件的Gravity Scale
设置为3.1
,表示将角色的重力加大至原有的3.1
倍。
从上面的图片可以看到,我将Player
这一Physics Material 2D
设置到角色的Rigidbody2D
上,这表示角色的所有没设置Physics Material 2D
的Collider都会使用Player
这一Physics Material 2D
。
设置完成之后,运行游戏,可以看到之前的滑动和下落缓慢的问题都消失了,但是出现了一个新的问题,那就是当我们按住方向键不动的时候,角色会贴在平台边缘。之所以会出现这个问题,是因为角色和平台之间有摩擦力,为了避免这一情况,我们需要创建一个名为Wall
的Physics Material 2D
,将其Friction
属性都设置为0
,然后在各个平台的末端处创建一个使用Wall
这一Physics Material 2D
的Collider。
Foreground添加Collider的情况如下:
- env_TowerFull和env_TowerFull (1):
- 不添加新的Collider
- 设置原有Collider的
Physics Material 2D
为Wall
- env_PlatformBridge和env_PlatformBridge (1):
- 新添加一个BoxCollider2D
Material
:WallOffset
:(0.8, 0.8)Size
: (15.5, 0.6)- env_PlatformTop和env_PlatformTop (1):
- 新添加一个BoxCollider2D
Material
:WallOffset
:(4.7, 0.12)Size
: (0.5, 2.6)- env_PlatformUfo:
- 新添加两个CircleCollider2D
Material
:都为WallOffset
:(-15.3, -0.65)和(15.3, -0.65)Radius
: 都为0.5
添加完毕之后,再次运行游戏,可以看到不再出现角色贴在平台边缘的问题。
添加音效
接着,我们需要为角色添加音效。首先,在PlayerController.cs
内加入以下代码,其中...
表示原有的代码:
...
[RequireComponent(typeof(AudioSource))]
public class PlayerController : MonoBehaviour {
...
[Tooltip("跳跃音效")]
public AudioClip[] JumpClips;
...
private AudioSource m_AudioSource;
...
private void Awake() {
...
m_AudioSource = GetComponent();
}
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
完整代码如下:
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();
m_AudioSource = GetComponent();
}
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这个仓库到本地进行查看。
参考链接
- Gravity Scale
- Conventional Game Input
- Physics Material 2D