游戏开发中经常出现一些业务变更。如果是一个好的框架,重构起来很容易,如果是一个不好维护的框架,那基本上就要炸了,且为了完成这个功能会写很多“屎山”代码。
为了避免这种情况频繁发生,就需要框架制定者有很好的设计模式基础。说通俗点,就是多套几层了,或者多抽象几个类出来等。
现把个人在工作以及业务自己做的小型游戏中一些常用的设计模式贴出来,浅谈一下。
1.单例模式。
全局就一个实例,调用代码很方便就类似 someclass.Instance.function()。全局只有一份不希望有多个对象出现时使用。
常用的比如 数据块 xxxModule,管理类 xxxManager。
public class InstanceBase<T> where T:new()
{
private static T m_Instance;
private static object m_helpobj = new object();
public static T Instance
{
get
{
if (m_Instance == null)
{
lock (m_helpobj)
{
m_Instance = new T();
}
}
return m_Instance;
}
}
}
public class SomeInstance : InstanceBase<SomeInstance>
{
public int m_someValue;
}
public class TestUse
{
public void OutPut()
{
Console.WriteLine(SomeInstance.Instance.m_someValue);
}
}
2.工厂模式
游戏中可能会存在需要创建大量对象的情况,如果到处创建显得代码结构很乱,这时候就需要专门有个工厂来处理,把工厂生产的产品在抽象出个结构。比如之前做点消以及三消项目时需要用到的棋子
public enum CheeseEnum
{
Normal,
Box,
Bomb,
}
public class CheeseBase
{
public virtual void OnInit()
{
}
public virtual void OnDestory()
{
}
public virtual void OnDrop()
{
}
}
public class ACheese : CheeseBase
{
}
public class BCheese : CheeseBase
{
}
public class Factor
{
public CheeseBase GetCheeseByType(CheeseEnum cheeseEnum)
{
switch (cheeseEnum)
{
case CheeseEnum.Normal:
return new ACheese();
case CheeseEnum.Bomb:
return new BCheese();
default:
return null;
}
}
}
3.生产者消费者模式
用于解决生产者和消费者之间的数据传输问题,其主要思想是生产者负责生成数据并将其放入缓冲区,而消费者则从缓冲区中取出数据并进行处理。
通常服务器用到这个比较多,但客户端偶尔也会用到,比如处理网络消息。当需要考虑业务先后顺序情况时需要使用这样的模式
public class ProductMakeCustomer
{
public Queue<IMessage> m_MessageQueue = new Queue<IMessage>();
public void OnTick()
{
while (m_MessageQueue.Count != 0)
{
IMessage imessgae = m_MessageQueue.Dequeue();
imessgae.OnUse();
}
}
public void OnPush(IMessage message)
{
message.OnPush();
m_MessageQueue.Enqueue(message);
}
}
public interface IMessage
{
public void OnPush();
public void OnUse();
}
4.组合模式
前些年看过一句话,就维护而言,组合>继承。我的理解把需要的模块逻辑都细分成组件,一个容器中可以包含N个这样的组件,在某些需要的时机进行调用。主要解决因为继承导致的维护成本过高,但同时也有对开发人员需要理清这些组件划分粒度。ECS就是基于这样的思想开发的。
public enum ComponentType
{
None = 0,
Move = 1,
Attack = 2,
}
public class GameContainer
{
public List<IComponent> m_Components = new List<IComponent>();
public List<GameData> m_Data = new List<GameData>();
public void Tick()
{
foreach (var v in m_Components)
{
v.OnTick(this);
}
}
}
public interface IComponent
{
public ComponentType GetComponType();
public void OnTick(GameContainer container);
}
public class GameData
{
}
5.代理模式
日常生活中比如我们找房子,我们不知道都有什么房源,我们需要去链家 我爱我家等中介去咨询,这里面的中介就相当于是代理。当我们不知道业务要找谁处理就找代理帮助处理,这就是这个模式,其好处是面对复杂的业务需求,功能好抽象出来。并且调用链不会显得非常乱
网上粘的图,这里的代理类无需继承接口,只要代理类包含真实类并且在需要调用的时候使用即可。
public interface Entity
{
void Move();
}
public class Player : Entity
{
public void Move()
{
}
}
public class ProxyRent
{
private Entity m_CurEntity;
public void Move()
{
m_CurEntity?.Move();
}
}
public class Main
{
private ProxyRent m_Proxy;
public void Test()
{
m_Proxy?.Move();
}
}
6.观察者模式
业务中经常出现当一个数据发生改变,好几个UI页面需要同时刷新(比如玩家金币变了)。如果在轮训中不停的去刷显得太傻而且费性能,这时候需要用到这个观察者模式。
public class Observer
{
public static Action<int> OnMoneyChange;
}
public class UI1
{
public UI1()
{
Observer.OnMoneyChange += OnMoneyChange;
}
~UI1()
{
Observer.OnMoneyChange -= OnMoneyChange;
}
private void OnMoneyChange(int money)
{
Console.WriteLine("UI1 OnMoneyChange:" + money);
}
}
public class UI2
{
public UI2()
{
Observer.OnMoneyChange += OnMoneyChange;
}
~UI2()
{
Observer.OnMoneyChange += OnMoneyChange;
}
private void OnMoneyChange(int money)
{
Console.WriteLine("UI2 OnMoneyChange:" + money);
}
}
public class ObserverPlayer
{
public void OnMoneyChange(int nowMoney)
{
Observer.OnMoneyChange?.Invoke(nowMoney);
}
}
7.策略模式
比如业务中玩家数据是一样的,但是某个场景需要捡到个武器才能攻击别人,而另一个场景不需要。这时候把这个可攻击放到玩家身上也不合适,就需要把场景进行抽象,然后对应不同的场景分别实现,这样的模式就是策略模式。
public interface IStrategy
{
bool CanAttack(StrategyPlayer player);
}
public class StrategyA:IStrategy
{
public bool CanAttack(StrategyPlayer player)
{
return player.m_BulletCount > 0;
}
}
public class StrategyB:IStrategy
{
public bool CanAttack(StrategyPlayer player)
{
return true;
}
}
public class StrategyPlayer
{
public int m_BulletCount;
}
public class StrategyMain
{
private IStrategy m_Strategy;
public bool CheckPlayerCanAttack(StrategyPlayer player)
{
if (m_Strategy == null)
{
return false;
}
return m_Strategy.CanAttack(player);
}
}
8.状态模式
简化版的状态机。比如当需要进入到一个新的场景,有刚loading时,loading到某个进度,loading后,清除loading。这个模式主要是让代码维护起来看着清晰,不至于写几个莫名其妙的魔数让别人猜,使用多余的bool来记录状态。
public enum LoadingState
{
Start,
Loading,
SyncDataFinish,
ResLoadingFinish,
LoadingOtherFinish,
LoadingEnd,
LoadingClear,
}
public class State
{
private LoadingState m_State;
public void Tick()
{
switch (m_State)
{
case LoadingState.Start:
Start();
break;
case LoadingState.Loading:
Loading();
break;
case LoadingState.SyncDataFinish:
SyncDataFinish();
break;
case LoadingState.ResLoadingFinish:
ResLoadingFinish();
break;
case LoadingState.LoadingOtherFinish:
LoadingOtherFinish();
break;
case LoadingState.LoadingEnd:
LoadingEnd();
break;
case LoadingState.LoadingClear:
LoadingClear();
break;
}
}
public void Start()
{
}
public void Loading()
{
}
public void SyncDataFinish()
{
}
public void ResLoadingFinish()
{
}
public void LoadingOtherFinish()
{
}
public void LoadingEnd()
{
}
public void LoadingClear()
{
}
}
9.装饰模式
生活中点了个咖啡,没糖和有糖的都叫这个但价格不一样。这里的糖就叫装饰。业务模块如果考虑到扩展性的问题,需要用到这个模式。
public interface ExtraPrice
{
int GetPrice();
}
public class FullSugar : ExtraPrice
{
public int GetPrice()
{
return 100;
}
}
public class HalfSugar : ExtraPrice
{
public int GetPrice()
{
return 50;
}
}
public class Coffee
{
private ExtraPrice m_ExtraPrice;
public int GetSelfPrice()
{
return 100;
}
public int GetPrice()
{
int extraPrice = m_ExtraPrice == null ? 0 : m_ExtraPrice.GetPrice();
return GetSelfPrice() + extraPrice;
}
}
以下的10和11不是标准的设计模式,但游戏开发中也经常用,尤其是做战斗这块
10.行为树
把玩家或者AI的任何行为都抽象成节点,然后在通过组合排列的方式来做。如Unity的Behavior Tree插件,UE自带的行为树。这个功能比较复杂这里先不贴代码,等后续有时间再补上
11.时间轴
战斗中某个玩家释放技能,可能被打晕了放不了,可能在吟唱过程中被打晕了放不了,也可能持续施法过程中玩家自己移动了自己打断,情况很多种。如果一个个处理维护非常困难。可以把其过程化成一个流程图然后同步在代码里实现。
public class SKillTimeline
{
private float m_RunTime;
private LinkedListNode<SkillNode> m_CurSkillNode;
private LinkedList<SkillNode> m_SkillNode;
private SkillPlayer m_SkillPlayer;
public void StartSkill()
{
m_CurSkillNode = m_SkillNode.First;
m_CurSkillNode.Value.OnActive(m_SkillPlayer);
}
public void Tick(float time)
{
if (m_CurSkillNode == null)
{
return;
}
if (!m_CurSkillNode.Value.CheckInterrupt(m_SkillPlayer))
{
m_CurSkillNode.Value.Tick(time,m_SkillPlayer);
}
if (m_CurSkillNode.Value.CheckCanEnd(m_SkillPlayer))
{
m_CurSkillNode.Value.OnEnd(m_SkillPlayer);
LinkedListNode<SkillNode> nextNode = m_CurSkillNode.Next;
if (nextNode != null)
{
m_CurSkillNode = nextNode;
m_CurSkillNode.Value.OnActive(m_SkillPlayer);
}
}
}
}
public class SkillPlayer
{
}
public class SkillNode
{
public virtual void Tick(float time,SkillPlayer player)
{
}
public virtual void OnActive(SkillPlayer player)
{
}
public virtual bool CheckCanEnd(SkillPlayer player)
{
return false;
}
public virtual void OnEnd(SkillPlayer player)
{
}
public virtual bool CheckInterrupt(SkillPlayer player)
{
return false;
}
}
后记:写了这么多。这些都只是思想,实际开发中可能会出现混用的设计模式。还是得根据业务需求来。