策略模式

Tulenber 8 May, 2020 ⸱ Beginner ⸱ 4 min ⸱ 2019.3.13f1 ⸱

最佳计划一直持续到第一个箭头离开弓箭为止。 Mat Cauthon, The Fires of Heaven, Robert Jordan.

我们将继续致力于设计模式的周期 ,并讨论一种称为“策略”的方便模式。

理论

此模式允许您在运行时更改对象的行为,它也非常适合将相似的算法突出显示到单独的对象中,并随后进行重用。

模板的理论部分经常介绍,因此您可以参考这篇精彩的文章

实践

作为一个实际的例子,我们可以看一下战略游戏中单位人工智能的行为。

行为由一组可能的动作组成,这些动作之间的关系通常与状态描述,另一种模式在其他文章“状态模板”中描述。 每个此类操作都是一系列步骤:

  1. 检查行动的可能性-如果是从手枪射击,则检查弹药的可用性以及到目标的距离。 对于康复,检查包括急救箱的可用性和当前生命值水平
  2. 输入动画-部队用枪举手射击或取出急救包进行治疗
  3. 动作-在枪壳中射击(制造飞行子弹或从敌人身上移除生命点),并在治疗时为生命值
  4. 退出动画-部队用武器放下手或隐藏急救箱

因此,这些具有击球和恢复能力的示例可以转换为几种单独的策略:

  1. 动作-是描述序列检查动画动作动画的高级策略
  2. 检查目标物的存在-它应与射击等距离合适。
  3. 命中点验证-不要无视自己
  4. 投篮-如果游戏中有几杆投篮,它们之间的差异不会太大
  5. 康复-相当可重复的机制

这不是描述示例的完整策略,但是原理应该很清楚。如果我们以类似的方式描述所有单元行为的可能性,那么我们将获得有限的策略集,这些策略随着参数的使用变得非常灵活。结果,这将显着增加代码的重用性,从而减少错误的数量和人工成本。

实作

通过描述其接口实施的策略。使用该接口实现不同算法。控制对象选择适当的策略并通过所描述的界面调用它。

例如,我们实施了一个小型机械师,一名法师在无赖者身上发射了两个不同的火咒。我们将不给出完整的实现,因为在大多数情况下,它包括调整动画和选择资产。同样,尝试自己从头开始重复这个小例子也是使用Unity和资源的好习惯。

拼写选择使用Immediate Mode GUI (IMGUI),有关更多详细信息,请参见我们有关备忘面板的文章。动画的工作是通过Mecanim完成的,您可以从另一篇“状态模板”获得有关其的基本信息。

  1. 用作接口的抽象类:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;

public abstract class Spell
{
    // 触发以开始动画
    protected int TriggerName;
    // 法术预制
    protected GameObject Prefab;
    // 预制的产卵位置取决于咒语的类型
    protected Vector3 ProjectileOffset;

    public void Fire(Vector3 magePosition)
    {
        // 咒语的对象是通过适当移至外观点来创建的
        Object.Instantiate(Prefab, magePosition + ProjectileOffset, Quaternion.identity);
    }

    public int GetAnimationTriggerName()
    {
        // 这类咒语的动画信息
        return TriggerName;
    }
}
  1. 在我们的案例中,策略将负责所生成的咒语对象的类型和偏移量以及动画触发器:
  1. 基本法术
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using UnityEngine;

public class BasicSpell : Spell
{
    public BasicSpell(GameObject prefab)
    {
        Prefab = prefab;
        // 相对于魔术师的位置移动
        ProjectileOffset = new Vector3(0.6f, 0, 0);
        // 触发基本法术的动画
        TriggerName = Animator.StringToHash("Attack");
    }
}
  1. 高级咒语
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using UnityEngine;

public class AdvancedSpell : Spell
{
    public AdvancedSpell(GameObject prefab)
    {
        Prefab = prefab;
        // 相对于魔术师的位置移动
        ProjectileOffset = new Vector3(0.8f, 0.2f, 0);
        // 高级咒语动画触发器
        TriggerName = Animator.StringToHash("Attack_Extra");
    }
}
  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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using UnityEngine;

public class Mage : MonoBehaviour
{
    // 所有敌人的父对象
    public Transform enemyList = null;
    // 预制的基本法术
    public GameObject basicSpellPrefab = null;
    // 预制法术
    public GameObject advancedSpellPrefab = null;
    // 法师动画
    private Animator _animator;
    // 标记以控制施法
    public static bool ProjectileFlag = false;

    // 当前咒语的对象
    private Spell _spell = null;
    // 基本法术
    private BasicSpell _basicSpell = null;
    // 高级咒语
    private AdvancedSpell _advancedSpell = null;

    // 咒语选择设置
    private int _spellSelection = 0;
    private readonly string[] _spellNames = {"Basic", "Advanced"};

    void Start()
    {
        _animator = GetComponent<Animator>();
        
        _basicSpell = new BasicSpell(basicSpellPrefab);
        _advancedSpell = new AdvancedSpell(advancedSpellPrefab);

        // 最初选择基本法术
        _spell = _basicSpell;
    }
    
    void Update()
    {
        // 如果没有对手或已经有一个施法者
        if (enemyList.childCount == 0 || ProjectileFlag)
        {
            return;
        }

        // 启动法师动画以投射所选法术
        _animator.SetTrigger(_spell.GetAnimationTriggerName());

        ProjectileFlag = true;
    }

    private void OnGUI()
    {
        // 在当前咒语的施放和飞行过程中隐藏该咒语的选择
        if (ProjectileFlag)
        {
            return;
        }

        // 绘制咒语选择面板
        GUI.Box(new Rect (10, 10, 100, 100), "Spell panel");
        _spellSelection = GUI.SelectionGrid (new Rect (20, 40, 80, 60), _spellSelection, _spellNames, 1);

        // 如果没有变化,我们什么都不做
        if (!GUI.changed)
        {
            return;
        }

        // 选择基本咒语时,将其激活
        if (_spellSelection == 0)
        {
            _spell = _basicSpell;
            return;
        }

        // 选择高级咒语时,将其激活
        _spell = _advancedSpell;
    }

    // 在正确的时间从投射动画创建当前咒语
    public void Fire()
    {
        _spell.Fire(transform.position);
    }
}

结果

根据选择的法术,法师开始不同的法术动画并射出不同的弹丸。
Result

结论

策略模板本身的实现并不复杂。实际上,它只是对象继承的用法。最大的挑战是将游戏机制隔离成对特定项目有效的结构。最有可能的是,在此之前,它将经历多次重构。尽管如此,您越早开始负责任地开始创建此结构,就越容易在一个体面的水平上支持该项目。下次见!^_^


如果您喜欢这篇文章,可以为它提供支持



Privacy policyCookie policyTerms of service
Tulenber 2020