欢迎光临散文网 会员登陆 & 注册

【从零开始的Unity魔法学堂】委托

2023-07-29 22:54 作者:百尘而已  | 我要投稿

# Unity魔法学堂

## 准备

### 准备工作

+ Unity Hub

+ Unity Editer

+ IDE(集成开发环境)

  + VScode

  + VS



### 基本架构

+ 场景(Scene)

+ 游戏对象(GameObject)

+ 组件(Component)



### Unity界面

+ 场景窗口

  + 快捷键QWERTY切换工具

  + WASDQE自由移动

+ Hierarchy(层级窗口)

  + 查看所有游戏对象

+ Inspector(检视面板)

  + 查看更改选中物体的信息

  + AddComponent可挂载Unity预制组件或自己写的脚本

  + 直接将脚本拖拽也可挂载

+ Game游戏窗口

  + 查看游戏实际运行画面

  + 游戏运行中性能参数也可在Game窗口监视

+ Project项目窗口

  + Unity项目工程文件夹

+ Console控制台窗口

  + 双击信息可快速跳转到代码对应位置


### 基本工作流程

+ 建立工程,导入资源

+ 搭建场景并配置游戏对象

+ 编写代码,控制台查错

+ 配置项目信息,打包导出


### Hello Word!

+ Unity支持语言-有且仅有C#

+ 配置编辑器

  + 顶部Edit-Preference

+ Preference界面-偏好设置页面

+ 生命周期函数

  + Start()

    + 开始前调用

  + Update()

    + 持续刷新

***

代码

 ```C#

print("Hello Word!");   //发送到控制台窗口,但不会在游戏中体现

 ```

## C#初级

### 变量与常量

#### 常见类型

|类型|描述|范围|默认值|

|:----:|:----:|:----:|:----:|

|bool |布尔值| True 或 False  |False|

|byte |8位无符号整数  |0到255  |0|

|char |16位Unicode字符|U+0000 到 U+ffff|'\0'|

|decimal| 128 位精确的十进制值,28-29 有效位数 |$\frac{-7.9\times 10^{28} - 7.9\times 10^{28}}{10^{0-28}}$|0.0M|

|double|  64 位双精度浮点型  |$(+/-)5.0 \times 10^{-324} - (+/-)1.7 \times 10^{308}$|  0.0D|

|float| 32 位单精度浮点型  |-3.4 x 1038 到 + 3.4 x 1038 |0.0F|

|int  |32 位有符号整数类型  |-2,147,483,648 到 2,147,483,647 |0|

|long |64 位有符号整数类型  |-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |0L|

|sbyte  |8 位有符号整数类型 |-128 到 127 |0|

|short  |16 位有符号整数类型  |-32,768 到 32,767 |0|

|uint|  32 位无符号整数类型 |0 到 4,294,967,295  |0|

|ulong  |64 位无符号整数类型  |0 到 18,446,744,073,709,551,615 |0|

|ushort |16 位无符号整数类型  |0 到 65,535|  0|


+ 整数

  + sbyte

  + short

  + int

  + long

  + byte

  + ushort

  + uint

  + ulong

+ 小数

  + float

  + double

+ 文字

  + char

+ 逻辑

  + bool


#### 变量命名

#### 变量声明

如果要写float类型数字,需在后面加f

   

    float a=0.1f;


### 二进制与类型

+ 机器码

+ Unicode

+ 不能向bool直接赋值0或1,必须使用true和false

+ 类型转换



### 运算符

 + 算术运算符

 + 关系运算符

 + 逻辑运算符

 + 赋值运算符

 + 位运算符

   + 每一位进行运算

   + &

   + |

   + ^

     + 异或

   + ~

     + 取反

   + <<

     + 左移

   + \>>

     + 右移



### 控制流

+ 执行语句的顺序

+ 顺序

+ 分支

+ 循环


### 方法

+ 函数

+ 重载


### 数组

#### 多维数组和交错数组

```C#

//规则数组(类似矩阵,每一行列数相同)

//一维数组

char[] allMagics = new char[9]={...};

char[] allMagics = new char[9];

//二维数组

int[,] squareCrystal = new int[2,3];

//三维数组

int[,,] cubeCrystal = new int[2,3,4];


//交错数组(每一行的列数不同)

float[][] crossCrystal = new float[3][];  //定义时可只填入第一个维度


crossCrystal[0] = new float[4];

crossCrystal[1] = new float[6];

crossCrystal[2] = new float[8];

```

#### 字符串

```C#

string name = "SB";

int age = 12;

//字符串的拼接

print(name+age+"岁")    //int自动转化为字符串

```

**字符串支持空双引号"",但char不支持空单引号''**


### 枚举

+ enum枚举是一种特殊的基本类型

+ 可使用枚举来创建枚举类型

+ 因为限定了可包含的值,枚举类型可以防止无效输入

+ 枚举的成员直接使用英文名称来定义

+ 成员本质上也是数字

  + 隐含的值从0开始

+ 枚举默认值为第一个名字

```C#

修饰词(public、protected)enum 枚举类型名:整数类型

{

    成员1,

    成员2,

    ...

}



enum Gender

{

  Male;

  Famale;

  Other;

}

Gender gender=Gender.Female;

```


### 结构体

struct

+ 结构体中不仅可以放变量,还可以放函数

```C#

struct Apprentice

{

  public string name;

  public Gender gender;

  public int age;

  public string[] allMagic;

}


Apprentice newbody;

```


+ 权限修饰符

  + public

    + 公开

  + private(默认)

    + 仅在当前结构中可访问

  + protected

    + 受保护的


### 总结


## C#高级

### 封装

#### 类

+ 面向对象的思想

```C#

class Kami

{

public string name;


//构造函数可重载

public Kami(string name)

{

  this.name = name;   //为实例赋值

  print("神明"+name+"诞生了!");

}

public Kami(int tribute)

{

  print("收获了"+tribute+"贡品");

}


//方法和静态方法

public void RequestWish()

{

  print("许愿");

}

public static void RealizeWish()

{

  print("神明帮你实现了愿望");

}

}


Kami new_kami = new Kami("Anki");

new_kami.RequestWish();   //普通方法,需要通过实体实现

Kami.RealizeWish();       //静态方法,归属于Kami类无需创建变量,直接使用

```

#### 值与引用

+ 内存可分为堆和栈两块区域

  + 堆:大而慢

    +  值类型数据在栈上

  + 栈:小而快

    + 引用类型数据在栈上存放一个堆地址,数据在堆上

+ 值类型

  + 数字

  + 文字

  + 逻辑

  + 枚举

  + 结构体

+ 引用类型

  + 数组

  + 字符串

  + 类

+ 实际上Unity中所有变量都声明在类中,存在堆里,只有局部变量会在栈上开辟空间


### 继承

#### 继承

+ 面向对象三大特征之一

+ 被继承者称为父类或基类

+ 继承者称为子类或派生类

+ 子类继承后可添加自己的成员

+ 一父多子


```C#

//父类

class Apprentice

{

  public string name;

  public Gender gender;

  public int age;

}

//子类

class MagicApperentice:Apperentice  //继承学徒类,并可继续附加成员

{

  public void TellName()

  {

    print(name);    //子类只能继承public的属性和方法,如果是private则不会被继承,protected可以被继承,不能被外部调用

  }

}

class BlacksmithApperentice:Apperentice

{

 

}

class MusicianApperentice:Apperentice

{

 

}

```

#### MonoBehaviour

+ Unity的脚本都派生自该类,这些脚本可挂载在GameObject组件上

+ 被挂载的组件自动实例化

+ 除了Start、Update外,MonoBehaviour还提供了很多别的生命周期以及便捷的成员变量和方法

+ print()属于MonoBehaviour的方法,是对Debug.Log()方法的封装


### 多态

+ 面向对象三大特征之一

+ 当使用某个功能时,根据情况,功能会有不同的表现形态

  + 通过方法重载,在类内提供名称统一但表现不同的方法

  + 通过方法重写,为不同子类提供名称统一而表现不同的方法

+ 多态是一种思想,不是语法

+ 动态多态

  + 也叫子类型多态、包含多态

  + 父类通过定义虚方法为子类规定一种功能,子类可以放进父类型的变量中,直接通过父类型变量访问这个虚方法,此时不同子类在这个虚方法上表现出了多态

  + 面向对象编程所说的多态通常指这种多态

```C#

//父类

class Apprentice

{

  public string name;

  public Gender gender;

  public int age;

  public virtual void TellName()   //虚方法,方便子类进行重写,必须对子类公开(public或protected)

  {

     print(name);

  }

}

//子类

class MagicApperentice:Apperentice  //继承学徒类,并可继续附加成员

{

  public override void TellName()   //重写父类虚方法,方法头与父类保持一致,如果不重写,则直接继承

  {

    print(name+"魔法学徒");    

    base.TellName();    //调用父类的虚方法

  }

}

class BlacksmithApperentice:Apperentice

{

 

}

class MusicianApperentice:Apperentice

{

 

}


//父类变量的数组可以指向子类变量

Apprentice[] many = new Apprentice[3];


//构造方法后可以使用{成员变量1=值,成员变量2=值}的形式进行对象初始化

many[0]=new MagicApperentice(){name = "marlin"};

many[1]=new BlacksmithApperentice(){name = "Yaki"};

many[2]=new MusicianApperentice(){name = "Anna"};


```

### 抽象与接口

#### abstract抽象

+ 在class前加关键词abstract

  + 作为一个抽象的概念,不应具有实体,因此抽象类只能被继承,自身无法实例化

  + 抽象类的子类依然可以是抽象类

+ 在方法前面加关键词abstract,抽象方法

+ 抽象方法只能定义在抽象类中

  + 与虚方法类似,抽象方法不能私有

  + 一定会被重写,所以在父类中无需实现

  + 除非同为抽象类,否则子类必须重写所有抽象方法

  + 抽象方法规定了子类必须实现的功能

```C#

//定义抽象类

abstract class Animal

{

  //定义抽象方法

  public abstract void Run();

}

```

#### interface接口

+ 指一个功能模块提供给使用者的媒介

+ 类通过类似继承的方式加入接口,这个过程叫实现接口

+ 接口通过抽象方法规定了类中必须存在的公开方法,相当于规定了类提供给调用者的接口


```C#

interface IMagic

{

  //接口里面创造共同具有的抽象方法,权限修饰符和abstract可省略

  //public abstract void Invoke();

  void Invoke();

}


class Dragon:IMagic,IFly,IRun     //类通过继承的格式接收接口,继承的接口不限数量,添加别的接口以及父类使用逗号隔开

{

  public void Invoke(){};     //接口中的方法必须是public,不用写override

}


class Magician:Human,IMagic

{}


Class Elf:IMagic

{}


//当一组类拥有相同的接口,就能使用相同的数组来管理具备相同特征的实例

IMagic[] magicCreatures = new IMagic[3];

magicCreatures[0] = new Dragon();

magicCreatures[1] = new Magician();

magicCreatures[2] = new Elf();


```


### 属性与异常

#### 属性

+ 对类中成员的一层包装,也叫类的命名成员

+ 属性通过访问器get、set来读写包装内的数据

+ get和set内除了return语句外,还能添加其他语句,因此可以用于数据二次处理、关联其他逻辑等

+ 如果省略get或set,可实现只读或只写

```C#

//设置类的属性

int target;

public int targetinfo

{

  get

  {

    return target;

  }


  set

  {

    target = value;

  }

}


targetinfo = 100;   //将传入set的value中


```

#### 异常

+ 一些复合基本语法,但是运行过程中会出现的错误

+ 当问题发生时,程序就会抛出异常,若不处理,就会导致一系列问题

+ 用户可以使用throw关键字+异常实例来主动抛出异常,也可通过继承来自定义异常类型


|异常符号|异常内容|

|:----:|:----:|

|SystemException |  其他用户可处理的异常的基本类 |

|ArgumentException   |方法的参数是非法的 |

|ArgumentNullException  | 一个空参数传递给方法,该方法不能接受该参数 |

|ArgumentOutOfRangeException  | 参数值超出范围 |

|ArithmeticException   |出现算术上溢或者下溢 |

|ArrayTypeMismatchException   |试图在数组中存储错误类型的对象 |

|BadImageFormatException  | 图形的格式错误 |

|DivideByZeroException |  除零异常 |

|DllNotFoundException  | 找不到引用的DLL |

|FormatException  | 参数格式错误 |

|IndexOutOfRangeException  | 数组索引超出范围 |

|InvalidCastException  |使用无效的类 |

|InvalidOperationException  | 方法的调用时间错误 |

|MethodAccessException  | 试图访问思友或者受保护的方法 |

|MissingMemberException  | 访问一个无效版本的DLL |

|NotFiniteNumberException  | 对象不是一个有效的成员 |

|NotSupportedException  | 调用的方法在类中没有实现 |

|NullReferenceException   |试图使用一个未分配的引用 |

|OutOfMemoryException  | 内存空间不够 |

|PlatformNotSupportedException |  平台不支持某个特定属性时抛出该错误 |

|StackOverflowException   |堆栈溢出|

|SystemException  | 运行时产生的所有错误的基类。|

|IndexOutOfRangeException  | 当一个数组的下标超出范围时运行时引发。|

|NullReferenceException  | 当一个空对象被引用时运行时引发。|

|InvalidOperationException  | 当对方法的调用对对象的当前状态无效时,由某些方法引发。|

|ArgumentException  | 所有参数异常的基类。|

|ArgumentNullException   |在参数为空(不允许)的情况下,由方法引发。|

|ArgumentOutOfRangeException   |当参数不在一个给定范围之内时,由方法引发。|

|InteropException  | 目标在或发生在CLR外面环境中的异常的基类。|

|ComException  | 包含COM类的HRESULT信息的异常。|

|SEHException  | 封装Win32结构异常处理信息的异常。|

|SqlException  | 封装了SQL操作异常。|



#### 异常处理

+ 异常捕获:try-catch-finally

+ try中为可能抛出异常的代码,之后可跟多个catch块,每个catch块后可跟异常类型,并在花括号中处理异常

+ 如果catch后没有说明异常类型,则捕获所有类型异常

+ 无论是否捕获异常,finally都会执行,但finally可省略

```C#

try

{

  print(allMagics[1]);

}

catch(System.IndexOutOfRangeException)

{

  //如果try中语句引起此类型异常,则执行此处代码

  //如果省略小括号,则发生任何类型异常,都会执行此catch中内容

}

...

finally

{

  //无论是否捕获异常,finally都会执行

}

```


### 运算符重载与索引器

#### 运算符重载

+ 可以对C#内置的部分运算符进行重新定义

+ 可重载运算符

  + 算术运算符

  + 条件运算符

+ 参数和返回类型符合运算符自身要求

+ 参数有两个且不同的类时,参数左右顺序决定使用时对象的左右顺序

+ 部分运算符要求成对重载

  + \>和<

  + == 和 !=

 

```C#

class Apprentice

{

  public static Apprentice operator +(Apprentice a,Apprentice b);  

  //运算符重载函数的参数与运算符操作数数量相同

  //返回类型不能是void


}

```

#### 封装的作用

+ 将高频使用的功能单独提取出来,可以重复使用以提高效率

+ 单独提取部分作为独立模块,内部逻辑和外部分离,若修改独立模块内部逻辑,不会影响其他模块

+ 若更换关联模块,只要满足同样的对接方式,即可直接替换

+ 规定系统中各模块的对接方式,是接口存在的意义


#### 索引器

+ 允许一个对象可以像数组一样使用下标来访问,适合具有成组特性的类

+ 与属性类似,通过get、set访问器来定义索引方式

+ 方括号中的参数即为访问时的下标,可以定义为各种类型

+ 利用索引器和类中的数组,可以组合出功能更高级的数据结构

+ 对于数组,包含着length等相关成员可供访问

```C#

class MagicSet

{

  public int this[int index]

  {

   // get 访问器

   get

   {

      // 返回 index 指定的值

   }


   // set 访问器

   set

   {

      // 设置 index 指定的值

   }

}

}

```

#### 举例

运算符重载

```C#

public class Main:MonoBehaviour

{

  Apprentice apprentice = new Apprentice();

  Provision p1=new Provision(10),p2=new Provision(50);


  void Start()

  {

    Apprentice newApprentice = apprentice + (p1 + p2);

    print(newApprentice.hp+"  "+newApprentice.mp);

  }


  void Update()

  {

   

  }

}



class Apprentice    //学徒

{

  public int hp,mp

}

class Provision     //补给包

{

  public int hp,mp;

  public Provision(){};

  public Provision(int number){bp=mp=number;}

  public static Provision operator +(Provision a,Provision b)

  {

    return new Provision(){hp=a.hp+b.hp,mp=a.mp+b.mp};

  }


  public static Apprentice operator + (Apprentice a,Provision b)

  {

    return new Apprentice(){hp=a.hp+b.hp,mp=a.mp+b.mp};

  }

}

```

索引器的使用

```C#

public class Main:MonoBehaviour

{

  MagicSet magicset=new MagicSet(1);


  void Start()

  {

    magicset[0]="Truth";

    print(magicset[1]);  

  }


  void Update()

  {

   

  }

}


class MagicSet

{

  string[] magics;          //数组

  public MagicSet(int length)   //构造函数

  {

    magics = new string[length];    //为数组开辟空间

  }

  //定义索引器

  public string this[int index]

  {

    get

    {

      if(index>magics.Length-1||index<0)

      {

        return "Index out of range";

      }

      return magics[index];

    }

    set

    {

      magics[index]=value;

    }

  }

}



```


### 泛型与集合

#### 泛型

+ 一种延迟编写类型的规范

  + 先拟定一个类型,在调用时再填入具体类型

  + 可以看作将类型作为一种可变参数,进一步提高代码的复用性

+ 泛型除了作用在方法,还能作用于类、接口等

  + 作用于类时,在类名后加尖括号<>,在其中为泛型起名即可

    + class GClass<T>{...}

  + 一次可定义多个泛型,在尖括号中用逗号隔开

    + <T1,T2>

```C#

public T GenericMethod<T>(T t);


//为各种数组添加元素的方法

Type[] AddElement<Type>(ref Type[] array,Type newElement)     //ref类似于取地址,变量以引用的方式进入函数修改值等效于修改外部变量

{

  Type[] newArray=new Type[array.Length+1];

  for(int i=0;i<array.Length;i++)

  {

    newArray[i]=array[i];

  }

  newArray[newArray.Length-1]=newElement;

  return newArray;

}

```

#### ref和out

+ 用来修改参数方法的两个关键词

+ 若将一个参数定义为ref和out,调用时必须填入可赋值的变量,且要加上对应的ref或out词缀

+ 修改方法内的ref或out变量,将会直接改变调用者外部填入的变量

+ 区别在于

  + ref可以不修改,作为普通参数使用

  + out必须修改,且在修改后才能在方法内使用

+ 可以通过定义多个ref或out参数,来实现一个方法带出多个返回值


#### List类

+ List <T>是强类型对象的集合,可以通过索引对其进行访问,并具有用于排序,搜索和修改列表的方法

+ 它是System.Collection.Generic命名空间下的ArrayList的泛型版本。


List的属性和方法

|属性|  用法|

|:----:|:----:|

|Items| 获取或设置指定索引处的元素|

|Count| 返回List <T>中存在的元素总数|


|方法|  用法|

|:----:|:----:|

|Add| 在List <T>的末尾添加元素。|

|AddRange|  将指定集合的元素添加到List <T>的末尾。|

|BinarySearch|  搜索元素并返回该元素的索引。|

|Clear  |从List <T>中删除所有元素。|

|Contains|  检查指定元素在List <T>中是否存在。|

|Find |根据指定的谓词函数查找第一个元素。|

|Foreach| 遍历 List <T>。|

|Insert|  在List <T>中的指定索引处插入元素。|

|InsertRange| 在指定的索引处插入另一个集合的元素。|

|Remove |删除指定元素的第一次出现。|

|RemoveAt|  删除指定索引处的元素。|

|RemoveRange| 删除所有与提供的谓词功能匹配的元素。|

|Sort |对所有元素进行排序。|

|TrimExcess|  将容量设置为实际的元素数。|

|TrueForAll |确定List <T>中的每个元素是否与指定谓词定义的条件匹配。|

```C#

List<string> magics = new List<string>();       //不必确定空间大小

magics.Add();   //添加元素等

```


#### Dictionary字典

+ Dictionary < TKey,TValue > 是一个泛型集合,它以不特定的顺序存储键值对。

+ 属于System.Collection.Generic命名空间。

+ 实现 IDictionary <TKey,TValue>接口。

+ 键必须是唯一的,不能为null。

+ 值可以为null或重复。

+ 可以通过在索引器中传递相关键来访问值,例如 myDictionary[key]

+ 元素存储为 KeyValuePair <TKey,TValue> 对象。


|方法|  用法|

|:----:|:----:|

|Comparer|  获取用于确定字典中的键是否相等的 IEqualityComparer|

|Count |获取包含在 Dictionary中的键/值对的数目|

|Item| 获取或设置与指定的键相关联的值|

|Keys |获取包含 Dictionary中的键的集合|

|Values |获取包含 Dictionary中的值的集合|

|Add |  将指定的键和值添加到字典中|

|Clear |从 Dictionary中移除所有的键和值|

|ContainsKey |确定 Dictionary是否包含指定的键 |

|ContainsValue |确定 Dictionary是否包含特定值 |

|GetEnumerator |  返回循环访问 Dictionary的枚举数 |

|GetType  |获取当前实例的 Type (从 Object 继承)|

|Remove | 从 Dictionary中移除所指定的键的值 |

|ToString |返回表示当前 Object的 String (从 Object 继承)|

|TryGetValue |  获取与指定的键相关联的值 |


```C#

IDictionary<int, string> numberNames = new Dictionary<int, string>();

numberNames.Add(1,"One"); //使用Add()方法添加键/值

```

#### 泛型集合

+ 微软在.Net架构中,封装好的、支持泛型的各种数据存储结构

+ 这些结构以类的形式提供支持,并且可以动态扩展大小,其中包含一系列数据处理相关的方法

+ 常用的有

  + List<T>

  + Dictionary<TKey,TValue>

  + Stack<T>

  + Queue<T>

+ 此外还有非泛型集合

  + 非泛型集合中字典叫做Hashtable哈希表


### 委托

#### 事件驱动

+ 一种程序设计思路,可以很好的解除各个模块间的强关联,将各种用户操作、游戏事件进行抽象提取,集中管理起来

+ 作为事件的发布方,在合适的时机发布事件即可,无需关心谁订阅了事件

+ 作为事件订阅方,在事件被发布时执行相关操作即可,无需关心是谁发布了事件

+ 以跨平台游戏为例,游戏核心层只需监听操作事件,无需考虑触发设备是屏幕、键盘还是手柄


#### delegate委托

+ 使用关键词delegate声明委托

+ 委托声明决定了该委托可以绑定什么格式的方法

+ 使用委托声明来创建委托变量,之后可以通过实例化来绑定方法

+ 委托变量本质上存储着一个方法的引用,类似于C++中函数指针

```C#

public class Main:MonoBehaviour

{

  public delegate void Event(string s);   //定义委托

  Event weather,notice;


  void Start()

  {

    //发布委托

    weather("晴");

    notice("考试");

    //订阅委托

    weather = new Event(GoToLibrary);//同weather += GoToLibrary;

    //如果为=,则只能绑定一个方法,如果要添加委托,则使用+=

    weather += Business;

    //解绑委托

    weather -= Business;


    weather += (s)=>{print("今天"+s+"去玩");};    //匿名函数

  }


  void Update()

  {

   

  }


  void GoToLibrary(string s)

  {

    print("今天"+s+"去图书馆");

  }

  void Business(string s)

  {

    print("今天"+s+"去工作");

  }

}

```

#### 匿名方法

+ 没有名称只有主体的方法,专用于委托

+ 简化格式也称为Lambda表达式

  + delegate (string s){···}

  + (s)=>{···}

+ 头部只有一个参数或主体只有一行非return语句时,对用括号可以省略

  + s=>print("")


### 总结

【从零开始的Unity魔法学堂】委托的评论 (共 条)

分享到微博请遵守国家法律