前言
自写文章《 Unity实现人物移动、旋转、跳跃解决方案(电脑端)》已经过了好长时间了,今天给大家带来移动端的教学!起初我在网上也找了很多实现的方法,基本都是通过Touch类来实现,但是跟着写代码后都很难实现我想要的流畅效果,后面只能自己含泪去研究😭,不过最后还是实现了,在这篇文章中我会将过程详细的描述出来,带着大家一起去实现类似cf手游的人物控制系统。
实现效果
这里左边摇杆操控操控移动,右边的按钮控制跳跃,其余空白部分操控视角。
实现流程
这里我继续使用当时做电脑端时搭的模型,角色的创建流程和环境的搭建都是一样的,新来的小伙伴可以先去上一篇文章学习一下。
设计触控面板
1、点开人物预设体,在预设体中创建一个画布,这里我命名为“PlayerCanvas”,于Camera同层级。
2、把视角切换成2D,调整好画布位置,方便我们操作。将“PlayerCanvas”的UI Scale Mode设置成Scale With Screen Size,这样摇杆和跳跃按钮才可以跟着屏幕的大小适配。
3、在“PlayerCanvas”中创建Panel面板,命名为“MovePanel”。在“MovePanel”中添加一张图片和一个按钮,图片命名为“JoySliceOut”,是摇杆的外框,按钮命名为“Jump”,是跳跃按钮。将图片锚点和中心点设置在“MovePanel”在左下角,按钮的锚点和中心点设置在“MovePanel”的右下角。快速定位锚点和中心点可以点击每个需要定位的UI中的Rect Transform,通过Alt和Shift同时按下选择。
4、设置“JoySliceOut”和“Jump”的宽高,这里需要注意一下:因为Canvas模式设置成了Scale With Screen Size,所以调节宽高尽量不要直接使用拖拉和缩放的方式,应该通过数据面板上Rect Transform中的Width和Height来修改。这里“JoySliceOut”我设置成了 140 x 140 并添加了Sprite背景,“Jump”设置成了 70 x 70,去除了子组件Text中的文字,也添加了Sprite背景。最后将两个组件调节好合适的位置(可通过拖拉的方式)。
5、在“JoySliceOut”中添加图片,命名为“JoySlice”,这个是摇杆的内芯,设置大小 70 x 70 ,添加摇杆内芯的Sprite背景图。
6、去除“MovePanel”的背景色,将透明度调成0就可以了。至此,触控面板就做好了。
功能设计思路
1、摇杆带动人物移动,跳跃按钮实现人物跳跃,其余空着的区域控制视角转换,因此需要设计两个类JoySlicePlayerController和JoySliceCameraController,分开控制人物移动、跳跃和视角转换,并且每一个类需要继承Drag的一些类:IBeginDragHandler、IDragHandler、IEndDragHandler,将Drag操作事件分开写,以免在同一个屏幕上相互干扰。
2、摇杆内芯的拖动方向作为人物移动的方向,其方向向量可以通过前后不同触点的位置来计算,因此可以将控制人物移动的JoySlicePlayerController脚本挂载在摇杆内芯“JoySlice”上.
3、相机和人物的旋转可以通过获取在“MovePanel”上的前后触点位置,计算方向向量实现,因此可以将控制视角转换的JoySliceCameraController脚本挂载在“MovePanel”面板对象上。
4、由于摇杆处在“MovePanel”上,因此控制相机时需要去除“JoySliceOut”和“JoySlice”区域带来的影响。
摇杆控制人物移动
人物移动的脚本我们在上一章就已经介绍过了原理和代码实现,在移动端我们只需要对其进行一次改造就行。对人物移动参数有什么疑问的可以看下上一章的介绍,这里就不重复介绍了( 传送门: Unity实现人物移动、旋转、跳跃解决方案(电脑端))。将JoySliceCameraController.cs挂在“JoySlice”组件上。
JoySliceCameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class JoySlicePlayerController : MonoBehaviour, IBeginDragHandler,IDragHandler,IEndDragHandler
{
[Header("移动速度")]
public float speed = 2f;
[Header("检测范围")]
public float checkRadius = 0.5f;
[Header("检测层级")]
public LayerMask checkLayout;
[Header("跳跃高度")]
public float jumpHeight = 5f;
[Header("重力")]
public float gravity = 9.8f;
//玩家预设体
private Transform player;
private CharacterController characterController;
//碰撞检测体
private Transform checkGround;
//是否接触地面
private bool isGround;
//下降的速度
private Vector3 velocity;
//首次触碰对象名称
private string firstTouchName;
//画布
private Transform playerCanvas;
//摇杆外框
private Transform joySliceOut;
//摇杆内芯
private Transform joySlice;
//跳跃按键
private Button jumpBtn;
//内芯初始位置
private Vector3 originalPosition;
//内芯移动最大半径
private float moveMaxRadius = 0f;
//内芯摇杆的移动方向
private Vector3 joySliceMoveDir;
private void Awake()
{
//获取摇杆内芯
joySlice = transform;
//获取摇杆外框
joySliceOut = transform.parent;
//获取画布
playerCanvas = joySliceOut.parent.parent;
//获取跳跃按钮
jumpBtn = playerCanvas.Find("MovePanel/Jump").GetComponent<Button>();
//获取人物预设体
player = playerCanvas.parent;
//获取人物预设体第一人称组件
characterController = player.GetComponent<CharacterController>();
//获取检测点
checkGround = player.Find("CheckGround");
}
// Start is called before the first frame update
void Start()
{
//获取内芯最大移动半径,确保摇杆内芯的中心点最大只能在摇杆外框的边缘移动
moveMaxRadius = joySliceOut.GetComponent<RectTransform>().rect.width +joySlice.GetComponent<RectTransform>().rect.width;
//记录摇杆内芯的初始位置,为了取消移动可以回到初始位置准备
originalPosition = joySlice.position;
//跳跃按钮绑定跳跃方法
jumpBtn.onClick.AddListener(Jump);
}
// Update is called once per frame
void Update()
{
isGround = Physics.CheckSphere(checkGround.position, checkRadius, checkLayout);
if (isGround && velocity.y < 0)
{
velocity.y = -2f;
}
//只有初始触点是JoySlice才可以移动,确保摇杆内芯移动才可以控制人物移动
if (firstTouchName == "JoySlice")
{
//这里只需要内芯移动方向的单位向量,不要距离只要方向,这样可保证人物移动速度稳定
Vector3 playerMoveDir = player.right * joySliceMoveDir.normalized.x+ player.forward * joySliceMoveDir.normalized.y;
characterController.Move(playerMoveDir * speed * Time.deltaTime);
}
velocity.y -= gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);
}
//手指刚触碰监听函数
public void OnBeginDrag(PointerEventData eventData)
{
//获取初次点击对象的名称
firstTouchName = eventData.pointerEnter.name;
}
//手指在屏幕移动监听函数
public void OnDrag(PointerEventData eventData)
{
//只有手指在JoySlice上才可以拖动
if (firstTouchName == "JoySlice")
{
//获取当前触点位置,由于eventData.position是Vector2,所以需要转换
Vector3 touchPosition = new Vector3(eventData.position.x, eventData.position.y, 0);
//计算出当前位置与初始位置的差向量
joySliceMoveDir = touchPosition - originalPosition;
if (joySliceMoveDir.magnitude < moveMaxRadius)
{
//如果触点在摇杆框内移动,则直接将触点的位置赋给joySlice,带动内芯移动
joySlice.position = touchPosition;
}
else
{
//如果触点在摇杆框外移动,则将joySlice内芯的位置限制住,内芯的中心最大只能在外框边缘移动
joySlice.position = (touchPosition - originalPosition).normalized * moveMaxRadius + originalPosition;
}
}
}
//手指结束拖动监听函数
public void OnEndDrag(PointerEventData eventData)
{
//摇杆内芯位置复原
joySlice.position = originalPosition;
//摇杆内芯移动方向向量初始化
joySliceMoveDir = Vector3.zero;
//首次触点名称初始化
firstTouchName = "";
}
//跳跃
private void Jump()
{
if (isGround)
{
velocity.y = Mathf.Sqrt(jumpHeight * 2f * gravity);
}
}
}
问题:触点在摇杆框“JoySliceOut”外,摇杆内芯的位置该怎么计算?
这里先上一张图!
黑色箭头代表内芯到触点的方向向量。
红色箭头是内芯到限制内芯的方向向量。
它们两个方向相同,长度不同,可通过求单位向量乘以限制范围长度来获取限制内芯的位置(虚线圈圈)。让我们来解个方程:
x-originalPosition=(touchPosition-originalPosition).normalized*moveMaxRadius
x=(touchPosition-originalPosition).normalized*moveMaxRadius+originalPosition
限制内芯的位置就是这么算出来的。
空白区域控制视角转换
有些开发者喜欢另外创建一个摇杆来控制视角转换,但是我觉得这样操控很不舒服,在玩cf手游和和平精英的时候都是在空白区域操控的,这样让玩家的游戏体验会更好,所以我们也来这样设计!我们将JoySliceCameraController.cs脚本挂载在“MovePanel”上,关于相机在人物预设中的摆放和相关参数解释在上一章已经说过了,有需要的小伙伴可以回到上一章看看( 传送门: Unity实现人物移动、旋转、跳跃解决方案(电脑端))。
JoySliceCameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class JoySliceCameraController : MonoBehaviour, IBeginDragHandler,IDragHandler,IEndDragHandler
{
[Header("鼠标灵敏度")]
public float mouseSensitivity = 0.5f;
[Header("上下旋转最小角度")]
public float minRotate = -70f;
[Header("上下旋转最大角度")]
public float maxRotate = 70f;
[Header("视角切换速度")]
public float speed = 3f;
//人物头部
private Transform head;
//上一次触点位置
private Vector2 prePosition;
//触点左右移动差量
private float touchSpaceX;
//触点上下移动差量
private float touchSpaceY;
//首次触碰对象名称
private string firstTouchName;
//判断相机是否可以旋转
private bool isRotate = false;
private Transform player;
private void Awake()
{
//获取任务预设体
player = transform.parent.parent;
//获取任务预设体头部相机
head = player.Find("Camera");
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (isRotate)
{
//触点左右差量控制相机和人物预设体左右旋转
Quaternion quaternionX = Quaternion.AngleAxis(touchSpaceX, Vector3.up);
Quaternion quaternionY = Quaternion.AngleAxis(0, Vector3.left);
player.rotation = quaternionX * quaternionY;
//触点上下差量控制相机上下旋转
quaternionY = Quaternion.AngleAxis(touchSpaceY, Vector3.left);
head.rotation = quaternionX * quaternionY;
}
}
public void OnBeginDrag(PointerEventData eventData)
{
//获取触点初始位置
prePosition = eventData.position;
//获取初次拖动触点下的对象名
firstTouchName = eventData.pointerEnter.name;
}
public void OnDrag(PointerEventData eventData)
{
//只有初次触点对象名为MovePanel才可以计算两触点间距差量
if (firstTouchName== "MovePanel")
{
//获取拖动过程中对象名
string touchName = eventData.pointerEnter.name;
//当前触点位置和上一次触点位置计算手指移动方向向量
Vector3 moveDir = eventData.position - prePosition;
//将当前触点位置作为初始位置,为下次计算使用
prePosition = eventData.position;
//获取触点左右移动差量
touchSpaceX += moveDir.x * mouseSensitivity;
//获取触点上下移动差量
touchSpaceY += moveDir.y * mouseSensitivity;
//对上下移动差量作范围限制,模拟人头部视角限制
touchSpaceY = Mathf.Clamp(touchSpaceY, minRotate, maxRotate);
//相机只有手指初次在MovePanel上,并且移动过程中不会触碰到JoySliceOut和JoySlice才可以移动,防止于摇杆拖动事件产生冲突
isRotate = firstTouchName == "MovePanel" && touchName != "JoySliceOut" && touchName != "JoySlice" ? true : false;
}
}
public void OnEndDrag(PointerEventData eventData)
{
//阻止相机转动
isRotate = false;
//初次触点对象名重置
firstTouchName = "";
}
}
最后我们将预设体拖入场景中,启动项目,试一试摇杆、跳跃按钮和空白区域,看操作顺不顺滑。如果有安卓手机的,可以点击 File->Build Settings->Android->Build 打包成安卓包在手机上玩😊,这里记得在Player Setting中将屏幕配置成横屏。
可能出现的问题
如果有小伙伴启动编辑器发现触屏使用不了,不要慌,那是因为初次编辑没有在场景内添加UI的EventSystem,解决方法很简单,在场景随便加入一个UI对象,再删除这个不需要的对象就可以了,这时候会发现留下一个EventSystem对象,后面就可以正常使用了。
结语
到这里移动端的教学就告一段落了,如果对这两篇人物控制的文章有什么疑问,都可以评论区提出,我只要看到都会回复你们。如果觉得文章对你有用或者喜欢这一栏目的文章都可以收藏、点赞和关注,后面我还会出很多与Unity相关的实用又有趣的文章给大家看,一起体验3D给我们带来的欢乐😝!