unity
Unity
unity是一个游戏引擎,其中uinity3d代表作<王者荣耀>版本4.6。unity4.0开始支持跨平台(一次开发 多平台发布)。
unity2017之前支持的开发脚本:C#, Js, Boo,之后只支持C#(微软对抗java)。

调节工具
Unity 编辑器左上角的一组按钮,正好也对应着键盘左上角的字母:
| 图标 | ||||||
|---|---|---|---|---|---|---|
| 快捷键 | Q | W | E | R | T | Y |
| 英文 | Hand Tool | Move Tool | Rotate Tool | Scale Tool | Rect Tool | Move, Rotate or Scale selected objects |
| 中文 | 手形工具 | 移动工具 | 旋转工具 | 缩放工具 | 矩形工具 | 移动、旋转或缩放选定对象 |
| 功能 | 在整个场景中移动漫游 | 按坐标轴移动选定对象 | 按三个维度旋转对象 | 在三个维度上缩放对象 | 以矩形的方式调节对象的尺寸 | 综合前面所有对选定对象的调节工具 |
除了使用 Q 打开手形工具随后用鼠标左键漫游场景外,使用鼠标中键也可以在任意工具下漫游场景(按住鼠标中键然后移动鼠标)。
按住鼠标右键移动可以以当前镜头处为轴心旋转视角,按住鼠标右键的同时按下 W A S D Q E 也可以前后左右下上移动镜头。
按住 Alt 键的同时,也可以在任意工具下使用鼠标左键移动镜头,不过与前面不同的是,这是以目标物体为轴心来移动和旋转的。
按住 Alt 键的同时,按住鼠标右键上下左右移动也可以移远和移近物体。
按住 Ctrl 键的同时,使用以上所有工具移动、旋转或缩放对象的话,可以对齐网格。
按住 V 键的同时,鼠标放到对象中心的移动格子上移动对象,可以让此对象对齐场景中的其他对象。
视图调节
F(置于中心):当在层级(Hierarchy)窗口或场景(Scene)窗口选中某个对象后,可按 F 将对象置于场景中心,并放大/缩小到合适的尺寸。
直接在当前场景视图设置为摄像机:ctrl+shift+f或者菜单栏-》GameObject-〉Align With View
窗口调整
Shift + 空格(最大化/还原):当你的焦点在 Unity 编辑器的任何子窗口中的时候,按下 Shift + Space 可以将此子窗口最大化或者还原。
进入退出播放模式
Ctrl + P(进入退出播放模式):相当于按下界面中的“播放”按钮。
通过代码修改为动态的
注意到我创建的Editor文件夹了吗?
虽然那个是我自己创建的,但是Unity中某些文件夹就是具有着某些特殊的作用
比如这个Editor它的作用就是:
以Editor命名的文件夹允许其中的脚本访问Unity Editor的API。如果脚本中使用了在UnityEditor命名空间中的类或方法,它必须被放在名为Editor的文件夹中。Editor文件夹中的脚本不会在build时被包含。
在项目中可以有多个Editor文件夹。
然后我们先创建一个Editor文件夹,然后再在这个文件夹下面创建一个脚本,名字随意都行,然后写代码
1 | using UnityEngine; |
C#
C#于2001微软创建用于对抗Java,效果不理想,因为Java可以用虚拟机跨所有平台,C#只跨微软平台。
C#与C++明显区别:
-
C#没有指针,采用引用替代C++指针优良传统。
-
去除了C++多继承,只允许单继承,因为多继承的派生类访问基类时存在二义性(多个值)。
-
无需手动释放内存,动态开辟的堆内存由C#自动释放(引用计数技术自动释放)。
-
没有指针操作符$\rightarrow$。
-
C#不再使用# include导入类(没有头文件.h),而是使用命名空间导入类。
-
C#没有全局函数和全局变量,因为是面向对象所以函数、变量要定义在类中,即函数必须属于某个对象。
-
C#访问类的静态成员(函数、变量)使用 类名.静态成员 而不是C++的 类名::静态成员。
-
C#没有宏定义。
-
C#自动类型是var,C++自动类型是auto。
-
C#基本数据类型都继承或间接继承Object, 实现C#完全面向对象。
-
值类型:枚举、结构体、13种基本数据类型。
值类型的结构体对象可以不用初始化对象直接访问成员。也可以使用new初始化对象(new的数据不存在堆区而存在栈区)
1
2
3
4
5
6
7//结构体
struct student {
public int id;
public string name;
}
student stu;
stu.id = 123; -
引用类型: 自定义类、数组、string(特殊的引用类型)、接口等。
-
引用类型的类对像必须用new初始化对象后才能访问成员。(new的数据存在堆区而不存在栈区)
-
string类型可以不用new初始化对象就可以使用对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//自定义类
class Hero {
public int atk;
public string name;
}
//Hero hero; 无法使用hero对象的成员,因为hero没有分配内存空间地址,是空指针。
Hero hero = new Hero();
hero.atk = 250;
//数据
//int arr[10]; //c++方式定义数组
int[] pArr; //c#方式定义数组
//pArr[0] = 1; 报错,必须用new初始化数组才能使用
pArr = new int[10];
for (int i=0; i<pArr.Length; i++){
pArr[i] = i*10;
Console.WriteLine(pArr[i]);
} -
-
值类型变量与引用类型变量区别:
- 值类型变量保存的是具体数据,而引用类型变量保存的是数据地址。(引用类型变量类似于C++指针)
- 有空引用(空指针)但没有空值的说法。(指针有空而值没有空)
- 值类型变量的数据存储在栈区,而引用类型变量的数据存储在堆区。(引用类型变量本身存在栈区,类似于指针本身存在栈区指向堆区内存数据地址)
-
-
类中的成员访问修饰符不在使用如下格式
1
2
3
4
5
6
7
8class Base {
public:
int mA;
private:
void fun(int a);
protected:
float mB;
}改成如下格式,如果不加访问修饰符默认使用私有修饰符
1
2
3
4
5class Base {
public int mA;
private void fun(int a);
protected float mB;
}
C#语法简要说明
-
函数参数传递:
- 值类型传递 函数体无法改变实参值 传递的是值而不是对象本身。
- 引用类型传递 传递的是数据地址 通过同一个地址访问的是同一个对象
- 值类型进行引用传递的方式 可以使函数体对实参进行改变
- 方式1: 使用ref关键字
- 定义函数时 使用ref修饰形参 代表引用传递。
- 调用函数时 使用ref修饰实参。 注意:实参必须初始化。
- 方式2: 使用out关键字
- 定义函数时 使用out修饰形参 代表引用传递 。注意:实参可以不初始化,但是函数体必须对实参进行赋值操作。
- 调用函数时 使用out修饰实参。
- 方式1: 使用ref关键字
- 引用传递中 ref和out 的区别
- ref: 调用函数时实参必须初始化。
- out: 调用函数时实参可以不初始化,但是在函数体中必须对实参进行赋值操作。
-
C#拆箱和装箱 (类型转换)
- C#完全面向对象 所有类型间接或直接继承了Object类。
- 装箱:将其他类型转成Object类型。强制类型转换
- 拆箱:将Object类型转成其他类型。强制类型转换
- 在程序开发中尽量避免拆装箱操作,有额外消耗。
- 值类型数据转换:
- 精度高与精度低的相互转换:低转高自动转换,高转低需强制转换。
- 结构体类型不允许与其他类型转换。
- 枚举值与int直接可以相互转换(强制转换)。
- 引用类型数据转换:
- is 关键字 用于判断一个类型是否是某一个类型, 例如: h is Anni -->真或假。
- as 关键字 用于引用类型转换(安全转换,程序不会抛异常), 成功则为非空(转换的类型对像),失败则为空。例如:h as Anni -->null 或者 对象。
- 子类转换为父类,低转高,自动转换。
- 父类转换为子类,高转低,强制转换。这是不安全转换(程序会抛异常),如果父对象指向的内存的类型不是该子类型,子对象会转化失败。
- 字符串与数字转换: 字符串无法强转为数字,反之亦然。
-
面向对像
-
封装:隐藏技术, 在类的内部实现并向外部隐藏,对类的数据成员进行保护。
-
继承:使用已存在的类派生出一个新的类, 两者存在继承关系,派生出的类拥有父类的特性并且还有自己类的独特特性。
-
多态:同一个成员函数(方法、操作)对应多种不同的实现,简单来说就是,同一个继承体系下使用基类引用派生类并调用派生类同名虚函数,对应着多个不同类的实现体。区分:基类和派生类的同名非虚函数,是派生类同名函数覆盖基类同名函数。
-
继承使用
-
特点
-
没有继承方式。
-
没有多继承 一个类只允许继承一个父类(单继承)。
-
一个类可以派生出多个类,但只有一个父亲。
1
2
3
4
5
6
7
8class 子类: 父类{
}
//特点:没有继承的访问修饰符(继承方式) public, private, protected
//c++中使用
/*
class 子类: public 父类{
}
*/ -
派生类构造函数需要调用基类构造函数,当基类有无参构造函数时派生类构造函数可以省略调用基类构造函数,否则派生类构造函数必须显式调用基类构造函数
1
2
3
4
5
6
7
8
9
10
11class Hero {
public string name;
public Hero (string name){
}
}
class Anni: Hero{
public Anni:Base("Anni"){//C#使用Base调用基类构造,C++使用父类名调用构造
}
} -
静态成员(static)无法访问非静态成员。静态成员函数体内无法使用this引用。
-
-
this 表示永远引用当前调用者对象,存在于类的非静态成员函数中~~,只能读不改~~。
-
base 代表基类,用于调用基类构造或成员函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Hero {
public string name;
public Hero (string name){
this.name = name;
}
public Hero()
{
}
public static void Speak()
{
// Console.WriteLine("Hero Speak: 我乃" + name);//静态成员(static)无法访问非静态成员
// this.name = "Hero"; //静态成员函数没有this引用
}
public void Atk()
{
Console.WriteLine(name + " 发起了攻击!");//name是类的成员变量,也可用this.name
}
}
class Anni: Hero{
public Anni:Base("Anni"){//C#使用Base调用基类构造,C++使用父类名调用构造
}
public void Atk()
{
base.Atk();//使用base调用基类的成员, C++使用Hero::Atk()调用基类函数
Console.WriteLine("Anni 发起了攻击:熊之咆哮。。。。。");
}
} -
C#不存在拷贝构造,因为自定义类是引用数据类型,使用地址传递而不是值传递。
-
-
多态使用
-
使用前提:同一继承体系 而且 定义(virtual修饰)有虚函数。
-
基类引用调用派生类同名虚函数,对应多个不同类的实现体。
-
区分派生类同名函数覆盖基类同名函数
- 重写基类同名虚函数,显式关键字override
- 覆盖基类同名函数,显式关键字new
1
2
3
4
5
6public new void Say(){
}
public override void Say(){//c++ void Say override {}
}- 基类引用派生类时调用派生类的覆盖函数,其实调用的是父类的该函数,因为他没有多态性质(触发动态调用)使用静态调用,调用的是基类的成员函数。
- new关键字用于覆盖基类的属性或方法 无法触发多态。
-
-
解决方案、项目、编译、调试
Bin目录用来保存项目生成后程序集,它有Debug和Release两个版本,分别对应的文件夹为bin/Debug和bin/Release,这个文件夹是默认的输出路径,我们可以通过:项目属性—>配置属性—>输出路径来修改。
obj目录是用来保存每个模块的编译结果,在.NET中,编译是分模块进行的,编译整个完成后会合并为一个.DLL或.EXE保存到bin目录下。因为每次编译时默认都是采用增量编译,即只重新编译改变了的模块,obj保存每个模块的编译结果,用来加快编译速度。是否采用增量编译,可以通过:项目属性—>配置属性—>高级—>增量编译来设置

1 | 创建一个.net core项目 |
.Net空项目包含:一个解决方案sln或slnx, 一个解决方案下可以创建多个项目。
- 给一个解决方案创建多个项目
- 创建解决方案(如尚未创建)
1 | 创建新的解决方案 |
- 创建新项目,创建要添加到解决方案中的项目
1
2
3
4
5
6
7
8
9
创建类库项目
dotnet new classlib -n MyClassLibrary
创建 Web API 项目
dotnet new webapi -n MyWebApi
创建控制台应用程序
dotnet new console -n MyConsoleApp
这将在各自的文件夹中创建三个不同类型的项目。
- 将项目添加到解决方案
1
2
3
4
5
6
7
将项目添加到解决方案
dotnet sln MySolution.slnx add MyClassLibrary/MyClassLibrary.csproj
dotnet sln MySolution.slnx add MyWebApi/MyWebApi.csproj
dotnet sln MySolution.slnx add MyConsoleApp/MyConsoleApp.csproj
提示: 可以使用通配符一次性添加多个项目:dotnet sln MySolution.sln add **/*.csproj
- 配置项目间的引用
如果项目之间需要相互引用,例如控制台应用引用类库:
1
2
3
4
在 MyConsoleApp 目录中执行
dotnet add reference ../MyClassLibrary/MyClassLibrary.csproj
或者指定引用的项目名,假设当前在MySolution目录
dotnet add MyConsoleApp/MyConsoleApp.csproj reference ../MyClassLibrary/MyClassLibrary.csproj
或者编辑MyClassLibrary.csproj文件
1
2
3
4
5
6
7
8
9
10
11
12
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>MyConsoleApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyClassLibrary\MyClassLibrary.csproj" />
</ItemGroup>
</Project>
- 配置构建任务,在 VSCode 中创建 tasks.json 文件来配置构建任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/MySolution.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"problemMatcher": "$msCompile"
}
]
}
- 配置调试启动设置,在 launch.json 中配置调试设置,可以指定要启动的项目:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch MyWebApi",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/MyWebApi/bin/Debug/net8.0/MyWebApi.dll",
"args": [],
"cwd": "${workspaceFolder}/MyWebApi",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
}
},
{
"name": "Launch MyConsoleApp",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/MyConsoleApp/bin/Debug/net8.0/MyConsoleApp.dll",
"args": [],
"cwd": "${workspaceFolder}/MyConsoleApp",
"stopAtEntry": false,
"console": "integratedTerminal"
}
]
}
一个项目可以包含多个模块,即一个项目下可以添加多个目录每个目录下可以创建多个.cs文件
-
为每个目录创建对应的 C# 类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// Modules/Calculator/CalculatorService.cs
namespace MultiModuleApp.Modules.Calculator;
public class CalculatorService
{
public static void RunCalculator()
{
Console.WriteLine("=== 计算器模块 ===");
Console.WriteLine("输入第一个数字:");
double a = double.Parse(Console.ReadLine());
// 更多计算器逻辑...
}
}
// Modules/StringUtils/StringOperations.cs
namespace MultiModuleApp.Modules.StringUtils;
public static class StringOperations
{
public static void RunStringDemo()
{
Console.WriteLine("=== 字符串工具模块 ===");
string text = "Hello, World!";
Console.WriteLine($"反转: {ReverseString(text)}");
// 更多字符串操作...
}
public static string ReverseString(string input)
{
return new string(input.Reverse().ToArray());
}
}
// Modules/DataProcessor/DataProcessor.cs
namespace MultiModuleApp.Modules.DataProcessor;
public class DataProcessor
{
public static void RunDataProcessing()
{
Console.WriteLine("=== 数据处理模块 ===");
int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine($"数组总和: {CalculateSum(numbers)}");
// 更多数据处理逻辑...
}
public static int CalculateSum(int[] numbers)
{
return numbers.Sum();
}
} -
配置 Program.cs, 主程序文件,提供模块选择菜单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53// Program.cs
using MultiModuleApp.Modules.Calculator;
using MultiModuleApp.Modules.StringUtils;
using MultiModuleApp.Modules.DataProcessor;
class Program
{
static void Main(string[] args)
{
while (true)
{
Console.Clear();
Console.WriteLine("=== 多模块应用程序 ===");
Console.WriteLine("1. 计算器模块");
Console.WriteLine("2. 字符串工具模块");
Console.WriteLine("3. 数据处理模块");
Console.WriteLine("4. 运行所有测试");
Console.WriteLine("0. 退出");
Console.Write("请选择要运行的模块: ");
string choice = Console.ReadLine();
switch (choice)
{
case "1":
CalculatorService.RunCalculator();
break;
case "2":
StringOperations.RunStringDemo();
break;
case "3":
DataProcessor.RunDataProcessing();
break;
case "4":
RunAllTests();
break;
case "0":
return;
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey();
}
}
static void RunAllTests()
{
Console.WriteLine("=== 运行所有模块测试 ===");
// 调用各模块的测试方法
CalculatorTests.RunAll();
StringTests.RunAll();
DataTests.RunAll();
}
} -
不经过Programs.cs调用直接调试CalculatorService.cs文件。修改 .csproj 文件设置启动对象,指定本项目使用哪个Main函数作为入口程序。
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- MultiModuleApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- 指定启动对象 -->
<StartupObject>MultiModuleApp.Modules.Calculator.CalculatorService</StartupObject> <!-- 使用命名空间和类的名字不是目录和文件的名字,虽然命名空间和类的名字与目录和文件名字相同 -->
</PropertyGroup>
</Project>

task.json
1 | { |
launch.json
1 | { |

using System; //使用命名空间System, c++是using namespace System;
System命名空间定义了标准的输入输出,基本类型。
基本类型继承关系
基本数据类型
1 | using System; |
值传递和引用传递
1 | using System; |
类型转换
1 | using System; |
1 | using System; |
面向对像
1 | using System; |
1 | using System; |
属性和字段
在C#中类的成员属性称为字段,为属性提出新的概念:属性是字段的访问器。
属性定义方式:注意:属性名和字段名是不同的名字!!!
方式一:使用属性前需要定义字段名,并将属性名和字段名关联起来。
public 属性类型 属性名 {
get {
return 字段名;
}
set {
字段名 = value;
}
}
**方式二:**自动属性, 不需要定义具体字段,直接写属性
public 属性类型 属性名 { get; } //只读
public 属性类型 属性名 { set; } //只写
public 属性类型 属性名 { get; set; } //可读可写
属性和字段的区别
- 在Unity中属性不可编辑。
- 属性不可以用来引用传递。
1 | //Hero.cs |
1 | //Student.cs |
1 | //PropertyField.cs |
接口
-
类向外部提供本类功能的一种方式称为接口,接口中只能定义方法(行为)是方法的集合。
-
在C#中接口是对类的成员方法的一种抽象,接口是类的功能的一种提取 即提取行为归为接口。(因为单继承所以如果希望外部其他派生类具有本类的某些功能又不能继承本类,就可以让他继承本类提供的接口,接口允许多继承。)
-
通过继承接口,赋予类的行为,这是C#弥补类不支持多继承的一种技术。
接口定义、实现和使用
一般将写接口的文件名的第一个字母用I表示,表明本C#文件是接口文件。
1 | //接口的定义---------------------------------------------------------------- |
接口特点
- 接口无法定义字段属性。
- 接口无法给出函数具体实现,是函数的抽象。
- 函数定义的访问权限默认public
且只能是public。 - 继承者需实现接口的所有方法且只能是public修饰,或者不使用public修饰但是函数名用接口名引用。
- 一个类不能继承多个类但是可以继承多个接口。
集合类
相同类型的连续数据组合称为数组。C#中数组是引用类型,必须用new初始化数组对象。
1 | //一维数组定义: |
泛型集合:泛型容器用于存储数据的一种数据结构。
-
List<T>:数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//列表----------------------------------------------------------
List<string> li = new List<string>();
//添加
li.Add("古力娜扎");
li.Add("迪丽热巴");
li.Add("热依扎");
//添加数组
string[] strList = {"赵云","马超","关羽","张飞"};
li.AddRange(strList);
//查询
foreach(var item in li) {
Console.WriteLine(item);
}
//索引
for(int i=0; i<li.Count; i++) {
Console.WriteLine(li[i]);
}
//修改
li[4] = "马耳他";
Console.WriteLine(li[4]);
//删除
//删值
li.Remove("马耳他");
//删索引对应的值
li.RemoveAt(4);
//删全部
li.Clear(); -
LinkedList<T>:链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//链表----------------------------------------------------------
LinkedList<string> link = new LinkedList<string>();
link.AddLast("胡八一");
link.AddFirst("雪莉杨");
//查找
LinkedListNode<string> node = link.Find("胡八一");
link.AddBefore(node, "刘备");
node = link.Find("胡八一");
link.AddAfter(node, "关羽");
foreach(var item in link) {
Console.WriteLine(item);
}
//索引不支持
// link[0] = "";
//查找
// node.Previous;//上一个节点
// node.Next;//下一个节点
//删除
node = link.Find("胡八一");
link.Remove(node);
link.RemoveFirst();
link.RemoveLast();
link.Clear(); -
Dictionary<T, T>:字典 关系型容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//字典----------------------------------------------------------
Dictionary<int, string> dic = new Dictionary<int, string>();
//添加键-值对
dic.Add(10052, "张颌");
dic.Add(10032, "张角");
dic.Add(10096, "张辽");
//查找 & 修改
dic[10052] = "张鲁";
foreach (var key in dic.Keys) {
Console.WriteLine(key);
}
foreach (var value in dic.Values) {
Console.WriteLine(value);
}
foreach (KeyValuePair<int, string> pair in dic) {
Console.WriteLine(pair.Key.ToString() + ":"+ pair.Value);
}
//查找是否存在键
if (dic.ContainsKey(10052)) {
Console.WriteLine("存在10052->"+dic[10052]);
}
//删除
dic.Remove(10052); -
命名空间:using System.Collections.Generic
非泛型集合:不推荐使用(频繁拆装箱操作),在泛型集合产生之前使用的集合,原理是保存Object类型数据,特点是可以存储任意类型的数据。
-
ArrayList
1
2
3
4
5
6
7
8
9//非泛型集合
ArrayList array = new ArrayList();
array.Add("张飞");
array.Add(250);
array.Add(2.4f);
array.Add('A');
foreach(var item in array) {
Console.WriteLine(item);
} -
HashTable
-
命名空间:using System.Collections
泛型编程
C++模版有模版类和模版函数,但C#没有模版关键字template<typename T>。
泛型编程:
-
将解决方案和数据类型分离,提取数据类型,数据类型参数化。让类或函数只注重解决方案,弱化数据类型,同一解决方案应用于多种不同的数据类型。
-
泛型函数:
定义:返回值类型 函数名<泛型参数>(形参表){函数体}
使用:
- 函数名(实参表); --------自动检测泛型参数类型
- 函数名<数据类型>(); 或 函数名<数据类型>(实参表);
1
2
3
4
5
6
7
8
9
10
11
12
13
14//T泛型参数(数据类型产生)
static void Swap<T>(ref T a, ref T b) {
T temp = a;
a = b;
b = temp;
}
static void FunTest<T>(){
T a = new T();
}
static void Main(){
int a=25, b=36;
Swap(a,b);
FunTest<Hero>();
} -
泛型类:
定义:class 类名<泛型参数>{}
使用:类名<数据类型> 对象名 = new 类名<数据类型>(); -------手动输入实际使用数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class CList<T> {
T[] arr;
int size;//存储数据的个数
int maxSize;//最大的容量
public CList(int _maxSize) {
maxSize = _maxSize;
arr = new T[maxSize];
size = 0;
}
public void Add(T _data) {
if (size >= maxSize) {
Console.WriteLine("容量已满,无法添加数据");
return;
}
arr[size++] = _data;
}
public void ShowAll() {
for (int i = 0; i < size; i++) {
Console.Write("{0},",arr[i]);
}
Console.WriteLine();
}
}
static void Main(){
CList<int> list = new CList<int>(10);
list.Add(1);
list.Add(2);
list.Add(3);
list.ShowAll();
CList<string> strList = new CList<string>(10);
strList.Add("古力娜扎");
strList.Add("迪丽热巴");
strList.Add("热依扎");
strList.ShowAll();
} -
泛型约束(约束条件不满足会报错):
-
约束泛型类型为值类型: where T: struct ---------T 必须是数值类型
1
2
3
4
5
6
7
8
9
10
11
12static void Swap2<T>(T a, T b) where T: struct{
T temp = a;
a = b;
b = temp;
}
//值类型约束
Swap2<int>(ref a, ref b);
Hero h1 = new Hero();
h1.name = "盖伦";
Hero h2 = new Hero();
h2.name = "提莫";
// Swap2<Hero>(ref h1, ref h2);//报错:使用了引用类型 值类型的约束 -
约束泛型类型为引用类型: where T: class ---------T 必须是引用类型
1
2
3
4
5
6
7
8static void Swap3<T>(ref T a, ref T b) where T: class {
T temp = a;
a = b;
b = temp;
}
Swap3<Hero>(ref h1, ref h2);
Console.WriteLine($"h1={h1.name}, h2={h2.name}");
// Swap3<int>(ref a, ref b);//报错:使用了值类型 引用类型的约束 -
约束泛型类型有无参构造: where T: new() ------- T 必须有无参数的构造函数否则报错
1
2
3
4
5
6//T 必须有无参数的构造函数否则报错
static void Funtest<T>() where T:new() {
T obj = new T();
}
Funtest<Hero>();
// Funtest<Student>();// Funtest<int>();//报错:int没有无参构造函数 -
约束泛型类型继承了某个类或接口: where T: 类名或接口名 -----------T 必须继承了指定的类或接口
1
2
3
4
5static void Funtest2<T>(T obj) where T: IPerson {
obj.Eat();
}
Funtest2<Hero>(h1);
// Funtest2<Student>(s);//报错 Student没有实现IPerson接口
-
事件和委托
委托(类似c++函数指针):委托是C#中一种自定义数据类型,该类型可以保存函数地址(其具有参数表和函数返回类型),使用委托类型的对象可以将一个函数作为参数传递 或者 批量调用多个函数。
-
委托类型定义:
1
2//delegate 返回值类型 委托类型名(参数表); ----可以保存一个参数表为空,返回值为void的函数地址
delegate void Action(); -
委托对象定义:
1
2//委托类型名 对象名; -----不需要new初始化
Action fun; -
委托对象使用(赋值和调用):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static void FunTest(){
Console.WriteLine("Hello World!");
}
static void FunTest2(){
Console.WriteLine("FunTest2 is called ....!");
}
static void FunTest3(){
Console.WriteLine("FunTest3 is called ....!");
}
fun = FunTest;//向委托对象赋值
fun();//使用委托对象调用
//批量调多个函数
fun += FunTest;
fun += FunTest2;
fun += FunTest3;
fun(); -
委托对象支持操作:+=, -=, =
事件:是基于委托定义的一种对象(变量),本质就是一个委托对象(变量),是委托对象的一种保护使用方式。C#官方提出 使用事件比使用委托对象更安全。
-
事件定义:
1
//event 委托类型 事件名;
-
和普通委托对象的区别:
-
委托对象支持=运算符,有极大的不安全性,=有覆盖操作(如果委托对象里有若干函数,一旦赋值里面的若干函数都没有了)。
事件对象不支持=运算符,极大的提高安全性。注意:如果定义的事件对象和给事件对象赋值都发生在同一个类中,那么事件对象支持赋值运算符。(只允许定义事件对象的本类使用=运算符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//Boss.cs
using System;
namespace Lesson05.事件和委托 {
class Boss {
public static Action dead;//普通委托对象
public static event Action deadEvent;//事件对象
}
}
//EventAndDelegate.cs
delegate void Action();
class EventAndDelegate {
static Action buttonClicked;//普通委托对象
static event Action btnOnEnter;//事件对象
btnOnEnter += Move;
btnOnEnter += Rotate;
btnOnEnter += Scale;
//事件btnOnEnter定义在同本类中
btnOnEnter = Scale;
Boss.dead = Move;
//事件deadEvent定义在其他类中
// Boss.deadEvent = Move;//报错:事件“Boss.deadEvent”只能出现在 += 或 -= 的左边(从类型“Boss”中使用时除外)
} -
委托对象支持类外进行触发(调用)绑定的函数。
事件对象只支持类中进行触发,提高了事件触发源头的确定性(谁定义的事件谁触发 更安全更可靠)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//Boss.cs
using System;
namespace Lesson05.事件和委托 {
class Boss {
public static Action dead;//普通委托对象
public static event Action deadEvent;//事件对象
public void BossDeadDispatch() {
if (deadEvent != null) {
deadEvent();
}
}
}
}
//EventAndDelegate.cs
class EventAndDelegate {
//事件谁绑定谁触发
Boss.dead += Rotate;
Boss.deadEvent += Rotate;
//触发即调用
Boss.dead();
Boss.deadEvent();//只能在定义的源头类触发,报错:事件“Boss.deadEvent”只能出现在 += 或 -= 的左边(从类型“Boss”中使用时除外)CS0070
Boss boss = new Boss();
//在Boss类中触发
boss.BossDeadDispatch();
}
-
-
事件使用:
-
事件对象支持的操作:+=, -= 没有=
C#修饰器
C#中的[] 中括号,是一种修饰器,用于标记相关属性或者函数的一些 属性
-
[SerializeField] 在Inspector版面中显示非public属性,并且序列化;若写在public前面,等于没写……(至于序列化是啥,自行脑补……)
-
[SerializeField] int wolf;1
2
3
4
5
6
* [NonSerialized]在Inspector版面中隐藏public属性,并且序列化;如果写在非public属性前面,等于没写
* ```c#
[SerializeField]
public int wolf;
-
-
[AddComponentMenu(“XXX/XX/XXX”)] 让Component菜单下出现你自定义的类,位置是“XXX/XX/XXX”,至于功能么,用过Component的都知道,不用解释了吧……
-
[AddComponentMenu("WolfsGirl/Lilisy")] public class Lilisy: MonoBehaviour{ }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
* [ExecuteInEditMode] 在编辑界面让你的功能(类)起作用,就是你不用点开始,就可你让你的功能起作用
* 
* [RequireComponent (typeof (ClassName))] 就是在你把**被这句话标记(修饰)的类**拖到(或者AddComponent)GameObject上时,自动再给你加上“ClassName”这个类
* 
* [MenuItem (“XXX/XXX”)] 在菜单中出现选项栏,点一下,执行对应功能。注:对应的功能必须是static,同时,使用的时候需要加上using UnityEditor,这个类也要找个Editor文件夹放(一般放“Assets\Editor”),要不……就等着纠结吧~~
* 
## C#的编译器指令
**预处理指令不是 C# 代码**,它们是**编译器的指令**,在代码编译之前执行。就像给建筑工人(编译器)的施工说明,告诉它如何处理建筑材料(代码)
`#region` 和 `#endregion` 是 [C# 语言](https://so.csdn.net/so/search?q=C%23 语言&spm=1001.2101.3001.7020)中的预处理器指令,主要用于代码组织和折叠功能,`#region` 标记用于标记一段代码的开始,后面可以跟一个可选的描述性名称。这个名称用于表示该区域所包含的代码的功能或逻辑分组,如“ClassDefinition”、“Methods”、“EventHandlers”等。假设你有一个非常大的类,其中包含了多个方法、属性以及可能的一些初始化逻辑,为了使代码更整洁,你可以按照功能划分成不同的区域。
# unity基础
unity游戏项目的架构:
游戏项目---->多个场景构成-------->每个场景由多个层构成----->每个层可以有多个游戏对象------->每个游戏对象由若干组件构成
unity程序界称为"组建编程",unity的所有功能都是以组件的形式呈现的, 即自己编写的脚本c#文件里的类都是组件名,不是游戏对象名。
一个最基本的对象(空对象)至少有一个Transform组建。
1. C#脚本名和脚本里的类名要保持一致,否则无法加载到unity的游戏对象。
2. 继承了MonoBehaviour类的类就可以挂载到unity游戏对象。
脚本模版修改
* 找到unity安装目录
Win: **/Unity/hub/Editor/版本号/unity/Editor/Data/Resources/ScriptTemplates/
Mac: /Applications/Unity/Hub/Editor/2022.3.62f2c1/Unity.app/Contents/Resources/ScriptTemplates
* 修改包含NewBehaviourScript名的c#模版文件
## unity五大视图

1. 场景视图
编辑游戏对象和显示游戏对象,可视化编辑界面。
2. 游戏视图
游戏运行实际效果。
3. 检视/属性视图
显示游戏对象的相关组件信息。
4. 层次视图
显示场景中所有对象的层次关系,管理场景界面中的对象:创建 复制 编辑 重命名等操作。
5. 工程视图
显示和管理整个项目所运用的资源。
运行时编辑游戏对象属性只能用于预览,无法保存属性,需要在非运行时编辑对象才能保存。可以在运行时拷贝修改的组件属性然后非运行是粘贴拷贝的修改内容。
**修改场景界面背景**
* 菜单栏-》窗口-〉Rendering->Lighting->Environment->Skybox Material
**修改游戏视图背景**
* 在层次视图中修改Main Camera的检查视图中的Camera组件的背景。
**父子层次结构**
在层次视图中父游戏对象会影响子游戏对象(坐标,缩放,旋转等)
## Inspector view中的Transform组件
Transform组件:描述一个3D物体的位置、旋转、缩放
1. 平移 transformer.position 世界坐标, transformer.localPosition 相对挂载该组件的游戏物体的坐标。
2. 旋转 transformer.rotation
3. 放缩 transformer.localscale
4. 子对象的 **世界位置** = 父对象的 **世界变换矩阵** × 子对象的 **局部位置**
```c#
// 数学表示
sonWorldPosition = parentWorldMatrix.MultiplyPoint(sonlocalPosition);
// 实际计算
//子世界位置 = 父世界位置 + (父世界旋转 × (父世界缩放 × 子局部位置))
-
- 子对象的 世界旋转 = 父对象的 世界旋转 × 子对象的 局部旋转
1 | // 四元数乘法(注意顺序:父 × 子) |
- 子对象的 世界缩放 = 父对象的 世界缩放 × 子对象的 局部缩放
1 | // 分量相乘 |
- Unity 使用一个 4×4 矩阵表示完整的变换:
1 | // 子对象的世界矩阵 = 父对象世界矩阵 × 子对象局部矩阵 |
获取组件
-
在**组件脚本(文件名)中的组件类(类名)**中获取组件:this.GetComponent<其他组件类名>(); 或者GetComponent<其他组件类名>()
-
游戏对象获取组件:
- 先找游戏对像:GameObject sun = GameObject.Find(“对象名”);
- 再找该游戏对象的组件: sun.GetComponent<组件名称>();
-
public字段可以显示在unity的某个组件(例如transform组件)中并编辑,但是属性不可以显示在unity的某个组件(例如transform组件)中编辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformTest : MonoBehaviour
{
//public字段可以显示在unity的transform组件中的
public Vector3 pos;
public Vector3 rotate;
public Vector3 scale;
//属性不可以显示在unity的transform组件中
public Vector3 cut {get; set;}
// Start is called before the first frame update
void Start()
{
print("游戏运行了 Start函数在第一帧更新前调用一次");
print("Hello Unity!");
var thisTransform = this.GetComponent<Transform>();
GameObject sun = GameObject.Find("Sun");
Transform sunTransform = sun.GetComponent<Transform>();
//移动两对象的位置
thisTransform.position = new Vector3(-10,0,0);
sunTransform.position = new Vector3(0, 5, 0);
//向量类
//2d
var v1 = new Vector2(10,20);
var v2 = new Vector2(11,5);
print(v1-v2);
print(v1+v2);
print(v1*2f);
print(Vector2.Distance(new Vector2(0,10), new Vector2(0,30)));
//3d
var v3 = new Vector3(10,0,0);
var v4 = new Vector3(5, 20);
print(v3+v4);
print(v3-v4);
print(v3*0.5f);
print(Vector3.Distance(v3,v4));
}
// Update is called once per frame
void Update()
{
print("unity游戏运行 每帧更新 都调用一次Update");
}
}
- 静态字段不能显示在Inspector的组件中进行编辑,非public字段也不能显示在Inspector的组件中进行编辑,属性同样不能显示在Inspector的组件中进行编辑。
编译器扩展命令:使得结构体对象可以在Inspector面板中显示,没有扩展命令无法在Inspector面板中显示该类型的变量
1 | [] //编辑器扩展命令,使得结构体对象可以在Inspector面板中显示(序列化显示编辑变量) |
生命周期函数测试
生命周期函数:对象在生存到死亡期间所调用的函数。
-
Awake函数:唤醒,当对象实例化到场景中时调用一次(且仅一次)的函数
private void Awake(){}
-
一个对象做好后可以当作别的对像的模版可以将其放入预置体中或预设中。

-
预置体是unity的一种资源不是游戏对象,使用时直接从项目中直接拖拽到层次视图中转成游戏对象。
- 游戏对象可以将预置体资源作为属性进行添加,但是预置体资源不能将游戏对象作为属性进行添加。
-
用于批量创建,批量修改对象的一种方式。
-
预置体创建的批量对象可以通过修改一个对象后使用预置体属性的Overrides来更新其他对象。
-
预置体创建的对象也会调用Awake()函数。
-
GameObject GameObject.Instantiate(GameObject prefab);函数会创建后同时调用对象的Awake函数但不会调Start函数。
-
-
Start()函数:开始函数,在Awake之后调用,当对象激活时调用一次(仅一次)
-
Update()函数:普通函数更新函数,每帧执行一次 约0.02秒 根据计算机刷帧频率决定调用频率。
-
LateUpdate()函数:紧接Update之后调用一次Update,Update每调用一次,LateUpdate紧接其后调用一次。
-
FixedUpdate()函数:固定更新函数,每隔0.02秒固定更新调用一次。通常该函数用于物理运动模拟。
-
OnEnable()函数:每当对象被激活时调用一次。
-
OnDisable()函数:每当对象被禁用时调用一次。

-
OnDestroy()函数:当对象销毁时调用一次(仅一次)。在层次窗口中删除了对象即是销毁。
-
Reset()函数:当对象重制时调用一次。
unity常用类
-
Time时间类
- Time.deltaTime: 返回自上一帧完成以来经过的时间量。
- Time.time:返回自项目开始播放以来的时间量。
-
Debug调试类
- Debug.LogError()
- Debug.Log()
- Debug.LogWarning()
-
GameObject游戏对象类
-
类的属性:gameObject{get;}
-
创建对象:public GameObject prefab; GameObject go = GameObject.Instantiate(prefab);
-
删除对象:GameObject.Destroy(go, 3);//3秒后删除
-
找对象:
-
使用对象名找对象:GameObject GameObject.Find(string name)
-
使用对象标签找对象:GameObject GameObject.FindGameObjectWithTag(string name)
-
使用标签找许多游戏对象:GameObject[] GameObject.FindGameObjectsWithTag(string name)
-
-
对象添加组件:GameObject cat; cat.AddComponent<组件类名>(); 例如:cat.AddComponent<RotateAround>()
-
-
Input输入类: 例如通过键盘控制对象移动,可以参考:Edit->Project Settings->Input Manager
-
float hor = Input.GetAxis(“Horizontal”); //返回-1到1,左键-1,右键1,不按键0
float ver = Input.GetAxis(“Vertical”);//返回-1到1,下键-1,上键1,不按键0 -
如果想让摄像机跟随游戏对象运动只需将摄像机丢给移动的物体
-
bool Input.GetKeyDown(KeyCode key):按下<key>键调用一次
-
bool Input.GetKeyUp(KeyCode key):释放<key>键调用一次
-
bool Input.GetKey(KeyCode key): 按下<key>键后持续调用
-
bool Input.GetButtonDown(string name): 按下名称为name的虚拟按钮调用一次
-
Input.GetButtonDown(“Fire1”) -------按left ctrl --------鼠标左键 ---------手机触屏 都可触发开火 查-》Edit->Project Settings->Input Manager

-
-
bool Input.GetMouseButtonDown(int button): 鼠标按下,button可取0—鼠标左键,1----鼠标右键,2----中键
-
-
Mathf数学类
- float Mathf.Abs(float value)
- float Mathf.Clamp(float value, float min, float max) 限制 —限制一个值的取值范围 返回值为限定后的值
- float Mathf.Sqrt(float value)
- float Mathf.Pow(float f, float p)
- float Mathf.Repeat(float value, float length) 从value开始输出直到length让后再从value开始循环输出
-
Random随机类
- float Random.Range(float min, float max); [min, max)之间随机生成一个数
-
Transform变换组件类
鼠标事件函数
- private void OnMouseDown():鼠标按下
- private void OnMouseUp():鼠标抬起
- private void OnMouseDrag():鼠标拖拽
- private void OnMouseEnter(): 鼠标进入
- private void OnMouseExit():鼠标离开
- private void OnMouseOver():鼠标悬浮
- private void OnMouseUpAsButton():鼠标按下并抬起 点击
物理引擎
物理效果模拟工具,unity自带物理引擎,用于模拟3D虚拟世界中物体的受力、运行等状态。
刚体组件
组件名为Rigidbody, 用于模拟3D世界的物体受力的运行效果,受到力后不会变形的游戏物体,用于挂在到游戏对象中的一种组件。

组件介绍
- Sphere(Mesh Filter): 决定物体对象形状, 可以下载别人通过blender等建模好的物体形状供自己使用。
- Mesh Renderer: 渲染物体外表面、光影、材质
- Sphere Collider: 碰撞器, 刻画物理过程
- Rigidbody: 刚体,刻画物理过程
- Mass质量:
- Drag平动阻力:
- Angular Drag转动阻力:
- Use Gravity是否使用重力
- is Kinematic物体是否受力的作用,选中不受力的作用,不选受力的作用。
- Collision Detection:检测物体碰撞的方式,离散时(如果物体运动过快可能捕捉不到碰撞而穿过物体)、连续式(费资源)等
- Constraints约束
- Freeze Position
- Freeze Rotation
碰撞器
组件名为Collider用于检测物体之间的碰撞区域并可编辑,无碰撞体组件则物体不检测碰撞。
- Edit Collider编辑碰撞区域。
- Is Trigger触发器,勾线后没有碰撞效果。只检测碰撞但是没有碰撞效果。
- Material碰撞材质。
- Center编辑碰撞区域的中心位置。
- Size编辑碰撞区域的大小。
Unity提供检测碰撞的调用函数: 碰撞器检测函数、触发器检测函数。
碰撞器检测函数:双方是碰撞器(非触发器)时并且有一方有刚体组件方能触发碰撞器
- void OnCollisionEnter(Collision col):(this是本物体)进入碰撞(col是其他物体),碰撞,碰撞进入时调用一次,仅一次
- void OnCollisionEnter2D(Collision2D col)
- void OnCollisionExit(Collision col):碰撞离开时调用一次,仅一次
- void OnCollisionExit2D(Collision2D col)
- void OnCollisionStay(Collision col):碰撞持续调用,离开时停止调用
- void OnCollisionStay2D(Collision2D col)
触发器检测函数:双方至少有一方是触发器并且又一方有刚体组件
- void OnTriggerEnter(Collider col)
- void OnTriggerEnter2D(Collider2D col)
- void OnTriggerExit(Collider col)
- void OnTriggerExit2D(Collider2D col)
- void OnTriggerStay(Collider col)
- void OnTriggerStay2D(Collider2D col)
让摄像机跟随球平动而非平动加转动

如果父对象有碰撞器子对象也有碰撞器则监测到的是父碰撞器而不是自碰撞器,父对象没有碰撞器子对象有碰撞器则检测到的是子对象碰撞器。
发布游戏
- logo图片-》组件中Texture Type选择2D and UI->应用
- Unity->File->Build Settings->添加场景(两种方式)
- Add Open Scenes
- 拖拽Project窗口中的场景
- 选择平台
- PC端(Windows, MacOS, Linux)
- Android
- WebGL
- IOS
- 打包设置player settings
- 公司名Company Name
- 项目名Project Name
- 版本号Version
- Logo图片 Default Icon
- 鼠标图片 Default Cursor
- 发布Build
- 发布包的存储位置
飞机大战
- 飞机管理类
注意:使用单例模式,可以方便通过类名调用挂载的游戏对象而不是生成一个对象,挂载的对象和生成的对象是两个不同的对象。
1 | /* |
- 玩家类
1 | /* |
- 爆炸类
1 | /* |
- 敌方类
1 | /* |
- 子弹类
1 | /* |
- 背景类
注意:使用项目窗口的材料中的偏移量实现背景的动态移动
1 | using System.Collections; |
- 陨石类
1 | /* |
枚举值以combobox形式展现
1 | public enum BulletType |

开始UI界面
在做UI->panel的背景图片时如果把图片放到纹理文件家中是无法给panel添加背景图片的,只有将图片放到Sprite文件夹中且图片的Inspector的Texture Type设置为Sprite (2D and UI)后才能使用图片作为panel背景

切换场景
使用SceneManger.LoadScene(场景名), 场景必须添加到游戏发布中的场景管理里面才有效。
1 | public void OnClickedStartGame() |
点击按钮切换场景

多线程
多线程之间的切换:cpu处理器(主频计时)动态随机切换执行。
多线程:多个线程(彼此独立)可以在多核CPU上真正并行运行,或者在单核CPU上通过时间片轮转模拟并发。每个线程都有独立的栈空间和执行路径。线程之间可以共享内存数据,因此需要进行同步控制,以避免数据竞争和死锁问题。线程调度由操作系统控制,可能涉及上下文切换,带来一定的开销。
协同程序:在主线程中开一个协同程序,协助主线程执行实现类似多线程异步执行效果,比多线程开销小。协程不是多线程(由一个主线程产生其他协程),但有多线程的异步执行效果,开销小使用率高。协程不会并行运行,由编程语言或运行时环境来管理,单个线程中可以运行多个协程。协程之间共享执行线程,但不需要上下文切换,切换开销非常小。
使用协程步骤
-
定义协同程序函数
-
函数样子
- 无参协程
1
2
3IEnumerator FuncName(){
yield return null;
}- 带参协程
1
2
3IEnumerator FuncNameParam(float t){
yield return null;
} -
返回值类型为IEnumerator.
-
函数体内必需有yield return语句, 不同于return语句, yield return类似红绿灯 就是等待效果(不是结束执行)。
- yield return null 等待一帧的时间 然后向下执行。
- yield return new WaitForSeconds(5) 等待5秒 然后向下执行。
- yield return StartCoroutine(“协同程序名”) 等待该协同程序执行完后 再向下执行。
- yield return new WWW(url) 等待一个网络下载任务执行完后 再向下执行。
- yield return new FixedUpdate() 等待固定更新函数执行完后 再向下执行。
-
-
开启协程
- StartCoroutine(string FuncName); 输入无参函数名字符串
- StartCoroutine(函数名(参数表)); 输入带参函数调用StartCoroutine(FuncNameParam(3.0))
-
关闭协程
-
StopAllCoroutine() 关闭所有协程
-
UnityEngine.Coroutine coroutineTime;
coroutineTime = StartCoroutine(“Func”);
StopCoroutine(coroutineTime); 关闭指定的协程。StopCoroutine(协程)
-
欧拉角和四元数
欧拉角
欧拉角:Rotation X(俯仰$\theta$): 绕X轴的角度 Y(偏航$\psi$):绕Y轴的角度 Z(滚动$\phi$): 绕Z轴的角度


右侧陀螺仪是初始状态,左侧陀螺仪是沿绿轴(中间轴)旋转90度后的状态。
- 优点:描述角度直观
- 缺点:
- 有万向锁(当俯仰角(中间轴) θ=±90°时,随物体的偏航旋转轴与父坐标系下的滚动旋转轴共线, 物体的位姿无法区分是因为旋转了随物体的偏航旋转轴还是因为旋转了父坐标系下的滚动旋转轴造成的)。
- 数值不连续(俯仰角接近 ±90° 时),不利于累积旋转优化计算, 旋转顺序(先内再中间再外轴)非常关键(嵌套关系最外层的旋转会改变内层的旋转状态),不同领域定义不同
- 航空学:z-y-x(Yaw-Pitch-Roll)
- 机器人学 / SLAM:常用 x-y-z(Roll-Pitch-Yaw)
- Unity: x-y-z (Pitch-Yaw-Roll) (内轴、中间轴、外轴)
- three.js: x-y-z (Pitch-Yaw-Roll)
- blender: x-y-z (Roll-Pitch-Yaw)
坐标系
假设有一个刚体局部坐标系 和固定参考坐标系 。欧拉角描述的是:
- 将局部坐标系 BBB 从初始与 III 对齐状态旋转到目标姿态,经过三个依次旋转角度。
- 一般记作 $R=R_z(\phi) R_y(\psi) R_x(\theta)$,其中 R 是旋转矩阵。
矩阵
- 优点:
- 旋转轴可以是任意向量;
- 缺点:
- 旋转其实只需要知道一个向量+一个角度,一共4个值的信息,但矩阵法却使用了16个元素;
- 而且在做乘法操作时也会增加计算量,造成了空间和时间上的一些浪费;
四元数
四元数:$q=w+xi+yj+zk$, w,x,y,z是实数,,i,j,k是虚数单位, 简化为实部 (旋转角度)和虚部(旋转轴)$q=w+\boldsymbol{u}=[w,(x,y,z)]$。
- 优点:
- 解决万向锁。
- 数值稳定。
- 缺点:
- 不直观,应用不广(不知道怎么旋转)。
在Unity里,tranform组件有一个变量名为rotation,它的类型就是四元数。很多初学者会直接取rotation的x、y、z,认为它们分别对应了Transform面板里R的各个分量。当然很快我们就会发现这是完全不对的。
欧拉角转四元数
1 | //已知一个欧拉角度(60,0,0) 需要赋给一个游戏物体 |
解决方法:通过Unity提供的四元数类的静态成员函数将一个欧拉角转换为四元数
1 | transform.rotation = Quaternion.Euler(new Vector3(60, 0, 0)); |
四元数转欧拉角
1 | //已知物体的旋转(四元数)希望通过欧拉角描述进行观察角度变化 |
在Unity引擎中,编辑器使用欧拉角描述使得使用者直观描述3D空间角度,在程序开发API中使用四元数并且做了计算过程的隐藏,不让其他开发者了解具体实现。
Unity开发者需要使用四元数:
- 编辑器中编辑游戏物体时使用欧拉角。
- 在使用脚本时 将欧拉角转换为四元数。
- 将执行的四元数转换为欧拉角去打印/理解。
射线检测一
射线检测是Unity中除碰撞组件之外的一种碰撞检测方式,(注:射线检测不是组件名)。
射线:从一个源点向一个指定方向发射一条无止境的射线,Unity可以检测射线路径过程中所有碰撞对象。
-
检测前提
- 被检测物体必需有碰撞体组件
-
创建射线对象
- Ray ray = new Ray(firePos.position, firePos.forward);
- Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);// 从主摄像机(源点)向屏幕坐标点创建一条射线
-
创建射线碰撞目标信息对象(射线碰撞到了谁、距离射线源多远等信息)
- RaycastHit hitInfo;
-
物理类发射射线 获取碰撞信息
-
bool isHit = Physics.Raycast(ray, out hitInfo);
-
//如果碰撞 if (isHit){ Debug.Log("碰撞到了"+ hitInfo.transform.name); Debug.Log("距离目标"+ hitInfo.distance+"米"); }1
2
3
4
5
6
7
8
9
10
11
12
13
如果想看到射线可以使用辅助工具Gizmos
```c#
//射线辅助显示工具,OnDrawGizmos 会在程序启动之前就开始调用
private void OnDrawGizmos()
{
Debug.Log("OnDrawGizmos is call...");
if (isDraw)
{
Gizmos.DrawLine(firePos.position, hitPos);
}
}
-
射线检测二

1 | void Update() |
大方块小游戏
Physic Material: 物理材质 (弹性、摩擦力)
unity 小球打板碰墙游戏中,如果小球游戏对象是板游戏对象的子对象的话,小球弹墙后发生形变,如果小球对象不是板的子对象而是同级对象就不发生形变,是因为transform组件的局部坐标和全局坐标的原因。小球随板子的坐标影响跟随板子一起动。
1 | // 问题出现在这里: |

形变可能和没有勾选冻结z方向旋转有关

XML
1 | /* |
-
Application.dataPath是获取当前工作目录中Assets目录的路径
-
目录是否存在判断和文件夹创建使用IO文件操作命名空间System.IO
1
2
3
4
5
6//判断文件夹是否存在
if(!System.IO.Directory.Exists(Application.dataPath + "/Resources/"))
{
//创建文件夹
System.IO.Directory.CreateDirectory(Application.dataPath + "/Resources/");
} -
判断文件是否存在
1
2
3
4
5
6
7//判断文件是否存在
if (System.IO.File.Exists(Application.dataPath + "/Resources/" + fileName))
{
//读取文件
string fileStr = System.IO.File.ReadAllText(Application.dataPath + "/Resources/" + "file.txt");
Debug.Log("file.txt文件已存在,读取内容:" + fileStr);
} -
删除文件
1
File.Delete(Application.dataPath + "/Resources/" + "file.txt");
-
创建文件
1
File.Create(Application.dataPath + "/Resources/" + "love.txt");
-
向文件中追加内容
1
File.WriteAllText(Application.dataPath + "/Resources/" + "love.txt", "新内容");
-
读文件
1
File.ReadAllText(Application.dataPath + "/Resources/" + "love.txt");
2D图元
sprite(精灵)是 2D/3D 游戏最常见的显示图像的方式,在节点上添加 Sprite 组件,就可以在场景中显示项目资源中的图片。
order in layer中层次关系数值越负图元越在内层,一般由内到外的次序是: 背景层-》主精灵层-〉UI层
动画制作
-
Hierarchy图中选中人物,必需选中要播放动画的对象。
-
菜单栏-窗口中-选择Animation
-
创建动画文件《动画名》.anim,《对象名》.controller
-
将一组连续图片拖动到上边右侧窗口中
-
播放动画时必需选中添加动画时的人物,否则无法播放
-
选中所有帧,拉长时间轴,更改播放时间
-
更改某一帧的图像属性(例如颜色),将时间游标放到要更改的时间轴位置,点击红色录制按钮,更改Inspector时图中的属性
-
起名字为Idel代表站,可以添加其他动作动画例如跑run
-
在Animations中双击添加动画的对象名,即Role.controller文件,或者窗口-》Animation->Animator,进入动画编辑模式,可以编排动作顺序,和添加触发条件等。右击动画状态可以设置添加过渡线、默认动画状态等设置信息。矩形方框代表动画状态机。
-
点击连接线编辑触发条件如图右侧Conditions,左侧Parameters中可以添加设置条件
-
相机跟随目标对象移动
- 相机跟随目标对象的代码CameraFollow.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/*
*Author: #Name#
*CreateTime: #CreateTime#
*Title:
*Description:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target; // 要跟随的目标人物
public float smoothTime = 0.3f; // 平滑跟随的时间系数
public Vector3 offset; // 摄像机相对于人物的偏移量(如:保持一定高度)
private Vector3 velocity = Vector3.zero; // 平滑速度
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// 计算相机位置:人物位置 + 偏移量
Vector3 cameraPosition = target.position + offset;
// 使用平滑插值,让摄像机移动更自然
transform.position = Vector3.SmoothDamp(transform.position, cameraPosition, ref velocity, smoothTime);
}
}- 将CameraFollow.cs组件添加到主摄像机上
- 设置字段
坦克大战
切割图片
Sprite Mode选择Multiple模式,点击apply应用修改,点击Sprite Editor切割图像
slice可以选自自动切割图片(自动切图片可能不是我们想要的)和Grid By Cell Size等方式,两指触摸屏缩放,option+三指移动图片,ctrl显示切割的边框(绿色)。
点击上图Apply应用切割后的图片生成img_tile_0~img_tile_6。


