Unity 游戏开发设计模式

在学习银鸟工作室SC-102时的笔记

[视频链接](https://www.bilibili.com/video/BV1vL411i7qv/?spm_id_from=333.788&vd_source=c207d130414b95c492ab89714eede578)

所谓游戏的设计模式,就是我们在实现游戏功能时可以遵循的模板或者思想,它们通常是前人在遇到经常出现的问题时总结出来的解决方案。

常见的设计模式有:

  • 单例模式
  • 指令模式
  • 工厂模式
  • 观察者模式
  • 策略模式

一、单例模式

单例模式是一个最简单、最常用、但也最容易被滥用的设计模式
单例模式有两个特点:

  1. 始终只允许有一个该类的实例
  2. 对于该类的实例有一个全局的访问点

一个简单的单例类模板为:

public class designPattern : MonoBehaviour
{
    
    void Start()
    {
        Singleton.Instance.show();
    }

    
}

public class Singleton : MonoBehaviour
{
    private static Singleton instance;
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if(instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }

    public void show()
    {
        print("Singleton");
    }
}

单例类最核心的一行代码是

private Singleton() { }

将构造函数设为private,使得这个类在类外不能够被实例化,实现了对该类实例数量的控制,使其无法在类外实例化:
无法在其他地方实例化
此外,单例类的唯一实例,以及访问这个实例的方法都需要是static的,可以保证该实例全局有效,而且可以通过类名访问实现全局访问。

当我们需要很多个单例类时,可以创建一个通用的单例类,其他单例类继承自它就好了。

public class Singleton<T>:MonoBehaviour where T:class,new()
{
    protected Singleton() { }

    private static T instance = null;

    public static T Instance => instance ??= new T();

    public static void Clear()
    {
        instance = null;
    }
}

使用这个通用单例可以这样使用:

public class GameManage : Singleton<GameManage>
{
    public void Show()
    {
        print("show");
    }
}

需要注意的是,GameManage类是可以有多个实例的,但是所对应的instance只有一个,比如调用Show函数可以有两种方法:

	GameManage gameManage = new();
    gameManage.Show(); 
    GameManage.Instance.Show();

只有Instance和gameManage都是GameManage的实例,但是只有Instance是全局唯一且有效的,而新创建的gameManage中没有全局的数据。
也就是说,只有Instance是唯一有效的全局单例。

二、指令模式

指令模式是将每一个操作抽象成一个指令,每一个指令都有一个执行操作和撤销操作。
指令模式通常用来实现操作撤回的效果,将执行过的每一条指令存储起来,撤回时使用调用撤回操作即可。

1、指令模式下,每一条指令都有一个执行操作和撤销操作,可以设置一个抽象类作为父类,包含一个执行操作和一个撤回操作:

public abstract class Command
{
    public abstract void Execute(GameObject gameObject, float input);
    public abstract void Undo(GameObject gameObject, float input);
}

每一个实际的指令都是Command的子类,并实现具体的执行和撤回操作

public class MoveForwordCommand : Command
{
    public override void Execute(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(Vector3.forward * input);
    }

    public override void Undo(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(-Vector3.forward * input);
    }
}

public class MoveBackCommand : Command
{
    public override void Execute(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(Vector3.back * input);
    }
    public override void Undo(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(-Vector3.back * input);
    }
}

public class MoveRightCommand : Command
{
    public override void Execute(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(Vector3.right * input);
    }
    public override void Undo(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(-Vector3.right * input);
    }

}

public class MoveLeftCommand : Command
{
    public override void Execute(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(Vector3.left * input);
    }
    public override void Undo(GameObject gameObject, float input)
    {
        gameObject.transform.Translate(-Vector3.left * input);
    }
}


2、所有命令在声明是同一使用父类声明,在实现时使用对应的子类实现,便于管理。

	//声明
	private Command moveForwordCommand;
    private Command moveBackCommand;
    private Command moveLeftCommand;
    private Command moveRightCommand;
	
	//实现
	private void Awake()
    {
        moveForwordCommand = new MoveForwordCommand();
        moveBackCommand = new MoveBackCommand();
        moveLeftCommand = new MoveLeftCommand();
        moveRightCommand = new MoveRightCommand();
    }

3、每次指令一个命令时,我们需要将命令保存起来,这里可以使用栈来存储已经执行过的命令。
每次执行完一条命令,就将这条命令压栈。

Stack<Command> commands = new();

	private void Update()
    {
        if(Input.GetKeyDown(KeyCode.W))
        {
            moveForwordCommand.Execute(cube, moveSpeed);
            commands.Push(moveForwordCommand);
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            moveBackCommand.Execute(cube, moveSpeed);
            commands.Push(moveBackCommand);
        }
        if(Input.GetKeyDown(KeyCode.D))
        {
            moveRightCommand.Execute(cube,moveSpeed);
            commands.Push(moveRightCommand);
        }
        if( Input.GetKeyDown(KeyCode.A))
        {
            moveLeftCommand.Execute(cube,moveSpeed);
            commands.Push(moveLeftCommand);
        }
    }

4、当执行撤回操作时,将栈顶元素出栈,然后执行该元素的撤回操作。这里就体现了抽象子类的好处。由于我们声明时统一声明的是父类,存储的也是父类,在执行撤回操作时,不需要知道具体是什么操作,只需要直接调用相同的Undo函数即可。

private void Update()
    {
        if(Input.GetKey(KeyCode.LeftControl))
        {
            if (Input.GetKeyDown(KeyCode.Z))
            {
                if(commands.Count > 0)
                {
                    Command command = commands.Pop();
                    command.Undo(cube, moveSpeed);
                }
            }
        }
    }
  • 可以留意下Ctrl+Z的实现方法
  • 可以看到,在执行Updo函数时还提供了参数,其实最好将参数也封装在子类中,使Updo操作没有参数也没有返回值,这样不容易出错

三、工厂模式

工厂模式就是一个用于生产物体的工厂,它将类的实例化过程包装起来,使得使用者可以方便且可控的获得一个类的实例。

比如:

public abstract class Animal
{
    public abstract string Speak();
}

public class Dog : Animal
{
    public override string Speak()
    {
        return "Woof";
    }
}

public class Cat : Animal
{
    public override string Speak()
    {
        return "Meow";
    }
}

看起来和指令模式有点像,其实就是在我们创建敌人、物体等实例时,可以方便我们创建这些实例,而且可以对实例进行同一的管理。

比如:

public static class AnimalFactory
{
    public static Animal CreatAnimals(string type)
    {
        switch(type)
        {
            case "Dog":
                return new Dog();
            case "Cat":
                return new Cat();
            default:
                throw new NotSupportedException();
        }
    }
}

上面时一个简单的工厂,我们可以在创建不同的实例时,进行不同的加工,然后再返回实例。一个实例工厂可以通过不同的静态方法,返回不同的父类实例对象。

此外,也可以将工厂本身进行抽象化:

public interface IAnimalFactory
{
    Animal CreatAnimals(string type);
}

public class AnimalFactory : IAnimalFactory
{
    public Animal CreatAnimals(string type)
    {
        switch(type)
        {
            case "Dog":
                return new Dog();
            case "Cat":
                return new Cat();
            default:
                throw new NotSupportedException();
        }
    }
}

这样就可以实现多个不同的工厂,每个工厂之间具体的实现方法可以有差别

四、观察者模式

观察者模式存在观察者和通知者两方。当通知者发生变化时,会通知所有观察者做出反应。

Unity中的委托就是观察者模式的实现,Action和Func都是观察者模式,Action、Func函数本身是通知者,其中包含的函数是观察者。当调用Action和Func时,会通知其包含的所有函数执行操作。

此外Button也是一个经典的观察者模式
在这里插入图片描述
OnClick()函数就是通知者,包含的所有函数是观察者,当按钮按下时,会通知其中添加的所有函数进行执行操作。

五、策略模式

策略模式就是指再运行程序时,根据具体情况选择一种算法来执行任务,是一种很常见也很常用的思路

非常简单的例子:

void TakeDamage()
    {
        if (InWater())
        {
            TakeDamageInWater();
        }
        else
        {
            TakeDamageInLand();
        }
    }

    private void TakeDamageInLand() //在陆地收到的伤害计算
    {
        throw new NotImplementedException();
    }

    private void TakeDamageInWater() //在水中收到的伤害计算
    {
        throw new NotImplementedException();
    }

    private bool InWater() //是否在水中判断逻辑
    {
        throw new NotImplementedException();
    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值