3.结构型模式-适配器模式
3.1 基础知识
学习难度:2
使用频率:4
总分:8
定义
适配器模式(Adapter Pattern)将一个类的接口转换成客户端希望的另外一个接口。适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作。
说人话
将不兼容的接口转换为可兼容。
结构图
实现步骤
- 目标接口:定义请求的方法。
- 被适配类:定义特殊请求方法。
- 适配器类:实现目标接口,定义被适配对象并在构造函数传入初始化,请求方法封装调用被适配对象的特殊请求进行适配。
- 客户端:创建被适配对象,创建适配器传入被适配对象,使用适配器进行请求。
说明
可以把适配器理解为接口转换器。比如大陆的插头在香港或者国外不能用,那么大陆的插头就是被适配对象,就得用买接口转换器进行适配。
3.2 模版代码
被适配类
// 被适配类,需要被适配的类
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("被适配类的特殊请求");
}
}
目标接口和适配器类
// 目标接口
interface ITarget
{
void Request();
}
// 适配器类,实现目标接口,同时持有被适配类的实例
class Adapter : ITarget
{
private Adaptee adaptee = new Adaptee();
// 重写目标类的请求方法,利用被适配类的方法来实现请求
public void Request()
{
adaptee.SpecificRequest();
}
}
客户端
class Program
{
static void Main(string[] args)
{
// 创建适配器对象,并将其赋值给目标接口类型的变量
ITarget target = new Adapter();
// 调用目标接口的请求方法
target.Request();//被适配类的特殊请求
}
}
3.3 CSharp实践
实践需求
使用适配器模式,模拟中国球员姚明在翻译官的帮助下载美国打篮球。
中国球员类
//中国球员类
class ChineseBasketballPlayer
{
public string name { get; set; }
public ChineseBasketballPlayer(string name)
{
this.name = name;
}
public void 打篮球()
{
Console.WriteLine("中国球员 {0} 打篮球", name);
}
}
美国球员接口、美国球员类和翻译官类
// 美国篮球运动员接口
interface IAmericanBasketballPlayer
{
protected string name { get; set; }
public abstract void PlayBasketball();
}
// 美国篮球运动员
class AmericanBasketballPlayer : IAmericanBasketballPlayer
{
public string name { get; set; }
public AmericanBasketballPlayer(string name)
{
this.name = name;
}
public void PlayBasketball()
{
Console.WriteLine("美国球员 {0} 打篮球", name);
}
}
//翻译官类
class Translator : IAmericanBasketballPlayer
{
private ChineseBasketballPlayer chineseBasketballPlayer;
public string name { get; set; }
public Translator(string translatorName,string chineseBasketballPlayerName)
{
name = translatorName;
this.chineseBasketballPlayer = new ChineseBasketballPlayer(chineseBasketballPlayerName);
}
public void PlayBasketball()
{
Console.WriteLine("翻译官 {0} 叫 中国球员 {1} 打篮球",name,chineseBasketballPlayer.name);
chineseBasketballPlayer.打篮球();
}
}
客户端
class Program
{
static void Main(string[] args)
{
IAmericanBasketballPlayer kobe = new AmericanBasketballPlayer("科比");
kobe.PlayBasketball(); //美国球员 科比 打篮球
IAmericanBasketballPlayer yaoMing = new Translator("科林潘", "姚明");
yaoMing.PlayBasketball();
//翻译官 科林潘 叫 中国球员 姚明 打篮球
//中国球员 姚明 打篮球
}
}
3.4 Unity实践
实践需求
- 使用适配器模式。
- 使用状态机,实现暗夜猎手薇恩的空闲状态、移动状态和攻击状态。
- 创建输入接口,模拟英雄联盟玩家可能的输入,如A键按下,鼠标左右键按下。
- 使用适配器让暗夜猎手薇恩能适配玩家的输入:
- 左键点击敌方英雄无极剑圣易大师查看易大师属性。
- 左键点击地形无效。
- 右键点击敌方英雄无极剑圣易大师,向易大师移动,在攻击范围内则攻击易大师。
- 右键点击地形移动至该目的地。
- A键显示和隐藏攻击范围。
- A键显示攻击范围后左键点击敌方英雄无极剑圣易大师,隐藏攻击范围,向易大师移动,在攻击范围内则攻击易大师。
- A键显示攻击范围后右键地形,隐藏攻击范围,移动至该目的地。
准备工作
薇恩动画状态机
薇恩预制体挂载薇恩控制脚本和寻路组件
薇恩真正的模型对象是薇恩预制体子对象 挂载动画控制器和控制脚本 攻击结束后要修改攻击动画标识为False 在攻击动画文件添加事件
public class HeroAnimator : MonoBehaviour
{
private Animator animator;
private static readonly int AttackBool = Animator.StringToHash("attackBool");
private void Start()
{
animator = GetComponent<Animator>();
}
public void SetAttackBoolFalse()
{
animator.SetBool(AttackBool, false);
}
}
剑圣改标签 用于检查
攻击范围使用的线组件 在对应路径添加Resources材质
薇恩控制类
//薇恩状态枚举
public enum VayneState
{
Idle,
Move,
Attack
}
//薇恩控制类
public class VayneInputAdaptee : MonoBehaviour
{
private Animator animator; // 角色的动画组件
private NavMeshAgent navMeshAgent; // 角色的导航代理组件
// 用于动画状态机的参数(布尔和浮点数)
private static readonly int AttackBool = Animator.StringToHash("attackBool");
private static readonly int SpeedFloat = Animator.StringToHash("speedFloat");
private static readonly int MoveBool = Animator.StringToHash("moveBool");
private VayneState nowState = VayneState.Idle; // 当前角色状态
private GameObject attackTarget; // 攻击目标
private Vector3 posTarget; // 位置目标
private float rotationSpeed = 20f; // 旋转速度
private const float IdleSpeed = 0f; // 空闲状态下的速度
private const float MoveSpeed = 4f; // 移动状态下的速度
[HideInInspector] public float attackRange = 3; // 攻击范围
private void Start()
{
// 初始化动画和导航组件
animator = GetComponentInChildren<Animator>();
navMeshAgent = GetComponent<NavMeshAgent>();
}
private void Update()
{
// 重置模型的位置和旋转,然后更新当前状态
var modelTransform = transform.GetChild(0).transform;
modelTransform.localPosition = Vector3.zero;
modelTransform.localRotation = Quaternion.identity;
// 状态机 每帧更新状态
StateOnUpdate();
}
// 改变状态方法
public void ChangeState(VayneState state)
{
nowState = state;
switch (nowState)
{
case VayneState.Idle:
IdleOnEnter();
break;
case VayneState.Move:
MoveOnEnter();
break;
case VayneState.Attack:
AttackOnEnter();
break;
default:
throw new ArgumentOutOfRangeException(nameof(nowState), nowState, null);
}
}
#region OnEnter
// 进入空闲状态
public void IdleOnEnter()
{
animator.SetBool(MoveBool, false);
animator.SetBool(AttackBool, false);
navMeshAgent.speed = IdleSpeed;
animator.SetFloat(SpeedFloat, IdleSpeed);
navMeshAgent.isStopped = true;
}
// 进入移动状态
public void MoveOnEnter()
{
animator.SetBool(MoveBool, true);
animator.SetBool(AttackBool, false);
animator.SetFloat(SpeedFloat, MoveSpeed);
navMeshAgent.speed = MoveSpeed;
navMeshAgent.isStopped = false;
}
// 进入攻击状态
public void AttackOnEnter()
{
animator.SetBool(MoveBool, false);
if (attackTarget.transform != null)
{
transform.LookAt(attackTarget.transform);
}
navMeshAgent.speed = IdleSpeed;
animator.SetFloat(SpeedFloat, IdleSpeed);
navMeshAgent.isStopped = true;
if (IsAttackTargetInRange())
{
animator.SetBool(AttackBool, true);
}
else
{
ChangeState(VayneState.Move);
}
}
#endregion
//帧更新状态
public void StateOnUpdate()
{
switch (nowState)
{
case VayneState.Idle:
break;
case VayneState.Move:
MoveOnUpdate();
break;
case VayneState.Attack:
break;
default:
throw new ArgumentOutOfRangeException(nameof(nowState), nowState, null);
}
}
//移动帧更新
public void MoveOnUpdate()
{
// 在移动状态时,旋转角色朝向目标,检查攻击目标是否在范围内,检查位置目标是否达到
RotateTowardsTarget();
if (attackTarget != null && IsAttackTargetInRange())
{
// 如果攻击目标在攻击范围内,切换到攻击状态
ChangeState(VayneState.Attack);
}
if (posTarget != null && IsPosTargetReach())
{
// 如果位置目标已到达,切换到空闲状态
ChangeState(VayneState.Idle);
}
}
// 旋转角色朝向攻击目标或位置目标
private void RotateTowardsTarget()
{
if (attackTarget != null || posTarget != null)
{
Vector3 direction = (attackTarget != null)
? attackTarget.transform.position - transform.position
: posTarget - transform.position;
Quaternion targetRotation = Quaternion.LookRotation(direction);
Quaternion newRotation =
Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
transform.rotation = newRotation;
}
}
// 设置攻击目标,重置导航,设置导航目标为攻击目标位置
public void SetAttackTarget(GameObject attackTarget)
{
navMeshAgent.velocity = Vector3.zero;
this.attackTarget = attackTarget;
this.posTarget = attackTarget.transform.position;
navMeshAgent.ResetPath();
navMeshAgent.SetDestination(attackTarget.transform.position);
}
// 设置位置目标,重置导航,设置导航目标为位置目标
public void SetPosTarget(Vector3 posTarget)
{
navMeshAgent.velocity = Vector3.zero;
this.posTarget = posTarget;
this.attackTarget = null;
navMeshAgent.ResetPath();
navMeshAgent.SetDestination(posTarget);
}
// 检查攻击目标是否在攻击范围内
public bool IsAttackTargetInRange()
{
var position1 = this.transform.position;
Vector3 thisPos = new Vector3(position1.x, 0, position1.z);
var position2 = attackTarget.transform.position;
Vector3 attackTargetPos = new Vector3(position2.x, 0, position2.z);
Vector3.Distance(thisPos, attackTargetPos);
return Vector3.Distance(thisPos, attackTargetPos) <= attackRange + Mathf.Epsilon;
}
// 检查是否到达位置目标
public bool IsPosTargetReach()
{
return navMeshAgent.remainingDistance <= 0.01f + Mathf.Epsilon;
}
}
用户输入接口和薇恩输入适配器类
// 定义一个输入目标接口,用于处理不同的输入事件
public interface IInputTarget
{
// 当用户按下键盘上的 "A" 键时调用此方法
void OnAKeyPress();
// 当用户点击鼠标左键时调用此方法
void OnLeftMouseClick();
// 当用户点击鼠标右键时调用此方法
void OnRightMouseClick();
}
//薇恩输入适配器
public class VayneInputAdapter : MonoBehaviour, IInputTarget
{
public VayneInputAdaptee vayneInputAdaptee; // 适配的角色控制器
public bool isShowAttackRange; // 是否显示攻击范围
public float attackRange; // 攻击范围
private GameObject lineRendererGameObject; // 攻击范围线段渲染器的游戏对象
private LineRenderer lineRenderer; // 攻击范围线段渲染器
void Start()
{
vayneInputAdaptee = FindObjectOfType<VayneInputAdaptee>(); // 查找并获取适配的角色控制器
attackRange = vayneInputAdaptee != null ? vayneInputAdaptee.attackRange : 10; // 设置攻击范围,默认为10
CreateLineRenderer(); // 创建攻击范围的线段渲染器
}
void Update()
{
UpdateAttackRangePosition(); // 更新攻击范围的位置
}
// 当用户按下键盘上的 "A" 键时调用
public void OnAKeyPress()
{
if (Input.GetKeyDown(KeyCode.A))
{
isShowAttackRange = !isShowAttackRange;
if (isShowAttackRange)
{
ShowAttackRange(); // 显示攻击范围
}
else
{
HideAttackRange(); // 隐藏攻击范围
}
}
}
// 当用户点击鼠标左键时调用
public void OnLeftMouseClick()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit raycastHit;
Camera mainCamera = Camera.main;
if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out raycastHit))
{
if (raycastHit.collider.gameObject.CompareTag("Enemy"))
{
if (isShowAttackRange)
{
SetAttackTargetAndChangeState(raycastHit.collider.gameObject);
}
else
{
ShowEnemyName(raycastHit.collider.gameObject);
}
}
if (raycastHit.collider.gameObject.CompareTag("Plane"))
{
if (isShowAttackRange)
{
SetPositionTargetAndChangeState(raycastHit.point);
}
}
}
}
}
// 当用户点击鼠标右键时调用
public void OnRightMouseClick()
{
if (Input.GetMouseButtonDown(1))
{
if (isShowAttackRange)
{
HideAttackRange(); // 隐藏攻击范围
}
RaycastHit raycastHit;
Camera mainCamera = Camera.main;
if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out raycastHit))
{
if (raycastHit.collider.gameObject.CompareTag("Enemy"))
{
SetAttackTargetAndChangeState(raycastHit.collider.gameObject);
}
if (raycastHit.collider.gameObject.CompareTag("Plane"))
{
SetPositionTargetAndChangeState(raycastHit.point);
}
}
}
}
// 创建攻击范围的线段渲染器
private void CreateLineRenderer()
{
lineRendererGameObject = new GameObject("AttackRange");
lineRenderer = lineRendererGameObject.AddComponent<LineRenderer>();
lineRenderer.material = Resources.Load<Material>("Lesson12_结构型模式_适配器模式/MaterialBule");
lineRenderer.startColor = Color.blue;
lineRenderer.endColor = Color.blue;
lineRenderer.startWidth = 0.1f;
lineRenderer.endWidth = 0.1f;
lineRenderer.loop = true;
lineRendererGameObject.SetActive(false);
lineRendererGameObject.transform.position = vayneInputAdaptee.transform.position;
}
// 更新攻击范围的位置
private void UpdateAttackRangePosition()
{
if (isShowAttackRange)
{
lineRendererGameObject.transform.position = vayneInputAdaptee.transform.position;
DrawAttackRange(vayneInputAdaptee.transform.position, attackRange, 360);
}
}
// 显示攻击范围
private void ShowAttackRange()
{
lineRendererGameObject.SetActive(true);
DrawAttackRange(vayneInputAdaptee.transform.position, attackRange, 360);
}
// 隐藏攻击范围
private void HideAttackRange()
{
lineRendererGameObject.SetActive(false);
}
// 设置攻击目标并切换角色状态
private void SetAttackTargetAndChangeState(GameObject targetObject)
{
isShowAttackRange = false;
lineRendererGameObject.SetActive(false);
vayneInputAdaptee.SetAttackTarget(targetObject);
vayneInputAdaptee.ChangeState(VayneState.Attack);
}
// 显示敌人的名字(用于调试)
private void ShowEnemyName(GameObject enemyObject)
{
Debug.Log("敌人的名字是" + enemyObject.name);
}
// 设置位置目标并切换角色状态
private void SetPositionTargetAndChangeState(Vector3 targetPosition)
{
isShowAttackRange = false;
lineRendererGameObject.SetActive(false);
vayneInputAdaptee.SetPosTarget(new Vector3(targetPosition.x, 0, targetPosition.z));
vayneInputAdaptee.ChangeState(VayneState.Move);
}
// 绘制攻击范围的线段
public void DrawAttackRange(Vector3 centerPos, float r, int pointNum)
{
lineRenderer.positionCount = pointNum;
float angle = 360f / pointNum;
for (int i = 0; i < pointNum; i++)
{
Vector3 pos = Quaternion.AngleAxis(angle * i, Vector3.up) * Vector3.forward * r + centerPos;
lineRenderer.SetPosition(i, pos);
}
}
}
客户端
public class TestAdapterPattern : MonoBehaviour
{
private IInputTarget target; // 适配器的接口引用
private void Start()
{
// 创建一个新的游戏对象作为 VayneInputAdapter
GameObject vayneInputAdapter = new GameObject();
vayneInputAdapter.name = "VayneInputAdapter";
// 添加 VayneInputAdapter 组件,它实现了 IInputTarget 接口
target = vayneInputAdapter.AddComponent<VayneInputAdapter>();
}
private void Update()
{
// 在每帧更新中模拟用户输入事件
target.OnAKeyPress(); // 模拟按下 "A" 键
target.OnLeftMouseClick(); // 模拟鼠标左键点击
target.OnRightMouseClick(); // 模拟鼠标右键点击
}
}
运行结果
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com