策略模式
Tulenber ⸱
8 May, 2020 ⸱
Beginner ⸱
4 min ⸱
2019.3.13f1 ⸱
最佳计划一直持续到第一个箭头离开弓箭为止。 Mat Cauthon, The Fires of Heaven, Robert Jordan.
我们将继续致力于设计模式的周期 ,并讨论一种称为“策略”的方便模式。
理论
此模式允许您在运行时更改对象的行为,它也非常适合将相似的算法突出显示到单独的对象中,并随后进行重用。
模板的理论部分经常介绍,因此您可以参考这篇精彩的文章。
实践
作为一个实际的例子,我们可以看一下战略游戏中单位人工智能的行为。
行为由一组可能的动作组成,这些动作之间的关系通常与状态描述,另一种模式在其他文章“状态模板”中描述。 每个此类操作都是一系列步骤:
- 检查行动的可能性-如果是从手枪射击,则检查弹药的可用性以及到目标的距离。 对于康复,检查包括急救箱的可用性和当前生命值水平
- 输入动画-部队用枪举手射击或取出急救包进行治疗
- 动作-在枪壳中射击(制造飞行子弹或从敌人身上移除生命点),并在治疗时为生命值
- 退出动画-部队用武器放下手或隐藏急救箱
因此,这些具有击球和恢复能力的示例可以转换为几种单独的策略:
- 动作-是描述序列检查动画动作动画的高级策略
- 检查目标物的存在-它应与射击等距离合适。
- 命中点验证-不要无视自己
- 投篮-如果游戏中有几杆投篮,它们之间的差异不会太大
- 康复-相当可重复的机制
这不是描述示例的完整策略,但是原理应该很清楚。如果我们以类似的方式描述所有单元行为的可能性,那么我们将获得有限的策略集,这些策略随着参数的使用变得非常灵活。结果,这将显着增加代码的重用性,从而减少错误的数量和人工成本。
实作
通过描述其接口实施的策略。使用该接口实现不同算法。控制对象选择适当的策略并通过所描述的界面调用它。
例如,我们实施了一个小型机械师,一名法师在无赖者身上发射了两个不同的火咒。我们将不给出完整的实现,因为在大多数情况下,它包括调整动画和选择资产。同样,尝试自己从头开始重复这个小例子也是使用Unity和资源的好习惯。
拼写选择使用Immediate Mode GUI (IMGUI),有关更多详细信息,请参见我们有关备忘面板的文章。动画的工作是通过Mecanim完成的,您可以从另一篇“状态模板”获得有关其的基本信息。
- 用作接口的抽象类:
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
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
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
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);
}
}
|
结果
根据选择的法术,法师开始不同的法术动画并射出不同的弹丸。
结论
策略模板本身的实现并不复杂。实际上,它只是对象继承的用法。最大的挑战是将游戏机制隔离成对特定项目有效的结构。最有可能的是,在此之前,它将经历多次重构。尽管如此,您越早开始负责任地开始创建此结构,就越容易在一个体面的水平上支持该项目。下次见!^_^
如果您喜欢这篇文章,可以为它提供支持