创建项目

打开 unity hub,点击新项目,选择 3d 模板

创建完毕后是这样

unity1.png

unity 资源商店

可以使用别人做好的素材,AssetStore

找到想要的素材,点击在 unity 中打开,此时编辑器会弹出一个 Package Manager。点击 download 下载,下载好之后可以 import 导入,选择需要的资源导入即可。导入完毕在 Assets 会多一些文件夹

这里导入一个Prototyping Pack (Free)

unity1-1.png

添加素材

在刚刚的资源中,选择一个扁平地形,这里选的是 PTK_Cuboid_5
unity2.png

添加纹理

在文件夹 materias,选择想要的纹理直接拖到物体上

把地图多个物体作为一个整体

可以右键 create empty,重命名后可以将多个物体拖到这个下,操作整个地图只需要操作这个 empty

unity1-2.png

删除Main camera

由于该项目是一个FPS(第一人称射击游戏),所以不需要第三视角的相机

创建角色

这里以一个球体来表示玩家,右键添加一个 sphere,重命名为 Player,右键 Player 创建一个 empty,重命名为 Graphics,这个用来管理纹理。在编辑器右侧把Player带 mesh 的组件(这是用来控制纹理的,纹理我们用Graphics来管理)移除掉,右键 remove component。在 graphics 里面添加一个 Sphere,因为外侧的 Player 也是一个 Sphere,删除掉里面 sphere 的碰撞检测组件,Sphere collider。

unity1-3.png

unity1-4.png

把相机放到第一视角

在 Player 上右键创建一个 Camera,由于物体的面都是有向的,所以可以把相机塞到球体里面,此时相机可以看到外面,外面看不到相机。

unity3.png

添加玩家的枪

在资源商店选择3D GUNS | Guns Pack。下载并导入
unity4.png

把喜欢的枪添加到相机里面,直接把枪拖到 Player 里的 camera 上。调整枪的方向、大小和位置,第一人称能看到枪

unity1-5.png

玩家移动

注意一个问题,玩家移动视角时,左右看是玩家左右旋转,而抬头低头就只旋转 Camera。

实现键盘控制玩家移动

给 Player 增加一个 rigidbody 组件,在 Unity 中,Rigidbody 组件用于为游戏对象添加物理属性,使其能够受到物理引擎的控制。由于 Player 是一个球体需要浮空一点,所以关闭 rigidbody 的重力

给 Player 添加代码,选择 Player,add component 一个 new script。添加两个脚本文件: PlayerInput 和 PlayerController。

unity1-6.png

打开代码后:
PlayerController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}
}

PlayerInput

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerInput : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}
}

获取键盘前后左右的输入,注意带[SerializeField]的变量要在unity editor中赋值,PlayerInput.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// PlayerInput类用于处理玩家的输入
public class PlayerInput : MonoBehaviour
{
// [SerializeField]使私有变量在Unity编辑器中可见并可编辑
// speed变量用于控制玩家的移动速度
[SerializeField]
private float speed = 2f;

// controller是PlayerController组件的引用,用于调用移动逻辑
[SerializeField]
private PlayerController controller;

// Start方法在游戏对象初始化时调用一次
void Start()
{

}

// Update方法每一帧调用一次,用于处理实时输入和逻辑
void Update()
{
// 获取水平轴(Horizontal)的输入值
// Input.GetAxisRaw("Horizontal")返回一个值:
// -1 表示玩家按下左键(A或左箭头)
// 1 表示玩家按下右键(D或右箭头)
// 0 表示没有按下任何键
float xMov = Input.GetAxisRaw("Horizontal");

// 获取垂直轴(Vertical)的输入值
// Input.GetAxisRaw("Vertical")返回一个值:
// -1 表示玩家按下下键(S或下箭头)
// 1 表示玩家按下上键(W或上箭头)
// 0 表示没有按下任何键
float yMov = Input.GetAxisRaw("Vertical");

// 计算移动方向
// transform.right 是游戏对象的局部右方向(X轴)
// transform.forward 是游戏对象的局部前方向(Z轴)
// 将水平和垂直输入值分别与右方向和前方向相乘,得到移动方向向量
// .normalized 将向量归一化,确保移动速度不会因为斜向移动而变快
// 乘以 speed 以控制移动速度
Vector3 velocity = (transform.right * xMov + transform.forward * yMov).normalized * speed;

// 调用PlayerController的Move方法,传递计算出的速度
// 这里 velocity 已经乘以了 speed,因此不需要再乘以 speed
controller.Move(velocity);
}
}

控制玩家移动的逻辑,PlayerController.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// PlayerController类用于控制玩家的移动逻辑
public class PlayerController : MonoBehaviour
{
// [SerializeField]使私有变量在Unity编辑器中可见并可编辑
// rb是玩家的Rigidbody组件,用于物理移动
[SerializeField]
private Rigidbody rb;

// velocity用于存储玩家的移动速度
private Vector3 velocity = Vector3.zero;

// Move方法用于设置玩家的移动速度
public void Move(Vector3 _velocity)
{
velocity = _velocity;
}

// PerformMovement方法用于执行玩家的移动
private void PerformMovement()
{
// 如果速度不为零,则移动玩家
if (velocity != Vector3.zero)
{
// 使用Rigidbody的MovePosition方法移动玩家
// rb.position + velocity * Time.fixedDeltaTime 计算新的位置
// Time.fixedDeltaTime 是固定时间步长,确保物理更新与帧率无关
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}
}

// FixedUpdate方法在固定时间间隔调用,用于物理更新
private void FixedUpdate()
{
PerformMovement();
}
}

实现旋转视角

PlayerController.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// PlayerController类用于控制玩家的移动和旋转逻辑
public class PlayerController : MonoBehaviour
{
// [SerializeField]使私有变量在Unity编辑器中可见并可编辑
// rb是玩家的Rigidbody组件,用于物理移动
[SerializeField]
private Rigidbody rb;

// cam是摄像机组件,用于控制视角旋转
[SerializeField]
private Camera cam;

// velocity用于存储玩家的移动速度,每秒移动的速度
private Vector3 velocity = Vector3.zero;

// yRotation用于存储角色的旋转值(绕Y轴旋转)
private Vector3 yRotation = Vector3.zero;

// xRotation用于存储摄像机的旋转值(绕X轴旋转)
private Vector3 xRotation = Vector3.zero;

// Move方法用于设置玩家的移动速度
public void Move(Vector3 _velocity)
{
velocity = _velocity;
}

// Rotate方法用于设置角色和摄像机的旋转值
public void Rotate(Vector3 _yRotation, Vector3 _xRotation)
{
yRotation = _yRotation;
xRotation = _xRotation;
}

// PerformMovement方法用于执行玩家的移动
private void PerformMovement()
{
// 如果速度不为零,则移动玩家
if (velocity != Vector3.zero)
{
// 使用Rigidbody的MovePosition方法移动玩家
// rb.position + velocity * Time.fixedDeltaTime 计算新的位置
// Time.fixedDeltaTime 是固定时间步长,确保物理更新与帧率无关
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}
}

// PerformRotation方法用于执行角色和摄像机的旋转
private void PerformRotation()
{
// 如果yRotation不为零,则旋转角色(绕Y轴旋转)
if (yRotation != Vector3.zero)
{
rb.transform.Rotate(yRotation);
}

// 如果xRotation不为零,则旋转摄像机(绕X轴旋转)
if (xRotation != Vector3.zero)
{
cam.transform.Rotate(xRotation);
}
}

// FixedUpdate方法在固定时间间隔调用,用于物理更新
private void FixedUpdate()
{
PerformMovement(); // 执行移动
PerformRotation(); // 执行旋转
}
}

PlayerInput.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// PlayerInput类用于处理玩家的输入
public class PlayerInput : MonoBehaviour
{
// [SerializeField]使私有变量在Unity编辑器中可见并可编辑
// speed变量用于控制玩家的移动速度
[SerializeField]
private float speed = 2f;

// lookSensitivity变量用于控制鼠标视角旋转的灵敏度
[SerializeField]
private float lookSensitivity = 8f;

// controller是PlayerController组件的引用,用于调用移动和旋转逻辑
[SerializeField]
private PlayerController controller;

// Start方法在游戏对象初始化时调用一次
void Start()
{
// 锁定鼠标光标到屏幕中心,隐藏光标
Cursor.lockState = CursorLockMode.Locked;
}

// Update方法每一帧调用一次,用于处理实时输入和逻辑
void Update()
{
// 获取水平轴(Horizontal)的输入值
// Input.GetAxisRaw("Horizontal")返回一个值:
// -1 表示玩家按下左键(A或左箭头)
// 1 表示玩家按下右键(D或右箭头)
// 0 表示没有按下任何键
float xMov = Input.GetAxisRaw("Horizontal");

// 获取垂直轴(Vertical)的输入值
// Input.GetAxisRaw("Vertical")返回一个值:
// -1 表示玩家按下下键(S或下箭头)
// 1 表示玩家按下上键(W或上箭头)
// 0 表示没有按下任何键
float yMov = Input.GetAxisRaw("Vertical");

// 计算移动方向
// transform.right 是游戏对象的局部右方向(X轴)
// transform.forward 是游戏对象的局部前方向(Z轴)
// 将水平和垂直输入值分别与右方向和前方向相乘,得到移动方向向量
// .normalized 将向量归一化,确保移动速度不会因为斜向移动而变快
// 乘以 speed 以控制移动速度
Vector3 velocity = (transform.right * xMov + transform.forward * yMov).normalized * speed;

// 调用PlayerController的Move方法,传递计算出的速度
controller.Move(velocity);

// 获取鼠标在X轴和Y轴上的移动输入
float xMouse = Input.GetAxisRaw("Mouse X"); // 鼠标水平移动
float yMouse = Input.GetAxisRaw("Mouse Y"); // 鼠标垂直移动

// 计算角色和摄像机的旋转值
// yRotation 是角色绕Y轴旋转的值(左右旋转)
// xRotation 是摄像机绕X轴旋转的值(上下旋转)
// 乘以 lookSensitivity 以控制旋转的灵敏度
Vector3 yRotation = new Vector3(0f, xMouse, 0f) * lookSensitivity;
Vector3 xRotation = new Vector3(-yMouse, 0f, 0f) * lookSensitivity;

// 调用PlayerController的Rotate方法,传递旋转值
controller.Rotate(yRotation, xRotation);
}
}

导出项目

File->Build Settings->选择平台->PlayerSettings 选择合适选项

unity1-7.png

这里以 build 一个 Windows 为例:

unity5.png