基于官方Input System案例分析其角色移动原理
注:本篇重点在于分析案例之中的角色移动实现。
所用编程语言:C#
简介:
本学习笔记基于Unity官方的例子:InputSystem_Warriors_Project
本例子能从Github上下载,但我同时上传了腾讯云盘版本方便大家下载
Github:InputSystem_Warriors-master.zip.
腾讯云盘:InputSystem 密码:jjsffv
本案例在B站也有官方的教学视频:官方教学视频传送门
这个教学视频的内容已经可以满足大部分需求,基本操作已经完全展示,故在此不再分析InputSystem的用法。
本笔记将从官方的例子,以及本人的试验中解析官方项目中一些脚本编写的特点以及其一些功能的实现原理比如:
- 基于2D的角色移动,让角色会跟随玩家的二维输入而旋转至对应方向。
- 基于3D的角色移动,镜头跟随原理以及远近调整。
开始:
先从最根本的物体对象:Warrior,也就是战士这个物体开始了解。
其具有的组件有
- Player Controller(Script)
- Player Input (用以接收InputSystem信息的组件)
- Capsule Collider(胶囊碰撞体)
- Rigidbody(刚体)
- Player Visuals Behaviour(Script)
- Player Movement Behaviour(Script)
注意:Warrior这个物体是不包含模型在内的,模型属于它的子物体。
在运行时,是Warrior整个对象在旋转和移动,但其模型对于父对象是完全不旋转的,在某些情况下这种做法可以有效隔离模型和实际效果之间产生的冲突(比类似于模型与坐标分开方便处理)。
Warrior的跟随显示
对于Warrior头上显示的设备名称,有一个基于世界坐标的Canvas作为Warrior的子物体跟随显示。
注意这里有一点:并不是Canvas直接设置为Warrior的子对象,其上还有一个用于定位的Anchor_Canvas物体对象。这种做法属于“留后路”(我认为),把物体按照其职责分开是可以让后续修改操作变得十分方便的。
如果这个Canvas内的对象出现问题,比如数字不在圆圈内等等,直接按照平时操作UI的方法调整一下对齐位置就好。
角色的移动以及对应移动方向的旋转
与玩家操控相关的脚本有:
- Player Controller(Script)
- Player Visuals Behaviour(Script)
- Player Movement Behaviour(Script)
- Player Animation Behaviour(Script)
而Player Controller则是中枢,由它延申至另外三个Behaviour分别执行对应方法。
Visuals Behaviour对应的功能是换色、新出现的Warrior生成之后会换色以区别。
Movement Behaviour对应功能是移动,角色的移动,角色的旋转。
Animation Behaviour对应的功能是动画控制,控制模型动画的播放。
Player Controller
其第一个方法则是用于初始化角色
public void SetupPlayer(int newPlayerID)
{
playerID = newPlayerID;
currentControlScheme = playerInput.currentControlScheme;
playerMovementBehaviour.SetupBehaviour();
playerAnimationBehaviour.SetupBehaviour();
playerVisualsBehaviour.SetupBehaviour(playerID, playerInput);
}
传入一个int作为ID,随后初始化本脚本的ID,得到当前操作表(种类,比如键盘,Xbox,PS4等),为三个Behaviour初始化。
这里需要注意,Warrior下的脚本之中,PlayerController的三个Behaviour都已经引用好,如果没引用好会触发引用为空的错误。
对于InputSystem的Callback使用
public void OnMovement(InputAction.CallbackContext value)
{
Vector2 inputMovement = value.ReadValue<Vector2>();
rawInputMovement = new Vector3(inputMovement.x, 0, inputMovement.y);
}
//这个方法旨在把从InputSystem获取的WASD的2维映射输入转化为3维坐标用于移动角色。
//rawInputMovement是直接转化,PlayerController中还有一个三维坐标变量叫smoothInputMovement用于平滑处理移动与旋转。
public void OnAttack(InputAction.CallbackContext value)
{
if(value.started)
{
playerAnimationBehaviour.PlayAttackAnimation();
}
}
//此方法则是接收到的攻击按钮映射后,使控制动画的组件播放攻击动画
//注意:value.started只会响应一次,可以从InputSystem中设置Button的响应方法:按下、释放、按下与释放。
//如果想要按住响应的话,可以试试将映射设置成其他形式,比如一个值。
public void OnTogglePause(InputAction.CallbackContext value)
{
if(value.started)
{
GameManager.Instance.TogglePauseState(this);
}
}
//对应激活暂停的功能。
在PlayerController从InterSystem中得到关于移动的Vector2变量,转变成Vector3后,需要一个平滑处理的方法。这就是为什么把rawInputMovement和smoothInputMovement区分开来的原因。
smoothInputMovement是还没应用于物理系统的变量,可以放在Update内快速处理玩家输入。
//以下三个方法放在Update内处理。
void CalculateMovementInputSmoothing()
{
smoothInputMovement = Vector3.Lerp(smoothInputMovement, rawInputMovement, Time.deltaTime * movementSmoothingSpeed);
}
void UpdatePlayerMovement()
{
playerMovementBehaviour.UpdateMovementData(smoothInputMovement);
}
void UpdatePlayerAnimationMovement()
{
playerAnimationBehaviour.UpdateMovementAnimation(smoothInputMovement.magnitude);
}
CalculateMovementInputSmoothing()
这个方法内采用Vector自带的线性插值方法Lerp(vector3 now , vector3 target , float speed)
线性插值还有一个差不多的叫Slerp,球形插值,越远越快,越近越慢,个人觉得效果更佳。
这个smoothInputMovement经过线性处理后,递交到playerMovementBehaviour处执行实际移动效果。
关于角色的转动以及角色的实际移动则在PlayerMovementBehaviour处实现,这里仅仅是处理数据后提交。
剩下的则为对InputSystem出现的一些情况比如设备断开,更换设备等事件的响应处理方法。
Player Visuals Behaviour
此组件用于控制角色的变色处理。
它会根据PlayerID改变材质的颜色,同时对于Warrior的头上标记,由此改变:如颜色
public void SetupBehaviour(int newPlayerID, PlayerInput newPlayerInput)
{
playerID = newPlayerID;
playerInput = newPlayerInput;
SetupShaderIDs();
UpdatePlayerVisuals();
}
设置基础数值如玩家ID、新的玩家输入设置,设置Shader渲染的对应材质的颜色的int值。随后更改玩家材质,以及修改抬头显示的颜色。
注意:如果直接改变Warrior衣服的颜色,对应的毛发以及胡须的颜色也会响应改变,官方已经为这个模型提前设置好颜色了。
Player Animation Behaviour
只有单纯的方法与变量引用:如Animator,对应的攻击和移动动画的ID,以及执行动画的方法。
如果想要学习动画系统,直接去UnityCN -》 用户手册里学习会更快。
Player Movement Behaviour
这是我探究的重点,因为这是基础组件,只要了解清楚基础操作,再加上自己的一些奇思妙想就可以让其成为极其强力的基础组件,减轻开发初期的时间消耗并把核心放在验证玩法之上。与之同等重要(在我看来)的组件还有摄像机跟随组件,自带了视角转动跟随以及视角固定角度跟随。从中可以很轻易的制作出类似于基础类RPG、基础类第三人称、3D俯视角等类型游戏。
首先是基础数值类变量定义:刚体、移速、角色转动速度、摄像机、移动的方向三维变量
[Header("Component References")]
public Rigidbody playerRigidbody;
[Header("Movement Settings")]
public float movementSpeed = 3f;
public float turnSpeed = 0.1f;
//Stored Values
private Camera mainCamera;
private Vector3 movementDirection;
核心移动相关
public void UpdateMovementData(Vector3 newMovementDirection)
{
movementDirection = newMovementDirection;
}//在PlayerController中调用了,使本脚本得到Vector3变量
void FixedUpdate()
{
MoveThePlayer();
TurnThePlayer();
}
记得,与物理相关的方法,都应该放在FixedUpdate()内,这样才能确保结果正确。
核心-移动
void MoveThePlayer()
{
Vector3 movement = CameraDirection(movementDirection) * movementSpeed * Time.deltaTime;
playerRigidbody.MovePosition(transform.position + movement);
}
从CameraDirection()之中得到处理过的实际移动方向之后,乘以移速,乘以一帧的时间
最后Rigidbody以其作为数据移动。
核心-转动
void TurnThePlayer()
{
if(movementDirection.sqrMagnitude > 0.01f)
{
Quaternion rotation = Quaternion.Slerp(playerRigidbody.rotation,
Quaternion.LookRotation (CameraDirection(movementDirection)),
turnSpeed);
playerRigidbody.MoveRotation(rotation);
}
}
Vector3.sqrMagnitude可以求出这个三维数值的长度,由于movementDirection已经是最终移动数据,设置这个if判定就可以在有输入的时候才调用这个方法。
Quaternion是四元数,我不会,真的不会,会出一期笔记想办法从简单入手教学,出了就编辑一下在这里放链接。
采取的也是插值,不过是球形插值,以基于摄像机方向得出的Vector3信息为目标点,让Rigidbody旋转至对应位置。
核心-摄像机方向
Vector3 CameraDirection(Vector3 movementDirection)
{
var cameraForward = mainCamera.transform.forward;
var cameraRight = mainCamera.transform.right;
cameraForward.y = 0f;
cameraRight.y = 0f;
return cameraForward * movementDirection.z + cameraRight * movementDirection.x;
}
把想要移动的变量基于摄像机视角转换成实际移动数据的方法。
解析原理:假设现实正北方向为Z,我在看着东北的情况下向左移动,实际是走向西北,但我的输入只有一个(-1,0)的二维变量,
这个方法就把这个(-1,0)的我的输入,转换成我的实际移动(-根号2,根号2)。
先得到摄像机的正方向,也就是摄像机的局部坐标Z轴。随后取得局部坐标X轴。
把两个Vector3变量的Y轴取为0。
返还一个相乘融合之后的Vector3变量。
稍微提一嘴:类似于直接操作位置,比如移动x轴正方向1m,是不可以直接对transform.postition.x +=1;这样操作的,它只能被一个新的三维或二维变量覆盖
但仅仅只是对一个Vector3或者Vector2的变量操作,是可以单独操作x,y,z的。
总结
以上内容仅仅适用于刚体移动玩家的情况,且只包含角色移动原理。
仅为学习笔记,如果有错误请指出,会尽快修改以免误导其他人。
Slerp和Lerp真是个好东西2333333