项目创建与角色移动
课程目标:制作一款可以多人对战的FPS游戏,最终成品可以是网页版或者客户端
有开房间功能,也有匹配功能
(题外话,发这个分享的时候cod19也快迎来最后一个赛季了。这个我第一次入手的cod正代作品,夹杂情怀在内,整体的感受仍然是五味杂陈。优秀到令人上瘾的射击手感、精美的动画设计,搭配上超级答辩的地图设计、战役设计、枪械平衡、SBMM和游戏运营,活生生的是鲜花插在了牛粪上。希望最后的第五赛季能带给玩家一点惊喜,希望小锤的cod20能比cod19有所改善,也希望这个系列能回到468代的水平,不要让如此伟大的射击游戏陨落得那么荒唐。)
欢迎看我分享的各位指出我的错误,希望我的文字能对大家的学习有那么一点点的帮助作用。
思维导图
环境搭建
Unity3D软件下载地址
- 安装Unity Hub:https://unity.com/cn/
- 进入网址后下载对应操作系统的版本
- 所有选项默认,如果有需要的话可以改一下安装路径
- 下载完成后电脑上会有一个 unity hub
- 安装最新的长期支持(LTS)版
- 第一次打开unity hub时会有一个弹窗,可以选择安装unity editor,选择一个LTS版本就行,建议最新版。如果没有出现弹窗,可以在页面左上角找到install unity editor,选择一个LTS版本安装
- 安装不一定会顺畅。失败后直接点重连就行
- 注册Unity账号,获取免费版License
安装Visual Studio(用其他编辑器也可以,只要支持代码自动补全就行)
- 安装Visual Studio Install,选免费版(Community)即可:https://visualstudio.microsoft.com/zh-hans/
- 安装Visual Studio:勾选上”使用Unity的游戏开发”选项。
创建项目
(unity在一开始使用的时候会需要进行一些预先操作,所有操作都可以按照提示一步一步走)
点击Projects页面左上角的New Project, 选择3D, 项目名称为FPS,点击创建
如果出现如下报错:
解决方法:去到unity hub主页,在左边点击 projects
,然后点击 cloud projects
,再去右上角点击 重置PlasticSCM
,一般可以解决;不行的话还可以退出 unity,把 C:\Users\(电脑用户名)\AppData\Loca\plastic4
这个目录备份后删掉,再按上述步骤重置一次。
如果上述操作依旧不能解决问题,有可能是先前创建过名字一模一样的项目。这种情况下可以将项目名字更改。
点击创建后,新用户可能会要求登录或创建unity账号。按照提示创建好之后,选择一个个人免费的许可证,就可以开始unity的使用啦
unity编辑器的使用
(许多通用的快捷键,比如复制粘贴和撤销,在unity编辑器里都可以用)
进入unity编辑器后,会出现上面的界面。下面介绍一下基本的操作
操作视图(重要,以后编辑视图要经常用到)
将鼠标放在3D视图上
- 按住
Alt
, 然后按住鼠标左键在页面上滑动,可以旋转整个视图- 直接按鼠标右键可以旋转,但旋转方式不太一样
- 按住鼠标滚轮键(中键)拖动,可以平移视图
- 滚轮可以缩放视图
- 按住
Alt
, 按住鼠标右键也可以缩放视图
- 按住
创建物体
在3D视图右边的Hierachy处点击右键,选择创建一个方块
如果方块不在视图中心,可以双击一下方块,方块会自动回到视图中心
- 也可以选中物体后按F键,使物体处于视图中心
选择方块后,视图右边显示的是物体的所有属性
- Transform是物体的位置属性,包块物体中心点的位置、旋转角度和缩放程度
- 此处的位置属性指的是相对父组件的位置属性
- Mesh有关的属性是物体的纹理属性
- collider与碰撞检测有关
移动物体
平移物体
如果物体上没有显示坐标轴,点击视图右侧的图标
坐标轴红色线为x轴,红色面为y-z平面;绿色线为y轴,绿色面为x-z平面;蓝色线为z轴,蓝色面为x-y面。想在哪个轴或面上移动物体,就按住相应的线或面拖动即可
移动物体也可以在Transform上直接更改数值,或将光标放到x, y, 或z上后按住左键拖动,也可以更改数值实现物体移动
复位:在Transform上右键后点击reset即可复位
旋转物体
点击左上角的旋转图标,物体中心会出现旋转样式。
红色圆圈为绕x轴旋转,绿色圆圈为绕y轴旋转,蓝色圆圈为绕z轴旋转。按住任意一个圆圈后拖动鼠标即可按照对应的轴旋转
在Transform上的操作同平移
伸缩物体
点击左上角的伸缩图标,物体中心会出现伸缩样式
红色为x轴,绿色为y轴,蓝色为z轴。按住任意轴线,即可在对应轴上伸缩物体
将鼠标按住坐标原点后拖动,可以使物体在三维上等比例缩放
在Tranform上的操作同平移,但是只能在一个轴方向上缩放。如果想等比例缩放,则注意这个图标:
点击这个图标后
这时将鼠标放到x, y, 或者z上按住拖动,物体就会被等比例缩放
创建地图
unity有自带的官方资源商店,可以在里面使用免费的资源,或者上淘宝买一些付费但是效果更好的资源(这些付费资源当然可以在unity商店里买,但是会比较贵)
我们这里使用免费的资源
地图资源包:prototying
点开资源之后进入如下页面
点击“在unity中打开”,按照提示操作,之后会显示如下界面
如果没有下载过该资源,需要先Download下载。下载过后点击import,出现如下界面
这里可以选择要import哪些资源,默认全选。这里直接全选,点击import导入。导入后的资源,以及以后我们要写的C#脚本,都在Assets文件夹里
Assets文件夹下除Scenes外的两个文件夹都是我们导入的。
打开 DigitalKonstrukt
文件夹,进入图中路径。选择 Meshes
,里面的物体是带阴影的。进入后选择 Cuboids
, 选择最后一个长方体,将它拖入视图中。(记得把之前创建的立方体删掉)。之后将长方体复位。
再打开 Meshes
里的 Cubes
,选择一个正方体拖到长方体上面,复位。
复制两份正方体(ctrl-c
, ctrl-v
),将它们合适拜访,可以进行一些旋转和缩放操作。
给物体添加颜色和花纹:在 Resources
的 Materials
文件夹里,选择一个喜欢的颜色纹理,拖到想添加到的物体上即可
如果发现方块有部分嵌入的长方体中,可以将方块y坐标修改为0.05
我们在操作地图时希望将地图作为一个物体整体操作,但现在地图由多个物体构成。想把地图变成一个物体,我们可以创建一个空物体。
选择空物体,按 F2
可以对空物体改名。之后按住 ctrl
键,选择组成地图的所有物体
将选中的物体拖入刚刚创建的空物体中即可
一般来说,光线和地图是在一起的,所以光线也可以拖进来
视角
双击左边的 Main Camera
,之后视图右下角的小视图就是摄像机视角
拖动摄像机可以改变摄像机视角。由于我们要做的是一个FPS游戏,因此摄像机应该在角色头上,和角色绑定在一起。这个 Main Camera
可以删掉。
创建角色
为了方便讲解,这里使用一个简单的球体作为玩家。创建一个球体,放在地图中,将地图和玩家进行复位。调整一下地图和玩家,地图整体放大5倍,y坐标设为2;玩家y坐标设为2(根据自己视图内的情况自行调整即可)
角色Player也是许多物体的一个复合体,包括纹理、摄像头、武器等。我们在Player中创建一个 Graphics
空物体进行纹理渲染,将Player自带的所有 Mesh
有关的属性全部删除
之后在 Graphics
里创建一个球即可
这样Player有两个collider。这里选择删掉刚创建的球的collider
创建角色视角
在Player上右键,创建camera,之后reset camera
在unity里面,物体的面是有向的,从一个方向看过去有面,从另一个方向看过去就没有面了。(事实上,目前大部分FPS游戏里,物体的面都是有向面)。比如这里的Player,摄像头在球体里面,但是可以看到球体外的视图,因为球体从里往外看是看不到球面的
给角色加上枪
结合上述摄像机视角,我们按照CS的样式为Player加上枪(摄像机和枪是整体,摄像机怎么转,枪也要怎么转),并能在摄像机里看见
枪械资源:guns
引入资源操作同上
导入后进入 LowPoly Guns
的 Meshes
文件夹里,选择一个喜欢的枪,移动到摄像头上,移动之后复位
Game里的视角是Player的视角,我们将它移出来和视图放在一起,方便调整枪的位置
之后我们拖动枪,调整枪在摄像机里的位置。我们可以在 LowPoly Guns
里的 Material
里找自己喜欢的纹理颜色,加到枪上。调整枪的位置时,最好不要让枪嵌入球体中
查看摄像机的属性,其实就是一个普通物体加上一个camera属性和audio listener属性,既可以看,也可以听(这里的audio listener区分了左右声道,可以听声辨位)
角色移动
要点:
- 角色左右移动在x轴上
- 角色上下移动在y轴上
- 角色前后移动在z轴上
- 角色向上下看时,身体不动,只动摄像头
- 角色左右看时,身体也要旋转
下面我们要实现自己在键盘上按 wsad
实现人物移动,移动鼠标实现视角移动
游戏里角色移动原理
游戏里移动角色时涉及一个概念:帧数。帧数是一秒钟显示屏刷新的次数,一般来说,游戏开发是的视图帧数是60帧,即我们看到的unity里的视图,显示器在显示时每秒钟刷新60次,每一次都会画一遍unity里的视图后呈现给我们(这里默认我们的显示器刷新率不小于60Hz)
角色要在视图中运动起来,其实就是每次视图刷新时,角色依照给定的速度,或者接收到的操作,变动一定的距离。每次刷新时都变动一点距离,一秒60次刷新的画面呈现在我们眼前,就是物体在向一个方向运动,并且是比较平滑的运动。物体运动的平滑度会随着帧数的升高而上升。
我们要控制角色移动,就需要在代码里告诉unity,当我们按下按键使角色移动时,在接下来的每一帧中,角色的坐标是多少。unity知道了接下来每一帧中角色的坐标是多少之后,unity在渲染每一帧时,角色都会被渲染在指定坐标上,然后一帧一帧放给我们,角色此时看起来就像在移动一般。
实现移动
我们先在Player中点击 Add Component 加入一个属性 Rigidbody
, 这个属性是unity帮我们实现好的可以模拟物理世界行为的属性,包括重力、质量等等。
我们不需要自带的重力,把重力去掉
之后加入代码脚本。点击 Add Component, 选择 new script
这里添加两个脚本,一个是 PlayerInput
, 用来处理用户输入; 一个是 PlayerController
,用来改变角色位置和物理属性
在进行代码编写前,先确认是否把 Visual Studio 设定为默认的代码编辑器。点击右上角的 Edit
, 找到 Preferences
点击 Preferences
,进去后找到 External Tools
,查看 External Script Editor
是否选择了Visual Studio. 如果没有选择到的话,用vs打开脚本后可能不支持自动补全
确认好之后,双击下图划红线的区域,即可打开对应的脚本
打开后的界面如下。
刚下载好的Visual Studio,默认字体应该是微软雅黑,而不是和vs code一样的 consolas 样式的字体。如果想更改Visual Studio代码字体的话,可以在上方的 工具 -> 选项
中的 环境
里找到 字体和颜色
,在里面更改即可
也可以直接在上方的搜索栏中搜索“字体”,找到 字体和颜色
。对于所有不清楚位置和具体名字的选项,都可以在搜索栏中搜索
看到代码。
PlayerInput.cs
Start()
函数会在物体被加入到游戏中时执行一次,可以把初始化的代码放进去Update()
函数会在unity每渲染一帧动画前调用,可以把计算角色移动位置的代码放在里面,用户输入也可以放在里面获取float xMove = Input.GetAxisRaw("Horizontal");
: 这句代码是获取用户操作角色在水平方向的移动方向,0表示不动,1表示在x轴正方向动(右),-1表示在x轴负方向动(左)。在unity左上角的Edit -> Project Settings
里 有Input Manager
,里面有unity的按键绑定信息和数据,可以在代码中获取这些信息和数据来控制角色。 我们可以看到在Horizontal
一项中,Name
被设定为Horizontal
因此在获取Horizontal
的数据时传入的参数是"Horizontal"
;Button
和键盘上的左右方向键以及ad键绑定在了一起,因此当我们按下左右键或ad时,会产生角色在水平方向上移动的距离。要获取这个距离,就可以使用上述的代码。GetAxisRaw
表示获取原值,如果不加Raw,unity会把原值平滑处理后返回。
- 同理可以获取角色在垂直方向的移动方向
Vector3 velocity = (transform.right * xMove + transform.forward * zMove).normalized * speed;
: 角色在视图里移动具有三个方向,因此速度向量有三维。目前角色需要在x-z平面上移动(还不考虑跳跃),因此我们需要将角色在x轴上的移动方向和z轴上的移动方向叠加起来。transform.right transform.forward
: 分别给出的是物体x轴方向向量和z轴方向向量。在Visual Studio中可以把光标放到变量上,界面会出现该变量的解释。.normalized
: 两个方向向量加起来之后向量长度不一定为1。为了让角色在任意方向上都有相同的速度,速度方向向量需要通过.normalized
标准化,使长度变为1。
- 如果想设定一个变量,并且在unity里自由调整它的值,可以在该变量前加上一个
[SerializeField]
注解
PlayerController.cs
要控制角色的物理属性,我们需要获取角色的 Rigidbody
。这里我们先定义 Rigidbody
类型的变量,并加上 [SerializeField]
注解。之后在unity中把我们想要该脚本操控的 Rigidbody
,即角色自身的 Rigidbody
拖进变量中即可。
操纵角色移动,也就是操纵 Rigidbody
移动。在unity中操纵刚体(rigidbody)移动时,不在 Update()
函数中操作,而是在 FixedUpdate()
中操作。
- 原因:
FixedUpdate()
在一秒中执行固定的次数,且每两次执行的时间间隔不变,而Update()
在一秒中一般执行固定次数(帧数没有波动的前提下),但相邻两次执行的时间间隔变动比较大。对于物理轨迹的计算,如果是在Update()
中计算,那么渲染出的轨迹与期望的轨迹差别会比较大 上图中,红色的轨迹是在Update()
中执行曲线运动,蓝色的轨迹是FixedUpdate()
中。因此,操纵刚体在FixedUpdate()
中,其他操作在Update()
中。
在编写代码时可以有意识的对游戏进行一些优化,比如移动操作,当角色速度为0时,就不执行移动操作的代码,减少代码运行量。
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
- $x_{t} = x_{t - 1} + \vec{v} \cdot t$
Time.fixedDeltaTime
表示相邻两次FixedUpdate()
执行的时间间隔;Time.DeltaTime
表示相邻两次Update()
执行的时间间隔
PlayerInput
中接收到了用户输入,得到的速度要传给 PlayerController
来使角色运动。因此在 PlayerInput
中引入 PlayerController
变量,并通过 [SerializeField]
在unity中手动绑定,调用 PlayerController
中的 Move
函数,将速度传递,实现运动。
角色旋转
鼠标在水平方向的移动是 Mouse X
,垂直方向的移动是 Mouse Y
。
调试:print()
或 Debug.Log()
, 可以输出变量值。输出在下方的 Console
里查看
按照上述分析,鼠标水平移动 Mouse X
为角色围绕y轴旋转,身体要一起转;鼠标垂直移动 Mouse Y
为角色围绕x轴旋转,身体不跟着一起转。两种旋转对应的操作不太一样,因此在实现时要把旋转向量分开,不能合并
在旋转时涉及视角,因此 PlayerController
中要获取角色摄像头。通过 [SerializeField]
实现。
如果发现视角移动方向反了,取反即可
Cursor.lockState = CursorLockMode.Locked;
: 进入游戏时将鼠标锁住,使鼠标消失。按下 Esc
键使鼠标解锁。
导出项目
右上角 File -> Build Settings
里(可以用快捷键 Ctrl + Shift + B
打开) 点击 Player Settings
后可以进行一些设置,比如设置导出的游戏为窗口化并设定分辨率 之后回到 Build Settings
界面并选择 Build
。按照指引将项目创建到一个文件夹中 打开文件夹中的 .exe
文件,就可以开始玩刚写好的游戏啦! 光标不见,但是想关闭游戏,可以按下键盘上的win键使光标出现,然后关闭游戏即可
阶段成品以及修改的代码
PlayerInput.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerInput : MonoBehaviour
{
[SerializeField]
private float speed = 5f; // 加上SerializeField注解,可以在unity里调整speed的值
[SerializeField]
private float lookSensitivity = 8f; // 鼠标灵敏度
[SerializeField]
private PlayerController controller;
// Start is called before the first frame update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
float xMove = Input.GetAxisRaw("Horizontal"); // 获取用户在x轴上的移动方向,1表示右,-1表示左,0表示不动
float zMove = Input.GetAxisRaw("Vertical"); // 获取用户在z轴上的移动方向,1表示前,-1表示后,0表示不动
Vector3 velocity = (transform.right * xMove + transform.forward * zMove).normalized * speed;
controller.Move(velocity); // 将速度传递给PlayerController来实现运动
// 获取鼠标移动数据
float xMouse = Input.GetAxisRaw("Mouse X"); // 鼠标水平移动
float yMouse = Input.GetAxisRaw("Mouse Y"); // 鼠标垂直移动
// Debug.Log(xMouse.ToString() + " " + yMouse.ToString()); // 调试信息
Vector3 yRotation = new Vector3(0f, xMouse, 0f) * lookSensitivity; // 角色y轴旋转向量
Vector3 xRotation = new Vector3(-yMouse, 0f, 0f) * lookSensitivity; // 角色x轴旋转向量
controller.Rotate(yRotation, xRotation);
}
}
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField]
private Rigidbody rb; // 加上注解后在unity里将要绑定的rigidbody拖进变量中
[SerializeField]
private Camera cam; // 要操作视角,需要摄像头
private Vector3 velocity = Vector3.zero; // 速度:每秒移动的距离,初始为0(一定要清楚速度对应的时间单位是帧还是秒)
private Vector3 yRotation = Vector3.zero; // 旋转角色
private Vector3 xRotation = Vector3.zero; // 旋转视角
public void Move(Vector3 _velocity)
{
velocity = _velocity; // 将用户输入的速度向量传过来
}
public void Rotate(Vector3 _yRotation, Vector3 _xRotation) // 将用户输入的旋转向量传过来
{
yRotation = _yRotation;
xRotation = _xRotation;
}
private void PerformMovement()
{
if (velocity != Vector3.zero) // 这里是一个优化,只有当角色有速度时才进行移动操作
{
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}
}
private void PerformRotation()
{
if (yRotation != Vector3.zero)
{
rb.transform.Rotate(yRotation); // 旋转刚体
}
if (xRotation != Vector3.zero)
{
cam.transform.Rotate(xRotation); // 不旋转刚体,只旋转摄像头
}
}
private void FixedUpdate() // 每秒中以固定时间间隔执行固定次数
{
PerformMovement();
PerformRotation();
}
}
著作权信息
- 源视频作者:yxc
- 文章作者: jaylenwanghitsz
- 链接:https://www.acwing.com/file_system/file/content/whole/index/content/8111736/
- 来源:AcWing
- 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
白嫖unity课(确信)
哈哈:)