Unity项目开发※
主讲人:唐寅权
学习目标:
- 掌握Unity3D进行游戏项目、虚拟现实项目、数字孪生项目的开发能力
- 了解企业开发习惯、规范变成习惯、跟企业无缝衔接
- 通过项目优化能力的培养,进行项目优化改进、提升项目性能
- 熟练掌握项目开发的整个流程以及各部门之间的配合
学习内容:
- C#相关内容学习以及对应的数据结构和算法
- Unity引擎各功能模块以及对应API
- 项目开发过程中常用的设计模式
- 项目开发资源管理和代码的组织方式
0x01 背景※
Unity为哪些行业带来了新的解决方案※
- 游戏开发:Unity可以用于创建2D和3D游戏,支持各种游戏类型,如动作游戏、射击游戏、角色扮演等
- 教育行业:开发交互式教育应用程序、模拟器、虚拟实验室等
- 医疗保健行业:医学培训模拟器、手术模拟器等
- 建筑和房地产行业:
- 汽车行业
- 航天航空行业
- 娱乐行业
- 广告和营销行业
Unity特点※
- 游戏开发:Unity可以用于创建2D和3D游戏,支持各种游戏类型,如动作游戏、射击游戏、角色扮演等
- 跨平台:移动端(Android、IOS) PC(Windows、MacOS、Linux)主机(PS、Xbox)虚拟现实VR、增强现实AR等
- 强大的编辑器:场景编辑、资源管理、物理引擎、动画编辑等功能
- 脚本支持:支持多种编程语言,如C#、JavaScript(现为UnityScript)等
- 生态系统:资源商店、文档、教程、社区支持等
安装Unity※
登入Unity中国区官网,下载Unity安装器UnityHub (Windows、MacOS、Linux)安装引擎
没有账号,需要注册个账号,有账号的直接登入,就会自动下载UnityHub
按照安装指引安装软件,Archlinux可以安装unityhub-cn
安装好UnityHub后,打开UnityHub,选择Installs标签页,安装官方最新release版本
可以选择LTS版本、最新版本、等,带c1后缀是中国版本
0x02 开始动手※
- 创建项目选择3D模板
- 更改Project Name
- 选择位置
- 创建

Unity3D 基本操作※
视图操作※
Scene窗口
| 快捷键 | 功能 |
| Alt+鼠标左键 | 以物体为中心、围绕物体进行观察 |
| F | 聚焦物体 |
| Q | 视图工具,平移整个Scene视图 |
| W | 移动工具,移动场景中所选的对象 |
| E | 旋转工具。按任意角度旋转场景中的对象 |
| R | 缩放工具,缩放场景中所选对象 |
| T | 横切面工具沿着横切面缩放场景中所选对象 |
| Z | 轴心工具,改变游戏对象轴心点 |
| X | 坐标系变换工具,本地坐标和世界坐标间切换 |
| Ctrl+Shift+F | 摄像机对齐到当前窗口 |
Unity使用左手坐标系,坐标单位为米
编程模式※
编程模式※
面向过程编程※
面向过程编程是一种基于解决问题步骤的编程范式,其中程序主要由一系列按照顺序执行的命令和函数组成。在面向过程编程中,重点是算法和函数的设计,而不是将数据和行为封装在对象内部。
在面向过程编程中,程序主要由函数组成,每个函数负责执行特定的任务。数据通常是分散存储
的,函数可以直接操作这些数据。面向过程的代码通常具有以下特点:
- 顺序执行:程序按照代码编写的顺序依次执行命令和函数,逐步完成任务。
- 函数:函数是面向过程编程的核心,用于执行特定的操作或任务。函数可以接受参数和返回值。
- 数据和函数分离:数据和函数通常是分开处理的,函数直接操作数据,实现特定的功能。
面向过程编程适用于简单的、直线的问题解决方案,对于复杂的系统和大型项目来说,面向过程编程可能会导致代码难以维护、理解性差等问题。相比之下,面向对象编程更适合于需要更好组织代码、提高复用性和可维护性的项目。
在Unity中,虽然更倾向于使用面向对象编程和面向组件编程的方式来开发游戏,但也可以在某些情况下使用面向过程的思维来解决问题。例如,在一些简单的功能实现或工具编写时,面向过程的方式可能更加直接和高效。
面向对象编程※
在Unity中,面向对象编程(OOP)是一种常见的编程范式,让开发者可以通过创建对象、定义类和使用继承等方式来组织和实现代码逻辑。在Unity中使用C#编程语言进行面向对象编程时,以下是一些常见的概念和实践:
- 类与对象:类是一种抽象数据类型,用于定义对象的属性和行为。对象是类的实例。在Unity中,游戏对象(GameObject)就是类的实例,可以通过脚本(Script)定义游戏对象的属性和行为。
- 封装:封装是一种将数据和行为封装在类内部,并通过公有接口暴露给外部的机制。在Unity中,可以使用访问修饰符(public、private、protected等)来控制属性和方法的可访问性。
- 继承:继承是一种类之间的关系,子类可以继承父类的属性和方法,并可以扩展或重写这些内容。在Unity中,可以使用继承来实现不同游戏对象之间共享行为或属性的功能。
- 多态:多态是指不同类的对象可以对相同的消息作出响应,实现了统一的接口。在Unity中,多态可以通过虚方法、抽象类、接口等概念来实现。
通过面向对象编程,Unity开发者可以更好地组织代码、提高代码复用性和可维护性,从而更高效地开发出功能强大的游戏。
一句话总结:封装用于隐藏对象的内部实现细节,继承用于实现类之间的层次关系,多态用于实现对象的多种形态。
面向组件编程※
在Unity中,面向组件编程是一种常见的编程风格,它基于Unity的组件化体系结构。在Unity中,游戏对象(GameObject)是通过将组件(Component)挂载到其上来实现功能和行为的。
面向组件编程在Unity中的工作方式如下:
- 每个游戏对象都可以有多个组件。
- 每个组件可以包含特定的功能或行为,例如渲染器(Renderer)、碰撞器(Collider)、脚本(Script)等。
- 开发者可以通过编写脚本来定义组件的行为和交互逻辑
- 组件之间可以相互影响和通讯,实现复杂的游戏逻辑
面向组件编程在Unity中的优点包括:
- 可重用性:组件可以在不同的游戏对象之间重复使用。
- 易于维护:通过将功能拆分成组件,易于单独管理和修改。
- 灵活性:可以动态添加、删除和修改组件,方便地调整游戏对象的功能和行为。
总的来说,Unity中的面向组件编程使得开发者可以更轻松地构建复杂的游戏系统,提高了开发效率和可维护性。
unity中的编程模式※
在Unity中,通常会将面向对象编程、面向组件编程和面向过程编程结合起来,以便充分利用各种编程范式的优势来开发游戏。下面是一些方法可以在Unity项目中结合这三种编程风格:
- 使用类和对象来组织游戏中的逻辑和数据,实现代码的重用性和可维护性。
- 定义游戏对象的属性和行为,如玩家、敌人、道具等。
- 使用继承、多态和封装等面向对象的概念来构建游戏系统的层级结构。
面向组件编程
- 利用Unity的组件化体系结构,将功能和行为分解为独立的组件并挂载到游戏对象上
- 通过组件之间的交互实现复杂的游戏逻辑,提高灵活性和可拓展性
- 编写组件类来定义特定功能,如碰撞检测、移动控制、动画播放等。
面向过程编程
- 在处理简单、线性的问题时,可以采用面向过程的方式来实现相关功能。
- 在编写一次性、独立的任务或功能时,可以使用面向过程编程风格。
- 可以结合使用函数来执行特定任务,保持代码清晰简洁。
通过结合这三种编程风格,开发者可以根据具体需求和场景选择合适的方式来编写代码,充分利用各种方法的优势。例如,可以使用面向对象编程来定义游戏对象及其行为,使用组件来实现复杂的功能模块,而在某些简单的功能实现上则可以使用面向过程的方法。这样可以使代码逻辑更清晰、易于维护,并提高开发效率。
规范※
命名规范※
- 英文单词命名。禁止使用拼音或无意义的字母命名。
- 直观易懂。使用能够描述其功能或有意义的英文单词或词组。
- 不要采用下划线命名。
- 常量、静态字段、类、结构体、非私有字段、方法等名称采用大驼峰式命名法。
- 私有字段、方法形参、局部变量采用小驼峰式命名法。
- 私有字段以下划线开头。
- 接口命名以大写字母I开头。
- 枚举以大写字母E开头。
int car_type // 错误:下划线命名。
public const float MaxSpeed = 100f; // 常量
public static float MaxSpeed = 100f; // 静态字段
public class GameClass; // 类名
public struct GameStruct; // 结构体
public string FirstName; // public字段
protected string FirstName; // protected字段
public void SendMesssage(string message); {} // 方法
private string _firstName; // 私有字段
public void FindFirstName(string firstName); // 方法参数
string firstName; // 局部变量
public interface IState; // 接口
public enum EGAmeType{Simple,Hard} // 枚举以及枚举值编码规范※
- 声明变量时,一行只声明一个变量。
- 类的字段声明统一放置于类的最前端。
- 一行代码长度不要超过屏幕宽度。如果超过了,将超过部分换行。
private string _firstName;
private string _lastName;
public class Student
{
private string _firstName;
private string _lastName;
public string GetFirstName()
}注释规范※
- 公共方法注释,采用///形式自动产生XML标签格式的注释。包括方法介绍、参数含义、返回内容。私有方法可以不注释。
- 公共字段注释,采用///形式自动产生XML标签格式注释。
- 私有字段注释,注释位于代码后面,中间Space隔开。
- 方法内的代码块注释。
/// <summary>
/// 设置场景名称
/// </summary>
/// <param name=“sceneName”>场景名</param>
/// <retuens>如果设置成功返回TRUE</returns>
public bool SetSceneName(string sceneName)
{
}0x03 C#基础※
流程控制※
流程控制(Flow Control)※
流程控制是控制程序执行路径的结构。
条件分支※
if (x > 10) { }
else if (x > 5) { }
else { }
// switch(C# 8 起可用表达式)
switch (day)
{
case "Mon": ...
case "Tue" or "Wed": ...
default: ...
}
// 三元
var result = x > 0 ? "positive" : "negative";
循环※
for (int i = 0; i < 10; i++) { }
foreach (var item in list) { }
while (condition) { }
do { } while (condition);
// break: 退出循环; continue: 跳到下一次迭代
跳转※
return; // 返回
break; // 退出循环/switch
continue; // 继续下一轮
goto; // 跳转标签(少用)
异常处理※
try { }
catch (Exception ex) { }
finally { }
运算符※
运算符(Operators)※
运算符是编程语言中执行计算/比较/赋值操作的符号。
分类※
| 类别 | 运算符 |
|---|---|
| 算术 | + - * / % ++ -- |
| 赋值 | = += -= *= /= %= |
| 比较 | == != > < >= <= |
| 逻辑 | && || ! |
| 位运算 | & | ^ ~ << >> |
| 三元 | ? : |
| null 相关 | ?? ?. ??= ?? |
示例※
int a = 10, b = 3;
Console.WriteLine(a / b); // 3(整数除法)
Console.WriteLine(a % b); // 1(取余)
Console.WriteLine(a == b); // False
// 短路求值
bool r = a > 0 && b < 10;
// null 合并
string? s = null;
string result = s ?? "default";
// 空条件
int? len = s?.Length;
优先级※
括号 > 一元 > 乘除 > 加减 > 移位 > 比较 > 逻辑与 > 逻辑或 > 赋值
数组※
数组(Array)※
数组是存储同类型元素集合的连续内存结构,索引从 0 开始,长度固定不可变。
// 声明和初始化
int[] nums = new int[5];
int[] nums2 = { 1, 2, 3, 4, 5 };
int[] nums3 = new int[] { 1, 2, 3 };
// 访问和修改
nums[0] = 10;
int x = nums2[2]; // 3
// 遍历
for (int i = 0; i < nums2.Length; i++) { }
foreach (var n in nums2) { }
// 常用方法
Array.Sort(nums2);
Array.Reverse(nums2);
Array.IndexOf(nums2, 3);
Array.Resize(ref nums, 10);
多维数组※
int[,] matrix = new int[3, 3];
int[][] jagged = new int[3][]; // 交错数组
值类型和引用类型※
值类型和引用类型※
这是 C#/.NET 中最重要的概念之一,决定了变量如何存储和传递数据。
值类型(Value Type)※
- 数据直接存储在栈/结构体内
- 赋值时复制数据(副本独立)
- 类型:int、float、bool、char、struct、enum
int a = 10;
int b = a; // b 是 a 的副本
b = 20;
Console.WriteLine(a); // 10(a 不受影响)
引用类型(Reference Type)※
- 变量存的是堆上的地址引用
- 赋值时复制引用(指向同一对象)
- 类型:class、string、数组、接口、委托
class Person { public int Age; }
Person p1 = new Person { Age = 10 };
Person p2 = p1; // p2 指向同一对象
p2.Age = 20;
Console.WriteLine(p1.Age); // 20(同一对象被改)
参数传递※
void Modify(int x) { x = 100; } // 值传递:外部不变
void Modify(ref int x) { x = 100; } // ref:外部改变
void Modify(out int x) { x = 100; } // out:必须赋值
特殊:string※
string 是引用类型但不可变(immutable),任何修改都创建新字符串,所以表现像值类型。
0x04 组件※
Transform (3D):控制物体在世界的位置、旋转和缩放。(必要组件)
Rect Transform (2D)
Mesh Filter:管理物体顶点结构
Mesh Renderer:渲染器,渲染物体
Collider:碰撞器
Rigidbody:刚体
新建C# Script 模板
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 装备类
/// </summary>
public class Equip
{
// 数据成员
int id;
string name;
float price;
// 函数成员
}
/// <summary>
/// 类的结构
/// 1 数据成员
/// 1.1 基本数据成员
/// 整数:byte、short、int、long
/// 小数:float、double
/// 字符:char
/// 布尔:bool
/// 1.2 复杂数据成员
/// 字符串:string
/// 结构体:struct
/// 枚举:enum
/// 类:class
/// 委托:deleget
/// 2 函数成员
/// </summary>
public class ClassName : MonoBehaviour
{
// 数据成员
uint level; // 等级
double money; // 钱
float hp; // 血量
bool isDead; // 是否死亡
string name; // 名字
// 函数成员
Equip equip; // 装备,默认为null
// Start is called before the first frame update
// 初始化函数,Play时只执行一次
void Start()
{
// 操作数据成员
// 定义数据,不初始化有默认值
// 低精度可以给高精度赋值,高精度不能给低精度赋值
// 高精度给低精度赋值使用强制类型转换,会丢失精度
level = 1;
hp = 100f;
money = 9999;
exp = 0.0;
idDead = false;
name = "刀"
equip = new Equip();
print("level:"+level);
}
// Update is called once per frame
// 每一帧执行一次
void Update()
{
}
}0x05 获取对象/组件※
获取对象/组件(Unity)※
Unity 开发中获取对象和组件是最基础的操作。
获取 GameObject※
// 自身
gameObject
this.gameObject
// 通过名称(场景中查找,性能较低)
GameObject.Find("Enemy");
GameObject.FindWithTag("Player");
// 实例化
Instantiate(prefab);
Instantiate(prefab, position, rotation);
// 通过父子关系
transform.parent.gameObject;
transform.Find("Child/SubChild").gameObject;
获取组件(Component)※
// 自身组件
GetComponent<Rigidbody>();
GetComponentInChildren<Renderer>(); // 子物体
GetComponentInParent<Collider>(); // 父物体
// 给其他对象加组件/取组件
otherGameObject.GetComponent<ScriptName>();
otherGameObject.AddComponent<ScriptName>();
// 查找多个
GetComponents<Collider>(); // 数组
常用单例模式※
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake() => Instance = this;
}
生命周期函数※
生命周期函数与异步调用※
Unity 脚本的生命周期函数由引擎在特定时机自动调用,理解执行顺序是编写正确逻辑的基础。
执行顺序※
Awake() // 对象加载时调用一次(最先,用于初始化)
OnEnable() // 对象启用时
Start() // 首次 Update 前调用一次(用于启动逻辑)
FixedUpdate() // 固定频率(默认 0.02s),物理计算用
Update() // 每帧调用,常规逻辑
LateUpdate() // Update 之后
OnDisable() // 对象禁用时
OnDestroy() // 销毁时
异步调用※
// 延迟调用
Invoke("MethodName", 2f); // 2 秒后调用
InvokeRepeating("Method", 1f, 2f); // 1 秒后每 2 秒重复
CancelInvoke();
// 协程延迟(见协程笔记)
StartCoroutine(WaitAndDo());
// Unity 2020+:C# async/await(需 UniTask 或自定义)
async void LoadAsync()
{
await Task.Delay(1000);
Debug.Log("done");
}
Update 与 FixedUpdate 区别※
- Update:每帧,帧率相关,适合输入/UI/逻辑
- FixedUpdate:固定步长,与帧率无关,适合刚体/物理