初探Unity3D

-
-
2024-05-26

Unity项目开发

 主讲人:唐寅权

学习目标:

  • 掌握Unity3D进行游戏项目、虚拟现实项目、数字孪生项目的开发能力
  • 了解企业开发习惯、规范变成习惯、跟企业无缝衔接
  • 通过项目优化能力的培养,进行项目优化改进、提升项目性能
  • 熟练掌握项目开发的整个流程以及各部门之间的配合

学习内容:

  • C#相关内容学习以及对应的数据结构和算法
  • Unity引擎各功能模块以及对应API
  • 项目开发过程中常用的设计模式
  • 项目开发资源管理和代码的组织方式

0x01 背景

Unity为哪些行业带来了新的解决方案

  1. 游戏开发:Unity可以用于创建2D和3D游戏,支持各种游戏类型,如动作游戏、射击游戏、角色扮演等
  2. 教育行业:开发交互式教育应用程序、模拟器、虚拟实验室等
  3. 医疗保健行业:医学培训模拟器、手术模拟器等
  4. 建筑和房地产行业:
  5. 汽车行业
  6. 航天航空行业
  7. 娱乐行业
  8. 广告和营销行业

Unity特点

  1. 游戏开发:Unity可以用于创建2D和3D游戏,支持各种游戏类型,如动作游戏、射击游戏、角色扮演等
  2. 跨平台:移动端(Android、IOS) PC(Windows、MacOS、Linux)主机(PS、Xbox)虚拟现实VR、增强现实AR等
  3. 强大的编辑器:场景编辑、资源管理、物理引擎、动画编辑等功能
  4. 脚本支持:支持多种编程语言,如C#、JavaScript(现为UnityScript)等
  5. 生态系统:资源商店、文档、教程、社区支持等

安装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使用左手坐标系,坐标单位为米

编程模式

编程模式

面向过程编程

    面向过程编程是一种基于解决问题步骤的编程范式,其中程序主要由一系列按照顺序执行的命令和函数组成。在面向过程编程中,重点是算法和函数的设计,而不是将数据和行为封装在对象内部。

    在面向过程编程中,程序主要由函数组成,每个函数负责执行特定的任务。数据通常是分散存储
的,函数可以直接操作这些数据。面向过程的代码通常具有以下特点:

  1. 顺序执行:程序按照代码编写的顺序依次执行命令和函数,逐步完成任务。
  2. 函数:函数是面向过程编程的核心,用于执行特定的操作或任务。函数可以接受参数和返回值。
  3. 数据和函数分离:数据和函数通常是分开处理的,函数直接操作数据,实现特定的功能。

    面向过程编程适用于简单的、直线的问题解决方案,对于复杂的系统和大型项目来说,面向过程编程可能会导致代码难以维护、理解性差等问题。相比之下,面向对象编程更适合于需要更好组织代码、提高复用性和可维护性的项目。
    在Unity中,虽然更倾向于使用面向对象编程和面向组件编程的方式来开发游戏,但也可以在某些情况下使用面向过程的思维来解决问题。例如,在一些简单的功能实现或工具编写时,面向过程的方式可能更加直接和高效。

面向对象编程

    在Unity中,面向对象编程(OOP)是一种常见的编程范式,让开发者可以通过创建对象、定义类和使用继承等方式来组织和实现代码逻辑。在Unity中使用C#编程语言进行面向对象编程时,以下是一些常见的概念和实践:

  1. 类与对象:类是一种抽象数据类型,用于定义对象的属性和行为。对象是类的实例。在Unity中,游戏对象(GameObject)就是类的实例,可以通过脚本(Script)定义游戏对象的属性和行为。
  2. 封装:封装是一种将数据和行为封装在类内部,并通过公有接口暴露给外部的机制。在Unity中,可以使用访问修饰符(public、private、protected等)来控制属性和方法的可访问性。
  3. 继承:继承是一种类之间的关系,子类可以继承父类的属性和方法,并可以扩展或重写这些内容。在Unity中,可以使用继承来实现不同游戏对象之间共享行为或属性的功能。
  4. 多态:多态是指不同类的对象可以对相同的消息作出响应,实现了统一的接口。在Unity中,多态可以通过虚方法、抽象类、接口等概念来实现。
     

    通过面向对象编程,Unity开发者可以更好地组织代码、提高代码复用性和可维护性,从而更高效地开发出功能强大的游戏。
    一句话总结:封装用于隐藏对象的内部实现细节,继承用于实现类之间的层次关系,多态用于实现对象的多种形态。

面向组件编程

    在Unity中,面向组件编程是一种常见的编程风格,它基于Unity的组件化体系结构。在Unity中,游戏对象(GameObject)是通过将组件(Component)挂载到其上来实现功能和行为的。
面向组件编程在Unity中的工作方式如下:

  1. 每个游戏对象都可以有多个组件。
  2. 每个组件可以包含特定的功能或行为,例如渲染器(Renderer)、碰撞器(Collider)、脚本(Script)等。
  3. 开发者可以通过编写脚本来定义组件的行为和交互逻辑
  4. 组件之间可以相互影响和通讯,实现复杂的游戏逻辑

面向组件编程在Unity中的优点包括:

  1. 可重用性:组件可以在不同的游戏对象之间重复使用。
  2. 易于维护:通过将功能拆分成组件,易于单独管理和修改。
  3. 灵活性:可以动态添加、删除和修改组件,方便地调整游戏对象的功能和行为。

    总的来说,Unity中的面向组件编程使得开发者可以更轻松地构建复杂的游戏系统,提高了开发效率和可维护性。

unity中的编程模式

    在Unity中,通常会将面向对象编程、面向组件编程和面向过程编程结合起来,以便充分利用各种编程范式的优势来开发游戏。下面是一些方法可以在Unity项目中结合这三种编程风格:

面向对象编程(OOP)

  • 使用类和对象来组织游戏中的逻辑和数据,实现代码的重用性和可维护性。
  • 定义游戏对象的属性和行为,如玩家、敌人、道具等。
  • 使用继承、多态和封装等面向对象的概念来构建游戏系统的层级结构。

面向组件编程

  • 利用Unity的组件化体系结构,将功能和行为分解为独立的组件并挂载到游戏对象上
  • 通过组件之间的交互实现复杂的游戏逻辑,提高灵活性和可拓展性
  • 编写组件类来定义特定功能,如碰撞检测、移动控制、动画播放等。
     

面向过程编程

  • 在处理简单、线性的问题时,可以采用面向过程的方式来实现相关功能。
  • 在编写一次性、独立的任务或功能时,可以使用面向过程编程风格。
  • 可以结合使用函数来执行特定任务,保持代码清晰简洁。

 

    通过结合这三种编程风格,开发者可以根据具体需求和场景选择合适的方式来编写代码,充分利用各种方法的优势。例如,可以使用面向对象编程来定义游戏对象及其行为,使用组件来实现复杂的功能模块,而在某些简单的功能实现上则可以使用面向过程的方法。这样可以使代码逻辑更清晰、易于维护,并提高开发效率。

规范

命名规范

  1. 英文单词命名。禁止使用拼音或无意义的字母命名。
  2. 直观易懂。使用能够描述其功能或有意义的英文单词或词组。
  3. 不要采用下划线命名。
  4. 常量、静态字段、类、结构体、非私有字段、方法等名称采用大驼峰式命名法
  5. 私有字段、方法形参、局部变量采用小驼峰式命名法
  6. 私有字段以下划线开头。
  7. 接口命名以大写字母I开头。
  8. 枚举以大写字母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} // 枚举以及枚举值

编码规范

  1. 声明变量时,一行只声明一个变量。
  2. 类的字段声明统一放置于类的最前端。
  3. 一行代码长度不要超过屏幕宽度。如果超过了,将超过部分换行。
private string _firstName;
private string _lastName;

public class Student
{
	private string _firstName;
	private string _lastName;
	
	public string GetFirstName()
}

注释规范

  1. 公共方法注释,采用///形式自动产生XML标签格式的注释。包括方法介绍、参数含义、返回内容。私有方法可以不注释。
  2. 公共字段注释,采用///形式自动产生XML标签格式注释。
  3. 私有字段注释,注释位于代码后面,中间Space隔开。
  4. 方法内的代码块注释。

 

/// <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:固定步长,与帧率无关,适合刚体/物理

 

 

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝


目录