IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 手撸第三人称角色控制器——Unity制作魂类游戏笔记 01 -> 正文阅读

[游戏开发]手撸第三人称角色控制器——Unity制作魂类游戏笔记 01

本文包含内容:

  1. 类似黑魂的角色控制器(适配鼠标和手柄)
  2. 以及第三人称相机

参考教程链接:
Unity从零开始制作魂类游戏
?该文章为教程的P1、P2内容

代码架构

?由一个InputSystem PlayerControl和 四个Scripts InputHandleAnimatorHandlePlayerLocomotionCameraHandle组成

PlayerControl(InputSystem):负责处理存储键鼠、手柄的输入。

InputHandle:处理PlayerControl里的数据,把InputSystem里的数据类型转化为float、Vector2这种常用、直接的数据类型。

AnimatorHandle:负责给animator传入数值,改变动画状态

PlayerLocomotion:负责Player的rigidbody.velocity,以及transform.rotation

CameraHandle:负责控制相机的跟随,旋转。

InputHandle处理完输入数据后,其他脚本进行计算和封装。Player的相关函数在PlayerLocomotion的Update中统一执行,CameraHandle则在InputSystem中执行。

?

代码解读

InputSystem 分析

在这里插入图片描述

??在PlayerControl中,有Movement控制角色的移动输入,Camera控制相机的旋转输入。
??在InputHandle对这两个Action进行处理,加工成float、Vector2类型,具体如下:

inputActions.PlayerMovement.Movement.performed += inputActions => movementInput = inputActions.ReadValue<Vector2>();
inputActions.PlayerMovement.Camera.performed += i => cameraInput = i.ReadValue<Vector2>();

👆将PlayerControl中的Movement传给Vector2 movementInput,把Camera传给Vector2 cameraInput中。

private void MoveInput(float delta)
{
	horizontal = movementInput.x;
	vertical = movementInput.y;
	moveAmount = Mathf.Clamp01(Mathf.Abs(horizontal) + Mathf.Abs(vertical));    //先对两个方向取绝对值,再约束到0—1
	mouseX = cameraInput.x;
	mouseY = cameraInput.y;
}

👆然后在MoveInput中再转化成x轴移动输入horizontal,y轴移动输入vertical,移动速度moveAmount(因为手柄的摇杆输入是有轻按的,可能会小于1,这时候就要让Player用walk动画而非run)。
同理把cameraInput转化成相机的x轴旋转输入mouseX,y轴移动输入mouseY

Player移动部分分析

我们改变的所有rigidbody、rotation都是在外层的gameobject上改动的,内层模型的位置、旋转等始终不发生变化,只负责动画的播放。

moveDirection = cameraObject.forward * inputHandle.vertical;    //z
moveDirection += cameraObject.right * inputHandle.horizontal;   //x
moveDirection.y = 0.0f;
moveDirection.Normalize();
moveDirection *= speed;

👆用cameraObject.forward等求出Player的移动方向,乘以speed得出位移。关于Transform.forward的用法可以看看这篇文章
简单来说就以相机的世界坐标为坐标系,得到相机视角下的前后、左右方向,但因为相机会有一些上下倾斜,所以要对y轴归零,最后归一化再乘以speed,就得到一个长度为speed的Vector2位移了。
在这里插入图片描述
至于后面的那句 Vector3 projectedVelocity = Vector3.ProjectOnPlane(moveDirection, normalVector);
作用是投影,可能后面才会用到?
最后把求到的Velocity 赋给rigidbody就可以了。

Player旋转处理

private void HandleRotation(float delta)
{
	Vector3 targetDir = Vector3.zero;   //targetDir为移动的方向
    float moveOverride = inputHandle.moveAmount;
    targetDir = cameraObject.forward * inputHandle.vertical;
    targetDir += cameraObject.right * inputHandle.horizontal;

    targetDir.Normalize();
   	targetDir.y = 0;    
	if (targetDir == Vector3.zero)
		targetDir = myTransform.forward;

	float rs = rotationSpeed;

    Quaternion tr = Quaternion.LookRotation(targetDir); //转化成四元数形式
	Quaternion targetRotation = Quaternion.Slerp(myTransform.rotation, tr, rs * delta);

	myTransform.rotation = targetRotation;
}

首先计算出移动的方向,使用LookRotation函数求出四元数形式的朝向,再用slerp做球形插值。

动画部分分析

public void UpdateAnimatorValue(float verticalMovement,float horizontalMovement)
{
	#region Vertical
	float v = 0;
	if(verticalMovement > 0 && verticalMovement < 0.55f)
    {
    	v = 0.5f;
    }
    else if(verticalMovement > 0.55f)
    {
    	v = 1;
    }
    else
    {
    v = 0;
    }
}

👆使用上一步求出的moveAmount,分三级分别播放run、walk、idle动画,反应到手柄上就是轻按摇杆为walk,按到底是run。

相机跟随旋转分析

相机的transform,外层CameraHolder负责相机位置和Y轴旋转,中间的CameraPivot负责X轴旋转,内层Camera本身不受输入影响。

public void FollowTarget(float delta)
{
	Vector3 targetPosition = Vector3.Lerp(myTransform.position, targetTransform.position, delta / followSpeed);
	myTransform.position = targetTransform.position;
}

👆外层CameraHolder初始位置为(0,0,0),与Player相同,之后每一帧也跟着Player移动,做到跟随效果。

public void HandleCameraRotation(float delta,float mouseXInput,float mouseYInput)
{
	lookAngle += (mouseXInput * lookSpeed) / delta;

	Vector3 rotation = Vector3.zero;
    rotation.y = lookAngle;
    Quaternion targetRotation = Quaternion.Euler(rotation);
    myTransform.rotation = targetRotation;
}    

用lookAngle存储y轴旋转度数,每次赋值给rotation.y,再赋值给CameraHolder.rotation

public void HandleCameraRotation(float delta,float mouseXInput,float mouseYInput)
{
    pivotAngle -= (mouseYInput * pivotSpeed) / delta;
    pivotAngle = Mathf.Clamp(pivotAngle, minimumPivot, maximumPivot);

    rotation = Vector3.zero;
    rotation.x = pivotAngle;
    targetRotation = Quaternion.Euler(rotation);    //将欧拉角度数转化成四元数
	cameraPivotTransform.localRotation = targetRotation;
}

z轴同理,不过要加一个clamp()函数,防止出现过高、过低的角度。

?

结果

在这里插入图片描述

源代码

PlayerControl


注意Movement和Camera的Action Type设置成Pass Through,Vector2。

InputHandle

//InputHandle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ASH {
    public class InputHandle : MonoBehaviour
    {
        public float horizontal, vertical,moveAmount,mouseX,mouseY; //水平、竖直的移动
        PlayerControl inputActions;
        CameraHandle cameraHandler;

        Vector2 movementInput, cameraInput;

        private void Awake()
        {
            cameraHandler = CameraHandle.singleton;
        }

        private void FixedUpdate()
        {
            float delta = Time.deltaTime;
            if(cameraHandler != null)
            {
                cameraHandler.FollowTarget(delta);
                cameraHandler.HandleCameraRotation(delta, mouseX, mouseY); 
            }
        }
        public void OnEnable()
        {
            if(inputActions == null)
            {
                inputActions = new PlayerControl();
                inputActions.PlayerMovement.Movement.performed += inputActions => movementInput = inputActions.ReadValue<Vector2>();
                inputActions.PlayerMovement.Camera.performed += i => cameraInput = i.ReadValue<Vector2>();
            }
            inputActions.Enable();
        }

        private void OnDisable()
        {
            inputActions.Disable();
        }

        public void TickInput(float delta)
        {
            MoveInput(delta);
        }
        private void MoveInput(float delta)
        {
            horizontal = movementInput.x;
            vertical = movementInput.y;
            moveAmount = Mathf.Clamp01(Mathf.Abs(horizontal) + Mathf.Abs(vertical));    //先对两个方向取绝对值,再约束到0—1
            mouseX = cameraInput.x;
            mouseY = cameraInput.y;
        }
    }
}

AnimatorHandle

//AnimatorHandle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ASH
{
public class AnimatorHandle : MonoBehaviour
{
        public Animator anim;
        int vertical, horizontal;

        public bool canRotate;

        public void Initialize()
        {
            anim = GetComponent<Animator>();
            vertical = Animator.StringToHash("Vertical");
            horizontal = Animator.StringToHash("Horizontal");
        }

        public void UpdateAnimatorValue(float verticalMovement,float horizontalMovement)
        {
            #region Vertical
            float v = 0;
            if(verticalMovement > 0 && verticalMovement < 0.55f)
            {
                v = 0.5f;
            }
            else if(verticalMovement > 0.55f)
            {
                v = 1;
            }
            else if(verticalMovement < 0 && verticalMovement > -0.55f)
            {
                v = -0.5f;
            }
            else if(verticalMovement < -0.55f)
            {
                v = -1;
            }
            else
            {
                v = 0;
            }
            #endregion

            #region Horizontal
            float h = 0;
            if(horizontalMovement > 0 && horizontalMovement < 0.55f)
            {
                h = 0.5f;
            }
            else if(horizontalMovement > 0.55f)
            {
                h = 1;
            }
            else if(horizontalMovement < 0&& horizontalMovement > -0.55f)
            {
                h = -0.5f;
            }
            else if(horizontalMovement < -0.55f)
            {
                h = -1;
            }
            else
            {
                h = 0;
            }
            #endregion

            anim.SetFloat(vertical, v, 0.1f, Time.deltaTime);
            anim.SetFloat(horizontal, h, 0.1f, Time.deltaTime);
        }

        public void CanRotate()
        {
            canRotate = true;
        }
        public void StopRotation()
        {
            canRotate = false;
        }
    }
}

PlayerLocomotion

//PlayerLocomotion.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ASH
{
    public class PlayerLocomotion : MonoBehaviour
    {
        Transform cameraObject;
        InputHandle inputHandle;
        Vector3 moveDirection;

        [HideInInspector]
        public Transform myTransform;
        [HideInInspector]
        public AnimatorHandle animatorHandle;

        public new Rigidbody rigidbody;
        public GameObject normalCamera;

        [Header("Stats")]
        [SerializeField] float moveSpeed = 5.0f;
        [SerializeField] float rotationSpeed = 10.0f;



        void Start()
        {
            rigidbody = GetComponent<Rigidbody>();
            inputHandle = GetComponent<InputHandle>();
            animatorHandle = GetComponentInChildren<AnimatorHandle>();
            cameraObject = Camera.main.transform;
            myTransform = transform;
            animatorHandle.Initialize();
        }

        private void Update()
        {
            float delta = Time.deltaTime;
            inputHandle.TickInput(delta);
            moveDirection = cameraObject.forward * inputHandle.vertical;    //z
            moveDirection += cameraObject.right * inputHandle.horizontal;   //x
            moveDirection.y = 0.0f;
            moveDirection.Normalize();


            float speed = moveSpeed;
            moveDirection *= speed;
            Vector3 projectedVelocity = Vector3.ProjectOnPlane(moveDirection, normalVector);
            rigidbody.velocity = moveDirection;

            animatorHandle.UpdateAnimatorValue(inputHandle.moveAmount, 0);
            if(animatorHandle.canRotate)
            {
                HandleRotation(delta);
            }
        }


        #region movement
        Vector3 normalVector;
        Vector3 targetPositon;

        private void HandleRotation(float delta)
        {
            Vector3 targetDir = Vector3.zero;   //targetDir为移动的方向
            float moveOverride = inputHandle.moveAmount;
            targetDir = cameraObject.forward * inputHandle.vertical;
            targetDir += cameraObject.right * inputHandle.horizontal;

            targetDir.Normalize();
            targetDir.y = 0;    
            if (targetDir == Vector3.zero)
                targetDir = myTransform.forward;

            float rs = rotationSpeed;

            Quaternion tr = Quaternion.LookRotation(targetDir); //转化成四元数形式
            Quaternion targetRotation = Quaternion.Slerp(myTransform.rotation, tr, rs * delta);

            myTransform.rotation = targetRotation;
        }
        #endregion
    }
}

CameraHandle

//CameraHandle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ASH
{ 
public class CameraHandle : MonoBehaviour
{
        public Transform targetTransform,       //相机移动目标点
            cameraTransform,                    //相机当前位置
            cameraPivotTransform;               //相机围绕旋转的枢轴
        private Transform myTransform;          //脚本
        private Vector3 cameraTransformPosition;//相机位置
        private LayerMask ignoreLayers;         //

        public static CameraHandle singleton;

        public float lookSpeed = 0.1f, followSpeed = 0.1f, pivotSpeed = 0.03f;

        private float defaultPosition,lookAngle,pivotAngle;
        public float minimumPivot = -35;
        public float maximumPivot = 35;

        private void Awake()
        {
            singleton = this;
            myTransform = transform;
            defaultPosition = cameraTransform.localPosition.z;
            ignoreLayers = ~(1 << 8 | 1 << 9 | 1 << 10);
        }

        public void FollowTarget(float delta)
        {
            Vector3 targetPosition = Vector3.Lerp(myTransform.position, targetTransform.position, delta / followSpeed);
            myTransform.position = targetTransform.position;
        }

        public void HandleCameraRotation(float delta,float mouseXInput,float mouseYInput)
        {
            lookAngle += (mouseXInput * lookSpeed) / delta;

            Vector3 rotation = Vector3.zero;
            rotation.y = lookAngle;
            Quaternion targetRotation = Quaternion.Euler(rotation);
            myTransform.rotation = targetRotation;


            pivotAngle -= (mouseYInput * pivotSpeed) / delta;
            pivotAngle = Mathf.Clamp(pivotAngle, minimumPivot, maximumPivot);

            rotation = Vector3.zero;
            rotation.x = pivotAngle;
            targetRotation = Quaternion.Euler(rotation);    //将欧拉角度数转化成四元数
            cameraPivotTransform.localRotation = targetRotation;
        }
    }
}
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:55:11  更:2022-07-17 16:55:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:40:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码