oynix

于无声处听惊雷,于无色处见繁花

Unity基础纲要

以下内容基本来自于GPT-4o模型的回答。

1. C#(C Sharp)据结构可以分为以下几类:

  1. 基本数据类型

    • 整型(Integer Types)int, long, short, byte, uint, ulong, ushort, sbyte
    • 浮点型(Floating-point Types)float, double
    • 十进制型(Decimal Type)decimal
    • 字符型(Character Type)char
    • 布尔型(Boolean Type)bool
    • 字符串(String Type)string
  2. 数组(Arrays)

    • 固定大小的同质数据集合。
    • 实现原理:内存中的连续块,索引访问。
  3. 集合(Collections)

    • 列表(List)List<T> - 动态数组,大小可变。
      • 实现原理:基于数组实现,动态调整大小。初始分配一定的空间,当空间不够时,分配一个更大的新数组并复制旧数据。
    • 链表(LinkedList)LinkedList<T> - 双向链表。
      • 实现原理:每个节点包含数据和前后指针(链接到前一个和后一个节点),允许高效插入和删除操作。
    • 队列(Queue)Queue<T> - 先进先出(FIFO)。
      • 实现原理:通常基于环形数组或链表实现。基于数组时,当队列满时可能需要调整大小。
    • 栈(Stack)Stack<T> - 后进先出(LIFO)。
      • 实现原理:通常基于数组或链表实现,允许快速插入和删除操作(在数组末尾进行)。
    • 字典(Dictionary)Dictionary<TKey, TValue> - 键值对集合。
      • 实现原理:基于哈希表实现,使用哈希函数将键映射到桶中,处理冲突时常用链地址法(每个桶保存一个链表)。
    • 集合(HashSet)HashSet<T> - 不重复元素的无序集合。
      • 实现原理:基于哈希表实现,元素的哈希值用于快速查找、插入和删除操作。
    • 有序列表(SortedList)SortedList<TKey, TValue> - 按键排序的列表。
      • 实现原理:基于两个内部数组实现,一个用于存储键,一个用于存储对应的值。插入元素时,通过二分查找找到插入点,然后在数组中插入新元素,移动后续元素以保持有序性。查找、插入和删除操作的平均时间复杂度为 O(log n)(二分查找)+ O(n)(插入/删除元素时移动数组),最坏情况下的时间复杂度为 O(n)。
    • 有序字典(SortedDictionary)SortedDictionary<TKey, TValue> - 按键排序的字典。
      • 实现原理:基于红黑树(自平衡二叉搜索树)实现。红黑树是一种平衡二叉搜索树,插入、删除和查找操作的时间复杂度为 O(log n)。红黑树通过节点的颜色属性(红或黑)和旋转操作保持平衡,确保在最坏情况下操作的时间复杂度仍为 O(log n)。
  4. 特殊集合(Specialized Collections)

    • BitArray:处理比特位的数组。
      • 实现原理:使用整数数组来存储比特位。
    • Queue:非泛型版本的队列。
    • Stack:非泛型版本的栈。
  5. 并发集合(Concurrent Collections)

    • ConcurrentDictionary:线程安全的字典。
      • 实现原理:分段锁机制,使用锁或无锁编程技术(如基于 Interlocked 类的原子操作)实现线程安全。
    • ConcurrentQueue:线程安全的队列。
      • 实现原理:锁自由算法。
    • ConcurrentStack:线程安全的栈。
      • 实现原理:锁自由算法。
    • ConcurrentBag:线程安全的无序集合。
      • 实现原理:线程局部存储。
  6. 不可变集合(Immutable Collections)

    • ImmutableList:不可变列表。
      • 实现原理:结构共享来避免复制。
    • ImmutableDictionary:不可变字典。
      • 实现原理:结构共享来避免复制。
    • ImmutableHashSet:不可变哈希集。
      • 实现原理:结构共享来避免复制。
    • ImmutableQueue:不可变队列。
      • 实现原理:结构共享来避免复制。
    • ImmutableStack:不可变栈。
      • 实现原理:结构共享来避免复制。
  7. 其他数据结构

    • Tuple:多个元素的有序集合。
      • 实现原理:包含多个属性的类。
    • ValueTuple:值元组,轻量级元组。
      • 实现原理:结构(struct),包含多个字段。

解释:

红黑树:一种自平衡二叉搜索树,通过以下属性保持平衡:
每个节点是红色或黑色。
根节点是黑色。
每个叶子节点(NIL节点,表示空节点)是黑色。
如果一个节点是红色,则其两个子节点必须是黑色(即红节点不能连续)。
从任一节点到其每个叶子的所有路径都包含相同数量的黑色节点。
通过上述属性和旋转操作(左旋和右旋)保持树的平衡,确保插入、删除和查找操作的时间复杂度为 O(log n)。

实现原理的进一步解释:

  • 数组和列表:基本的动态数组会在当前容量满时,通常扩展为当前容量的两倍,以摊销的方式实现动态增长。
  • 哈希表:哈希函数将键映射到桶中,处理冲突的方法包括开放地址法和链地址法。哈希表通常在装载因子(元素数量与桶数量的比率)超过一定值时,重新调整大小。
  • 红黑树:一种自平衡二叉搜索树,通过节点颜色和旋转操作保证在最坏情况下依然有O(log n)的时间复杂度。
  • 链表:每个节点包含数据和指针,支持高效的插入和删除操作,尤其是当需要频繁在中间进行操作时。
  • 队列和栈:基于数组实现时,可以通过模运算实现环形数组。链表实现时,通过前后指针实现元素的快速插入和删除。

2. Unity 内存优化策略

在Unity中,优化内存的管理是确保游戏流畅运行和减少内存使用的重要步骤。以下是一些常见的内存优化策略:

  1. 资源管理
  • 使用对象池(Object Pooling): 对于需要频繁创建和销毁的对象,使用对象池来复用对象,减少GC(垃圾回收)的开销。
  • 精简资源: 删除未使用的资源,避免在游戏包中包含无用的资源。
  • 纹理压缩: 使用合适的纹理压缩格式(如ETC、ASTC、DXT等)来减少显存占用。
  • 优化网格(Mesh): 减少网格的多边形数,使用LOD(细节层次)技术。
  • 音频压缩: 使用合适的音频压缩格式(如MP3、OGG)并调整采样率。
  1. 内存管理
  • 管理堆内存和栈内存: 尽量避免频繁的内存分配和释放,可以使用对象池来复用对象。
  • 减少GC触发: 避免在Update等高频调用的函数中进行内存分配,尽量使用预分配的数组或列表。
  • 合理使用缓存: 缓存频繁访问的数据,避免重复计算或查找。
  1. 场景管理
  • 按需加载资源: 使用AddressableAssetBundle来按需加载和卸载资源。
  • 分割场景: 将大的场景分割成多个小场景,按需加载和卸载。
  • 异步加载: 使用异步加载资源和场景,避免在主线程中卡顿。
  1. 脚本优化
  • 避免内存泄漏: 确保事件订阅者正确地取消订阅,防止因持有对象引用而导致的内存泄漏。
  • 使用结构体(struct)而非类(class): 在性能关键代码中使用结构体来减少GC压力。
  • 优化脚本逻辑: 减少不必要的计算和循环,尽量避免在Update中进行复杂逻辑处理。
  1. 动画优化
  • 精简动画剪辑: 删除未使用的动画剪辑,合并相似的动画。
  • 使用Animator的参数驱动: 使用Animator参数而不是复杂的动画控制器。
  1. UI优化
  • 减少UI元素数量: 合理布局和组织UI元素,避免过多的UI层级。
  • 优化UI纹理: 使用合适的UI纹理压缩和图集打包。
  1. 使用Profiler工具
  • Unity Profiler: 定期使用Unity Profiler分析内存使用情况,找出内存泄漏和优化点。
  • Memory Profiler: 使用Memory Profiler深入分析内存分配和GC情况。
  1. 平台特定优化
  • 针对目标平台优化: 不同平台(如移动设备、PC、主机)有不同的内存限制和性能特点,针对目标平台进行优化。
  • 使用平台特定的优化工具和技术: 如使用Xcode的Instruments工具优化iOS平台的内存使用。

通过以上这些策略,开发者可以有效地优化Unity项目中的内存使用,提升游戏的性能和用户体验。

3. Unity UGUI 概述

UGUI(Unity Graphical User Interface)是Unity引擎的用户界面系统。它在Unity 4.6版本中引入,旨在提供一个更加灵活、高效和现代化的UI解决方案。UGUI允许开发者轻松创建各种UI元素,如按钮、文本、图片、滑动条等,并提供了丰富的布局和事件处理功能。

UGUI的主要组成部分

  1. Canvas
  • Canvas 是所有UI元素的根容器。所有UI元素必须放置在一个Canvas上。Canvas负责控制UI元素的绘制顺序和分辨率。
  • Canvas 渲染模式
    • Screen Space - Overlay:UI元素直接渲染到屏幕空间,通常用于全屏UI。
    • Screen Space - Camera:UI元素渲染在一个特定的摄像机空间,适用于3D场景中的UI。
    • World Space:UI元素作为3D对象存在于世界空间,允许UI与3D对象进行交互。
  1. RectTransform
  • RectTransform 是UGUI中所有UI元素的变换组件,它扩展了传统的Transform组件,提供了更强大的UI布局和对齐功能。
  • 锚点与对齐
    • 锚点:RectTransform允许UI元素通过锚点进行定位,锚点可以设置为父容器的不同部分。
    • 尺寸与对齐:可以通过RectTransform调整UI元素的尺寸,并设置其相对于锚点的位置。
  1. UI 组件
  • 常见UI组件
    • Text:用于显示文本内容。
    • Image:用于显示图片。
    • Button:用于创建按钮,支持点击事件。
    • Slider:用于创建滑动条,支持滑动事件。
    • Toggle:用于创建开关按钮,支持开关事件。
    • InputField:用于创建输入框,支持用户输入。
  1. 布局组件
  • 布局组件用于自动排列和调整UI元素的布局
    • Horizontal Layout Group:水平布局组,将子元素水平排列。
    • Vertical Layout Group:垂直布局组,将子元素垂直排列。
    • Grid Layout Group:网格布局组,将子元素按网格排列。
    • Content Size Fitter:根据内容自动调整UI元素的尺寸。
  1. 事件系统
  • UGUI 内置了强大的事件系统,支持各种用户交互事件
    • EventSystem:管理和处理UI事件的核心组件。
    • Standalone Input Module:处理传统鼠标和键盘输入。
    • Touch Input Module:处理触摸输入。
    • Event Triggers:用于在UI元素上添加各种事件监听器,如点击、拖动、悬停等。

创建和使用UGUI

  1. 创建Canvas

在Unity编辑器中,可以通过以下步骤创建Canvas:

  1. 右键点击层级视图(Hierarchy)。

  2. 选择 UI -> Canvas

  3. 创建的Canvas会自动包含一个 CanvasScaler 组件,用于设置UI的缩放模式和分辨率。

  4. 添加UI元素

在Canvas下,可以通过以下步骤添加各种UI元素:

  1. 右键点击Canvas。

  2. 选择 UI 来添加不同的UI元素,如 ButtonTextImage 等。

  3. 布局和对齐

使用 RectTransform 组件可以设置UI元素的锚点、位置、旋转和缩放。通过添加布局组件,如 Horizontal Layout GroupVertical Layout Group,可以自动排列和调整UI元素的布局。

  1. 事件处理

UGUI提供了事件触发器(Event Triggers),可以在UI元素上添加各种事件监听器,如 Pointer ClickPointer EnterPointer Exit 等。也可以直接在UI元素的组件中(如 ButtonOnClick 事件)添加回调函数来处理用户交互。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using UnityEngine.UI;

public class UIButtonHandler : MonoBehaviour
{
public Button myButton;

void Start()
{
// 为按钮添加点击事件监听器
myButton.onClick.AddListener(OnButtonClick);
}

void OnButtonClick()
{
Debug.Log("Button clicked!");
}
}

这段代码展示了如何在代码中为一个Button添加点击事件监听器,当按钮被点击时,触发 OnButtonClick 方法并输出日志信息。

UGUI 优化

  1. 使用图集:将多个UI纹理打包到一个图集中,减少绘制调用(Draw Call)。
  2. 分层Canvas:通过分层Canvas控制UI的显示和隐藏,提高渲染效率。
  3. 对象池:复用UI元素,减少内存分配和释放的开销。
  4. Canvas Group:使用Canvas Group来控制多个UI元素的透明度、交互性和可见性。

4. Unity Audio

在Unity中,处理音频文件是游戏开发的重要部分。音频文件的处理包括导入、配置、优化和播放音频。以下是详细的步骤和最佳实践。

  1. 导入音频文件

将音频文件(如WAV、MP3、OGG)拖拽到Unity的Assets文件夹中,Unity会自动将其导入项目。

  1. 配置音频文件

导入音频文件后,可以在Inspector窗口中配置其属性。

常见属性

  • AudioClip: 这是音频文件的实际数据。

  • Load Type: 决定音频数据的加载方式。

    • Decompress on Load: 在加载时解压缩音频,适用于较小的音频文件。
    • Compressed in Memory: 在内存中保留压缩的音频数据,适用于较大的音频文件。
    • Streaming: 从磁盘流式传输音频数据,适用于非常大的音频文件或背景音乐。
  • Compression Format: 决定音频文件的压缩格式。

    • PCM: 无损压缩,高质量,但占用空间大。
    • ADPCM: 有损压缩,适用于音效。
    • Vorbis: 有损压缩,适用于背景音乐和对文件大小有要求的情况。
  • Quality: 压缩音频的质量设置,影响文件大小和音质。

  • Sample Rate Setting: 采样率设置,通常选择“Optimize Sample Rate”以获得最佳效果。

  1. 创建音频源

为了播放音频,需要在场景中创建一个AudioSource组件。可以将AudioSource添加到任何GameObject上。

添加AudioSource组件

  1. 选择一个GameObject,点击Add Component
  2. 搜索AudioSource并添加它。

配置AudioSource组件

  • Audio Clip: 选择要播放的音频文件。
  • Play On Awake: 勾选后,当场景加载时自动播放音频。
  • Loop: 勾选后,音频将循环播放。
  • Volume: 音量设置,范围从0到1。
  • Pitch: 音调设置,默认值为1。
  • Spatial Blend: 空间混合,0为2D音频,1为3D音频。
  1. 播放音频

可以通过脚本控制音频的播放、暂停和停止。

示例代码

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
using UnityEngine;

public class AudioManager : MonoBehaviour
{
public AudioSource audioSource;
public AudioClip clip;

void Start()
{
// 设置音频剪辑
audioSource.clip = clip;
}

void Update()
{
// 按下空格键播放音频
if (Input.GetKeyDown(KeyCode.Space))
{
PlayAudio();
}

// 按下P键暂停音频
if (Input.GetKeyDown(KeyCode.P))
{
PauseAudio();
}

// 按下S键停止音频
if (Input.GetKeyDown(KeyCode.S))
{
StopAudio();
}
}

void PlayAudio()
{
audioSource.Play();
}

void PauseAudio()
{
audioSource.Pause();
}

void StopAudio()
{
audioSource.Stop();
}
}
  1. 优化音频文件

为了在Unity项目中有效地使用音频资源,以下是一些优化音频文件的建议:

  • 使用合适的格式: 对于音效,使用WAV或ADPCM格式;对于背景音乐,使用压缩的OGG或MP3格式。
  • 调整采样率: 降低采样率可以减少文件大小,但会影响音质。选择适合项目需求的采样率。
  • 音频剪辑长度: 尽量使用短音频剪辑。长音频文件可以考虑使用流媒体播放(Streaming)。
  • 避免冗余: 删除未使用的音频文件,以减少项目的体积。
  1. 高级功能

音频混音器(Audio Mixer)

Unity提供了音频混音器,用于更高级的音频管理和效果处理。

  1. 创建Audio Mixer:在Assets中右键点击选择Create -> Audio Mixer
  2. 配置Audio Mixer:通过Audio Mixer窗口配置音量、效果和路由。
  3. 关联AudioSource:在AudioSource组件中,将输出(Output)设置为创建的Audio Mixer。

3D音频设置

配置AudioSource的3D音频属性,可以实现逼真的音频效果。

  • Spatial Blend: 设置为1以启用3D音频。
  • 3D Sound Settings: 配置最小和最大距离、音量衰减曲线等。

通过以上步骤和优化方法,可以在Unity中高效地处理音频文件,为游戏和应用提供优质的音频体验。

5. delegate与event

在C#中,eventdelegate都是与事件驱动编程相关的重要概念。虽然它们在使用上有一定的关联,但它们有不同的作用和特性。以下是它们的区别和关联的详细说明:

1. delegate的定义和作用

委托(delegate)是一种类型安全的函数指针,能够存储对方法的引用。它允许方法作为参数传递,便于回调和事件处理。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个委托类型
public delegate void MyDelegate(string message);

// 定义一个方法,该方法的签名与委托类型兼容
public void MyMethod(string message)
{
Console.WriteLine(message);
}

// 使用委托
MyDelegate del = MyMethod;
del("Hello, World!"); // 输出: Hello, World!

2. event的定义和作用

事件(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
25
26
27
28
29
30
31
32
33
34
35
// 定义一个委托类型
public delegate void MyEventHandler(string message);

// 定义一个包含事件的类
public class MyPublisher
{
// 声明事件
public event MyEventHandler MyEvent;

// 触发事件的方法
public void RaiseEvent(string message)
{
// 触发事件
MyEvent?.Invoke(message);
}
}

// 定义一个订阅者类
public class MySubscriber
{
public void OnMyEventRaised(string message)
{
Console.WriteLine($"Event received: {message}");
}
}

// 使用事件
MyPublisher publisher = new MyPublisher();
MySubscriber subscriber = new MySubscriber();

// 订阅事件
publisher.MyEvent += subscriber.OnMyEventRaised;

// 触发事件
publisher.RaiseEvent("Hello, Event!"); // 输出: Event received: Hello, Event!

3. 区别与关联

区别:

  • 定义和使用delegate用于定义类型,可以存储方法引用并直接调用。event是对委托的封装,用于事件机制,限制了外部对委托实例的直接操作。
  • 访问控制delegate实例可以被外部代码直接调用,而event只能在声明它的类中触发,外部代码只能订阅或取消订阅。
  • 安全性event提供了对委托的更严格的控制,防止外部代码错误地触发或修改事件。

关联:

  • 事件使用委托event实际上是委托的一个包装。定义事件时,需要先定义一个委托类型。
  • 委托用于回调和事件:委托可以用于回调函数,也可以作为事件机制的一部分。
  • 事件的本质:从底层实现来看,事件依赖于委托来存储订阅的方法列表。

通过理解delegateevent的区别和关联,可以更好地利用C#语言特性来实现复杂的事件驱动编程模式。

6. 协程

在Unity中,协程(Coroutine)是用于在多个帧中分步执行代码的方法。它允许你暂停代码的执行,然后在后续帧中继续执行,非常适合处理需要延迟、等待或分步进行的操作,如动画、异步操作或时间控制。

1. 定义和使用协程

协程通常通过C#中的IEnumerator接口来实现,并使用StartCoroutine方法来启动。协程可以在任何继承自MonoBehaviour的类中定义和使用。

基本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using System.Collections;

public class CoroutineExample : MonoBehaviour
{
void Start()
{
// 启动协程
StartCoroutine(MyCoroutine());
}

IEnumerator MyCoroutine()
{
Debug.Log("Coroutine started");
yield return new WaitForSeconds(2); // 等待2秒
Debug.Log("Coroutine resumed after 2 seconds");
}
}

在这个示例中,MyCoroutine协程在启动后会立即打印”Coroutine started”,然后暂停2秒钟,最后继续执行并打印”Coroutine resumed after 2 seconds”。

2. yield return的用法

协程的核心在于yield return语句,它可以用来暂停协程的执行并返回控制权。常见的yield return用法包括:

  • 等待指定时间

    1
    yield return new WaitForSeconds(1.0f); // 等待1秒
  • 等待下一帧

    1
    yield return null; // 等待下一帧
  • 等待另一个协程完成

    1
    yield return StartCoroutine(AnotherCoroutine());
  • 等待特定条件

    1
    yield return new WaitUntil(() => someCondition); // 等待直到条件为真
  • 等待某一条件为假

    1
    yield return new WaitWhile(() => someCondition); // 等待直到条件为假

3. 停止协程

协程可以通过多种方式停止:

  • **使用StopCoroutine**:

    1
    2
    3
    4
    // 停止指定的协程
    IEnumerator myCoroutine = MyCoroutine();
    StartCoroutine(myCoroutine);
    StopCoroutine(myCoroutine);
  • **使用StopAllCoroutines**:

    1
    2
    // 停止当前MonoBehaviour上的所有协程
    StopAllCoroutines();
  • 通过条件在协程内停止

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    IEnumerator MyCoroutine()
    {
    while (true)
    {
    if (someCondition)
    {
    yield break; // 退出协程
    }
    yield return null;
    }
    }

4. 协程的实际应用

协程在游戏开发中有广泛的应用,以下是一些常见的例子:

  • 动画和特效:通过协程控制动画播放和特效展示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    IEnumerator Animate()
    {
    for (float t = 0; t < 1; t += Time.deltaTime / duration)
    {
    transform.position = Vector3.Lerp(startPosition, endPosition, t);
    yield return null;
    }
    transform.position = endPosition;
    }
  • 加载和异步操作:处理异步加载资源或等待下载完成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    IEnumerator LoadResource(string url)
    {
    UnityWebRequest request = UnityWebRequest.Get(url);
    yield return request.SendWebRequest();
    if (request.result == UnityWebRequest.Result.Success)
    {
    Debug.Log("Download complete");
    }
    else
    {
    Debug.Log("Download failed");
    }
    }
  • 时间控制和延迟:在特定时间后执行某些操作。

    1
    2
    3
    4
    5
    IEnumerator DelayedAction(float delay)
    {
    yield return new WaitForSeconds(delay);
    DoSomething();
    }

5. 性能和注意事项

  • 协程的性能:协程本质上是轻量级的,但大量使用协程仍可能影响性能,特别是在频繁创建和销毁协程的情况下。
  • 内存管理:协程不会自动清理,需要确保在不需要时正确停止以防止内存泄漏。
  • 错误处理:在协程内捕获和处理异常,否则可能导致未处理的异常影响游戏运行。

协程是Unity中强大且灵活的工具,合理使用它们可以显著简化异步操作和时间控制的实现,使游戏开发更加高效。

7. MonoBehavior生命周期

在Unity中,MonoBehaviour是所有脚本的基类,它提供了一系列生命周期函数,用于管理游戏对象的创建、更新和销毁等过程。这些函数在游戏对象的不同阶段被Unity自动调用。以下是MonoBehaviour生命周期函数的详细介绍:

1. 初始化阶段

  • **Awake()**:

    • 在脚本实例被加载时调用。适用于初始化脚本中的数据或状态。
    • 只调用一次,不受脚本的启用/禁用状态影响。
    • 在其他任何函数(如Start)之前调用。
    1
    2
    3
    4
    5
    void Awake()
    {
    // 初始化逻辑
    Debug.Log("Awake called");
    }
  • **OnEnable()**:

    • 当脚本被启用时调用。如果脚本在游戏开始时已经启用,则在Awake之后调用。
    • 可以多次调用,每次启用时都会执行。
    1
    2
    3
    4
    void OnEnable()
    {
    Debug.Log("OnEnable called");
    }
  • **Start()**:

    • 在脚本启用后的第一帧更新之前调用。适用于需要在所有AwakeOnEnable调用后初始化的逻辑。
    • 只调用一次,且仅在脚本启用时调用。
    1
    2
    3
    4
    void Start()
    {
    Debug.Log("Start called");
    }

2. 更新阶段

  • **Update()**:

    • 每帧调用一次,用于处理常规的更新逻辑,如输入检测和游戏对象的非物理运动。
    • 调用频率依赖于帧率。
    1
    2
    3
    4
    5
    void Update()
    {
    // 每帧更新逻辑
    Debug.Log("Update called");
    }
  • **FixedUpdate()**:

    • 在固定时间间隔调用,用于处理物理相关的更新逻辑,如物理引擎的模拟。
    • 调用频率与物理引擎的时间步长一致(默认为0.02秒)。
    1
    2
    3
    4
    5
    void FixedUpdate()
    {
    // 物理更新逻辑
    Debug.Log("FixedUpdate called");
    }
  • **LateUpdate()**:

    • 在每帧的所有Update函数调用之后调用,适用于需要在所有更新完成后执行的逻辑,如跟随摄像机的移动。
    • 用于确保其他对象的更新已完成。
    1
    2
    3
    4
    5
    void LateUpdate()
    {
    // 后期更新逻辑
    Debug.Log("LateUpdate called");
    }

3. 渲染阶段

  • **OnPreCull()**:

    • 在相机裁剪场景之前调用,可以在此对相机做额外的设置。
    1
    2
    3
    4
    void OnPreCull()
    {
    Debug.Log("OnPreCull called");
    }
  • **OnBecameVisible()**:

    • 当对象变得可见时调用,适用于当对象在视野中时执行的逻辑。
    1
    2
    3
    4
    void OnBecameVisible()
    {
    Debug.Log("OnBecameVisible called");
    }
  • **OnBecameInvisible()**:

    • 当对象变得不可见时调用,适用于当对象不在视野中时执行的逻辑。
    1
    2
    3
    4
    void OnBecameInvisible()
    {
    Debug.Log("OnBecameInvisible called");
    }

4. 销毁阶段

  • **OnDisable()**:

    • 当脚本被禁用时调用,用于处理禁用脚本时需要执行的逻辑,如取消订阅事件等。
    1
    2
    3
    4
    void OnDisable()
    {
    Debug.Log("OnDisable called");
    }
  • **OnDestroy()**:

    • 当对象被销毁时调用,用于处理对象销毁时需要执行的清理逻辑。
    1
    2
    3
    4
    void OnDestroy()
    {
    Debug.Log("OnDestroy called");
    }

5. 碰撞检测和触发

  • **OnCollisionEnter()**:

    • 当碰撞开始时调用,用于处理物理碰撞逻辑。
    1
    2
    3
    4
    void OnCollisionEnter(Collision collision)
    {
    Debug.Log("Collision detected with " + collision.gameObject.name);
    }
  • **OnTriggerEnter()**:

    • 当触发器被激活时调用,用于处理触发器逻辑。
    1
    2
    3
    4
    void OnTriggerEnter(Collider other)
    {
    Debug.Log("Trigger entered by " + other.gameObject.name);
    }

6. GUI相关

  • **OnGUI()**:

    • 用于绘制和处理GUI事件,每帧都会调用。应避免在此方法中进行复杂的逻辑处理,因为它在每帧调用多次。
    1
    2
    3
    4
    void OnGUI()
    {
    GUI.Label(new Rect(10, 10, 100, 20), "Hello, World!");
    }

8. LZ4与LZMA

LZ4和LZMA是两种常见的压缩算法,各有其优缺点,适用于不同的使用场景。以下是它们在几个关键方面的对比:

1. 压缩率

  • LZ4:压缩率较低。LZ4的设计目标是实现非常快的压缩和解压缩速度,因此在压缩率上有所妥协。通常情况下,LZ4的压缩率大约在2:1到3:1之间。
  • LZMA:压缩率较高。LZMA(Lempel-Ziv-Markov chain Algorithm)使用了更复杂的算法,能够实现更高的压缩率,通常在5:1到7:1之间,有时甚至更高。

2. 压缩和解压速度

  • LZ4:非常快。LZ4的主要优点是其极高的压缩和解压缩速度,特别适合对速度要求高的场景,比如实时数据传输、游戏数据压缩等。
  • LZMA:较慢。LZMA的压缩和解压缩速度都较慢,特别是压缩速度,因为它需要更多的计算来实现更高的压缩率。解压速度相对较快,但仍不如LZ4。

3. 内存使用

  • LZ4:内存占用较低。LZ4算法的设计使得它在压缩和解压过程中都能保持较低的内存使用,这对于嵌入式系统或资源受限的环境非常重要。
  • LZMA:内存占用较高。LZMA为了达到更高的压缩率,使用了更复杂的数据结构和算法,因此需要更多的内存,这在资源受限的环境中可能成为一个问题。

4. 复杂性

  • LZ4:简单。LZ4的算法实现相对简单,容易集成和使用,这也是其被广泛采用的一个重要原因。
  • LZMA:复杂。LZMA的算法实现复杂,使用了多种高级技术(如熵编码、Markov链等),实现和优化起来相对困难。

5. 应用场景

  • LZ4:适用于需要快速压缩和解压的场景,比如实时数据压缩、日志压缩、网络传输等。
  • LZMA:适用于需要高压缩率且不太在意压缩速度的场景,比如软件分发、备份存储等。

总结

  • 如果你的应用场景对速度要求较高且能够容忍较低的压缩率,那么LZ4是一个很好的选择。
  • 如果你的应用场景需要高压缩率且可以接受较长的压缩时间,那么LZMA是一个更合适的选择。

根据具体的需求选择合适的压缩算法,可以在性能和存储效率之间找到最佳的平衡。
这些生命周期函数帮助开发者控制脚本在不同阶段的行为,通过合理利用这些函数,可以构建复杂的游戏逻辑,管理对象的状态和行为。

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2024/05/c3c1723eef0a/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道