unity学习——c#初级编程
1.作为行为组件的脚本
(Unity 中的脚本是什么?了解作为 Unity 脚本的行为组件,以及如何创建这些脚本并将它们附加到对象)
首先新建一个cube立方体
然后新建一个c#脚本,脚本用来实现立方体cube的三种颜色变化(按键实现)
脚本代码如下:
using UnityEngine; using System.Collections; public class color : MonoBehaviour { void Update() { if (Input.GetKeyDown(KeyCode.R)) { GetComponent<Renderer>().material.color = Color.red; } if (Input.GetKeyDown(KeyCode.G)) { GetComponent<Renderer>().material.color = Color.green; } if (Input.GetKeyDown(KeyCode.B)) { GetComponent<Renderer>().material.color = Color.blue; } } }
之后将脚本挂载到cube立方体上,如果出现下图问题
请将c#脚本中color改成你设置的脚本名称
最后保存运行,效果如下:
R(red)
G(green)
B(blue)
在运行过程中,点击下图所示的元素0
可以在下图界面通过调整Main Color更加细节的调整想要的颜色
2.变量和函数
(什么是变量和函数、它们如何为我们存储和处理信息)
我们可以编写一个data的脚本,并创建一个空对象,将data脚本挂载到空对象上,运行后,可以在控制台上看到设定好的数据
脚本代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class data : MonoBehaviour { int myInt = 5; void Start() { Debug.Log(myInt); } }
上述代码中的start函数是一个不返回任何结果的函数,它的返回类型是void
当一个脚本绑定的对象进入场景时,start函数就会被调用
脚本代码:
using UnityEngine; using System.Collections; public class data : MonoBehaviour { int myInt = 5; void Start() { myInt = MultiplyByTwo(myInt); Debug.Log(myInt); } int MultiplyByTwo(int number) { int ret; ret = number * 2; return ret; } }
3.约定和语法
(了解编写代码的一些基本约定和语法:点运算符、分号、缩进和注释 )
句点运算符(dot operator):
- 句点运算符是一个句点或句号,常用于代码内的单词之间,它的作用就像是编写一行地址
- 句点运算法能让我们能有效分割或访问Unity中复合项的各个元素,复合项指包含多个元素的项
分号(semicolon):
- 分号的作用是终止语句
- 通常花括号中间的语句末尾都要加上分号。
缩进(indenting):
- 缩进是代码编写的重要组成部分,它另代码清晰易读,技术角度上,缩进代码不是必需的。
- 缩进代码能显示代码的功能结构,它令代码清晰易读
- 通常情况下,Visual studio会帮我们自动设置缩进,但为了避免发生操作失误或者误按了TAB键等情况,程序员应该确保所有缩进都符合规定,这点在与他人合作编写代码时尤为重要。
- 注释的作用是写下关于某一段代码的注释给自己留下备注或提醒
- 单行注释编写:双正斜杠(//)
多行注释编写:一个正斜杠(/)+一个星号(*),终止时调转顺序,一个星号(*)+一个正斜杠(/),在这个范围内可以写入任意行数的注释
注释的作用:①给自己或其他程序员留下备注
②暂时禁用部分代码
②例:
禁用一段if语句,在if语句前后插入起止多行注释符
4.IF语句
(如何使用 IF 语句在代码中设置条件)
编写代码时,经常遇到根据条件做出决策的情况,这个时候常使用if语句
例子:喝咖啡
using UnityEngine; using System.Collections; public class IfStatements : MonoBehaviour { 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."); } } }
5.循环(Loop)
(如何使用 For、While、Do-While 和 For Each 循环在代码中重复操作)
循环是编程中重复操作的方式,接下来介绍三种循环。
①while loop
作用:在满足条件时执行操作
例子:洗茶杯
using UnityEngine; using System.Collections; public class WhileLoop : MonoBehaviour { int cupsInTheSink = 4; void Start () { while(cupsInTheSink > 0) { Debug.Log ("I've washed a cup!"); cupsInTheSink--; } } }
②Dowhile Loop
作用:功能上大致与while loop相同,区别在于whileLoop在循环主体前检验条件,DoWhileLoop在循环主体结束时检验条件,这意味着DoWhileLoop主体至少会运行一次
语法:先是关键词do起始,然后是花括号,花括号中的代码构成了循环主体,循环主体后面是关键词while,再后面跟着条件句,注意条件句最后要加上分号(;)
WhileLoop不使用分号,但DoWhileLoop使用分号
using UnityEngine; using System.Collections; public class DoWhileLoop : MonoBehaviour { void Start() { bool shouldContinue = false; do { print ("Hello World"); }while(shouldContinue == true); } }
功能:利用可控数量的迭代创建循环,ForLoop会先检查循环中的条件,如果符合条件,就执行循环主体中的指令(即花括号中的代码),一个循环为一次迭代。
using UnityEngine; using System.Collections; public class ForLoop : MonoBehaviour { int numEnemies = 3; void Start () { for(int i = 0; i < numEnemies; i++) { Debug.Log("Creating enemy number: " + i); } } }
for循环的语法由三个参数组成
参数一:迭代子变量int i =0,它的作用是计算循环迭代次数,即循环次数
参数二:条件i<numEnemies,只有当条件为true时循环才会继续
参数三:i++,定义了每次循环中对迭代子的处理
简而言之,任何需要执行指定次数的运算都可以使用ForLoop实现。
6.作用域和访问修饰符
(了解变量和函数的作用域和可访问性)
- 变量作用域指代码中可使用这个变量的的区域
- 变量局限于代码中可以使用这个变量的位置,代码块通常用于定义变量作用域,用花括号表示
- 类内定义的变量不同于函数内声明的变量,前者分配有访问修饰符 。访问修饰符是在声明变量时放在数据类型前的关键词,其用途是定义能够看到变量或函数的位置。
- 一般而言,如果其他脚本需要访问某个变量或函数,就应该将其公开,否则就应设为私有
公开访问修饰符:
- 将变量设为公开意味着可从类外部访问这个变量,也意味着这个变量可在Inspector中的组件上显示和编辑。
- 虽然公开变量可在组件上调整,但这并不表示公开变量不能回到代码中,在运行时通过脚本更改变量值
- 将变量和函数设为公开也意味着可以通过其他脚本访问它们
注意:如果变量在类中初始化为默认值,它仍会被Inspector中的值覆盖,但如果这些值在函数中设置,它们则出现在Inspector中设置过变量之后,因此不会被Inspector覆盖
私有访问修饰符:
- 私有变量只能在类内编辑,在c#中,未指定访问修饰符的任意变量默认使用私有访问修饰符。
- 最好是将所有成员变量(所有属于类而非函数的变量)都设为私有,除非需要将它们公开以满足特定需要。
7.Awake和Start
(如何使用Unity的两个初始化函数Awake和Start)
- Awake和Start是在加载脚本时自动调用的两个函数,首先调用Awake,即使还未启用脚本组件也没关系,它非常适合用于在脚本与初始化之间设置任何引用。
- Start在Awake之后调用,而且是直接在首次更新之前调用,但前提是已经启用了脚本组件,即启用脚本组件的情况下,可以用Start启动任何所需操作,这样就可以将初始化代码的任何部分延迟到真正需要的时候再运行。
- Awake和Start帮助我们在启用脚本组件之前初始化对象的设置,而不需要将脚本拆分成几个不同的脚本。
注意:Start和Awake在一个对象绑定脚本的生命周期内只能调用一次,因此不能通过禁用和重新启用脚本来重复执行Start函数。
8.Update和FixedUpdate
(如何使用 Update 和 FixedUpdate函数实现每帧的更改,以及它们之间的区别)
Update函数:
- Update是Unity中最常用的函数之一,在每个使用它的脚本中每帧调用一次,基本上只要需要变化或调整都需要使用Update来实现。
- 非物理对象的移动,简单的计时器,输入检测等一般都在Update中完成
注意:Update并不是按固定时间调用的,如果某一帧比下一帧的处理时间长,那么Update调用的时间间隔就会不同
FixedUpdate函数:
- FixedUpdate按固定时间调用,调用的时间间隔相同,调用FixedUpdate之后,会立即进行任何必要的物理计算,因此,任何影响刚体(即物理对象)的动作都应使用FixedUpdate执行而不是Update。
- 在FixedUpdate循环中编写物理脚本时,最好使用力来定义移动。
Update和Unity中的许多其他特殊函数都可以在Visual Studio中迅速轻松创建,通过使用MonoBehaviour脚本编写向导即可实现,在Visual Studio中将光标放在需要插入新函数的位置,然后按Ctrl+Shift+M启动向导,在Create Script Methods窗口中勾选想要添加的各个方法名称旁边的复选框,选择“OK”按钮退出向导,这些方法就会插入到代码中了。通过这种方法使用向导有助于避免代码错误,也有助于一边学习一边了解可以使用的函数。
9.矢量数学
(矢量数学入门以及有关点积和叉积的信息)
在游戏开发中,我们利用向量来定义网格方向和所有其他类型的计算。
向量在两点之间绘制的线条,向量的长度称为大小。
二维向量(2D vectors)
二维向量表示二维平面上的点,它以原点为参照,即图中的(0,0)点,指向二维平面的任意点。因为从原点出发,因此有隐含方向,它由X和Y这两个坐标组成,它们分别代表X和Y轴上与0的距离。
三维向量(3D vectors)
三维向量与二维向量的原理相同,但延伸出一个Z轴,它表示深度,x轴和z轴构成水平面,Y轴代表朝上的方向。
Unity采用左手坐标系,这意味着如果你举起左手,食指朝上,拇指朝外成L形,中指朝前,则拇指代表X轴,食指代表Y轴,中指代表Z轴
计算两个三维向量坐标之间的距离时,Unity为了方便计算,引入了一个帮助函数Vector3.magnitude。
适用于三维向量的函数有很多,点积和叉积是典型的两种。
点积(Dot Product):
点积需要2个向量,根据它们计算出一个值,即标量。要计算2个向量的点积,需要取向量坐标,XY和Z值并分别相乘,然后将乘积相加得出最终结果。
利用点积可以了解指定的2个向量的相关信息,如2个向量之间的点积为0,则表明这2个向量互相垂直。
例:创建飞行模拟器
这时可以检查场景向上向量,与飞机向前向量的关系。如果2个向量互相垂直,即点积等于0的话,飞机阻力最小;随着点积正值增大,表明飞机正在爬升,我们可以增加阻力;如果点积负值增大,表明飞机正在俯冲。Unity有一个帮助函数可轻松完成点积计算(Vector3.Dot(VectorA,VectorB))
叉积(Cross Product):
Unity针对叉积提供了一个帮助函数:Vector3.Cross(VectorA,VectorB)
例:确定围绕哪个轴施加扭矩来转动坦克的炮塔
假设你已知炮塔目前的朝向也知道炮塔的目标朝向,就可以对2个向量进行叉积运算,确定对哪个轴施加转动扭矩。
10.启用和禁用组件
(如何在运行时通过脚本启用和禁用组件)
启用和禁用Unity中的组件,只需使用“Enabled”标记。
需要记住的是,脚本也是组件,所以也可以使用.enabled标记来禁用脚本
例:开关灯
using UnityEngine; using System.Collections; public class EnableComponents : MonoBehaviour { private Light myLight; void Start () { myLight = GetComponent<Light>(); } void Update () { if(Input.GetKeyUp(KeyCode.Space)) { myLight.enabled = !myLight.enabled; } } }
11.激活游戏对象
(如何使用SetActive和activeSelf/activeInHierarchy单独处理以及在层级视图中处理场景内部游戏对象的活动状态)
SetActive函数:
在场景中激活或停用对象。
using UnityEngine; using System.Collections; public class CheckState : MonoBehaviour { public GameObject myObject; void Start () { Debug.Log("Active Self: " + myObject.activeSelf); Debug.Log("Active in Hierarchy" + myObject.activeInHierarchy); } }
12.Translate和Rotate
(如何使用两个变换函数Translate和Rotate来更改非刚体对象的位置和旋转)
平移(Translate)和旋转(Rotate)是2种常用函数,用来更改游戏对象的位置和旋转。
平移(Translate):
代码:
using UnityEngine using System.Collections; public class TransformFunctions : MonoBehaviour { void Update() { transform.Translate(new Vector3(0,0,1)); } }
平移参数为Vector3,这个例子仅沿Z轴向下平移,所以X和Y的值都为0,每帧移动1个单位,因为它在Update函数中。
通常在进行平移操作中会乘以Time.deltaTime,这意味着它会按每秒多少米的速度移动,而不是每帧多少米,我们不需要输入Vector3(0,0,1),而是可以使用Vector3.forward作为快捷方式,接着可以用它乘以另一个值,这个值可以定义为一个单独的变量,这样就可以通过调整Inspector中的变量加以控制。
使物体可以通过按键进行上下移动:
using UnityEngine; using System.Collections; public class TransformFunctions : MonoBehaviour { 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);
13.Look At
(如何使用LookAt函数使一个游戏对象的变换组件面向另一个游戏对象的变换组件)
在上图所示的脚本中,可以看到我们引用了想要寻找的对象,一个名为target的transform类型变量,我们可以使用transform.LookAt函数让对象看向target。回到unity界面时,我们需要将次脚本应用于摄像机,并将我们想要对准的游戏对象拖入target变量字段,运行后,摄像机持续朝向移动对象。
注意:可以在界面顶部切换Global和Local访问形式。Local:可以看到正向朝向对象
using UnityEngine; using System.Collections; public class CameraLookAt : MonoBehaviour { public Transform target; void Update () { transform.LookAt(target); } }
14.线性插值
// 在此示例中,result = 4 float result = Mathf.Lerp (3f, 5f, 0.5f);
Vector3 from = new Vector3 (1f, 2f, 3f); Vector3 to = new Vector3 (5f, 6f, 7f); // 此处 result = (4, 5, 6) Vector3 result = Vector3.Lerp (from, to, 0.75f);
void Update () { light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f); }
如果光的强度从 0 开始,则在第一次更新后,其值将设置为 4。下一帧会将其设置为 6,然后设置为 7,再然后设置为 7.5,依此类推。因此,经过几帧后,光强度将趋向于 8,但随着接近目标,其变化速率将减慢。请注意,这是在若干个帧的过程中发生的。
如果我们不希望与帧率有关,则可以使用以下代码:
void Update () { light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f * Time.deltaTime); }
15.Destroy
(如何在运行时使用Destroy()函数删除游戏对象和组件)
using UnityEngine; using System.Collections; public class DestroyBasic : MonoBehaviour { void Update () { if(Input.GetKey(KeyCode.Space)) { Destroy(gameObject); } } }
引用与脚本关联的游戏对象,按空格键时,游戏对象就会被销毁(不应该销毁游戏对象,因为脚本组件也会随之被删除,二者是关联的)
为了解决上述提到的问题,应该引用另一个对象,设置一个名为other的公开变量,用来引用另一个对象,接着在Inspector中拖入另一个要使用的对象,之后运行时就可将想要销毁的对象销毁。
using UnityEngine; using System.Collections; public class DestroyOther : MonoBehaviour { public GameObject other; void Update () { if(Input.GetKey(KeyCode.Space)) { Destroy(other); } } }
也可以使用destroy命令移除组件,而不是整个游戏对象,为此,在destroy括号中使用GetComponent函数来引用组件。
例:销毁MeshRenderer组件
using UnityEngine; using System.Collections; public class DestroyComponent : MonoBehaviour { void Update () { if(Input.GetKey(KeyCode.Space)) { Destroy(GetComponent<MeshRenderer>()); } } }
这样这个对象就不会再渲染出来,可以看到对象仍在游戏中,而且所有其他元素都在。
除了刚刚移除的MeshRenderer,上述所有示例都可以使用数字,作为第2个参数用来创建延时——Destroy(gameobject,3f)
按空格键后,就会出现3秒延时,接着对象会被移除,这同样适用于销毁组件。
16.GetButton 和 GetKey
(如何在Unity项目中获取用于输入的按钮或键,以及这些轴的行为或如何通过Unity Input Manager进行修改)
在unity中,GetKey或GetButton通过unity的输入类接收来自按键或操纵杆按钮的输入。
两者的代码差异在于GetKey会使用KeyCode明确指定按键名称
例如:空格键表示为KeyCode.Space,这虽然适用于键盘按键,但建议使用GetButton指定你自己的控制。
输入管理器允许指定输入名称,然后给它指定一个键或按钮,要访问这个功能,可从顶部菜单选择“Edit”“Project Settings”找到“Input”,调用时可以用字符串来引用名称。
例如:Jump这是空格键表示的默认输入,但我们可以输入其他键或按钮代码来更改表示Jump的输入,接着当我们调用这个按钮时,可以使用字符串Jump引用名称。
使用GetKey或GetButton时,这些输入有3种状态,都会返回布尔值true或false。
首先是GetKey还是GetButton,根据有没有按下按钮来记录true或false。
没有按键按下时,GetButton返回false,第一次按下按键时,第一帧返回true,然后随着帧数的增加,我们按住按钮,GetButtonDown返回false,GetButton仍等于true,这样我们就能确认是否按住了按钮,当我们松开按钮时,GetButtonUp显示true,但也仅限第一帧,继续操纵,所有值都恢复为false。
整体流程:未按按钮时,所有值都为false。第一次按按钮时,可以用GetButtonDown查看这个状态,它会返回true而GetButton也会返回true。第一帧结束后,GetButtonDown恢复为false。这可以用于武器射击等。继续按住按钮,只有GetButton会返回true,接着松开按钮时,GetButtonUp返回true持续一帧,接着恢复为false。
注意:GetKey的行为完全相同,只是代码写法略有差异。
要查看按钮的状态,使用输入管理器内输入的标题字符串jump,但如果要查看特定键的状态,可以使用KeyCode,因为KeyCode只与特定键相关,建议使用GetButton,并用输入管理器指定输入。
KeyInput代码:
using UnityEngine; using System.Collections; public class KeyInput : MonoBehaviour { public GUITexture graphic; public Texture2D standard; public Texture2D downgfx; public Texture2D upgfx; public Texture2D heldgfx; void Start() { graphic.texture = standard; } void Update () { bool down = Input.GetKeyDown(KeyCode.Space); bool held = Input.GetKey(KeyCode.Space); bool up = Input.GetKeyUp(KeyCode.Space); if(down) { graphic.texture = downgfx; } else if(held) { graphic.texture = heldgfx; } else if(up) { graphic.texture = upgfx; } else { graphic.texture = standard; } guiText.text = " " + down + "\n " + held + "\n " + up; } }
ButtonInput:
using UnityEngine; using System.Collections; public class ButtonInput : MonoBehaviour { public GUITexture graphic; public Texture2D standard; public Texture2D downgfx; public Texture2D upgfx; public Texture2D heldgfx; void Start() { graphic.texture = standard; } void Update () { bool down = Input.GetButtonDown("Jump"); bool held = Input.GetButton("Jump"); bool up = Input.GetButtonUp("Jump"); if(down) { graphic.texture = downgfx; } else if(held) { graphic.texture = heldgfx; } else if(up) { graphic.texture = upgfx; } else { graphic.texture = standard; } guiText.text = " " + down + "\n " + held + "\n " + up; } }
17.GetAxis
(如何在Unity中为游戏获取基于轴的输入,以及如何通过Input Manager修改这些轴)
GetAxis会返回浮点值,这个值介于-1到1之间,轴在输入管理器中设置。
访问方法:从顶部菜单选择“Edit”,“Project Settings”,“Input”。
按钮操作时,只需考虑“Positive Button”的值,对于轴“Positive Button”和“Negative Button”都要考虑,以及“Gravitty”,"Sensitivity","Dead"和"Snap" 。
因为GetAxis返回的是浮点值,所以它相当于是介于正负一之间的滑尺。
轴的Gravity会影响滑尺,在按钮松开后归零的速度。Gravity越高,归零速度越快。
Sensitivity与Gravity相反,它控制着输入的返回值,到达1或-1的速度有多快,Sensitivity值越大,反应速度就越快,值越小,移动越流畅。
如果用操纵杆表示轴,难免会出现操作杆轻微移动的情况,为避免这种情况,需要一个盲区。Dead值越大,盲区越大,因此操作杆的移动的幅度也必须增大才能让GetAxis返回非0值。
Snap选项的作用是同时按下正负按钮时归零,为获得横轴或竖轴的值,只需在代码中添加一个Input.GetAxis(“Horizontal”)或Input.GetAxis(“Vertical”),也可使用Input.GetAxis(“Raw”),仅返回整数不返回非整数,这十分适合需要精准控制的二维游戏,不适用于需要平滑值的游戏。
注意:Snap不需要使用Gravity或Sensitivity
18.OnMouseDown
(如何检测碰撞体或GUI元素上的鼠标点击)
OnMouseDown及其相关函数可检测对碰撞体或GUI文本元素的点击。
MouseClick代码:
using UnityEngine; using System.Collections; public class MouseClick : MonoBehaviour { void OnMouseDown () { rigidbody.AddForce(-transform.forward * 500f); rigidbody.useGravity = true; } }
():给物体对象施加了一个反作用力
19.GetComponent
(如何使用GetComponent函数来处理其他脚本或组件的属性)
脚本被视为自定义组件,我们通常需要访问与同一个游戏对象关联的其他脚本甚至是与其他游戏对象关联的脚本。访问其他脚本和组件要使用GetComponent。
using UnityEngine; using System.Collections; public class UsingOtherComponents : MonoBehaviour { public GameObject otherGameObject; private AnotherScript anotherScript; private YetAnotherScript yetAnotherScript; private BoxCollider boxCol; void Awake () { anotherScript = GetComponent<AnotherScript>(); yetAnotherScript = otherGameObject.GetComponent<YetAnotherScript>(); boxCol = otherGameObject.GetComponent<BoxCollider>(); } void Start () { boxCol.size = new Vector3(3,3,3); Debug.Log("The player's score is " + anotherScript.playerScore); Debug.Log("The player has died " + yetAnotherScript.numberOfPlayerDeaths + " times"); } }
在Awake函数中进行变量初始化,GetComponent函数使用的调用类型与常用的略有差异,普通括号前的尖括号,尖括号的作用是让类型成为参数。
上述代码中,类型是AnotherScript,也可以调用GetComponent来访问
虽然GetComponent最常用于访问其他脚本,但它也用于访问API未公开的其他组件
注意:GetComponent会占用大量处理能力所以应该尽量减少调用,最好是在Awake或Start函数中调用或仅在首次需要时调用一次
20.Delta Time
(什么是Delta Time,如何在游戏中将其用于对值进行平滑和解释)
Delta指两个值之间的差
using UnityEngine; using System.Collections; public class UsingDeltaTime : MonoBehaviour { public float speed = 8f; public float countdown = 3.0f; void Update () { countdown -= Time.deltaTime; if(countdown <= 0.0f) light.enabled = true; if(Input.GetKey(KeyCode.RightArrow)) transform.position += new Vector3(speed * Time.deltaTime, 0.0f, 0.0f); } }
time类的DeltaTime属性基本上指两次更新或固定更新函数调的间隔时长。它的作用是让用于移动其他增量计算的值变得平滑。
帧与帧之间的时差不是固定的。
假设某个对象每帧移动固定距离,整体效果可能并不流畅,这是因为完成一帧所需的时间是不同的,虽然移动的距离是固定不变的。如果使用Time.deltaTime修改变化量,所需时间较长的帧变化较大;所需时间较短的帧变化较小,所以最后整体效果是使用了time类的DeltaTime属性后,在一段时间内物体的移动变化看起来很流畅。
使用了 time类的DeltaTime属性后,即使帧率变化,速度也会保持恒定。Time.deltaTime的这种用法让我们能更改每秒的值而非每帧的值。
21.数据类型
(了解“值”和“引用”数据类型之间的重要区别,以便更好地了解变量的工作方式)
两种主要的数据类型为值类型(value)和引用类型(reference)
Value:整数(int)、浮点数(float)、双精度(double)、布尔型(bool)、字符(char)、Structs(包含一个或多个其他变量,Unity中最常见的2种Structs为Vector3和Quaternion)
Reference:任何属于类对象的变量都叫做引用类型,在unity中最常见的2个类——引用类型Transform和GameObject
值类型和引用类型区别:值类型变量包含某个值,所有引用类型变量都包含值存储位置的存储地址
因此如果值类型改变则只会影响特定变量,但如果引用类型改变,所有包含特定存储地址的变量都会受到影响。
值类型赋值其实就是复制变量,引用类型赋值需要记住所需值的存储地址,在需要时返回这个地址获取变量的值。值类型包含其自己的数据副本,更改它们只会影响特定变量。
22.类
(如何使用类来存储和组织信息,以及如何创建构造函数以便处理类的各个部分)
using UnityEngine; using System.Collections; public class SingleCharacterScript : MonoBehaviour { public class Stuff { public int bullets; public int grenades; public int rockets; public Stuff(int bul, int gre, int roc) { bullets = bul; grenades = gre; rockets = roc; } } public Stuff myStuff = new Stuff(10, 7, 25); public float speed; public float turnSpeed; public Rigidbody bulletPrefab; public Transform firePosition; public float bulletSpeed; void Update () { Movement(); Shoot(); } void Movement () { float forwardMovement = Input.GetAxis("Vertical") * speed * Time.deltaTime; float turnMovement = Input.GetAxis("Horizontal") * turnSpeed * Time.deltaTime; transform.Translate(Vector3.forward * forwardMovement); transform.Rotate(Vector3.up * turnMovement); } void Shoot () { if(Input.GetButtonDown("Fire1") && myStuff.bullets > 0) { Rigidbody bulletInstance = Instantiate(bulletPrefab, firePosition.position, firePosition.rotation) as Rigidbody; bulletInstance.AddForce(firePosition.forward * bulletSpeed); myStuff.bullets--; } } }
将上述例子中实现三个功能的一个脚本更改为三个短的脚本分别实现功能,这样做会使脚本更易于管理,更易阅读,编程效率更高。
在创建实例时,new Stuff后面的括号表明使用的是构造函数,创建类或struct时都要调用构造函数,类或struct可能有多个构造函数,这些函数使用不同参数,构造函数允许程序员设置默认值限制实例化以及编写灵活易读的代码。
注意(构造函数):①构造函数的名称始终是类的名称②构造函数一定不会有返回类型,连void都不会有
③一个类可能有多个不同的构造函数,但对象初始化时只会调用其中一个构造函数
在了解了类对于创建游戏时组织数据的用处,在编写脚本时,建议先全面仔细地设计脚本结构,然后再开始编写一个大类将各种不同的内容囊括其中
23.Instantiate
(如何在运行期间使用 Instantiate 创建预制件的克隆体)
Instantiate函数的作用是克隆游戏对象,它常用于克隆prefab。prefab就是指项目素材中的预配置对象。
例:从发射器发出的抛射体
每一个抛射体都需要实例化到游戏世界中,从而实现发射操作。
本例中使用Fire1来激活instantiate函数,最基本的instantiate形式只需一个参数,也就是需要克隆的对象。
在本例中,创建了一个名为projectile的公开变量,将这个变量传递到instantiate函数,但这意味着prefab将在其默认位置实例化,本例中是位置0。
将上述例子中的代码运行后,物体不能实现抛射体的效果,只会在一个固定位置生成。
更改代码如下:
using UnityEngine; using System.Collections; public class UsingInstantiate : MonoBehaviour { public Rigidbody rocketPrefab; public Transform barrelEnd; void Update () { if(Input.GetButtonDown("Fire1")) { Rigidbody rocketInstance; rocketInstance = Instantiate(rocketPrefab, barrelEnd.position, barrelEnd.rotation) as Rigidbody; rocketInstance.AddForce(barrelEnd.forward * 5000); } } }
using UnityEngine; using System.Collections; public class RocketDestruction : MonoBehaviour { void Start() { Destroy (gameObject, 1.5f); } }
24.数组
(使用数组将变量集合在一起以便于管理)
using UnityEngine; using System.Collections; public class Arrays : MonoBehaviour { 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); } } }
25.Invoke
(如何在 Unity 脚本中使用 Invoke、InvokeRepeating 和 CancelInvoke 函数)
Invoke函数的作用是将函数调用安排在指定延时后发生,可以借此构建对时间敏感的有效的方法调用系统。
Invoke函数有两个参数,一个是字符串,包含需要调用的方法名称,另一个是以秒为单位的延时时长
注:只有不包含参数且返回类型为void的方法才能用Invoke调用
using UnityEngine; using System.Collections; public class InvokeScript : MonoBehaviour { public GameObject target; void Start() { Invoke ("SpawnObject", 2); } void SpawnObject() { Instantiate(target, new Vector3(0, 2, 0), Quaternion.identity); } }
这段代码中Invoke函数只单一运行一次,会在指定位置生成一个游戏对象
如果想要生成多个游戏对象,使用InvokeRepeating函数即可
例:实例化游戏对象(多个)
using UnityEngine; using System.Collections; public class InvokeRepeating : MonoBehaviour { public GameObject target; void Start() { 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); } }
InvokeRepeating函数此时有三个参数,前两个和Invoke函数一样,最后一个参数指重复此函数的时间间隔(秒)
26.枚举
(如何在代码中声明和使用枚举)
如果需要更改枚举的类型,可在枚举名称后添加一个冒号,然后在后面输入类型
例:方向枚举
using UnityEngine; using System.Collections; public class EnumScript : MonoBehaviour { enum Direction {North, East, South, West}; void Start () { Direction myDirection; myDirection = Direction.North; } Direction ReverseDirection (Direction dir) { if(dir == Direction.North) dir = Direction.South; else if(dir == Direction.South) dir = Direction.North; else if(dir == Direction.East) dir = Direction.West; else if(dir == Direction.West) dir = Direction.East; return dir; } }
27.Switch语句
(如何编写和使用 switch 语句)
也可以输入switch语句将要评估的变量名称,对于枚举这样的特定变量,系统会自动填充switch语句的各种实例。
样例脚本如下(根据智商高低游戏NPC发言):
using UnityEngine; using System.Collections; 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; } } }
default与if/else语句中的else类似,default涵盖先前条件语句未涵盖的所有其他情况。