目录 CONTENT
第 1 章 … 3D数学基本知识
3D 游戏开发是一门与数学紧密结合的技术。无论是游戏中三维坐标系的建立,还是图形的移动、旋转与缩放,都是基于数学基础进行计算的。
1.1 … 三维坐标系
在程序开发中,需要将三维图形的坐标进行量化和线性化,这样才能在程序计算中更为容易和可行。程序开发一般使用的是笛卡尔三维坐标系。
需要强调的是,空间坐标系分为左手系和右手系,一般中学数学中使用的是右手系。左右手系的简单区分方法:
(1)左手系:将左手大拇指方向对准
z
z
z轴正方向,四指绕圈闭合,若
x
x
x轴能以 与四指指向相同的方向 转动到
y
y
y轴,则该系为左手系;
(2)右手系:将右手大拇指方向对准
z
z
z轴正方向,四指绕圈闭合,若
x
x
x轴能以 与四指指向相同的方向 转动到
y
y
y轴,则该系为右手系;
注意,Unity使用左手系计算,美工人员常使用的3Dmax、Maya等三维模型制作软件则是使用右手系。
1.2 … 向量与矩阵
“向量是标量的数组,矩阵是向量的数组。”
1.2.1 … 矩阵的表示
定义一个 R × C R \times C R×C 矩阵,指的是该矩阵有 R R R行、 C C C列。如定义 m × n m \times n m×n 矩阵如下:
A = [ a 11 a 12 ⋅ ⋅ ⋅ a 1 n a 21 a 22 ⋅ ⋅ ⋅ a 2 n a 31 a 32 ⋅ ⋅ ⋅ a 3 n ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ a m 1 a m 2 ⋅ ⋅ ⋅ a m n ] A = \begin{bmatrix} a_{11} & a_{12} & ··· & a_{1n} \\ a_{21} & a_{22} & ··· & a_{2n} \\ a_{31} & a_{32} & ··· & a_{3n} \\ ··· & ··· & ··· & ··· \\ a_{m1} & a_{m2} & ··· & a_{mn} \end{bmatrix} A=⎣⎢⎢⎢⎢⎡a11a21a31⋅⋅⋅am1a12a22a32⋅⋅⋅am2⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅a1na2na3n⋅⋅⋅amn⎦⎥⎥⎥⎥⎤
这
m
×
n
m \times n
m×n个数称为矩阵
A
A
A的元素(简称为“元”),其中数
a
i
j
a_{ij}
aij位于矩阵的第
i
i
i行第
j
j
j列,称为矩阵
A
A
A的
(
i
,
j
)
(i,j)
(i,j)元。则以数
a
i
j
a_{ij}
aij为
(
i
,
j
)
(i,j)
(i,j)元的矩阵可记为
(
a
i
j
)
(a_{ij})
(aij)或
(
a
i
j
)
m
×
n
(a_{ij})_{m \times n}
(aij)m×n,
m
×
n
m \times n
m×n矩阵
A
A
A亦可记作
A
m
n
A_{mn}
Amn。
特殊的,一个
N
N
N维的向量可以看作
1
×
N
1\times N
1×N矩阵或
N
×
1
N\times 1
N×1矩阵。前者称为行向量,后者称为列向量。如:
行向量: ( 1 , 2 , 3 ) \begin{pmatrix} 1,&2,&3\\ \end{pmatrix} (1,2,3)
列向量: [ 4 5 6 ] \begin{bmatrix} 4\\ 5\\ 6 \end{bmatrix} ⎣⎡456⎦⎤
另外,行数与列数都等于 N N N的矩阵称为 N N N阶矩阵或 N N N阶方阵。
1.2.2 … 矩阵的基本运算
-
加减法
矩阵的加减法均满足交换律、结合律。当两个同型矩阵相加减时,行列数不变,矩阵各项对应相加减。
-
数乘
矩阵的数乘满足交换律、结合律、分配率。当一个矩阵与一个实数 λ \lambda λ 数乘时,将矩阵各项乘以该实数即可。
-
转置
把矩阵 A A A的行和列互相交换所产生的矩阵称为 A A A的转置矩阵,记为 A T A^T AT。
矩阵的转置满足以下运算律:
-
共轭
在复数中,两个实部相等,虚部互为相反数的复数互为共轭复数,如 z = 1 + i z=1+i z=1+i 与 z ˉ = 1 − i \bar{z}=1-i zˉ=1−i 共轭。矩阵的共轭即是将矩阵各项转变为与其共轭的数。
则其共轭矩阵为:
-
共轭转置
矩阵的共轭转置定义为: A ∗ = A H = ( A ˉ ) T A^*=A^H=(\bar{A})^T A∗=AH=(Aˉ)T,有多种写法。
则其共轭转置矩阵为
- 乘法
当且仅当矩阵 A A A的列数与矩阵 B B B的行数相等时,两矩阵可以相乘。
若矩阵
A
A
A是一个
m
×
n
m\times n
m×n矩阵,矩阵
B
B
B是一个
n
×
p
n\times p
n×p矩阵,那么两者相乘的结果仍是一个矩阵
C
C
C,且该矩阵是一个
m
×
p
m\times p
m×p矩阵。定义为:
C
=
A
B
C=AB
C=AB。注意,上式不能写作
C
=
B
A
C=BA
C=BA,即矩阵相乘不满足交换律。
矩阵
C
C
C中元素
(
i
,
j
)
(i,j)
(i,j)的计算公式为:
显然,从这个式子中不易看出矩阵相乘的计算方法。事实上,矩阵 C C C的元素 ( i , j ) (i,j) (i,j)等于 A A A的第 i i i 行向量与 B B B的第 j j j 列向量的点积。
观察上式,矩阵 C C C的元素 ( 1 , 1 ) (1,1) (1,1)取的是矩阵 A A A的第一行向量 ( 1 , 0 , 2 ) (1,0,2) (1,0,2)与矩阵 B B B的第一列向量 ( 3 , 2 , 1 ) (3,2,1) (3,2,1)的点积,即 C 1 , 1 = 1 × 3 + 0 × 2 + 2 × 1 = 5 C_{1,1}=1\times 3 + 0\times 2 + 2\times 1 = 5 C1,1=1×3+0×2+2×1=5。
口诀:左行右列,行列向量取点积。
更新日期:2019/07/03
第 2 章 … 人称、视角与移动
2.1 … 第一人称
善良的 Unity 已经为大家准备好了人称控制器组件(Character Controller),使得刚入门 Unity 的小白们(不就是我么)很容易就可以成功建立自己的人物控制器,关于自带 Character Controller 的使用方法本篇不再赘述(想学习的看大佬博客),下面将向大家介绍如何在不借助外力的情况下自行建立第一人称视角。
2.1.1 … 创建玩家模型(Player)
呐,选定一个你喜欢的模型,做为玩家的模型(当然如果有条件的话可以导入精细的人物模型),因为我无德无能无才华,只能选用 Unity 自带的胶囊当人物了。记得把 Capsule 改名为 Player(或者任何你喜欢的名字也可以,例如隔壁老王)。
2.1.2 … 物体属性解释
观察上图,选定Player后,右端属性栏会出现许多项目。下面做一些解释。
对勾表示物体是否启用,去除后物体会被隐藏。Tag 意为标签,举个例子,一个僵尸捉玩家的游戏,场景中的LiHua、Mary、Jordan等均为玩家的话,那么 Tag 设置为 【Player】,一旦僵尸碰到物体只需要检测物体的 Tag 是不是 【Player】就可以了,恰当的使用 Tag 会使代码的实现变得更加简洁。Position 和 Rotation 分别意为“位置”和“旋转”。Scale 即比例,Scale 中的三个参数分别表示物体沿三个坐标轴的缩放比例,缩放比例是 1 当然就是保持原样了。
胶囊碰撞器和刚体组件。物体加上了碰撞器,就具有了碰撞功能,值得一提的是,Is Trigger 是触发器,碰撞器和触发器有很大区别,使用情况也有差异(深入了解看大佬博客)。
Rigidbody 意为“刚体”,通俗地讲,一个物体有个刚体组件,它就真正的可以在物理世界中运动,它就具有质量(Mass)、空气阻力(Drag)、转动阻力(Angular Drag),当然也可以选择是否受重力(Use Gravity)。若想实现一些特殊功能,可以选择锁定物体的移动轴、转动轴。
2.1.3 … 给玩家装上眼睛(Camera)
我不仅需要身体,还需要一双观察世界的眼睛。
物体的眼睛就是摄像机(Camera),Unity 中的摄像机可能是功能最丰富的组件了。(有一双勤劳的手,什么东西做不到?)
在右键菜单中找到 Camera,创建并拖拽到 Player 下,使它成为 Player 的一份子。然后选定 Camera,把它放在 Player 的头顶,调整到合适为止。(太高太低都不舒服)
眼睛装上了,还需要让眼睛富有神采,在 Unity 下方的文件管理器内创建一个 C# 脚本,拖拽给 Player。因为是要操控玩家嘛,所以这个脚本当然不能给摄像机了。现在开始编辑人物“眼睛”的代码。(鼠标控制视野)
void lookAround()
{
//视角随鼠标移动
//Debug.Log ("鼠标位置:( " + Input.GetAxis ("Mouse X") + " , " + Input.GetAxis ("Mouse Y") + " )");
if (Input.GetAxis ("Mouse X") != 0)
{
if (Input.GetAxis ("Mouse X") < 0.1f && Input.GetAxis ("Mouse X") > -0.1f)
{
return;
}
this.gameObject.transform.Rotate (new Vector3 (0, Input.GetAxis ("Mouse X") * Time.fixedDeltaTime * DPI, 0));//摄像机的旋转速度
}
if (Input.GetAxis ("Mouse Y") != 0)
{
if (Input.GetAxis ("Mouse Y") < 0.1f && Input.GetAxis ("Mouse Y") > -0.1f)
{
return;
}
Eye.transform.Rotate (new Vector3 (Input.GetAxis ("Mouse Y") * Time.fixedDeltaTime * -DPI, 0, 0));//摄像机的旋转速度
}
}
2.1.4 … 让玩家动起来
最经典的移动操作就是,WSAD加空格嘛。下面主要会用到三个函数:
(1)
I
n
p
u
t
.
G
e
t
K
e
y
(
K
e
y
C
o
d
e
.
X
)
Input.GetKey(KeyCode.X)
Input.GetKey(KeyCode.X)
当获取到键盘上某个键
X
X
X的信号时为
t
r
u
e
true
true,指按住
(2)
I
n
p
u
t
.
G
e
t
K
e
y
D
o
w
n
(
K
e
y
C
o
d
e
.
X
)
Input.GetKeyDown(KeyCode.X)
Input.GetKeyDown(KeyCode.X)
当刚按下某个键
X
X
X时接收信号,为
t
r
u
e
true
true
(3)
I
n
p
u
t
.
G
e
t
K
e
y
U
p
(
K
e
y
C
o
d
e
.
X
)
Input.GetKeyUp(KeyCode.X)
Input.GetKeyUp(KeyCode.X)
当按下某个键
X
X
X刚放开时接收信号,为
t
r
u
e
true
true
例如:
if(Input.GetKey(KeyCode.W))
{
moveForward();
}
if(Input.GetKeyDown(KeyCode.Space))
{
jump();
}
当然这只是个例子,不然我们在游戏中不是可以无限跳跃了?如何避免按按按按按空格无限跳跃呢?需要设置一个全局变量 b o o l i s J u m p i n g \ bool \ \ isJumping bool isJumping,顾名思义它表示“是不是在跳”,怎么判断是不是在跳呢?
v o i d O n C o l l i s i o n E n t e r ( C o l l i s i o n c o l l i s i o n ) void\ \ OnCollisionEnter(Collision\ \ collision) void OnCollisionEnter(Collision collision)是一个较为特殊的函数,只有当发生碰撞时才运行这个函数。每次落地时 Player 都会与地面撞一下,如果每次落地之后,都将 i s J u m p i n g isJumping isJumping 记为“没有在空中跳”,每次起跳之后,都将其记为“在空中跳”,这样的话,就可以避免无限跳了。
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using UnityEngine;
using UnityEngine.EventSystems;
public class MvJpMouseLook : MonoBehaviour
{
public float moveSpeed;//摄像机的移动速度
public float jumpForce;//跳跃力气
private bool isJumping;//物体跳跃后的某一时刻是否腾空
public static int DPI;//转向灵敏度
public Rigidbody Rig;//声明对象,获取组件
public GameObject Eye;//声明对象,获取组件,需要在 Player 的属性栏中找到这个脚本,并且在其属性下的 Eye 中选择创建的 Camera
void Start()//在第一个 Update 开始之前运行,一般用来初始化
{
moveSpeed = 3.5f;
jumpForce = 25000f;
isJumping = false;
DPI = 300;
Rig = gameObject.GetComponent<Rigidbody> ();
}
void Update()//每一帧运行一次,当心死循环
{
//键鼠控制移动
WASD ();
lookAround ();
jump ();
}
//以下是所须使用的子程序。C#、Java不像C++那样严格的限制函数位置,随便放!
void WASD()
{
//前后左右移动
if (Input.GetKey (KeyCode.W))
{
gameObject.transform.Translate (Vector3.forward * Time.deltaTime * moveSpeed);
}
if (Input.GetKey (KeyCode.S))
{
gameObject.transform.Translate (-Vector3.forward * Time.deltaTime * moveSpeed);
}
if (Input.GetKey (KeyCode.A))
{
gameObject.transform.Translate (-Vector3.right * Time.deltaTime * moveSpeed);
}
if (Input.GetKey (KeyCode.D))
{
gameObject.transform.Translate (Vector3.right * Time.deltaTime * moveSpeed);
}
}
void lookAround()
{
//视角随鼠标移动,建议较深入了解欧拉角和Time.fixedDeltaTime这东西
//Debug.Log ("鼠标位置:( " + Input.GetAxis ("Mouse X") + " , " + Input.GetAxis ("Mouse Y") + " )");
if (Input.GetAxis ("Mouse X") != 0)
{
if (Input.GetAxis ("Mouse X") < 0.1f && Input.GetAxis ("Mouse X") > -0.1f) {
return;
}
this.gameObject.transform.Rotate (new Vector3 (0, Input.GetAxis ("Mouse X") * Time.fixedDeltaTime * DPI, 0));//摄像机的旋转速度
}
if (Input.GetAxis ("Mouse Y") != 0)
{
if (Input.GetAxis ("Mouse Y") < 0.1f && Input.GetAxis ("Mouse Y") > -0.1f)
{
return;
}
Eye.transform.Rotate (new Vector3 (Input.GetAxis ("Mouse Y") * Time.fixedDeltaTime * -DPI, 0, 0));//摄像机的旋转速度
}
}
void jump()
{
if (Input.GetKeyDown (KeyCode.Space))
{
if(isJumping == false)
{
Rig.AddForce( Vector3.up * jumpForce );
isJumping = true;
}
}
}
//特殊函数:如果发生碰撞则执行此函数
private void OnCollisionEnter(Collision collision)
{
isJumping = false;
}
}
值得一提的是,在学习了以上内容之后,不妨在程序里加一个按【F1】可以无限跳跃的功能,也算是练练手。
更新时间:2019/07/05