常见的设计模式可以分为三大类:创建型模式、结构型模式和行为型模式。以下是这些设计模式的原理和实现:
一、创建型模式
1. 单例模式(Singleton Pattern)
原理:保证一个类只有一个实例,并提供全局访问点。 实现:
public class Singleton
{
private static Singleton instance;
private static readonly object lockObj = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
}
2. 工厂模式(Factory Pattern)
原理:定义一个接口或基类,由子类决定实例化哪个类。 实现:
public abstract class Product { }
public class ConcreteProductA : Product { }
public class ConcreteProductB : Product { }
public class Factory
{
public static Product CreateProduct(string type)
{
switch (type)
{
case "A": return new ConcreteProductA();
case "B": return new ConcreteProductB();
default: throw new ArgumentException("Invalid type");
}
}
}
3. 抽象工厂模式(Abstract Factory Pattern)
原理:提供一个接口创建一系列相关或相互依赖的对象,而无需指定具体类。 实现:
public abstract class AbstractProductA { }
public abstract class AbstractProductB { }
public class ConcreteProductA1 : AbstractProductA { }
public class ConcreteProductB1 : AbstractProductB { }
public abstract class AbstractFactory
{
public abstract AbstractProductA CreateProductA();
public abstract AbstractProductB CreateProductB();
}
public class ConcreteFactory1 : AbstractFactory
{
public override AbstractProductA CreateProductA() => new ConcreteProductA1();
public override AbstractProductB CreateProductB() => new ConcreteProductB1();
}
4. 建造者模式(Builder Pattern)
原理:将复杂对象的构建与其表示分离。 实现:
public class Product
{
public string PartA { get; set; }
public string PartB { get; set; }
}
public abstract class Builder
{
protected Product product = new Product();
public abstract void BuildPartA();
public abstract void BuildPartB();
public Product GetResult() => product;
}
public class ConcreteBuilder : Builder
{
public override void BuildPartA() => product.PartA = "PartA";
public override void BuildPartB() => product.PartB = "PartB";
}
二、结构型模式
5. 适配器模式(Adapter Pattern)
原理:将一个类的接口转换为客户希望的接口。 实现:
public interface ITarget
{
void Request();
}
public class Adaptee
{
public void SpecificRequest() { }
}
public class Adapter : ITarget
{
private Adaptee adaptee = new Adaptee();
public void Request() => adaptee.SpecificRequest();
}
6. 装饰器模式(Decorator Pattern)
原理:动态地给一个对象添加新的职责。 实现:
public abstract class Component
{
public abstract void Operation();
}
public class ConcreteComponent : Component
{
public override void Operation() => Console.WriteLine("ConcreteComponent Operation");
}
public class Decorator : Component
{
protected Component component;
public Decorator(Component component) => this.component = component;
public override void Operation()
{
component.Operation();
Console.WriteLine("Decorator Added Behavior");
}
}
7. 代理模式(Proxy Pattern)
原理:为其他对象提供代理以控制访问。 实现:
public interface ISubject
{
void Request();
}
public class RealSubject : ISubject
{
public void Request() => Console.WriteLine("RealSubject Request");
}
public class Proxy : ISubject
{
private RealSubject realSubject;
public void Request()
{
if (realSubject == null)
realSubject = new RealSubject();
realSubject.Request();
}
}
三、行为型模式
8. 观察者模式(Observer Pattern)
原理:定义对象间的依赖关系,当一个对象状态改变时通知所有依赖者。 实现:
public interface IObserver
{
void Update();
}
public class Subject
{
private List<IObserver> observers = new List<IObserver>();
public void Attach(IObserver observer) => observers.Add(observer);
public void Notify()
{
foreach (var observer in observers)
observer.Update();
}
}
public class ConcreteObserver : IObserver
{
public void Update() => Console.WriteLine("Observer Updated");
}
9. 策略模式(Strategy Pattern)
原理:定义一系列算法,将每种算法封装起来,使它们可以互换。 实现:
public interface IStrategy
{
void Execute();
}
public class ConcreteStrategyA : IStrategy
{
public void Execute() => Console.WriteLine("Strategy A");
}
public class Context
{
private IStrategy strategy;
public Context(IStrategy strategy) => this.strategy = strategy;
public void ExecuteStrategy() => strategy.Execute();
}
10. 状态模式(State Pattern)
原理:允许对象在内部状态改变时改变行为。 实现:
public interface IState
{
void Handle(Context context);
}
public class ConcreteStateA : IState
{
public void Handle(Context context)
{
Console.WriteLine("State A");
context.State = new ConcreteStateB();
}
}
public class ConcreteStateB : IState
{
public void Handle(Context context) => Console.WriteLine("State B");
}
public class Context
{
public IState State { get; set; }
public void Request() => State.Handle(this);
}
以上是常见设计模式的核心概念及代码实现,具体使用根据场景灵活选择。
设计模式快速理解:
设计模式可以简单理解为编程中的“套路”,就像盖房子有不同的方法,编程也有一些解决常见问题的通用方法。这些方法可以让代码更清晰、更好维护。以下用生活中的例子解释一些常见的设计模式:
1. 单例模式(Singleton)
概念:全世界只需要一个实例,比如总统。 例子:一个国家只能有一个总统,所有人都通过同一个渠道找总统。 在代码中:一个对象只能被创建一次,所有地方共享这个对象。
2. 工厂模式(Factory)
概念:用一个工厂生产你需要的东西,而不用自己造。 例子:去披萨店点披萨,你告诉店员要哪种披萨,店员去做,最后给你成品。 在代码中:把创建对象的逻辑封装起来,根据需求返回对应的对象。
3. 抽象工厂模式(Abstract Factory)
概念:多个工厂组成的体系,可以生产一系列相关的产品。 例子:家具公司 A 生产椅子和桌子,家具公司 B 也生产椅子和桌子,但风格不同。你可以选择“公司 A”或“公司 B”,然后得到配套家具。 在代码中:根据选择的工厂,返回成套的相关产品。
4. 建造者模式(Builder)
概念:一步步地建造一个复杂的东西。 例子:建造一辆车,有专门的人负责装轮子、装引擎、装座椅,最后整合成一辆车。 在代码中:将复杂对象的构建过程分成不同的步骤,每一步单独处理。
5. 适配器模式(Adapter)
概念:让本来不兼容的东西可以一起工作。 例子:充电头适配器,可以让国外买的电器插头插入国内的插座。 在代码中:转换一个类的接口,适配到另一个接口上。
6. 装饰器模式(Decorator)
概念:在不改变原来对象的情况下,动态地给它增加新功能。 例子:买了一杯咖啡,你可以额外加糖、加奶,但咖啡本身没有变。 在代码中:通过包装的方式给对象添加新功能。
7. 代理模式(Proxy)
概念:给某个对象加一个代理,控制对它的访问。 例子:你想租房子,但不想直接和房东打交道,于是找了一个中介。中介就是代理。 在代码中:代理对象可以帮你做一些额外的工作,比如控制权限。
8. 观察者模式(Observer)
概念:当一个对象发生变化时,自动通知相关对象。 例子:订阅微信公众号,公众号一有新文章,就会通知你。 在代码中:一个对象(被观察者)变化时,通知多个依赖它的对象(观察者)。
9. 策略模式(Strategy)
概念:根据情况选择不同的解决方案。 例子:导航软件根据是自驾还是骑车,规划不同的路线。 在代码中:一组算法根据需求切换使用。
10. 状态模式(State)
概念:根据对象当前的状态,执行不同的行为。 例子:电梯在不同的楼层有不同的操作:停在某层、上下移动、开门等。 在代码中:对象状态改变时,它的行为也会随之改变。
这些模式的目的是降低复杂性、提高代码复用性和可维护性,不同模式适合不同的场景。理解它们不用死记硬背,可以多想生活中的类似场景,再结合代码学习。
状态机:
状态机(State Machine)
通俗解释:
状态机就像一个机器人根据不同的“状态”执行不同的“动作”。每次状态改变,机器人的行为也会随之变化。
例子:
-
交通信号灯
-
状态:红灯、绿灯、黄灯
-
行为:每个状态持续一定时间,然后切换到下一个状态。
-
-
游戏角色
-
状态:站立、跑步、跳跃、攻击、死亡
-
行为:根据玩家的操作切换状态。例如,按跳跃键时从“站立”状态切换到“跳跃”状态。
-
核心概念:
-
状态(State):系统中某个时刻的状态。
-
转换(Transition):状态之间的切换规则。
-
事件(Event):触发状态转换的条件。
实现示例:
public enum State
{
Idle,
Running,
Jumping
}
public class StateMachine
{
private State currentState = State.Idle;
public void ChangeState(State newState)
{
currentState = newState;
Console.WriteLine($"Current state: {currentState}");
}
}
// 使用
var stateMachine = new StateMachine();
stateMachine.ChangeState(State.Running);
stateMachine.ChangeState(State.Jumping);
适用场景:
-
游戏角色的行为逻辑(比如:玩家动作、敌人 AI)。
-
设备控制系统(比如:洗衣机的工作流程)。
-
UI 状态切换(比如:按钮的不同状态——普通、悬停、点击)。
行为树(Behavior Tree)
通俗解释:
行为树是一种更灵活的逻辑管理方式,像“决策树”,通过树形结构决定行为的执行顺序。它特别适合用于实现复杂的 AI 行为。
例子:
-
游戏中的敌人 AI
-
敌人的行为可能是:
-
如果玩家在视野内 → 追逐玩家。
-
如果玩家不在视野内 → 巡逻。
-
如果受到攻击 → 还击。
-
-
这些行为通过行为树分层管理,可以轻松调整和扩展。
-
-
机器人任务
-
检查电量是否充足。
-
如果电量不足 → 去充电。
-
如果充满电 → 继续工作。
-
核心概念:
-
节点(Node):行为树的基本单元。
-
叶节点:执行具体的行为(如移动、攻击)。
-
组合节点:控制行为的执行顺序(如选择、顺序)。
-
-
根节点(Root):行为树的起点。
-
逻辑控制:
-
顺序节点:依次执行子节点,直到某个失败。
-
选择节点:尝试执行子节点,直到某个成功。
-
示例行为树结构:
mathematica复制代码Root
├── Sequence (巡逻)
│ ├── CheckEnergy
│ ├── MoveToPatrolArea
├── Selector (战斗)
├── IsPlayerInRange
├── AttackPlayer
代码实现示例:
public abstract class Node
{
public abstract bool Execute();
}
public class ActionNode : Node
{
private readonly Func<bool> action;
public ActionNode(Func<bool> action)
{
this.action = action;
}
public override bool Execute() => action();
}
public class SelectorNode : Node
{
private readonly List<Node> children;
public SelectorNode(List<Node> children)
{
this.children = children;
}
public override bool Execute()
{
foreach (var child in children)
{
if (child.Execute())
return true;
}
return false;
}
}
适用场景:
-
游戏 AI 逻辑(如敌人行为、NPC 任务分配)。
-
自动化流程管理(如机器人决策树)。
-
对复杂的条件逻辑进行分层处理,简化代码结构。
状态机 vs 行为树
特点 | 状态机 | 行为树 |
---|---|---|
结构 | 固定的状态和切换逻辑 | 树形结构,可灵活扩展 |
复杂度 | 随状态数量增加,复杂度上升 | 适合复杂逻辑,易扩展 |
适用场景 | 简单的逻辑(比如 UI 状态) | 复杂的 AI 或多条件决策 |
优点 | 简单直观 | 灵活强大,支持分层管理 |
缺点 | 扩展性差,状态多时易混乱 | 逻辑复杂,理解成本较高 |
如果逻辑较简单,用状态机更合适;如果需要复杂行为控制,用行为树更好。
Unity初始化:
初始化是编程中的一个常见术语,指的是在程序开始时为变量、对象或系统设置一个初始状态,使其能够正常运行。在 Unity 开发中,初始化通常指在游戏开始时设置对象、场景或系统的初始状态。
通俗解释
-
初始化就像开一家餐厅营业前的准备工作。
-
服务员站在自己的岗位。
-
-
厨房的材料准备好。
-
一切就绪后,餐厅才能正式接待客人。 在程序中,这些准备工作就是“初始化”。
-
Unity 开发中初始化的常见场景
1. 对象初始化
-
为脚本中的变量赋初始值。
-
例如,一个敌人角色需要初始化它的生命值、攻击力、位置等。
public class Enemy : MonoBehaviour
{
public int health;
public int attackPower;
void Start()
{
// 初始化属性
health = 100;
attackPower = 20;
Debug.Log("敌人已初始化");
}
}
2. 场景初始化
-
游戏开始时,设置场景中所有的物体状态。
-
例如,加载玩家角色、初始化灯光、设置摄像机位置等。
public class GameManager : MonoBehaviour
{
void Start()
{
// 初始化场景
SpawnPlayer();
SetupCamera();
}
void SpawnPlayer()
{
// 加载玩家角色到初始位置
Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
}
void SetupCamera()
{
// 设置摄像机跟随玩家
Camera.main.transform.position = new Vector3(0, 10, -10);
}
}
3. 系统或全局数据初始化
-
比如设置全局的游戏配置、加载存档数据等。
-
通常在游戏开始时完成。
public class GlobalSettings
{
public static int difficultyLevel;
public static string playerName;
public static void Initialize()
{
// 设置默认值
difficultyLevel = 1;
playerName = "Guest";
}
}
4. 使用 Unity 生命周期方法的初始化
Unity 提供了一些生命周期方法,常用于初始化操作:
-
Awake
:-
在脚本实例化时调用,优先于其他方法。
-
用于初始化与其他脚本无关的内容。
-
void Awake()
{
Debug.Log("Awake: 初始化独立逻辑");
}
-
Start
:-
在游戏开始的第一帧调用,通常用于初始化需要与其他对象交互的内容。
-
void Start()
{
Debug.Log("Start: 初始化依赖其他对象的逻辑");
}
-
OnEnable
和OnDisable
:-
用于在对象激活/停用时初始化或清理状态。
-
void OnEnable()
{
Debug.Log("对象激活时初始化逻辑");
}
Unity 初始化的常见注意点
-
初始化顺序:
-
如果多个脚本互相依赖,确保依赖的脚本已经初始化。
-
使用
Awake
和Start
时注意:Awake
先于Start
执行。
-
-
性能问题:
-
初始化操作尽量简化,避免影响游戏启动速度。
-
对于复杂的初始化操作,可以异步加载(如加载大文件或资源)。
-
-
默认值和动态配置:
-
设置合理的默认值,避免忘记初始化导致错误。
-
允许通过配置文件或外部数据修改初始化参数。
-
-
对象池初始化:
-
对于频繁生成和销毁的对象(如子弹、敌人),可以在开始时预先初始化一批对象,提高运行效率。
-
总结
在 Unity 中,初始化就是为游戏的运行做好“准备工作”。
-
简单来说:把游戏中的角色、场景或系统按照预期状态“摆放整齐”,就可以让后续逻辑顺利运行。
-
充分理解 Unity 的生命周期(
Awake
、Start
等)可以帮助更高效地完成初始化。
unity生命周期:
在 Unity 中,生命周期是指一个脚本或对象从创建到销毁过程中会自动触发的一系列方法。这些方法的执行顺序和作用非常重要,掌握它们有助于编写高效、清晰的代码。
Unity 的生命周期方法概览
Unity 的生命周期方法可以分为以下几个阶段:
1. 初始化阶段
Awake
-
触发时机:脚本实例化时(无论对象是否激活)。
-
用途:
-
初始化独立于其他对象的逻辑。
-
通常用于加载资源、初始化变量。
-
-
注意:
Awake
比Start
更早执行。
void Awake()
{
Debug.Log("Awake: 初始化对象");
}
OnEnable
-
触发时机:对象被激活时调用(包括游戏启动时激活的对象)。
-
用途:
-
用于注册事件监听器或重置状态。
-
-
注意:每次对象从禁用状态切换到启用状态时都会调用。
void OnEnable()
{
Debug.Log("OnEnable: 对象被激活");
}
Start
-
触发时机:游戏开始时的第一帧调用,且在所有
Awake
方法调用之后。 -
用途:
-
初始化需要依赖其他对象的逻辑。
-
-
注意:只调用一次。
void Start()
{
Debug.Log("Start: 初始化需要其他对象的逻辑");
}
2. 游戏运行阶段
Update
-
触发时机:每帧调用一次。
-
用途:
-
实现需要持续更新的逻辑,例如:玩家输入、移动逻辑、计时器等。
-
-
注意:逻辑较复杂时要优化,避免性能问题。
void Update()
{
Debug.Log("Update: 每帧调用一次");
}
FixedUpdate
-
触发时机:按照固定时间间隔调用(与帧率无关)。
-
用途:
-
用于物理模拟(如刚体运动)。
-
-
注意:适合需要精确时间间隔的逻辑。
void FixedUpdate()
{
Debug.Log("FixedUpdate: 用于物理逻辑");
}
LateUpdate
-
触发时机:每帧在
Update
之后调用。 -
用途:
-
处理需要依赖
Update
完成的逻辑,例如摄像机跟随。
-
-
注意:通常用于后续调整。
void LateUpdate()
{
Debug.Log("LateUpdate: 用于后续调整");
}
3. 渲染阶段
OnPreCull
-
触发时机:摄像机裁剪(Cull)场景之前调用。
-
用途:
-
修改摄像机渲染前的设置。
-
void OnPreCull()
{
Debug.Log("OnPreCull: 摄像机裁剪之前");
}
OnBecameVisible
/ OnBecameInvisible
-
触发时机:物体进入或离开摄像机视野时调用。
-
用途:
-
优化逻辑,如仅在物体可见时运行某些代码。
-
void OnBecameVisible()
{
Debug.Log("物体进入摄像机视野");
}
OnRenderObject
-
触发时机:每次渲染对象时调用。
-
用途:
-
自定义渲染逻辑。
-
void OnRenderObject()
{
Debug.Log("渲染对象");
}
4. 暂停和恢复阶段
OnApplicationPause
-
触发时机:游戏暂停或恢复时调用(例如切换应用、锁屏)。
-
用途:
-
保存游戏状态或暂停背景任务。
-
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
Debug.Log("游戏暂停");
else
Debug.Log("游戏恢复");
}
5. 停止阶段
OnDisable
-
触发时机:对象被禁用时调用。
-
用途:
-
释放资源、注销事件监听器。
-
void OnDisable()
{
Debug.Log("OnDisable: 对象被禁用");
}
OnDestroy
-
触发时机:对象销毁时调用。
-
用途:
-
清理内存、释放资源。
-
void OnDestroy()
{
Debug.Log("OnDestroy: 对象被销毁");
}
6. 其他特殊生命周期方法
OnCollisionEnter
/ OnCollisionStay
/ OnCollisionExit
-
触发时机:物体碰撞时调用(需要 Collider 和刚体)。
-
用途:
-
实现碰撞逻辑,例如扣血、击退。
-
void OnCollisionEnter(Collision collision)
{
Debug.Log("发生碰撞");
}
OnTriggerEnter
/ OnTriggerStay
/ OnTriggerExit
-
触发时机:物体进入或离开触发器区域时调用。
-
用途:
-
检测区域逻辑,例如玩家进入安全区。
-
void OnTriggerEnter(Collider other)
{
Debug.Log("进入触发器");
}
OnGUI
-
触发时机:每帧绘制 UI 元素时调用。
-
用途:
-
用于绘制简单的调试 UI。
-
-
注意:新项目中尽量用 Unity 的 UI 系统代替。
void OnGUI()
{
GUILayout.Label("调试信息");
}
生命周期执行顺序简要总结
-
对象初始化:
Awake
→OnEnable
→Start
-
运行更新:
Update
→FixedUpdate
→LateUpdate
-
渲染:
OnPreCull
→ 渲染逻辑(如OnRenderObject
) -
销毁:
OnDisable
→OnDestroy
总结
Unity 的生命周期方法提供了对游戏逻辑的全面控制。
-
初始化相关:
Awake
和Start
-
运行时更新:
Update
、FixedUpdate
和LateUpdate
-
渲染和物理:
OnRenderObject
、OnCollisionEnter
等 -
销毁相关:
OnDisable
和OnDestroy
根据需要选择合适的方法可以让代码逻辑更清晰,也更高效。
对象池:
对象池(Object Pool)
概念
对象池是一种优化技术,用来管理和复用频繁创建和销毁的对象,以减少内存分配和垃圾回收的开销,从而提升性能。
通俗解释
-
不使用对象池的情况:
-
每次需要一个对象,就新建一个。
-
不需要时就销毁,但销毁和重新创建会消耗性能。
-
-
使用对象池的情况:
-
提前创建一批对象(比如 10 个子弹)。
-
当需要用到时,从池子里拿出来,用完后放回池子。
-
避免频繁地创建和销毁对象。
-
现实中的例子: 餐厅的座位就是一种“池子”。你不需要每来一个客人就造一把新椅子,而是重复使用现有的椅子。
作用和优点
-
减少内存分配和回收开销:
-
对象池通过重复使用对象,避免了频繁的创建和销毁。
-
-
提升性能:
-
特别是在需要频繁创建和销毁对象的场景下(如子弹、敌人、粒子特效)。
-
-
提高稳定性:
-
避免频繁的垃圾回收(GC)导致游戏卡顿。
-
适用场景
-
游戏中频繁创建和销毁的对象:
-
子弹、敌人、特效等。
-
-
高频率出现的逻辑:
-
玩家操作触发的特效(如点击、爆炸特效)。
-
-
性能敏感的场景:
-
高帧率游戏(如 60 FPS)中。
-
对象池的原理
核心思想:
-
初始化阶段:预先创建一批对象并存储在池中。
-
获取对象:当需要用到对象时,从池中取出一个。
-
释放对象:当对象用完后,重置状态并放回池中。
关键步骤:
-
创建池:一个用于存储对象的容器(通常是一个
List
或Queue
)。 -
对象复用:
-
如果池中有可用对象,直接取出。
-
如果池为空,可以选择扩展池或创建新对象。
-
-
放回池:用完对象后,重置其状态并存回池中。
实践:Unity 中实现对象池
1. 创建一个通用对象池类
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool<T> where T : MonoBehaviour
{
private T prefab;
private Queue<T> pool = new Queue<T>();
public ObjectPool(T prefab, int initialSize)
{
this.prefab = prefab;
for (int i = 0; i < initialSize; i++)
{
var obj = GameObject.Instantiate(prefab);
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
// 获取对象
public T Get()
{
if (pool.Count > 0)
{
var obj = pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
else
{
var obj = GameObject.Instantiate(prefab);
return obj;
}
}
// 释放对象
public void Release(T obj)
{
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
2. 使用对象池管理子弹
创建一个子弹类 Bullet
:
using UnityEngine;
public class Bullet : MonoBehaviour
{
public float speed = 10f;
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
// 自动回收子弹
if (transform.position.z > 20)
{
BulletManager.Instance.ReleaseBullet(this);
}
}
}
3. 子弹管理器
管理子弹的生成和回收。
using UnityEngine;
public class BulletManager : MonoBehaviour
{
public static BulletManager Instance { get; private set; }
public Bullet bulletPrefab;
private ObjectPool<Bullet> bulletPool;
void Awake()
{
Instance = this;
bulletPool = new ObjectPool<Bullet>(bulletPrefab, 10);
}
public Bullet GetBullet()
{
return bulletPool.Get();
}
public void ReleaseBullet(Bullet bullet)
{
bulletPool.Release(bullet);
}
}
4. 测试对象池
在场景中创建一个脚本,用于发射子弹。
using UnityEngine;
public class Player : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// 从对象池获取子弹
var bullet = BulletManager.Instance.GetBullet();
bullet.transform.position = transform.position;
}
}
}
优化点
-
池大小管理:
-
可以设置最大池大小,避免内存过度占用。
-
当超出池容量时,可以销毁或记录日志。
-
-
对象复用规则:
-
在对象被放回池时,清理其状态(如位置、动画、粒子效果等)。
-
-
延迟回收:
-
如果对象(如粒子特效)有播放时间,可以设置延迟放回池。
-
总结
-
核心原理:通过复用对象来减少性能开销。
-
主要用途:适合频繁创建和销毁的对象(如子弹、特效)。
-
实现要点:管理好对象的获取和回收逻辑,确保状态重置。
-
注意事项:池的大小和管理策略需要根据实际场景优化。
LOD 和 HLOD:
LOD(Level of Detail)
LOD 是图形学中的一种优化技术,全称是 Level of Detail(细节层次)。其核心思想是根据物体与摄像机的距离动态调整物体的细节程度,从而减少不必要的计算,提升渲染性能。
原理
-
当物体距离摄像机较近时,显示高细节版本(高多边形模型、高分辨率贴图)。
-
当物体距离摄像机较远时,切换到低细节版本(低多边形模型、低分辨率贴图)。
-
极远处的物体可能直接隐藏或用一个简单的代理几何体代替。
实现流程
-
创建不同细节层次的模型:
-
使用 3D 工具生成高、中、低多边形版本的模型。
-
-
设置距离切换规则:
-
在不同的距离范围切换对应的模型。
-
-
运行时自动切换:
-
游戏引擎根据摄像机与物体的距离动态切换模型。
-
Unity 中的实现
Unity 提供了内置的 LOD 系统。
-
在 Unity 中为一个对象添加
LOD Group
组件。 -
设置不同的 LOD 模型(高、中、低细节)。
-
调整每个 LOD 的切换距离。
-
引擎会根据摄像机距离自动切换。
public class LODExample : MonoBehaviour
{
void Update()
{
// 摄像机距离可以通过 LOD Group 自动调整,开发者无需手动管理
}
}
HLOD(Hierarchical Level of Detail)
HLOD 是 LOD 的进阶版本,全称是 Hierarchical Level of Detail(分层细节层次)。HLOD 适用于复杂场景或大规模物体,通过对多个对象进行整体优化,进一步提升渲染性能。
原理
-
将场景中的多个对象合并成一个群组,根据距离统一处理这些对象的细节。
-
远距离时,使用该群组的简化版本代替所有对象。
-
近距离时,展开成细节模型,允许单独操作每个对象。
与 LOD 的区别
特性 | LOD | HLOD |
---|---|---|
应用对象 | 单个对象 | 一组对象(多个建筑物、复杂场景等) |
细节切换 | 每个对象独立切换细节 | 一组对象统一切换细节 |
适用场景 | 小型或独立物体 | 大型复杂场景,如城市、建筑群等 |
性能开销 | 较小(单个对象) | 较高(需要管理更多细节层次) |
HLOD 的优势
-
减少 Draw Call:
-
远距离时,多个对象可以合并成一个低细节版本,减少渲染调用。
-
-
更适合复杂场景:
-
对于大型场景(如城市、山脉等),HLOD 效果显著。
-
HLOD 的实现流程
-
创建 HLOD 层级
-
将场景中的多个对象分组(如建筑群、树木群)。
-
为每个组生成简化版本(低多边形模型、低分辨率贴图)。
-
-
设置距离切换规则
-
根据摄像机的距离,切换整个组的细节层次。
-
-
运行时动态加载
-
使用引擎或工具支持的 HLOD 系统,按需加载或卸载高细节对象。
-
Unity 实现 HLOD
Unity 默认不直接支持 HLOD,但可以借助插件或手动实现。常见工具:
-
Simplygon(第三方工具):用于生成 HLOD 模型。
-
Unity Asset Store 插件:一些插件提供了 HLOD 的自动化工具。
LOD 和 HLOD 的优缺点对比
特性 | LOD | HLOD |
---|---|---|
开发复杂度 | 较低(直接用引擎支持即可) | 较高(需要手动设置分组或使用插件) |
适用场景 | 单个物体或中小型场景 | 大型复杂场景 |
性能优化效果 | 一定程度优化(减少单个对象的渲染开销) | 显著优化(减少大规模场景的渲染开销) |
使用建议
-
LOD 使用场景:
-
小型或独立物体(如角色、单个树木)。
-
推荐使用 Unity 的
LOD Group
。
-
-
HLOD 使用场景:
-
大型复杂场景(如城市场景、大型森林)。
-
需要插件或第三方工具辅助实现。
-
LOD 和 HLOD 是图形优化的重要技术,可以显著提高帧率并降低资源消耗,根据项目规模和复杂度选择适合的技术进行优化。