今天来讲一下策略模式。
在我们进行游戏开发的时候,总避免不了改动自己的算法。特别是你做完一个功能之后,策划跑来跟你说,我觉得我们还可以加一个功能(这个功能跟前一个功能类似),第一次过来跟你说要加,那自然是很简单,但是如果一次又一次地跑过来,这样一遍又一遍要重新添加功能,也意味着要重新写算法,特别是关系特别复杂的算法,操作起来难免会特别麻烦,而且脚本代码多了,有可能连自己都不知道先前的在写什么。所以,针对这种情况,使用策略模式,便可以使事情更简便一些,通过写单独的算法(功能),每个算法都是实现类似的功能,只不过实现的效果不一样,这样可以减少算法类和实用类之间的耦合度。
下面是策略模式的一个大概的类图
从上面的图片可以很清晰地理解几个类之间的关系,Context类和Strategy类是聚合关系,而三个算法类和Strategy类是继承关系,对Context类的理解,可以认为是它对抽象类Strategy进行了初始化,为算法发生提供条件或者说环境。
可能这样文字解释有点迷,所以我们还是上代码和小案例。
现在有这样一个unity项目,项目里面有一个角色,策划告诉你,这个角色要有行走的功能,转身的功能,身体变大变小的功能。一听到这几个功能,可能我们想的就是,把所有功能放在一个脚本里,写几个if条件判断,简单就解决了。当然,这样的却能解决,但是,这只是对这个角色的算法设计,如果涉及到大量的角色,但是实现的功能不一样,那就麻烦许多了。
所以,我们使用策略模式来实现,首先,画一个类图来表示其中的关系
然后我们开始写脚本,首先是抽象策略类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class ManStrategy {
public abstract void AbstractBehaviour(GameObject GO);//这里需要获得被操作的游戏物体
}
然后是三个具体算法,首先是移动类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Man_Move : ManStrategy {
private string H, V;//垂直轴和纵向轴
private float MoveSpeed;//移动速度
public Man_Move(string h,string v,float movespeed)
{
this.H = h;
this.V = v;
this.MoveSpeed = movespeed;
}
public override void AbstractBehaviour(GameObject GO)
{
float mh = Input.GetAxis(H);
float mv = Input.GetAxis(V);
GO.transform.Translate(new Vector3(mh, 0, mv) * MoveSpeed * Time.deltaTime);
}
}
再就是旋转
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Man_Rotate :ManStrategy{
private float RotateSpeed;//旋转速度
private Vector3 R_Axis;//围绕旋转的轴
public Man_Rotate(Vector3 raxis,float speed)
{
this.RotateSpeed = speed;
this.R_Axis = raxis;
}
public override void AbstractBehaviour(GameObject GO)
{
if (Input.GetKey(KeyCode.Q))
{
GO.transform.Rotate(R_Axis, -RotateSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.E))
{
GO.transform.Rotate(R_Axis, RotateSpeed * Time.deltaTime);
}
}
}
接着是变大变小
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Man_BiggerAndShrink :ManStrategy{
private Vector3 ManScale;//缩放的三维向量
private float ScaleRate;//缩放的比例
public Man_BiggerAndShrink(Vector3 scale, float rate)
{
this.ManScale = scale;
this.ScaleRate = rate;
}
public override void AbstractBehaviour(GameObject GO)
{
if (Input.GetKey(KeyCode.B))
{
GO.transform.localScale += ManScale * ScaleRate;
}
if (Input.GetKey(KeyCode.N))
{
GO.transform.localScale -= ManScale * ScaleRate;
}
}
}
三个具体的实现算法都做好了,然后我们对他们的使用进行初始化和赋予条件。
ManContext类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum BehaviourType//算法行为类型
{
Bigger,
Move,
Rotate
}
public class ManContext {
ManStrategy _manstrategy=null;
public ManContext(BehaviourType type)//这里其实稍微结合了一点简单工厂模式,因为等一下我们真正运行,这些是不用写出来的
{
switch (type)
{
case BehaviourType.Bigger:
Man_BiggerAndShrink mb = new Man_BiggerAndShrink(new Vector3(2, 2, 2),0.01f);
_manstrategy = mb;
break;
case BehaviourType.Move:
Man_Move mm = new Man_Move("Horizontal", "Vertical", 2);
_manstrategy = mm;
break;
case BehaviourType.Rotate:
Man_Rotate mr = new Man_Rotate(Vector3.up, 30);
_manstrategy = mr;
break;
default:
break;
}
}
public void StartExecute(GameObject go)
{
_manstrategy.AbstractBehaviour(go);
}
}
然后,回到unity场景中,
随便找一个模型,再新建一个脚本,取名为Man,挂给它
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Man : MonoBehaviour {
ManContext mc1 = new ManContext(BehaviourType.Move);
ManContext mc2=new ManContext(BehaviourType.Bigger);
ManContext mc3=new ManContext(BehaviourType.Rotate);
void Start () {
}
// Update is called once per frame
void Update () {
mc1.StartExecute(gameObject);
mc2.StartExecute(gameObject);
mc3.StartExecute(gameObject);
}
}
运行,可以发现能够运行正确。在跟物体接触的脚本里面,我们发现,只用了6行!!!就简单解决了这些问题,而且,后台的算法也很清楚,我们知道哪个类是处理哪个功能的。所以说,现在想加什么功能也很简单,只要写上相应的类就可以了,然后在判断里面进行些许修改,就可以实现添加功能。
是不是觉得策略模式真的好方便啊啊,的确实,策略模式简化了单元测试,每一个算法都有自己的类,可以自己单独进行测试,这就是策略模式的独特之处。
好了,关于策略模式,就暂时说到这里,学习归学习,至于具体的使用,还是需要多去思考,多去琢磨。一起加油咯!!!