01.Properties 属性
位于类外的代码访问这个类的成员变量的两种方法:
- 使用public公开变量
- 使用属性
属性本身可以当做变量,并且可以封装成员变量,也称之为字段
一个属性可以有两个访问器 get 和 set ,在 get 中我们返回所封装的字段,在 set 中使用value给字段赋值
private int experience;
// 创建一个属性
public int Experience
{
get
{
return experience;
}
set
{
experience = value;
}
}
使用属性可以执行两个公共变量无法实现的操作:
- 通过省略 get 和 set 可以有效将字段设置为只写或只读
- 可以将访问器视为函数,可以在其内部运行其他代码或调用其他函数
private int experience;
// 创建一个属性
public int Experience
{
get
{
// some other code
return experience;
}
set
{
// some other code
experience = value;
}
}
public int Level{
get
{
// 可以返回运算后的等级而不是经验值
return experience / 1000;
}
set{
// 可以接收玩家等级和计算玩家所获得的经验数量,并存储在experience字段中
experience = value * 1000;
}
}
可以使用简写语法,Visual Studio 可以输入prop快速创建属性
public int Health { get; set; }
其他脚本调用
public class B01_Game : MonoBehaviour
{
void Start()
{
B01_Player myPlayer = new B01_Player();
//属性可以像变量一样使用
myPlayer.Experience = 5;
int x = myPlayer.Experience;
}
}
02.三元运算符
三元运算符用于根据布尔表达式,在两个值直接做出选择
int health = 10;
string message;
message = health > 0 ? "Player is Alive" : "Player is Dead";
三元运算符可相互嵌套,但会导致语句繁琐,难以理解,
message = health > 0 ? "Player is Alive" : health == 0 ? "Player is Barely Alive" : "Player is Dead";
使用三元表达式而不是if语句的情况:
- 代码需要简单的if-else结构
- 且每种情况只需要一个短表达式
03.Statics 静态成员
静态成员如变量和方法,是跨类的所有实例共享的成员。此外,静态成员可以直接通过类访问,无需先对类的对象进行实例化。
- 通常,成员变量对于类的每个对象是唯一的,即使类的没搞对象具有相同的变量,但它们都有自己的值
- 但对于静态成员,类的每个对象具有相同的变量和相同的值,如果在一处更改,其他也同时更改
public class B03_Enemy : MonoBehaviour
{
//静态变量是在类的所有实例之间共享的变量。
public static int enemyCount = 0;
public void Enemy()
{
//通过递增静态变量了解,已创建此类的多少个对象。
enemyCount++;
}
}
在另一个脚本生成几个敌人
public class B03_Game : MonoBehaviour
{
void Start()
{
B03_Enemy enemy1 = new B03_Enemy();
B03_Enemy enemy2 = new B03_Enemy();
B03_Enemy enemy3 = new B03_Enemy();
//可以使用类名和点运算符,来访问静态变量。
int x = B03_Enemy.enemyCount;
}
}
只要脚本挂在在对象上,玩家数量就会增加
public class B03_Player : MonoBehaviour
{
public static int plauerCount=0;
void Start()
{
plauerCount++;
}
}
可以在其他脚本访问静态变量来查看玩家数量
public class B03_PlayerManager : MonoBehaviour
{
void Start()
{
int x = B03_Player.plauerCount;
}
}
与静态变量一样,静态方法属于类,而不属于类的特定对象
public class B03_Utilities : MonoBehaviour
{
public static int Add(int num1,int num2){
return num1 + num2;
}
}
在另一个方法中就可以用类的名称和点来调用Add方法,无需通过实例化类的对象来使用其静态成员
public class B03_UtiltiesExample : MonoBehaviour
{
void Start()
{
int x = B03_Utilities.Add(6,7);
}
}
不能在静态方法内部,使用非静态类成员变量,静态变量属于类,而非静态变量属于类的实例
也可将整个类变成静态类,只需要在calss前加入static,类变成静态但不能创建类的实例
public static class B03_UtiltiesExample : MonoBehaviour
{
void Start()
{
int x = B03_Utilities.Add(6,7);
}
}
04.Method Overloading 方法重载
通过重载过程可以为单个方法提供多个定义,意味着可以使用同一方法名称执行两项不同的操作
重载方法的操作是为新方法指定名称相同,但形参不同
public class B04_SomeClass : MonoBehaviour
{
public int Add(int num1, int num2)
{
return num1 + num2;
}
public string Add(string str1, string str2)
{
return str1 + str2;
}
}
在其他类中,当我们尝试访问Add方法时,我们可以看到有两个版本,可能出现三种情况
- 与传入参数完全匹配,运行这个版本的已重载方法
- 如果不匹配,根据匹配项,选择需要最少转换量的方法
- 如果没有可能的匹配项,或者多版本所需的转换量相同,则抛出错误
05.Generics 泛型
泛型是一种特征,通过该特征类型可以作为参数,传递给类和方法等,实际上这允许你在不了解所处理数据的确切情况下进行一般编程
public class B05_SomeClass : MonoBehaviour
{
//这是一个通用方法。注意通用类型“T”。该“T”将在运行时替换为实际类型。
public T GenericMethod<T>(T param)
{
return param;
}
}
如果要添加多个泛型参数,可以继续添加,明明惯例常常遵循T
public T GenericMethod<T,U,V>(T param)
T只是个占位符,运行时会替换为实际类型,无论T最终成为什么类型,这个类型也将成为方法的返回类型和参数类型。因为它们都是用T作为其类型
对泛型参数施加限制,冒号后为具体的限制,通常有四种:class, struct, new(), MonoBehaviour, Ienumerable
- class 确保T是引用类型
- struct 确保它是值类型
- new() 确保它具备不含参数的公共构造函数
- 类名 就代表这个类或通过多态表示T代表从中衍生的任意类
- 接口名 表示T已实现这个接口
public T GenericMethod<T>(T param) where T : class
为了使用泛型,必须知道希望它使用的具体类型,假如想使用刚刚创建的泛型方法
public class B05_SomeOtherClass : MonoBehaviour
{
void Start()
{
B05_SomeClass myClass = new B05_SomeClass();
//为了使用此方法,必须告诉此方法用什么类型替换“T”。
myClass.GenericMethod<int>(5);
}
}
这个类使用泛型类型T,意味着在使用时,在类中作用类型的类型T的每个实例将替换实际类型,这个类又一个成员变量名为item类型为T,还有一个名为UpdateItem的方法,这个方法有一个类型为T的参数,并将其分配给成员item
public class B05_GenericClass<T> : MonoBehaviour
{
T item;
public void UpdateItem(T newItem)
{
item = newItem;
}
}
为了实例化这个类的对象,必须为为T指定一个类型,方法是输入类的名称加尖括号和所需类型,在输入构造函数的名称之后,并在构造函数的参数列表之前也必须执行这个操作
public class B05_GenericClassExample : MonoBehaviour
{
void Start(){
B05_GenericClass<int> myclass = new B05_GenericClass<int>();
myclass.UpdateItem(5);
}
}
泛型的一种常用方法是用于字典和列表等集合
06.Inheritance 继承
一个类继承自另一个类时,它会获得被继承类的特征,父类及子类。继承结果是父类中存在的项 ,也将出现在子类中。
假设父类中有两个方法,子类中也同时有这两个方法,无需在子类中创建。处理继承时需要注意三个访问修饰符:public、private、protected
- public公开的类特征将存在于子类中,并且可供访问
- private私有的类存在于子类中,但不可访问
- protected相当于两者混合:与公开的特征一样,受保护的父类的所有特征将在子类中且可供访问;但在父类或子类之外将不可访问,就像私有的特征一样
public class 类名 : 继承的父类名{}
在子类继承的项中,构造函数是一个例外,因为他们对类是唯一的不会共享。但是在子类中调用构造函数时,其父类的构造函数会立刻被调用。
由于类可能有多个不同的构造函数,因此我们可能想控制调用那个基类的构造函数,为此可以使用关键字base,通过在子类构造函数的参数列表后面加一个冒号,可以使用关键字base在构造函数的参数列表中,显式调用基类的具体构造函数。
public class Apple : Furit{
public Apple() :base("apple"){
//Constructor Code...
}
}
如果不显示调用基类的构造函数,则会隐式调用默认构造函数
除了调用基类构造函数,base关键字还可用来访问基类的其他成员
Fruit 类:
using UnityEngine;
using System.Collections;
//这是基类,也称为父类。
public class Fruit
{
public string color;
//这是 Fruit 类的第一个构造函数,不会被任何派生类继承。
public Fruit()
{
color = "orange";
Debug.Log("1st Fruit Constructor Called");
}
//这是 Fruit 类的第二个构造函数,不会被任何派生类继承。
public Fruit(string newColor)
{
color = newColor;
Debug.Log("2nd Fruit Constructor Called");
}
public void Chop()
{
Debug.Log("The " + color + " fruit has been chopped.");
}
public void SayHello()
{
Debug.Log("Hello, I am a fruit.");
}
}
Apple 类:
using UnityEngine;
using System.Collections;
//这是派生类,也称为子类。
public class Apple : Fruit
{
//这是 Apple 类的第一个构造函数。它立即调用父构造函数,甚至在它运行之前调用。
public Apple()
{
//注意 Apple 如何访问公共变量 color,该变量是父 Fruit 类的一部分。
color = "red";
Debug.Log("1st Apple Constructor Called");
}
//这是 Apple 类的第二个构造函数。它使用“base”关键字指定要调用哪个父构造函数。
public Apple(string newColor) : base(newColor)
{
//请注意,该构造函数不会设置 color,因为基构造函数会设置作为参数传递的 color。
Debug.Log("2nd Apple Constructor Called");
}
}
FruitSalad 类:
using UnityEngine;
using System.Collections;
public class FruitSalad : MonoBehaviour
{
void Start ()
{
//让我们用默认构造函数来说明继承。
Debug.Log("Creating the fruit");
Fruit myFruit = new Fruit();
Debug.Log("Creating the apple");
Apple myApple = new Apple();
//调用 Fruit 类的方法。
myFruit.SayHello();
myFruit.Chop();
//调用 Apple 类的方法。注意 Apple 类如何访问Fruit 类的所有公共方法。
myApple.SayHello();
myApple.Chop();
//现在,让我们用读取字符串的构造函数来说明继承。
Debug.Log("Creating the fruit");
myFruit = new Fruit("yellow");
Debug.Log("Creating the apple");
myApple = new Apple("green");
//调用 Fruit 类的方法。
myFruit.SayHello();
myFruit.Chop();
//调用 Apple 类的方法。注意 Apple 类如何访问Fruit 类的所有公共方法。
myApple.SayHello();
myApple.Chop();
}
}
07.Polymorphism多态
多态是继承的一个特征,它允许一个类具有多种类型。
在继承层次结构中,任何子类都可以作为父类使用。这意味着在需要基类的地方,可以使用派生类来代替它。
在调用 onTrigger 函数时,我们不知道会使用什么类型的 Collider。每个对象的特定 Collider 都会传入函数,由于它们都继承自 Collider 父类,因此它们都可以被正确处理。
public class SomeScript : MonoBehaviour{
void onTriggerEnter(Collider other){
//Do Something Fun!
}
}
多态的一种明智用法涉及构造函数和对象引用。你可以声明基类类型的对象,然后调用其中一个派生类的构造函数,因为变量需要的是基类的类型。子类的构造函数会创建衍生类型的对象。由于子类是父类的一种,因此这种转换是有效的,这个过程被称为向上转型。当对象向上转型时,它只能被视为其父类的一个对象。
在本实例中,子类向上转型时,它只能被视作父类,这表示只能使用父类中可用的变量和方法,在使用时会把他们视作位于父类对象中。
虚拟函数是一个例外,虚拟函数将调用最新覆盖版本
public class SomeScript : MonoBehaviour{
void Start(){
ParentsClass myClass =new ChildClass();
myClass.ParentMethod();
}
}
为了将这个子类视作子类,我们需要向下转型子类变量,使其恢复为子类类型,具体方法是将类型名称括在括号内并将其置于变量前面。我们可以再用一组括号括起来,并使用点运算符来访问成员,也可以创建对这个新版本的引用
public class SomeScript : MonoBehaviour{
void Start(){
ParentsClass myClass =new ChildClass();
myClass.ParentMethod();
ChildClass myClass=(ChildClass)myClass;
myChild.ChildMethod();
}
}
Fruit 类
using UnityEngine;
using System.Collections;
public class Fruit
{
public Fruit()
{
Debug.Log("1st Fruit Constructor Called");
}
public void Chop()
{
Debug.Log("The fruit has been chopped.");
}
public void SayHello()
{
Debug.Log("Hello, I am a fruit.");
}
}
Apple 类
using UnityEngine;
using System.Collections;
public class Apple : Fruit
{
public Apple()
{
Debug.Log("1st Apple Constructor Called");
}
//Apple 有自己的 Chop() 和 SayHello() 版本。
//运行脚本时,请注意何时调用Fruit 版本的这些方法以及何时调用Apple 版本的这些方法。
//此示例使用“new”关键字禁止来自 Unity 的警告,同时不覆盖Apple 类中的方法。
public new void Chop()
{
Debug.Log("The apple has been chopped.");
}
public new void SayHello()
{
Debug.Log("Hello, I am an apple.");
}
}
FruitSalad 类
using UnityEngine;
using System.Collections;
public class FruitSalad : MonoBehaviour
{
void Start ()
{
//请注意,这里的变量“myFruit”的类型是Fruit,但是被分配了对 Apple 的引用。这是由于多态而起作用的。由于 Apple 是 Fruit,因此这样是可行的。虽然 Apple 引用存储在 Fruit 变量中,但只能像 Fruit 一样使用
Fruit myFruit = new Apple();
myFruit.SayHello();
myFruit.Chop();
//这称为向下转换。Fruit 类型的变量“myFruit”实际上包含对 Apple 的引用。因此,可以安全地将它转换回 Apple 变量。这使得它可以像 Apple 一样使用,而在以前只能像 Fruit
一样使用。
Apple myApple = (Apple)myFruit;
myApple.SayHello();
myApple.Chop();
}
}
08.Member Hiding 成员隐藏
在子类中重新创建,即重新声类成员的过程被称为成员隐藏
为了隐藏基类的成员,应在成员的类型前面使用new声明子类成员。
如果我们对这个集合中的所有对象,则它们都将调用Humanoid版本的Yell。这是因为我们将orc和Enemy,对像声明为Humanoid,并且们已隐式向外转型为Humanoid
Humanoid 类
using UnityEngine;
using System.Collections;
public class Humanoid
{
//Yell 方法的基版本
public void Yell()
{
Debug.Log ("Humanoid version of the Yell() method");
}
}
Enemy 类
using UnityEngine;
using System.Collections;
public class Enemy : Humanoid
{
//这会隐藏 Humanoid 版本。
new public void Yell()
{
Debug.Log ("Enemy version of the Yell() method");
}
}
Orc 类
using UnityEngine;
using System.Collections;
public class Orc : Enemy
{
//这会隐藏 Enemy 版本。
new public void Yell()
{
Debug.Log ("Orc version of the Yell() method");
}
}
WarBand 类
using UnityEngine;
using System.Collections;
public class WarBand : MonoBehaviour
{
void Start ()
{
Humanoid human = new Humanoid();
Humanoid enemy = new Enemy();
Humanoid orc = new Orc();
//注意每个 Humanoid 变量如何包含
//对继承层级视图中
//不同类的引用,但每个变量都
//调用 Humanoid Yell() 方法。
human.Yell();
enemy.Yell();
orc.Yell();
}
}
09.Overriding 覆盖
覆盖是指更改子类中的父类方法,结果是当我们调用方法时将调用最新版本的方法或最新覆盖的版本
使用virtual和override关键字,这些关键字位于方法的返回类型之前,父类中的方法定义为virtual,而所有子类中的方法定义为override。声明为virtual的任何方法可被任何子类覆盖,
可以使用base关键字来同时调用方法的父版本
Fruit 类
using UnityEngine;
using System.Collections;
public class Fruit
{
public Fruit ()
{
Debug.Log("1st Fruit Constructor Called");
}
//这些方法是虚方法,因此可以在子类中
//将它们覆盖
public virtual void Chop ()
{
Debug.Log("The fruit has been chopped.");
}
public virtual void SayHello ()
{
Debug.Log("Hello, I am a fruit.");
}
}
Apple 类
using UnityEngine;
using System.Collections;
public class Apple : Fruit
{
public Apple ()
{
Debug.Log("1st Apple Constructor Called");
}
//这些方法是覆盖方法,因此
//可以覆盖父类中的任何
//虚方法。
public override void Chop ()
{
base.Chop();
Debug.Log("The apple has been chopped.");
}
public override void SayHello ()
{
base.SayHello();
Debug.Log("Hello, I am an apple.");
}
}
FruitSalad 类
using UnityEngine;
using System.Collections;
public class FruitSalad : MonoBehaviour
{
void Start ()
{
Apple myApple = new Apple();
//请注意,Apple 版本的方法
//将覆盖 Fruit 版本。另外请注意,
//由于 Apple 版本使用“base”关键字
//来调用 Fruit 版本,因此两者都被调用。
myApple.SayHello();
myApple.Chop();
//“覆盖”在多态情况下也很有用。
//由于 Fruit 类的方法是“虚”的,
//而 Apple 类的方法是“覆盖”的,因此
//当我们将 Apple 向上转换为 Fruit 时,
//将使用 Apple 版本的方法。
Fruit myFruit = new Apple();
myFruit.SayHello();
myFruit.Chop();
}
}
10.Interfaces 接口
实现接口的任何类,必须拥有其所有方法和属性。通过使用多态,其他类可将实现类视作接口。需要注意的是接口不是类,不能有首己的实例
- 继承是is a关系,即一个类继承自另一个类
- 而接口使用实现关系,即一个类实现一个接口
接口通常在类外部声明,声明接口时通常对每个接口使用一个脚本。由于接口通常描述实现类将具备的某种功能,因止此许多接口以后缀able结尾,不过这是非强制的
public interface IKillable
{
void Kill();
}
//这是一个通用接口,其中 T 是将由实现类提供的数据类型的占位符。
public interface IDamageable<T>
{
void Damage(T damageTaken);
}
要实现接口只需在类具有的任何继承之后添加一个逗号,后跟接口的名称;如果类不是从其他类继承而来,则不需要逗号;如果接口具有型类型,则名称应后跟尖括号在里面输入类型
Avatar 类
using UnityEngine;
using System.Collections;
public class Avatar : MonoBehaviour, IKillable, IDamageable<float>
{
//IKillable 接口的必需方法
public void Kill()
{
//执行一些有趣操作
}
//IDamageable 接口的必需方法
public void Damage(float damageTaken)
{
//执行一些有趣操作
}
}
因为实现接口,可以使两个完全不相于的类具有同一个功能,或者同一件事情,这样两个类有了共同点,一因为这样使得程序结构设计更加有逻辑,看到接口就知道功能大致的效果了
11.Extension Methods 拓展方法
通过护展方法可以尚类型添加功能而不必创建DriveType或更改原始类型,适用于向类添加功能但不能编辑类的情况。简单来说就是为一个修改不了的类添加更多自定义的静态方法
扩展方法必须放在泛型静态类中,常见做法是专门创建一个类来包含它们。要使函数成为拓展方法而非静态方法,需要在参数中使用this关键字,this关键字后跟Transform类和任意参数名称,
第一个参数将是调用对象,因此当我们调用这个数时无需提供这个参数,此外该第一个参数规定了,这个方法属于哪个类,如果我们想要更多参数,可以在此输入而不使用this关键字
现在我们可以编写代码来重置Transform
ExtensionMethods
using UnityEngine;
using System.Collections;
//创建一个包含所有扩展方法的类是很常见的做法。此类必须是静态类。
public static class ExtensionMethods
{
//扩展方法即使像普通方法一样使用,也必须声明为静态。请注意,第一个参数具有“this”关键字,后跟一个 Transform变量。此变量表示扩展方法会成为哪个类的一部分。
public static void ResetTransformation(this Transform trans)
{
trans.position = Vector3.zero;
trans.localRotation = Quaternion.identity;
trans.localScale = new Vector3(1, 1, 1);
}
}
SomeClass
using UnityEngine;
using System.Collections;
public class SomeClass : MonoBehaviour
{
void Start () {
//请注意,即使方法声明中有一个参数,也不会将任何参数传递给此扩展方法。调用此方法的Transform 对象会自动作为第一个参数传入。
transform.ResetTransformation();
}
}
12.Namespaces 命名空间
命名空间就像是类的容器,其目的是帮助组织脚本,避兔脚本之间发生冲突,脚本的前两行 using UnityEngine; using System.Collections; 都是命名空间
using UnityEngine;
using System.Collections;
namespace SampleNamespace
{
public class SomeClass : MonoBehaviour
{
void Start ()
{
}
}
}
通过三种方法使用来自特定命名空间的类:
- 脚本顶部使用
usingusing SampleNamespace - 使用点运算符:每次要引l用来自SampleNamespace的类时,都可以输入SampleNamespace点类 SampleNamespace Someclass myclass=new SampleNamespace.Someclass():
- 最后一种选择是将你编写的类,放入需要访问的命名空间中
只要类位于不同的命名空间中,它们就可以使用相同名称
命名空间可嵌套,要嵌套命名空间,只需将一个命名空间声明括在另一个命名空间声明内即可
13.Lists and Dictionaries 列表和字典
列表就像是大小动态变化的数组。创建列表需要添加System.Collections.Generic名称空间
using System.Collections.Generic;
首先comparable位于系统名称空间,所以需要声明我们正在使用它,接着需要声明这个类正在实现这个接口,这看起来类以于继承,我们将使用泛型lcomparable其泛型类型必须是这个类,最后为了完成comparable接口的协定我们需要声公开承数compareTo,这个承数返回整数并以我们的泛型类型BadGuy作为参数。CompareTo方法的思路是:
- 如果从中调用这个方法的对象大于被视作参数的对象,则函数返回正数
- 如果从中调用这个方法的对象小于被视作参数的对象,则函数返回负数
- 如果二者等,则返回零
对于我们的承数我们先要检查传递函数的BadGuy是否存在,如果不存在则这个BadGuy较大,函数应返回数,否则函数返回两个BadGuy的幂值差
using UnityEngine;
using System.Collections;
using System; //这允许 IComparable 接口
//这是您将存储在不同集合中的类。为了使用集合的 Sort() 方法,此类需要实现 IComparable 接口。
public class BadGuy : IComparable<BadGuy>
{
public string name;
public int power;
public BadGuy(string newName, int newPower)
{
name = newName;
power = newPower;
}
//IComparable 接口需要此方法。
public int CompareTo(BadGuy other)
{
if(other == null)
{
return 1;
}
//返回力量差异。
return power - other.power;
}
}
SomeClass
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SomeClass : MonoBehaviour
{
void Start ()
{
//这是创建列表的方式。注意如何在
//尖括号 (< >) 中指定类型。
List<BadGuy> badguys = new List<BadGuy>();
//这里将 3 个 BadGuy 添加到列表
badguys.Add( new BadGuy("Harvey", 50));
badguys.Add( new BadGuy("Magneto", 100));
badguys.Add( new BadGuy("Pip", 5));
badguys.Sort();
foreach(BadGuy guy in badguys)
{
print (guy.name + " " + guy.power);
}
//这会清除列表,使其
//为空。
badguys.Clear();
}
}
字典的工作原理与列表类似,但它有两种类型,这表示每个元素组成一个键值对有时简称为KVP,字典的预期用途也与列表不同,列表通常用于替代需要更多灵活性或功能的数组。字典用作可通过一个或多个键访问的值的集合
首先添加System.Collections.Generic名称空间,然后像之前一样声明变量,但具有两个泛型类型,第一个类型是键,这是为了访问第二个类型,而引用的类型第二个类型是值,
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SomeOtherClass : MonoBehaviour
{
void Start ()
{
//这是创建字典的方式。注意这是如何采用
//两个通用术语的。在此情况中,您将使用字符串和
//BadGuy 作为两个值。
Dictionary<string, BadGuy> badguys = new Dictionary<string, BadGuy>();
BadGuy bg1 = new BadGuy("Harvey", 50);
BadGuy bg2 = new BadGuy("Magneto", 100);
//可以使用 Add() 方法将变量
//放入字典中。
badguys.Add("gangster", bg1);
badguys.Add("mutant", bg2);
BadGuy magneto = badguys["mutant"];
BadGuy temp = null;
//这是一种访问字典中值的更安全
//但缓慢的方法。
if(badguys.TryGetValue("birds", out temp))
{
//成功!
}
else
{
//失败!
}
}
}
14.Coroutines 协程
协同程序可被视为按时间间隔执行的函数
Coroutine(协程)是一种轻量级的线程管理方式,它允许你编写异步代码,而不必使用多线程或多线程相关的复杂机制。协程主要用于处理那些需要在一定时间间隔后执行的任务,或者需要等待某些事件发生才能继续执行的任务。
使用Unity的协程功能来使某个游戏对象平滑地移动到目标位置
using UnityEngine;
using System.Collections;
public class CoroutinesExample : MonoBehaviour
{
public float smoothing = 1f;
public Transform target;
void Start ()
{
//启动了一个协程,名为MyCoroutine,传入target作为参数。
StartCoroutine(MyCoroutine(target));
}
//返回IEnumerator类型的协程方法
IEnumerator MyCoroutine (Transform target)
{
//使用Vector3.Distance方法计算当前游戏对象和目标之间的距离,当距离大于0.05时,循环继续。
while(Vector3.Distance(transform.position, target.position) > 0.05f)
{
//使用Vector3.Lerp方法使当前游戏对象的位置平滑地向目标位置移动
transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);
//等待下一个帧,这是协程中常用的等待方法,以便每次循环结束后都能重新开始下一次循环。
yield return null;
}
print("Reached the target.");
yield return new WaitForSeconds(3f);
print("MyCoroutine is now finished.");
}
}
PropertiesAndCoroutines
using UnityEngine;
using System.Collections;
public class PropertiesAndCoroutines : MonoBehaviour
{
public float smoothing = 7f;
// 目标位置的属性,使用自动实现的属性来简化访问
public Vector3 Target
{
get { return target; }
set
{
// 更新目标位置,并重启移动协程
target = value;
StopCoroutine("Movement");
StartCoroutine("Movement", target);
}
}
// 实际的目标位置变量
private Vector3 target;
// 移动协程
IEnumerator Movement(Vector3 target)
{
// Distance方法计算当前游戏对象和目标之间的距离
while (Vector3.Distance(transform.position, target) > 0.05f)
{
// 使用Lerp函数实现平滑移动
transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
yield return null; // 等待下一帧
}
}
}
ClickSetPosition
using UnityEngine;
using System.Collections;
/// ClickSetPosition 类负责在鼠标点击时,设置一个对象的位置。
/// 它依赖于另一个脚本中的协程来实现特定的行为。
public class ClickSetPosition : MonoBehaviour
{
/// coroutineScript 是一个公开的属性,它引用了另一个脚本实例,
/// 该脚本中含有必要的协程,用于处理位置的变化。
public PropertiesAndCoroutines coroutineScript;
void OnMouseDown()
{
// 创建一个从摄像机到鼠标位置的射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 用于存储射线投射的碰撞信息
RaycastHit hit;
// 使用射线投射来检测是否有碰撞
Physics.Raycast(ray, out hit);
// 检查碰撞的物体是否是当前游戏对象
if (hit.collider.gameObject == gameObject)
{
// 计算新的目标位置,稍微高于点击的位置
Vector3 newTarget = hit.point + new Vector3(0, 0.5f, 0);
// 将新的目标位置设置到协程脚本的Target属性中
coroutineScript.Target = newTarget;
}
}
}
15.Quaternions 四元数
选择存储为四元数,类似于向量但有四个组件:X、Y、Z、W,
Unity将旋转存储为四元数,但像是Inspector要将四元数转换为欧拉角,欧拉角要遵从万向节锁,而万向节锁会妨碍增量旋转正常工作,四元数不受万向节锁的影响。
LookAtScript:
使对象始终面向另一个对象。
using UnityEngine;
using System.Collections;
public class LookAtScript : MonoBehaviour
{
public Transform target;
void Update ()
{
//计算目标Transform的位置与当前游戏对象Transform的位置之间的相对位置,
Vector3 relativePos = target.position - transform.position;
//然后使用Quaternion.LookRotation()函数将该相对位置转换为朝向目标的旋转。将当前游戏对象的Transform的旋转设置为该值,从而使游戏对象始终面向目标。
transform.rotation = Quaternion.LookRotation(relativePos);
}
}
GravityScript:
using UnityEngine;
using System.Collections;
public class GravityScript : MonoBehaviour
{
public Transform target;
void Update ()
{
//计算目标对象与当前游戏对象的相对位置,将目标对象的位置向上偏移1.5单位。
Vector3 relativePos = (target.position + new Vector3(0, 1.5f, 0)) - transform.position;
//根据相对位置,计算出当前游戏对象应该朝向的旋转四元数。
Quaternion rotation = Quaternion.LookRotation(relativePos);
//获取当前游戏对象的局部旋转四元数。
Quaternion current = transform.localRotation;
//使用四元数插值方法"Slerp",在一定时间内平滑地从当前旋转向目标旋转过渡,以达到朝向目标的效果。
transform.localRotation = Quaternion.Slerp(current, rotation, Time.deltaTime);
//根据时间增量,沿着当前游戏对象的局部Z轴正方向移动一段距离,实现重力下落的效果。
transform.Translate(0, 0, 3 * Time.deltaTime);
}
}
16.Delegates 委托
在Unity中,Delegate(委托)是一种引用方法的类型。它们可以让你在运行时动态地选择要执行的方法,这对于事件处理、回调机制等非常有用。委托可以被简单视为函数的容器,可以进行传达或像变量一样使用,区别在于变量包含数据而委托包含函数。
- 动态绑定:可以在运行时动态地改变委托指向的方法。
- 回调机制:可以用来创建回调函数,例如响应用户输入或其他系统事件。
- 事件处理:Unity中的事件系统大量使用了委托,允许不同的脚本响应相同的事件。
- 解耦:可以让代码更加模块化,减少代码间的耦合度。
在Unity中,委托通常与事件一起使用,以便多个方法可以订阅同一个事件,当事件触发时,所有订阅的方法都会被调用。这种方式在处理游戏逻辑、UI交互等方面非常有用。
DelegateScript
using UnityEngine;
using System.Collections;
public class DelegateScript : MonoBehaviour
{
// 定义一个委托类型MyDelegate,该委托类型的方法有一个整数参数
delegate void MyDelegate(int num);
// 声明一个MyDelegate类型的实例变量
MyDelegate myDelegate;
void Start(){
// 将PrintNum方法赋给myDelegate实例,调用myDelegate实例,传入参数50
myDelegate = PrintNum;
myDelegate(50);
// 将DoubleNum方法赋给myDelegate实例调,用myDelegate实例,传入参数50
myDelegate = DoubleNum;
myDelegate(50);
}
void PrintNum(int num){
print("Print Num: " + num);
}
void DoubleNum(int num){
print("Double Num: " + num * 2);
}
}
MulticastScript
using UnityEngine;
using System.Collections;
public class MulticastScript : MonoBehaviour
{
// 定义一个委托类型MultiDelegate,用于引用无参数的void方法
delegate void MultiDelegate();
// 声明一个MultiDelegate类型的实例变量
MultiDelegate myMultiDelegate;
void Start(){
// 将PowerUp和TurnRed方法添加到myMultiDelegate的调用列表中
myMultiDelegate += PowerUp;
myMultiDelegate += TurnRed;
// 检查myMultiDelegate是否不为空
if (myMultiDelegate != null)
{
// 调用myMultiDelegate所引用的所有方法
myMultiDelegate();
}
}
void PowerUp(){
print("Orb is powering up!");
}
void TurnRed(){
// 修改渲染器的材质颜色为红色
renderer.material.color = Color.red;
}
}
17.Attributes 属性
使用属性可以将其他行为附加到所创建的方法和变量。
using UnityEngine;
using System.Collections;
public class SpinScript : MonoBehaviour
{
[Range(-100, 100)] public int speed = 0;
void Update ()
{
transform.Rotate(new Vector3(0, speed * Time.deltaTime, 0));
}
}
ColorScript
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ColorScript : MonoBehaviour
{
void Start()
{
renderer.sharedMaterial.color = Color.red;
}
}
18.Events 事件
在Unity中,事件(Event)主要用于实现不同游戏对象或组件之间的通信。事件允许一个对象向其他对象发送消息,这样就可以解耦各个部分的代码,使得它们不需要直接相互依赖就能进行交互。
在Unity中,事件通常与委托一起使用。委托定义了一个方法签名,而事件则是公开的委托字段,其他类可以通过+=操作符向事件添加方法,通过-=操作符移除方法。
例如,在Unity UI中,Button 组件有一个 onClick 事件,这是一个多播委托,你可以添加多个方法作为事件处理器。当按钮被点击时,所有注册的方法都会被调用。需要注意的是,Unity还提供了一些内置的事件系统,如 EventSystem 和 Physics 中的碰撞事件 (Collision, Trigger),这些都可以用来处理特定的游戏逻辑。
EventManager
using UnityEngine;
using System.Collections;
public class EventManager : MonoBehaviour
{
// 定义一个委托类型,用于点击事件的监听器
public delegate void ClickAction();
// 定义一个静态事件,当点击事件被触发时,会调用所有注册了此事件的监听器
public static event ClickAction OnClicked;
// 在屏幕上绘制GUI,并监听点击事件
void OnGUI()
{
// 如果用户点击了屏幕上的按钮
if (GUI.Button(new Rect(Screen.width / 2 - 50, 5, 100, 30), "Click"))
{
// 检查是否有任何监听器注册了OnClicked事件,如果有,则调用该事件
if (OnClicked != null)
OnClicked();
}
}
}
TeleportScript
using UnityEngine;
using System.Collections;
public class TeleportScript : MonoBehaviour
{
// 当脚本启用时,订阅OnClicked事件
void OnEnable()
{
EventManager.OnClicked += Teleport;
}
// 当脚本禁用时,取消订阅OnClicked事件
void OnDisable()
{
EventManager.OnClicked -= Teleport;
}
// 当点击事件触发时,执行Teleport方法
void Teleport()
{
// 获取当前物体的位置
Vector3 pos = transform.position;
// 随机生成一个1到3之间的数,并将其赋值给y坐标
pos.y = Random.Range(1.0f, 3.0f);
// 将新的位置赋值给物体的位置
transform.position = pos;
}
}
TurnColorScript
using UnityEngine;
using System.Collections;
public class TurnColorScript : MonoBehaviour
{
void OnEnable()
{
// 当此脚本启用时,将TurnColor方法注册为事件响应函数。当特定事件触发时,会调用TurnColor方法。
EventManager.OnClicked += TurnColor;
}
void OnDisable()
{
// 当此脚本禁用时,从事件监听器中移除TurnColor方法。这是为了避免在脚本禁用后仍然响应事件,从而引发错误。
EventManager.OnClicked -= TurnColor;
}
void TurnColor()
{
// 通过生成一个随机的Color值,并将其应用到对象的渲染材质上,以实现颜色的改变。
Color col = new Color(Random.value, Random.value, Random.value);
renderer.material.color = col;
}
}







Comments | NOTHING