变量和函数(Variables and Functions)
int myInt = 5;
void Start() {
myInt = MultiplyByTwo(myInt);
Debug.Log(myInt)
}
int MultiplyByTwo(int number) {
int result;
result = number * 2;
return result;
}
IF 语句(If Statements)
float coffeeTemperature = 85.0f;
float hotLimitTemperature = 70.0f;
float coldLimitTemperature = 40.0f;
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
TemperatureTest();
coffeeTemperature -= Time.deltaTime * 5f;
}
void TemperatureTest()
{
if(coffeeTemperature > hotLimitTemperature)
{
print("Coffee is too hot.")
}
else if (coffeeTemperature < coldLimitTemperature)
{
print("Coffee is too cold.")
}
else
{
print("Coffee is just right.")
}
}
循环(Loops)
循环是编程中重复操作的方式。
3中不同的循环类型:ForLoop, WhileLoop 和 DoWhileLoop。
WhileLoop
在满足条件时执行操作
int cupsInTheSink = 4;
void Start()
{
while (cupsInTheSink > 0)
{
Debug.Log("I've washed a cup!");
cupsInTheSink--;
}
}
DoWhileLoop
DoWhileLoop 的功能与 WhileLoop 几乎一样,只有一个明显区别,WhileLoop 在循环主体前检验条件,但 DoWhileLoop 在循环主体结束时检验条件。
这个区别意味着 DoWhileLoop 主体至少会运行一次。
void Start()
{
bool shouldContinue = false;
do
{
print("Hello DoWhileLoop")
}
while (shouldContinue == true);
}
ForLoop
ForLoop 或许是最常见最灵活的循环,ForLoop 利用可控数量的迭代创建循环。就功能而言,它会先检查循环中的条件,
int numEnemies = 3;
void Start()
{
for (int i = 0; i < numEnemies; i++)
{
Debug.Log("Creating enemy number: " + i);
}
}
作用域和访问修饰符(Scope and Access Modifiers)
变量作用域:代码中可以使用这个变量的区域
代码块通常用于定义变量作用域,用花括号表示。
public class ScopeAndAccessModifiers : MonoBehaviour
{
public int alpha;
private int beta = 0;
private int gamma = 5;
void Example(int pens, int crayons)
{
int answer;
answer = pens * crayons * alpha;
Debug.Log(answer);
}
void Update()
{
Debug.Log("Alpha is set to: " + alpha);
}
}
例如,这个类里的所有内容,都可以称为该类的局部代码。
公开和私有访问修饰符
类中定义的变量不同于函数内声明的变量,类拥有访问修饰符。访问修饰符是在声明变量时放在数据类型前的关键字,其用途是定义能否看到变量或函数。
将变量设为 public 意味着可以从类外部访问这个变量。也意味着可以通过其它脚本访问它们,
私有变量只能在类内编辑,在 C# 中未指定访问修饰符的任意变量,默认使用 private 访问修饰符。
Awake 和 Start
public class AwakeAndStart : MonoBehaviour
{
void Awake()
// References between scripts, initialization
{
Debug.Log("Awake called.");
}
void Start()
// Once script component is enabled
{
Debug.Log("Start called.");
}
}
Awake
和 Start
是在加载脚本时自动调用的两个函数。
首先调用 Awake
即使还未启用脚本组件,它非常适用于在脚本与初始化之间设置任何引用。
Start
在 Awake
之后调用,而且是直接在首次更新之前调用,前提是已经启用了脚本组件。在启用了脚本组件的情况下,可以用 Start 启动任何所需操作。这样就可以将初始化代码的任何部分延迟到真正需要的时候再运行。
Start 和 Await 在一个对象绑定脚本的生命周期内只能调用一次。
Update 和 FixedUpdate
private float fixedUpdateTimer;
private float UpdateTimer;
void FixedUpdate()
{
Debug.Log("FixedUpdate time :" + Time.deltaTime);
}
void Update()
{
Debug.Log("Update time :" + Time.deltaTime);
}
Update 是 Unity 中最常用的函数之一。在每个使用它的脚本中,每帧调用一次。基本上,只要是需要变化或调整都需要使用 Update 来实现。非物理对象的移动,简单的计时器,输入检测等等,一般都在 Update 中完成。
注意,Update 并不是按固定的时间调用的,如果某一帧比下一帧处理时间长,那么 Update 调用的时间间隔就会不同。
FixedUpdate 函数与 Update 类似,但有几点明显不同。
FixedUpdate 按固定时间调用。调用 FixedUpdate 之后,会立即进行任何必要的物理计算,因此,任何影响刚体(即物理对象)的动作都应使用 FixedUpdate 执行。
在 FixedUpdate 循环中编写物理脚本时,最好使用力来定义移动。
Ctrl + Shift + M 启动向导,勾选要添加的方法名。
矢量数学
启用和禁用组件
启用和禁用 Unity 中的组件,只需要使用 enabled
标记。
public class EnableComponents : MonoBehaviour {
private Light myLight;
void Start()
{
myLight = GetComponent<Light>()
}
void Update()
{
if(Input.GetKeyUp(KeyCode.Space))
{
myLight.enabled = !myLight.enabled;
}
}
}
激活游戏对象
要通过脚本激活或停用对象,可以使用 SetActive 函数。
void Start()
{
gameObject.SetActive(false);
}
要确认某个对象在场景或在层次结构中是否为活跃状态,可以使用 Active Self 和 Active in Hierarchy 状态查询。
public class CheckState : MonoBehaviour
{
public GemeObject myObject;
void Start()
{
Debug.Log("Active Self: " + myObject.activeSelf);
Debug.Log("Active in Hierarchy" + myObject.activeInHierarchy);
}
}
在层次结构中,如果停用父对象,子对象被激活,但所处的层次结构未被激活。
平移和旋转(Translate and Rotate)
平移和旋转是两个常用函数,用来更改游戏对象的位置和旋转。
void Update()
{
transform.Translate(new Vector3(0, 0, 1));
}
通常在使用平移操作时,会乘以 Time.deltaTime,这意味着它会按每秒多少米的速度移动,而不是每帧多少米。
public float moveSpeed = 10f;
void Update()
{
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}
按下某个键时发生运动
public float moveSpeed = 10f;
void Update()
{
if(Input.GetKey(KeyCode.UpArrow))
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
if(Input.GetKey(KeyCode.DownArrow))
transform.Translate(-Vector3.forward * moveSpeed * Time.deltaTime);
}
transform.Rotate
public float turnSpeed = 50f;
void Update()
{
if(Input.GetKey(KeyCode.LeftArrow))
transform.Rotate(Vector3.up, -turnSpeed * Time.deltaTime);
if(Input.GetKey(KeyCode.RightArrow))
transform.Rotate(Vector3.up, turnSpeed * Time.deltaTime);
}
应当注意,这些函数作用于局部轴而非世界轴。所以使用 Vector3.Forward 或 Vector3.Up 时,相对的是脚本所应用到的游戏对象的轴。
如果想用碰撞体移动某个对象,也就是将会产生物理作用的物体,则不应该使用 Translate 和 Rotate 函数,而应该考虑使用 Physics 函数。
LookAt
LookAt 用于让游戏对象的正向指向世界中的另一个 transform.
让镜头对准正在掉落的对象
public Transform target;
void Update()
{
transform.LookAt(target)
}
Destory
Destory 可用于在运行时移除游戏对象,或从游戏对象移除组件。
如果要销毁某个游戏对象,只需要引用与脚本关联的游戏对象。
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destroy(gameObject)
}
}
但是问题在于,你可能会将这个脚本用于不同用途,因此不应该销毁对象,否则脚本组件也会随之被删除,因为二者是关联的。
public GameObject other;
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destroy(other);
}
}
也可以使用 destroy 命令移除组件,而不是整个游戏对象。
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destroy(GetComponent<MeshRenderer>());
}
}
上述例子都可以使用数字作为第2个参数,用来创建延时。
Destroy(gameObject, 3f);
GetButton 和 GetKey
在 Unity 中,GetKey 和 GetButton 通过 unity 的输入类接收按键或手柄按钮的输入。
两者的差异在于 GetKey 会使用 KeyCode 明确指定按键名称。
例如空格键,表示为 KeyCode.Space,这虽然适用于键盘按键,但建议使用 GetButton 指定你自己的控制。
输入管理器允许指定输入名称,然后给它指定一个键或按钮。要访问这个功能,可以从顶部菜单,编辑 -> project Settings -> Input。如需了解 Positive button 中可以输入哪些内容,可以参阅文档中的参考资料。
使用 GetKey 或者 GetButton 时,这些输入有3种状态,都会返回 true or false。
- GetButtonDown 第一帧为true
- GetButton
- GetButtonUp 第一帧为true
void Update()
{
bool down = Input.GetButtonDown("Jump");
bool held = Input.GetButton("Jump");
bool up = Input.GetButtonUp("Jump");
if (down)
{
graphic.sprite = downgfx;
}
else if (held)
{
graphic.sprite = heldgfx;
}
else if (up)
{
graphic.sprite = upgfx;
}
else
{
graphic.sprite = standard;
}
}
GetAxis
GetAxis 会返回浮点值,介于-1到1之间。轴在输入管理器中设置,从顶部菜单选择 Edit -> Project Settings -> Input -> Axes。对于轴,Positive Button 和 Negative Button 都要考虑,以及 Gravity, Sensitivity, Dead, Snap。
轴的 Gravity 会影响滑尺在按钮松开后归零的速度。Gravity 越高,归零速度越快。
Sensitivity 控制着输入的返回值到达1或-1的速度有多快,Sensitivity 值越大,反应速度就越快。
如果使用操纵杆表示轴,我们就不希望感受到操纵杆轻微移动的作用,为了避免这种情况,我们需要一个盲区。Dead 值越大,盲区越大。
Snap 选项的作用是,同时按下正负按钮时归零。
获取横轴或竖轴的值:
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
仅返回整数,不返回非整数
Input.GetAxis("Raw");
OnMouseDown
OnMouseDown 及其相关函数,可检测对碰撞体或GUI文本元素的点击。
public class MouseClick : MonoBehaviour
{
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>()
}
void OnMouseDown()
{
rb.AddForce(-transform.forward * 500f);
rb.useGravity = true;
}
}
GetComponent
在 Unity 中,脚本被视为自定义组件,我们通常需要访问与同一个与游戏对象关联的其它脚本,甚至是其它游戏对象关联的脚本。
DeltaTime
delta 是指两个值之间的差。time 类的 DeltaTime 属性是指两次 Update 或 FixedUpdate 函数调用的间隔时长。它的作用是让用于移动其它增量计算的值变得平滑。
帧与帧之间的时差不是固定的,假设某个对象每帧移动固定距离,整体效果可能并不流畅,因为完成一帧所需的时间不同,虽然移动距离固定不变。
如果使用 Time.deltaTime 修改变化量,所需时间较长的帧,变化较大;所需时间较短的帧,变化较小。
public class UsingDeltaTime : MonoBehaviour
{
public float speed = 8f;
void Update()
{
if(Input.GetKey(KeyCode.RightArrow))
{
transform.position += new Vector3(speed * Time.deltaTime, 0.0f, 0.0f);
}
}
}
同时,我们可以控制立方体的移动,添加 speed 并由 Time.deltaTime 修改数量,移动变得流畅,即使帧率变化,速度也会保持恒定。
Time.deltaTime 的这种用法,让我们能更改每秒的值,而非每帧的值。
数据类型(DataTypes)
所有变量都会有数据类型。两种主要的数据类型为值类型(Value)和引用类型(Reference)。
Value
- int
- float
- double
- bool
- char
- Structs
- Vector3
- Quaternion
Reference
- Classes
- Transform
- GameObject
void Start()
{
Vector3 currentPosition = transform.position;
// 因为 currentPosition 是值类型 Structs 的 Vector3
// 所以只有 currentPosition 会受这行代码的影响
// transform.position 不受影响
currentPosition = new Vector3(0,2,0);
}
void Start()
{
Transform tran = transform;
// tran 是引用类型
// transform 赋值给 tran 用的是赋值运算符
// 所以 tran, transform 都指向同一个存储地址
// 因此更改其中一个,另一个也会随之改变
tran.position = new Vector3(0,2,0);
}
类(Classes)
public class Inventory : MonoBehaviour
{
public class Stuff
{
public int projectileA;
public int projectileB;
public int projectileC;
public float fuel;
public Stuff(int prA, int prB, int prC)
{
projectileA = prA;
projectileB = prB;
projectileC = prC;
}
public Stuff(int prA, float fu)
{
projectileA = prA;
fuel = fu;
}
public Stuff()
{
projectileA = 1;
projectileB = 1;
projectileC = 1;
}
}
public Stuff myStuff = new Stuff(50, 5, 5);
// 这个类的实例将用 第二个构造函数,因为参数匹配
public Stuff myOtherStuff = new Stuff(50, 1.5f)
void Start()
{
Debug.Log(myStuff.projectileA);
}
}
构造函数允许程序员设置默认值。
关于构造函数需要注意几点:
- 构造函数的名称始终是类的名称。
- 构造函数一定不会有返回类型,连 void 都不会有。
- 一个类可能有多个不同的构造函数,但对象初始化时,只会调用其中一个构造函数。
Instantiate
Instantiate 函数的作用是克隆游戏对象。
它常用于克隆 prefab, prefab 就是指预配置对象,保存在项目素材中。
这类例子包括,从发射器发出的抛射体,每个抛射体都需要实例化到游戏世界中,从而实现发射操作。
public class UsingInstantiate : MonoBehaviour
{
public Rigidbody projectile;
public Transform barrelEnd;
void Update()
{
if(Input.GetButtonDown("Fire1"))
{
Rigidbody projectileInstance;
projectileInstance = Instantiate(projectile, barrelEnd.position, barrelEnd.rotation) as Rigidbody;
projectileInstance.AddForce(barrelEnd.up * 350f);
}
}
}
在游戏中创建多个克隆体时,这些克隆体仍会存在于场景中,所以,可能需要考虑编写一个脚本,在经过一段特定时间后,将它们从世界中移除。
如让 prefab 使用这个 ProjectileDestruction 脚本:
public class ProjectileDestruction : MonoBehaviour
{
void Start()
{
Destroy(gameObject, 3.5f);
}
}
数组
数组的作用是存储同类型数据。
int[] myIntArray = new int [5];
void Start()
{
myIntArray[0] = 1;
myIntArray[1] = 2;
myIntArray[2] = 3;
myIntArray[3] = 4;
myIntArray[4] = 5;
}
// 也可以在一行中进行声明和初始化
int[] anotherArray = {1, 2, 3, 4, 5};
关于数组需要注意几点:
- 设为公开数组,就能在 Inspector 中看到这个数组,并为它分配值。
public GameObject[] players;
void Start()
{
players = GameObject.findGameObjectsWithTag("Player");
for (int i = 0; i < players.Length; i++)
{
Debug.Log("Player Number " +i+ " is named " + players[i].name);
}
}
Invoke
Invoke 函数的作用是将函数调用安排在指定延时后发生。
public class InvokeScript : MonoBehaviour
{
public GameObject target;
void Start()
{
// 单位秒
Invoke("SpawnObject", 2)
}
void SpawnObject()
{
Instantiate(target, new Vector3(0, 2, 0), Quaternion.identity);
}
}
值的注意的是,只有不包含参数且返回类型为 void 的方法才能用 Invoke 调用。
InvokeRepeating 函数可以反复调用方法。
public GameObject target;
void Start()
{
// 参数1: 方法名字符串
// 参数2: 调用方法前的延时,单位秒
// 参数3: 调用间隔的延时,单位秒
InvokeRepeating("SpawnObject", 2, 1);
}
void SpawnObject()
{
float x = Random.Range(-2.0f, 2.0f);
float z = Random.Range(-2.0f, 2.0f);
Instantiate(target, new Vector3(x, 2, z), Quaternion.identity);
}
停止这个脚本中 Invoke 调用的所有实例,可以使用 CancelInvoke()
方法。
如果只想停止某个特定的 Invoke 可以传递方法名字符串,如:CancelInvoke("SpawnObject")
枚举
在 Unity 中编写脚本时,有时我们需要变量。
想象一下指南针的方位,我们可以使用整数描述方位,0代表北,1代表东,2代表南,3代表西。但是这种描述方式不易阅读,也不利于编写代码,因为这意味着需要记住每个数字代表的方位。
相反,我们可以创建名为枚举的变量,枚举通常被称为 enums。
enum Direction {North, East, South, West};
枚举中声明的每个常量,都有一个值,默认为从0开始往上数的整数。因此 North 的值为0,East 的值为1,South 的值为2,West 的值为3。
如果需要的话,值的类型和值本身都可以被覆盖。
// North: 1; East: 2; South: 3; West: 4
enum Direction {North = 1, East, South, West};
Switch 语句
public class ConversationScript : MonoBehaviour
{
public int intelligence = 5;
void Greet()
{
switch (intelligence)
{
case 5:
print("Why hello there good sir! Let me teach you about Trigonometry!");
break;
case 4:
print("Hello and good day!");
break;
case 3:
print("Whadya want?");
break;
case 2:
print("Grog SMASH!");
break;
case 1:
print("Ulg, glib, Pblblblblb");
break;
default:
print("Incorrect intelligence level.");
break;
}
}
}