1.16 从0开始学习Unity游戏开发--人物控制

news2024/11/26 4:33:55

上一篇我们简单的做了一个玩家不动的情况下,如何控制准心来射击子弹,但是显然正常的游戏需要移动玩家本体,所以本篇我们需要补全这部分玩法所需的功能。

人物移动

在我们之前的篇章里面,讲解了如何通过物理引擎来实现物体的物理仿真移动,那么我们是不是可以通过物理引擎来实现人物的移动呢?

当然可以,在我们做之前,我们先给场景里面添加好一个地面和几面墙,我们都使用Cube来拉长实现,因为Cube自带了Collider组件,可以直接支持物理碰撞,实现我们的人物能站在上面不掉下去。之前那个Wall我们也加长一点:

然后我们再新建一个Cube来作为我们的玩家人物,因为我们的人物是需要受物理引擎改变位置的,所以不要忘记加入RigidBody组件:

我们把方块放在比较高的位置,如果跑起来的话可以看到这个Player会掉下来落到Ground上,但是不会掉下去。

ok,现在我们需要加入一个组件来实现对Player的控制效果,也就是我们使用wasd需要能给这个Player的刚体组件提供一个速度。

有请gpt帮我们快速写一个:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed = 20f; // 控制速度的变量
    private Rigidbody rb; // 刚体组件

    void Start()
    {
        rb = GetComponent<Rigidbody>(); // 获取刚体组件
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal"); // 获取水平方向的输入
        float moveVertical = Input.GetAxis("Vertical"); // 获取垂直方向的输入

        Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical); // 创建一个移动向量

        rb.AddForce(movement * speed); // 将移动向量乘以速度并添加到刚体上
    }
}

值得注意的是,gpt正确的意识到了物理引擎相关的每帧操作需要在FixedUpdate里面而非Update里面,这跟Unity内部各个系统执行的顺序有关,如果你用了错误的Update,那么可能就会出现你这帧设置的物理数据,没有及时的在本帧体现出来,而是延迟到下一帧才体现。

给Player这个方块加上这个PlayerController组件看看效果(注意需要在Scene窗口下观察,因为我们还没实现相机跟着人物的效果):

 

可以看到虽然确实是受我们的wasd输入影响了,但是感觉很奇怪,是这样几个问题:

  1. 正常人走路是不可能翻滚的
  2. 按下wasd会有一个加速的过程,并不是跟真实人走路一样立刻就以一个速度行走了,并且这个速度会随着按住时常加速到越来越快

对于问题1,主要原因是虽然我们只给予了水平方向的力(XZ轴),但是因为来自摩擦力或者碰撞带来的反作用力的计算精度问题,总会出现一些不那么水平的力,所以就会让物体无法保持稳定,就出现了翻滚,我们如果需要保持物体不翻滚,就可以锁定RigidBody组件里面的对应坐标轴:

这里很浅显易懂,Freeze就是让数据不要变动,而它支持我们单独的锁定位置信息或者旋转信息,现在我们需要锁定的是XYZ所有方向上的旋转,只希望我们的位置信息被物理引擎改变,那么勾选上Freeze Rotation的三个勾选框即可。

对于问题2,其实是因为我们人正常行走是来自脚蹬起来的,并不是单纯的一个小滑块进行水平加速形成的,这其中会涉及很多物理学上的东西,所以如果我们需要在游戏内模拟出真实的人类关节进行行走,那代价是挺大的,也没那么必要。所以我们可以通过直接修改刚体的速度而非施加力来近似这个效果(在Rigidbody.velocity的官方文档里面也提到了这些),但是需要注意的是,只有特定场景才需要这么直接修改速度,正常情况下应该都是施加力。

好,那我们再修改一下代码:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed = 1f; // 控制速度的变量
    private Rigidbody rb; // 刚体组件

    void Start()
    {
        rb = GetComponent<Rigidbody>(); // 获取刚体组件
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal"); // 获取水平方向的输入
        float moveVertical = Input.GetAxis("Vertical"); // 获取垂直方向的输入

        Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical) * speed; // 创建一个移动向量

        rb.velocity = new Vector3(movement.x, rb.velocity.y, movement.z);  // 直接修改速度
    }
}

这里我们直接修改了速度,但是需要注意的是,我们不是直接赋值整个vector3,因为还有Y轴上的速度分量我们并不希望置空,仍然希望它能受到重力影响而不至于漂浮在地面上。

跑起来看看:

 

这样看起来正常多了,但是目前为止,我们还是只实现了在Scene窗口里面看到我们的操作,实际上我们操作人物移动的时候,游戏视角应该会跟着变化,因此接下来我们需要进一步的控制我们的相机。

相机控制

在我们之前的文章中其实已经简单讲解过画面是如何通过相机的参数来确定如何渲染,以及我们如何移动相机来调整我们的视角。

简单来说,根据游戏玩法的不同,相机所代表的意义也不一样。

  • 在第一人称的游戏中,相机其实就是玩家的眼睛
  • 在第三人称或者俯视角的游戏中,相机其实跟我们在场景窗口里面漫游整个场景类似,是一个独立的不存在于真实世界逻辑中的视角。但是这样的视角能让玩家更加直观的操控和感知游戏世界,所以就存在了这样一种相机使用的方法。

对于第一人称来说比较好理解,直接把相机绑定在人物的固定位置(比如眼睛位置)上,就可以实现,所以我们本篇会直接讲解第三人称这种形式下,相机应该是如何处理的。

在制作之前,找一下各种第三人称的游戏视频先参考参考,可以发现,相机其实是一直跟在人物背后,人物移动的前进方向其实是视角的方向,而非当前人物面朝的方向,这一点可以尝试让角色面向你,然后操作向前跑,人物会立即转身朝着视线方向跑。

另外一点就是,相机都是会距离人物一定的距离,不至于让人物挡住了全部屏幕,也不至于让人物在屏幕中占比太小。

想明白这些规则之后,我们尝试做一下相机的处理:首先我们需要让相机跟人物离一个固定距离,这样我们需要在每帧计算人物所在的位置,然后倒推出相机所在的位置。

但是在3D世界里面,距离一个点固定长度的位置有无数多个,这些位置组合成了一个球形,所以相机可以在这个球面上的任意一个点上,如果我们再加上相机的Y轴坐标,就能限定相机到一个2D圆环上,最后如果再限定当前相机的朝向,则可以唯一确定一个坐标点,而这个点就是相机的位置。

如果画图的话大概是这样:

黑色表示距离人物位置的定长的所有点组成的球体,蓝色表示如果限定相机Y轴坐标能确定的一个圆面,红色表示如果限定相机当前看向哪里,则可以在圆面上找到唯一一个点(虽然可以有两个,但是另外一个点无法让人物在相机镜头内,显然不能用)。

那么我们来简单推导一下数学公式:

假设相机始终看向玩家的位置是(x,y,z)

蓝色圆心是: (x, y+h, z)

假设相机朝向是direction

那么蓝色圆上的任意一个点是 (x,y+h,z) + (direction.x, 0, direction.z) * r

圆形半径r^2 = distance^2 - h^2,distance是相机距离看向位置的距离

可以看到上面这些量全是已知,除了相机水平方向上的朝向

那么我们可以简单的让相机初始状态的朝向就是人物正前方朝向来计算这个逻辑

好了,有了这个数学基础,我们需要用代码实现一下:

using UnityEngine;

public class ThirdPersonCameraController : MonoBehaviour
{
    public Transform target; // 相机要跟随的目标
    public float distance = 10f; // 相机与目标的距离
    public float height = 5f; // 相机与目标的高度

    private void LateUpdate()
    {
        // 相机于目标的距离不能小于高度差,不然无论如何都无法维持
        if (Mathf.Abs(distance) < Mathf.Abs(height))
        {
            return;
        }
        
        // 先计算圆心的位置
        Vector3 lookAtPosition = target.transform.position;
        Vector3 circleCenter = lookAtPosition + new Vector3(0, height, 0);
        
        // 计算圆的半径
        float circleRadius = Mathf.Sqrt(distance * distance - height * height);

        // 简单处理让相机的水平朝向就是人物朝向
        Vector3 cameraDirection = target.transform.forward;
        // 记得干掉Y轴的数据,因为我们是在水平面的圆形上计算位置信息
        cameraDirection.y = 0;
        
        // 最后拿到相机的位置
        Vector3 cameraPos = circleCenter + (cameraDirection.normalized * circleRadius);
        
        // 设置相机的位置
        transform.position = cameraPos;
        // 让相机朝向目标
        transform.LookAt(target);
    }
}

把组件加到MainCamera上,然后将target赋值为Player,跑起来看看?

1.16 从0开始学习Unity游戏开发--人物控制-1

默认给的参数值不太合理,可以在运行时动态调整到我们满意的效果,但是记得这个调整在退出后会重置回去,所以需要记一下等退出运行后再改一下。

值得注意的是,我们用了LateUpdate而不是Update,可以查阅官方文档知道LateUpdate发生在Update和FixedUpdate之后,其实意思就是说我们需要等物体移动完毕后再修改相机位置,否则如果我们先修改相机位置,而这一帧物体又动了,这样我们的渲染结果可能就不是很准确。

Ok,现在我们初步完成了一个三人称相机的功能,虽然可以相机可以跟着Player走,但是我们还没有支持视角调整,也就是说我们需要支持鼠标移动来调整相机朝向,我们可以接收鼠标的移动输入信息来修改当前相机的朝向。

那我们又需要处理Input的鼠标移动的信息,如果鼠标水平移动,则是希望让相机的朝向发生改变,如果鼠标的竖直方向上移动,则是希望让相机相对于物体的高度差发生改变,如果鼠标滚动滑轮,我们认为是希望修改相机距离玩家人物的距离。

好,我们修改一下相机控制脚本来处理这几个输入信息:

对于高度和距离,我们可以简单的通过加减来实现,但是对于相机的朝向,我们需要通过旋转来实现,为了降低理解成本,我们换一种方法计算相机的朝向。

上面我们也说了,相机朝向本质上是用来确定相机在那个圆上的位置,相机和圆心的连线是有夹角的,我们可以通过改变夹角来旋转相机:

蓝色部分就是我们俯视这个圆形,那么相机在圆上的位置和圆心的连线跟初始状态(就是中间那根竖直的黑线)有一个夹角,夹角就是红色那个角,半径已知的情况下,我们可以使用sin和cos来计算出半径和相机距离圆心距离在XZ两个轴上的比值。我们只需要决定我们的相机0度角的时候是用X轴的方向还是Z轴的方向来做初始朝向,这里我们用X轴,那么sin对应的是z轴距离/r,cos对应的是x轴距离/r。

所以我们的相机控制脚本修改如下:

using UnityEngine;

public class ThirdPersonCameraController : MonoBehaviour
{
    public Transform target; // 相机要跟随的目标
    public float distance = 10f; // 相机与目标的距离
    public float height = 5f; // 相机与目标的高度

    public float directionChangeSpeed = 1.0f;
    public float heightChangeSpeed = 1.0f;
    public float distanceChangeSpeed = 1.0f;

    private float currentDegree;
    private float currentHeight;
    private float currentDistance;

    private void Start()
    {
        // 初始情况下对齐玩家的正向朝向
        currentDegree = 0;
        currentHeight = height;
        currentDistance = distance;
    }

    private void LateUpdate()
    {
        float directionChange = Input.GetAxis("Mouse X") * directionChangeSpeed;
        float heightChange = Input.GetAxis("Mouse Y") * heightChangeSpeed;
        float distanceChange = Input.GetAxis("Mouse ScrollWheel") * distanceChangeSpeed;

        currentDegree += directionChange;
        currentHeight += heightChange;
        currentDistance += distanceChange;
        
        // 相机于目标的距离不能小于高度差,不然无论如何都无法维持
        if (Mathf.Abs(currentDistance) < Mathf.Abs(currentHeight))
        {
            return;
        }
        
        // 先计算圆心的位置
        Vector3 lookAtPosition = target.transform.position;
        Vector3 circleCenter = lookAtPosition + new Vector3(0, currentHeight, 0);
        
        // 计算圆的半径
        float circleRadius = Mathf.Sqrt(currentDistance * currentDistance - currentHeight * currentHeight);
        
        // 使用夹角来计算相机相对圆心的XZ偏移
        float rad = Mathf.Deg2Rad * currentDegree;
        float xDistance = Mathf.Cos(rad) * circleRadius;
        float zDistance = Mathf.Sin(rad) * circleRadius;
        

        // 最后拿到相机的位置
        Vector3 cameraPos = circleCenter + new Vector3(xDistance, 0, zDistance);
        
        // 设置相机的位置
        transform.position = cameraPos;
        // 让相机朝向目标
        transform.LookAt(target);
    }
}

可以看到我们用currentDegree,currentHeight,currentDistance三个成员来存储当前的状态,每次接收鼠标输入后都进行修改,然后重新计算相机位置。

跑起来之后我们就可以得到一个可以移动鼠标调整视角的三人称相机,可以自己调节speed达到一个操作比较舒服的程度。

1.16 从0开始学习Unity游戏开发--人物控制-2

思考题

在完成了初步的人物控制之后,如果有自己尝试实现更多的场景的话,其实会发现一些问题:

  1. 如果有台阶的情况下,方块上不去,那是否可以换成其他的碰撞体形状?如果是的话,用哪个内置形状最好?
  2. 如果要实现角色跳跃如何处理?
  3. 下楼梯的情况下会像抛物线一样飞出去,这个如何处理?
  4. 既然我们的视角变成三人成了,我们肯定也不能从相机来发射子弹,那是不是要改成从人物那里发射,这个情况下,准心又是如何瞄准的呢?

下一章

在一个场景里面玩了太久了,我们如果希望切换场景要怎么做?如果切换场景的话,我们当前场景的逻辑都是基于场景内的GameObject来驱动的,这些在新的场景里面又何去何从?下一章我们会讲解场景的切换和切换带来的逻辑与资源的管理需求如何处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/424079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(学习日记)2023.4.11

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

360虚拟数字展厅vr全景展示实现高度开放空间的深度体验

随着VR虚拟现实技术的不断发展和普及&#xff0c;线上VR全景虚拟展厅逐渐成为了各行各业的展示和推广产品的重要手段之一。相比于传统的展览方式&#xff0c;线上VR全景虚拟展厅打破了许多传统展览的局限性 传统局限性&#xff1a; 1、重体验过于依赖线下&#xff0c;难以实现转…

CloudCompare如何使用基础功能?

文章目录0.引言1.File2.Edit3.Tools4.Display5.Plugins6.3D Views0.引言 CloudCompare是一款优秀的开源点云处理软件&#xff0c;在研究点云的过程中&#xff0c;会用到其中一些点云处理功能。为快速熟悉软件的基础功能&#xff0c;本文介绍相应的功能按键的作用。    1.Fil…

Nginx专题-基于多网卡的主机配置

文章目录Nginx 基于多网卡的主机实现一、虚拟机前置环境准备ifcfg-ens32配置文件的内容参考ifcfg-ens33配置文件的内容二、案例演示修改nginx.conf配置文件解决中文乱码Nginx 基于多网卡的主机实现 一、虚拟机前置环境准备 点击虚拟机右下角的 红色标框按钮&#xff0c;然后右键…

【GC垃圾回收算法】让内存垃圾无处藏身

文章目录概述垃圾对象的判定引用计数可达性分析回收垃圾标记清除复制算法标记整理分代回收概述 垃圾回收就是帮我们把不用的内存垃圾自动释放掉 什么是垃圾呢&#xff1f;就是指不再使用的垃圾 如果不进行垃圾回收就会导致一个严重的问题&#xff0c;内存泄漏 内存泄漏&#x…

【id:34】【20分】D. Point_Array(类+构造+对象数组)

题目描述 上面是我们曾经练习过的一个习题&#xff0c;请在原来代码的基础上作以下修改&#xff1a;1、增加自写的析构函数&#xff1b;2、将getDisTo方法的参数修改为getDisTo(const Point &p)&#xff1b;3、根据下面输出的内容修改相应的构造函数。 然后在主函数中根据…

HTTP伪造

打开后是一个精美的页面&#xff0c;再精美也没啥用&#xff0c;先查看源码吧可以看到这里有个点击触发的a标签&#xff0c;点进去后提示我不来自https://Sycsecret.buuoj.cn&#xff0c;结合题目是HTTP&#xff0c;立刻就想到了HTTP请求头伪造抓包&#xff0c;本身是没有Refer…

ActiveMQ使用(一):在JavaScript中使用stomp.js

ActiveMQ使用(一):在JavaScript中使用stomp.js 1. 环境准备 jQuery-1.10 下载地址:https://www.jsdelivr.com/package/npm/jquery-1.10.2?tabfilesstomp.js 2.3.3: 下载地址:https://www.jsdelivr.com/package/npm/stompjs 2. 相关代码 <!DOCTYPE html> <html l…

东用科技路由器连接上云助手配置指导手册

一、上云助手操作步骤1.安装“Device control center”并启动。2.点击“服务器设置”后设置端口号&#xff1a;1-65535&#xff0c;传输协议&#xff1a;TCP/UDP。##路由推送功能默认不勾选。其功能为将填写的远端子网及掩码信息推送给客户端&#xff0c;客户端就会生成一条目的…

java反序列化 cc链1 分析

这里我是跟白日梦组长学习&#xff0c;果然大佬就是大佬&#xff0c;讲的是真好&#xff0c;按他的配置&#xff0c;我们来配置环境。 环境搭建 环境&#xff1a; java&#xff1a;java8u_65 commons-collections&#xff1a;3.2.1 这里逛了很多圈&#xff0c;说实在的真的没有…

融云出海赋能会干货回顾 | 用户增长、场景玩法、安全合规实用指南

近期&#xff0c;“纵浪潜海 2023 融云社交泛娱乐出海赋能会”在上海、广州相继举行。移步【融云全球互联网通信云】&#xff0c;回复【出海】获取PPT。 作为更专业的出海服务商&#xff0c;融云联合多家出海服务企业&#xff0c;从热门出海地区的特性洞察、玩法解决方案、技…

BGP过滤(社团属性过滤器、AS路径过滤器)

通过路由策略来过滤 [r2]ip ip-prefix aa permit 172.16.1.0 24 [r2]route-policy aa deny node 10 [r2-route-policy]if-match ip-prefix aa [r2]route-policy aa permit node 20 [r2]bgp 200 [r2-bgp]peer 10.1.23.3 route-policy aa export 前缀列表进行过滤 [r3]ip ip-pref…

新手使用Python开发游戏pygame入门很合适-02

前面一篇博文&#xff0c;我们让飞机动起来了&#xff0c;但不是那么完美&#xff0c;我们继续来完善我们的游戏代码&#xff0c;本篇博文主要介绍获取按键的方式已经飞行速度的控制。 文章目录一、获取按键的三种方式1、通过event.get配合pygame.key枚举2、通过event.get配合o…

本地测试Segment Anything

一、下载GitHub代码 官网地址&#xff1a; https://github.com/facebookresearch/segment-anything git clone 或者 下载ZIP压缩包 二、下载.pth文件 官网中给出了三个训练好的参数文件 点击下载&#xff0c;我这里下载了最后一个358M大小的模型&#xff08;这里可以使用迅…

apache 配置与应用以及网页优化

Apache 配置与应用 --------构建虚拟 Web 主机-------- 虚拟Web主机指的是在同一台服务器中运行多个Web站点&#xff0c;其中每一个站点实际上并不独立占用整个服务器&#xff0c;因此被称为“虚拟”Web 主机。 通过虚拟 Web 主机服务可以充分利用服务器的硬件资源&#xff0c…

49.现有移动端开源框架及其特点—MACE( Mobile AI Compute Engine)

Mobile AI Compute Engine (MACE) 是一个专为移动端异构计算设备优化的深度学习前向预测框架 MACE覆盖了常见的移动端计算设备(CPU,GPU和DSP),并且提供了完整的工具链和文档,用户借助MACE能够很方便地在移动端部署深度学习模型MACE已经在小米内部广泛使用并且被充分验证具…

答疑——20年国赛题(JAVA解法)

题目链接&#xff1a;用户登录https://www.lanqiao.cn/problems/1025/learning/?page3&first_category_id1&sortstudents_count 题目描述 有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。 老师可以安排答疑的顺序&#xff0c;同学们要依次进入老…

SQL笔记(1)——MySQL创建数据库(收藏吃灰版)

本文详细记录MySQL创建一个数据库的过程&#xff0c;不只是构建步骤&#xff0c;更多的是每一步涉及到的知识点。一般创建数据库有两种方式&#xff0c;一种是命令&#xff0c;另外一种就是通过数据库管理工具&#xff0c;本文主要记录通过命令的方式创建&#xff1b; 后面的学…

Centos7升级make和gcc版本到最新

Background 遇到如下的问题可能就是你make和gcc的版本过低了&#xff0c;需要升级。 *** These critical programs are missing or too old: make compiler *** Check the INSTALL file for required versions. 1、更新make版本 下载最新版本 【make最新安装包下载地址】 #…

VuePress1.x使用及个人博客搭建

文章目录介绍快速开始安装目录页面配置介绍 VuePress 由两部分组成&#xff1a;一个以 Vue 驱动的主题系统的简约静态网站生成工具&#xff0c;和一个为编写技术文档而优化的默认主题。它是为了支持 Vue 子项目的文档需求而创建的。 快速开始 安装 首先需要安装Node.js &…