详解Unity的移动控制实现

Posted 梦小天幼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Unity的移动控制实现相关的知识,希望对你有一定的参考价值。

前言

上一篇写了数种Unity中的移动方式,有物理移动,有非物理移动等,这篇我们来谈谈Unity中的移动控制方式,来结合上一篇所说的方法,用起来。一般控制是通过获取用户输入来处理角色移动逻辑的,而用户输入的设备有键盘、鼠标、手柄等等,本篇仅介绍最常用的键鼠控制角色移动方式。

目录

前排提醒:本文仅代表个人观点,以供交流学习,若有不同意见请评论留言,笔者一定好好学习,天天向上。

视频讲解:
详解Unity的移动控制实现_BiLiBiLi

本篇使用Translate来演示移动控制,若想使用其他移动方法,更新其移动逻辑即可


一、监听指定键 Input.GetKey()

在Input类中有专门用于监听指定按键的,如GetKey(是否持续按下某键)、GetKeyDown(是否按下某键),可以通过if判断这些键是否被按下来触发相应的移动逻辑,将整体的逻辑放到更新函数中,循环监听执行,来达到移动控制目的。

使用GetKey函数来监听某键是否持续按下,如果你想同时响应多个方向,请不要写成if…else形式

    public float speed = 3.0f;
    void Update()
    
        Move_Update();
    
    private void Move_Update()
    
        if (Input.GetKey(KeyCode.W))
        
            transform.Translate(Vector3.forward * speed * Time.deltaTime);
        
        if (Input.GetKey(KeyCode.S))
        
            transform.Translate(Vector3.back * speed * Time.deltaTime);
        
        if (Input.GetKey(KeyCode.A))
        
            transform.Translate(Vector3.left * speed * Time.deltaTime);
        
        if (Input.GetKey(KeyCode.D))
        
            transform.Translate(Vector3.right * speed * Time.deltaTime);
        
    

二、监听虚拟键(轴向) Input.GetAxis()

在Input类中也有专门用于监听轴向的,你可以这么理解:
Unity内置了一些虚拟键,其中Vertical代表当你按下“上”键或“下”键时触发,Horizontal代表当你按下“左”或“右”键时触发,而触发则返回一个范围在正负1的float值,而这个返回值就可以代替上一个例子中的Vertor.forward、back等值。这样定义的好处在于移植性高,比如这个例子移植到手机上依旧能正常运行,当拖动手机屏幕摇杆向上则触发Vertical,其余同理。

关于GetAxis定义的虚拟键的详细信息,大家可以到Edit-Project Settings-Input Manager中找到。

使用GetAxis函数来监听虚拟键是否持续按下,这种写法要比上一种更加简洁,且移动停止也更加柔和,对比上一个可以看到明显的生硬

    public float speed = 3.0f;
    void Update()
    
        Move_Update();
    
    private void Move_Update()
    
        Vector3 v = Input.GetAxis("Vertical") * Vector3.forward;
        Vector3 h = Input.GetAxis("Horizontal") * Vector3.right;
        //此处乘以V3向量是为了Translate,因为此函数只接受V3向量
        
        transform.Translate(v * speed * Time.deltaTime);
        transform.Translate(h * speed * Time.deltaTime);
    

三、监听虚拟键 Input.GetButton()

上一节说Unity内置了一些虚拟键,其中Vertical代表当你按下“上”键或“下”键时触发…这些只是用于方向,鼠标等真正具有轴向的虚拟控制键。而对于哪些功能性很强的,如跳跃,射击,出拳等操作没法用轴来定义了,Unity就又定义了一些虚拟键,用于这些功能。监听使用GetButton函数。

关于GetAxis定义的虚拟键的详细信息,大家可以到Edit-Project Settings-Input Manager中找到。

使用GetAxis函数来监听轴向,使用GetButton函数来监听虚拟键

比如下面这个例子,使用了GetButton来监听开火键(鼠标左键)、跳跃键(空格),因为一般开火键都是鼠标左键嘛,空格都是跳跃嘛。所以这些是属于约定俗成的东西。当然你完全可以更改,你改成鼠标左键跳跃也行,只要有人愿意买单就行,毕竟游戏引擎只是工具而已。

    void Update()
    
        if (Input.GetButtonDown("Jump"))
        
            transform.GetComponent<Rigidbody>().AddForce(transform.up * 100);
        
        if(Input.GetButtonDown("Fire1"))
        
            Debug.Log("开火!");
        
    

四、物体跟随鼠标移动

上一个例子我们通过监听虚拟键来控制物体前后左右移动,这次我们通过控制鼠标移动来让物体跟随,依旧用到了Input类中的值,当设置好监听键然后当鼠标移动时,会触发返回一个范围在正负1的float值,如下图所示,这是一个屏幕范围,鼠标向屏幕正右移动则返回大于0且小于1的相对与屏幕的比例数字,向左则相反。若你只想让返回的数值是正负1的话,可避免使用GetAxis(),而是使用GetAxisRaw(),本例就使用这种方法来做演示吧。

再次重复关于Input定义的虚拟键的详细信息,大家可以到Edit-Project Settings-Input Manager中找到。

而本例则会使用Mouse X和Mouse Y这两个虚拟键。

本例仅简单的通过修改transform的XZ坐标来实现移动的,若要做出精准的物体跟随鼠标运动,需要将屏幕坐标转换为世界坐标,才能通过对转换后的世界坐标位置进行位移操作,下一个例子就用到了转换坐标的相关知识。

    public float speed = 10f;
    void Update()
    
        Move_Update();
    
    private void Move_Update()
    
        float mouseX = Input.GetAxisRaw("Mouse X");
        float mouseY = Input.GetAxisRaw("Mouse Y");
    
        transform.Translate(Vector3.right * mouseX * Time.deltaTime * speed);
        transform.Translate(Vector3.forward * mouseY * Time.deltaTime * speed);
    

五、鼠标点击移动角色

上述几个例子都是通过键盘控制或鼠标移动控制,也有很多游戏是通过鼠标点击来移动的,获取鼠标点击时的位置信息也有很多方法,但其重点在于如何将2维平面转换为3维世界的实际位置,这涉及到世界坐标和屏幕坐标的相互转换,以及射线相关知识,读者可自行查阅API了解。后续我也会写相关的这类文章。

通过鼠标点击来移动角色思路如下:当鼠标点击时,从当前鼠标点击位置生成一条射线,穿过屏幕通过转换来获取实际世界坐标,然后通过更新函数来移动角色抵达相应位置

    private bool isNextMove = false;
    private Vector3 point;
    void Update()
    
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hitInfo;

        if (Input.GetMouseButtonDown(0))
        //当鼠标点击时,才触发射线检测
        
            if (Physics.Raycast(ray, out hitInfo))
            //当检测到地面
            
                isNextMove = true;
                point = hitInfo.point;
                //将isNextMove设为true,然后保存当前撞击点位置
            
        

        if(isNextMove == true)
        //当isNextMove为真,则不停调用Move
        
            Move(point);
        
    
    void Move(Vector3 pos)
    
        //使用Vector3的插值函数来移动位置
        transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * 3.0f);

        if (transform.position == pos)
        //当目标抵达位置的时候,将isNextMove置为false,等待下一次移动指令
            isNextMove = false;
    

六、总结和资料参考

1.总结

本篇主要介绍了关于移动控制的实现

  • 通过Input.GetKey()监听键盘
  • 通过Input.GetMouseButton()监听鼠标
  • 通过Input.GetAxis()监听虚拟轴向
  • 通过Input.GetButton()监听虚拟键

2.资料参考

[1]网络.Unity官方API & Unity圣典API
[2]梦天幼.详解Unity的几种移动方式实现
[3]梦天幼.详解Unity中的射线与射线检测

Unity控制逻辑(移动,翻滚,瞬移)

目录

1.案例的前期准备

2.移动的实现

3.翻滚的实现

4.瞬移的实现


1.案例的前期准备

1.创建2D项目

 2.搭建场景

分别创建player(玩家),background(背景图),barrier(障碍物)。

 3.创建C#脚本

在对象player上创建CharacterController2D脚本

2.移动的实现

public class CharacterController2D : MonoBehaviour

    private const float MOVE_SPEED = 5f;
    
    private void Awake()
    
        rigidbody2D = GetComponent<Rigidbody2D>();
    
    
    private void Update()
    
        float moveX = 0f;
        float moveY = 0f;

        if (Input.GetKey(KeyCode.W))
        
            // transform.position += new Vector3(0,+1);
            moveY = +1f;
        
        if (Input.GetKey(KeyCode.S))
        
            // transform.position += new Vector3(0,-1);
            moveY = -1f;
        
        if (Input.GetKey(KeyCode.A))
        
            // transform.position += new Vector3(-1,0);
            moveX = -1f;
        
        if (Input.GetKey(KeyCode.D))
        
            // transform.position += new Vector3(+1,0);
            moveX = +1f;
        
        moveDir = new Vector3(moveX, moveY).normalized;
    

    private void FixedUpdate()
    
        rigidbody2D.velocity = moveDir * MOVE_SPEED;
    


实现方法:方向向量*移动速度

1,使用const储存物体速度(float)

2,在Update()中使用Input.GetKey(keyCode.按键)方法读取玩家相应按键 

3,使用moveX,moveY分别保存在X,Y轴的移动增量,以此创建Vector

4,使用normalized获取Vector对象的方向向量(单位向量)赋给moveDir              

Rigidbody2D.velocity

描述

刚体的线性速度,采用单位/秒形式。

在移动或旋转一个物体时,往往会直接使用Transform来执行这些操作。这种方法对于不具物理特性的GameObject来说,是可行的。但是一旦GameObject上附带有Rigidbody2D,这种方式就会带来性能的损失

3.翻滚的实现

public class CharacterController2D : MonoBehaviour

    //构建枚举类区分行走状态和翻滚状态
    private enum State
    
        Normal, //正常行走
        Rolling, //翻滚
    

    [SerializeField] private LayerMask dashLayerMask;

    private Rigidbody2D rigidbody2D;
    private Vector3 moveDir;
    private Vector3 rollDir; //翻滚的方向
    private Vector3 lastMoveDir; //保存最后一次非静止状态的Vector
    private float rollSpeed; //翻滚的初始速度
    private State state; 

    private void Awake()
    
        rigidbody2D = GetComponent<Rigidbody2D>();
        state = State.Normal;
    

    private void Update()
    
        switch (state)
        
            case State.Normal:
                float moveX = 0f;
                float moveY = 0f;
                if (Input.GetKey(KeyCode.W))
                
                    // transform.position += new Vector3(0,+1);
                    moveY = +1f;
                
                if (Input.GetKey(KeyCode.S))
                
                    // transform.position += new Vector3(0,-1);
                    moveY = -1f;
                
                if (Input.GetKey(KeyCode.A))
                
                    // transform.position += new Vector3(-1,0);
                    moveX = -1f;
                
                if (Input.GetKey(KeyCode.D))
                
                    // transform.position += new Vector3(+1,0);
                    moveX = +1f;
                

                moveDir = new Vector3(moveX, moveY).normalized;

                //保存最后一次非静止状态的Vector,防止原地翻滚
                if(moveX != 0f || moveY != 0)
                
                    lastMoveDir = moveDir;
                
                
                //按下空格键进入翻滚状态   
                if (Input.GetKey(KeyCode.Space))
                
                    //最后一次非静止状态的Vector,即翻滚的方向
                    rollDir = lastMoveDir;
                    //翻滚的初始速度
                    rollSpeed = 25f;
                    state = State.Rolling;
                
                break;
            case State.Rolling:
                //翻滚是一个减速过程
                //减速系数
                float rollSpeedDropMultiplier = 5f;
                //按帧减速
                rollSpeed -= rollSpeed * rollSpeedDropMultiplier * Time.deltaTime;

                //设置下限
                float rollSpeedMinimum = 5f;
                //小于下限切换至行走状态
                if(rollSpeed < rollSpeedMinimum)
                
                    state=State.Normal;
                
            break;
        


    

    private void FixedUpdate()
    
        switch (state)
        
            case State.Normal:
                rigidbody2D.velocity = moveDir * MOVE_SPEED;
                break;
            case State.Rolling:
                rigidbody2D.velocity = rollDir * rollSpeed;
                break;
        

    

4.瞬移的实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CharacterController2D : MonoBehaviour

    private const float MOVE_SPEED = 5f;

    private enum State
    
        Normal,
        Rolling,
    

    [SerializeField] private LayerMask dashLayerMask;//设置射线与哪些层级发生碰撞,此项为可序列化的

    private Rigidbody2D rigidbody2D;
    private Vector3 moveDir;
    private Vector3 rollDir;
    private Vector3 lastMoveDir;
    private float rollSpeed;
    private bool isDashButtonDown;//瞬移状态切换
    private State state;

    private void Awake()
    
        rigidbody2D = GetComponent<Rigidbody2D>();
        state = State.Normal;
    

    private void Update()
    
        switch (state)
        
            case State.Normal:
                float moveX = 0f;
                float moveY = 0f;
                if (Input.GetKey(KeyCode.W))
                
                    // transform.position += new Vector3(0,+1);
                    moveY = +1f;
                
                if (Input.GetKey(KeyCode.S))
                
                    // transform.position += new Vector3(0,-1);
                    moveY = -1f;
                
                if (Input.GetKey(KeyCode.A))
                
                    // transform.position += new Vector3(-1,0);
                    moveX = -1f;
                
                if (Input.GetKey(KeyCode.D))
                
                    // transform.position += new Vector3(+1,0);
                    moveX = +1f;
                

                moveDir = new Vector3(moveX, moveY).normalized;
                if(moveX != 0f || moveY != 0)
                
                    lastMoveDir = moveDir;
                
                
                //按F进入瞬移状态
                if (Input.GetKey(KeyCode.F))
                
                    isDashButtonDown = true;
                

                if (Input.GetKey(KeyCode.Space))
                
                    rollDir = lastMoveDir;
                    rollSpeed = 25f;
                    state = State.Rolling;
                
                break;
            case State.Rolling:
                float rollSpeedDropMultiplier = 5f;
                rollSpeed -= rollSpeed * rollSpeedDropMultiplier * Time.deltaTime;

                float rollSpeedMinimum = 5f;
                if(rollSpeed < rollSpeedMinimum)
                
                    state=State.Normal;
                
            break;
        


    

    private void FixedUpdate()
    
        switch (state)
        
            case State.Normal:
                rigidbody2D.velocity = moveDir * MOVE_SPEED;

                if (isDashButtonDown)
                
                    //瞬移距离
                    float dashAmount = 0.5f;
                    //无障碍物情况下的瞬移量
                    Vector3 dashPosition = transform.position + lastMoveDir * dashAmount;

                    //使用射线判断瞬移路径上是否有障碍物,有则被阻碍
                    RaycastHit2D raycastHit2D = Physics2D.Raycast(transform.position, lastMoveDir, dashAmount, dashLayerMask);
                    //射线碰撞的物体不为空
                    if (raycastHit2D.collider != null)
                    
                        //射线与物体碰撞的点即为瞬移的最终位置
                        dashPosition = raycastHit2D.point;
                    
                    rigidbody2D.MovePosition(dashPosition);
                    //切换至行走状态
                    isDashButtonDown = false;
                
                break;
            case State.Rolling:
                rigidbody2D.velocity = rollDir * rollSpeed;
                break;
        

    

​​​RigidBody2D.MovePosition

描述

将刚体移动到 /position/。

通过计算在下一次物理更新期间将刚体移动到指定 position 所需的适当线速度来将刚体移动到该位置。在移动过程中,重力或线性阻力都不会影响刚体。这使得对象能够快速从现有位置穿过世界移动到指定的 /position/。

由于该功能允许刚体穿过世界快速移动到指定的 /position/,因此附加到刚体的任何碰撞体都将按预期作出反应,也就是说,它们将产生碰撞和/或触发。这也意味着如果碰撞体产生碰撞,则将影响到刚体的运动,并可能阻止刚体在下一次物理更新期间到达指定的 /position/。如果是运动刚体,则任何碰撞都不影响刚体本身,只会影响任何其他动态碰撞体。

2D 刚体对其移动速度有固定限制,因此在短时间内尝试移动较远的距离会导致刚体无法在下一次物理更新期间到达指定 /position/。建议仅将该函数用于相对较短距离的移动。

请务必注意, 实际的位置更改只在下一次物理更新期间进行, 因此重复调用该方法而不等待下一次物理更新将导致使用最后一次调用。 因此,建议在 FixedUpdate 回调期间调用该函数。

Rigidbody2D.velocity:将钢体线性移动至某一位置

RigidBody2D.MovePosition:将钢体线性瞬移至某一位置

如何理解本案例中的射线RaycastHit2D

若没有使用射线进行判定,这钢体可能可以穿越碰撞体

RaycastHit2D raycastHit2D = Physics2D.Raycast(transform.position, lastMoveDir, dashAmount, dashLayerMask);

本案例的射线分别有如下参数

1.射线的起始位置

2.射线的方向

3.射线的距离

4.射线应于哪些层级发生碰撞

ps:若不设置此项,则与所有层级发生碰撞。可能导致本案例中的瞬移无法实现

以上是关于详解Unity的移动控制实现的主要内容,如果未能解决你的问题,请参考以下文章

[Unity]尝试实现第三人称控制器(其四移动控制②)

googlevr unity 怎么用蓝牙手柄控制移动

Unity3D 在运行时实现物体的移动旋转伸缩控制

unity HTC Vive 通过手柄控制3D物体移动、旋转

Unity依赖注入使用详解

详解Unity中的角色控制器