输入系统
上一章角色可以做不同的动作了,但是还是原地动作,现在我们让他真正的动起来。
控制角色移动需要输入设备,PC上一般是用键盘控制,手机用触摸屏(软摇杆),其它硬件也有不同的配套输入设备,我们要给每种设备都写一套事件响应逻辑吗?显然不是一个好主意,Unity为我们提供了封装。
以前的Unity版本中有Input,InputManager分别提供了读取设备输入值,以及通过虚拟轴映射游戏事件的作用,新版本提供了一套新的输入系统InputSystem.我们就介绍一下新输入系统的使用方法。
首先打开包管理器: Window->Package Manager,选择Input System进行安装:
随后弹出的对话框中选Yes.
Unity将重新启动并禁用旧版Input Api,启用新版Input System.即使这里没有选Yes,我们也可以手动去切换输入系统,方法是打开项目设置: Edit->Project Setting->Player->Active Input Handling,选择Input System Package(New):
遇到问题
好了,输入系统切换完成,运行项目,发现UI不响应了,又发生了什么?
其实这是因为UI默认采用的是老版输入系统作为其输入,我们只需要一个简单的设置就可以解决它。
在Hierarchy面板选中EventSystem,点击Inspector面板的 Replace with InputSystemUIInputModule.然后再运行项目,UI又恢复响应了。
控制角色
新输入系统准备好了,现在要开始控制角色了.
控制角色有多种方式,可以用物理引擎控制,可以用Untiy提供的CharacterController来控制,我们为了学习,尝试自己实现一个简单的:
先给角色加一个Player Input组件:
在红框位置有一个CreateActions的按钮,按一下,选择文件位置,会创建一个Input Action Assets,同时会打开左边的面板。这里面定义好了一些Action,以及对输入设备的映射。
写逻辑的时候,只要处理Action就好了,而不用管具体的是键盘输入的,还是手柄摇杆,达到了输入和逻辑相分离的目的。
逻辑代码和Action关联的方式有多种,可以通过Player Input组件的Behavior来控制,它表示当有动作触发时的响应方式:
- Send Messages
- Broadcast Messages
- Unity Events
- CSharp Events
这四种触发模式,从效率上看,Broadcast Messsages < Send Messages < Unity Events < Csharp Events。
前两种很好接入,只要在脚本中添加 On{ActionName}()函数就可以。
Unity Events 要在 Inspector 中分配各种回调函数,可视化操作,对新手友好,但在函数和动作逻辑较多时比较麻烦。
最后一个比较高效,但是所有的动作共享同一个回调事件 event Action<InputAction.CallbackContext> onActionTriggerd,需要在这个函数中自行区分具体的触发动作。
具体的接入方式可以参考Input System提供的Demo: 打开包管理器,选中Input System,在右边面板选中要查看的Demo,然后点击Import按钮(非必须):
现在我们来创建自己的角色控制逻辑,创建一个名为SimpleActorController的脚本,挂载到角色身上,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class SimpleActorController : MonoBehaviour
{
public float moveSpeed; //角色移动速度
private InputAction m_MoveAction;
private Animator animator;
private Vector3 rotationVector;
private Vector2 m_Move;
// Start is called before the first frame update
void Start()
{
animator = GetComponent<Animator>();
if (m_MoveAction == null)
{
PlayerInput input = GetComponent<PlayerInput>();
m_MoveAction = input.actions["move"];
}
}
private void Update()
{
MoveActor();
}
//移动角色
public void MoveActor()
{
bool isMoving = false;
if (m_MoveAction != null && m_MoveAction.IsPressed())
{
m_Move = m_MoveAction.ReadValue<Vector2>();
if(m_Move.sqrMagnitude > 0.1f)
{
Vector2 moveVector = m_Move * (moveSpeed * Time.deltaTime);
//求前进的方向
Vector3 dir = Camera.main.transform.localToWorldMatrix.MultiplyVector(new Vector3(moveVector.x, 0, moveVector.y));
dir.y = 0f;
dir = dir.normalized;
//把角色转向前进方向
transform.forward = Vector3.SmoothDamp(transform.forward, dir, ref rotationVector, 0.1f);
transform.position = transform.position + dir * moveVector.magnitude;
isMoving = true;
}
}
animator.SetFloat("speed", isMoving ? moveSpeed : 0.0f);
}
}
可以看到,我们用最简便快捷的方式,在Update函数中直接检测Action是否被按下,实际项目中不建议这么做。
然后在Inspector面板中设置移动速度为5左右,现在运行程序,并尝试按方向按钮或者ASDW键,可以看到角色动起来了。
再遇问题
角色是可以移动了,但是好像反应迟钝,先滑步好长距离,才开始跑步,停下的时候也原地踏步半天才停下,这是什么原因呢?
我们在实际项目中经常会遇到类似的状况,明明觉得该做的都做了,但总会出现一些意外。这就要考验大家知识掌握的是否扎实,或者经验是否老到。你可能会记起在学习Animator Controller的时候,有个Has Exit Time的选项,用来控制角色是否一定要播完上一个动作,再切换到下一个动作,目前这个现象很像是勾选了这个选项造成的,打开我们的RoleController,点击Idle状态,在Inspector面板中查看,果然是勾选状态,去掉勾选。
我们还要能够举一反三,想想还有没有类似的问题,好像子状态机里面还有状态,好打开后进行同样的操作,保存再试.
"果然好了,不那么迟钝了。可是为什么摄像机不跟着角色走呀?"
因为我们还没有写摄像机控制的代码呀,游戏界有个名词叫 "3C" 就是 "Character Camera Control(角色、相机、控制)" 三个单词的缩写, 它的好坏直接影响到游戏体验,是游戏中十分重要的功能,我们的SimpleActorController是帮助大家入门,所以写的越简单越好,真正项目中要复杂的多,就等你去完善了。
说到摄像机控制,我们下章再来实现吧。
同样给出本章项目连接 角色控制系统
返回主目录
【转载请注明出处】