在实际的游戏项目中,怪物往往作为底层对象长期稳定存在,但围绕怪物的各种行为逻辑(攻击、技能、结算规则)却会随着玩法不断变化,如何在不频繁修改怪物代码的前提下扩展新逻辑,是一个常见但容易被忽视的问题。
1.定义两个接口IMonsterVisitor和IMonster,一个表示访问者,他需要实现对各个对象的访问方法。然后一个访问对象,他需要实现应答方法,当别人访问它的时候它需要执行的方法。
cs
public interface IMonsterVisitor
{
void Visit(Slime slime);
void Visit(Goblin goblin);
void Visit(Boss boss);
void Visit(Wolf wolf);
}
public interface IMonster
{
void Accept(IMonsterVisitor visitor);
}
2.定义4个怪物脚本Boss,Goblin,Slime,Wolf。他们分别实现了应答方法,接收一个访问者,然后引导访问者去调用对应的方法。这两个方法效果是等价的
public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);
public void Accept(IMonsterVisitor visitor)
{
visitor.Visit(this);
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Monster;
public class Boss : MonoBehaviour, IMonster
{
public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Monster;
public class Goblin : MonoBehaviour, IMonster
{
public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Monster;
public class Slime : MonoBehaviour, IMonster
{
public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Monster;
public class Wolf : MonoBehaviour, IMonster
{
public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);
}
3.创建一个玩家访问者继承访问者接口,并实现访问者接口里面的所有方法访问,这样玩家就能知道攻击到不同的怪物该执行什么逻辑。
cs
using UnityEngine;
using static Monster;
public class PlayerAttackVisitor : IMonsterVisitor
{
public void Visit(Slime slime)
{
Debug.Log("Slime 受到伤害 + 弹跳");
}
public void Visit(Goblin goblin)
{
Debug.Log("Goblin 受到伤害 + 掉落金币");
}
public void Visit(Boss boss)
{
Debug.Log("Boss 受到伤害 + 进入狂暴状态");
}
public void Visit(Wolf wolf)
{
Debug.Log("狼被击中 → 流血效果 + 吼叫");
}
}
4.创建一个玩家,当玩家碰到怪物后会尝试通过怪物接口去获取被访者的应答方法。
visitor = new PlayerAttackVisitor();相当于新建一个访问者。
monster.Accept(visitor);//相当于说那个怪物过来一下我有问题要问你
怪物中的这个方法public void Accept(IMonsterVisitor visitor) => visitor.Visit(this);//"我不关心你是什么怪物,你自己决定该怎么被我处理"
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
PlayerAttackVisitor visitor;
void Start()
{
visitor = new PlayerAttackVisitor();
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.TryGetComponent<IMonster>(out var monster))
{
monster.Accept(visitor);
}
}
}
创建场景将玩家PlayerAttack加到玩家身上,并创建4个怪物,并且将怪物脚本加到对应怪物身上。

运行游戏拖动玩家,就会发现玩家碰撞到怪物执行了不同的方法

5.这样以后如果需要添加新的访问者十分容易,但是要添加新的被访问者需要修改所有的访问者接口,相当于你去不同的医院体检假如每个人的体检项目都一样,那么医生作为访问者那么只需要按照流程问你问题就行了,但是来了一个特殊的病人,只有修改访问者的接口了,相当于医生要去学习新的理论知识。
一句话总结访问者模式:
一句话总结访问者模式:当"对象结构稳定,但操作经常变化"时,把操作抽离成访问者,让对象自己决定"我该调用你哪个方法"。
还有一种常见场景是:当怪物逻辑来自第三方库或他人代码时,可以通过访问者模式在尽量不修改原有代码的前提下扩展新功能。