您的位置:首页 > 移动开发 > Unity3D

[Unity 设计模式]桥接模式(BridgePattern)

2017-09-13 18:40 471 查看

1.前言

继上一讲IOC模式的基础上继续本讲桥接模式,笔者感觉桥接模式是23种设计模式中桥接模式是最好用但也是最难理解的设计模式之一,23中设计模式就好武侠剧中一本武功秘籍,我们在工作过程中想要熟练运用其中的每一种设计模式就好比跟高手过招想要能运用好武侠秘籍中的每一招每一式,并且能随着对手出招的不同我们能随机应变对应的招数,这就要求我们对每一种设计模式都理解的非常深刻才能运用自如,打出组合拳的效果。

2.需求

我们在FPS类游戏中会碰到这样的需求——实现武器和角色,无论是敌人还是我方角色都能通过不同的武器击杀对方,武器有手枪、散弹枪、以及火箭炮,并以”攻击力”和”攻击距离”来区分他们的威力。我们可能会这样实现:



上面是我们的一个UML示例图,这里笔者很惭愧没用过UML专业的绘图工具,就临时用绘图软件简单的画了一下。下面就是我们的初步设计代码。

角色基类

publicabstractclassICharacter
{
//拥有一把武器
protectedWeaponm_Weapon=null;
//攻击目标
publicabstractvoidAttack(ICharactertarget);
}


武器类

usingUnityEngine;

publicenumENUM_Weapon
{
Null=0,
Gun,
Rifle,
Rocket,
}

publicclassWeapon
{
protectedENUM_Weaponm_EmEwapon=ENUM_Weapon.Null;
protectedintm_AtkValue=0;//攻击力
protectedintm_AtkRange=0;//攻击距离
protectedintm_AtkPlusValue=0;//额外加成

publicWeapon(ENUM_Weapontype,intatkValue,intatkRange)
{
m_EmEwapon=type;
m_AtkValue=atkValue;
m_AtkRange=atkRange;
}

publicENUM_WeaponGetWeaponType()
{
returnm_EmEwapon;
}

publicvoidFire(ICharactertarget)
{
//
}

publicvoidSetAtkPlusValue(intatkPlusValue)
{
m_AtkPlusValue=atkPlusValue;
}

publicvoidShowBulletEffect(Vector3targetPosition,floatlineWidth,floatdisplayTime)
{

}

publicvoidShowShootEffect()
{

}

publicvoidShowSoundEffect(stringclipName)
{

}
}


敌人使用武器

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;

publicclassIEnemy:ICharacter
{
publicIEnemy()
{}

publicoverridevoidAttack(ICharactertarget)
{
m_Weapon.ShowShootEffect();
intatkPlusValue=0;
switch(m_Weapon.GetWeaponType())
{
caseENUM_Weapon.Gun:
//显示武器特效
m_Weapon.ShowBulletEffect(target.GetPosition(),0.3f,0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue=GetAtkPluginValue(5,20);
break;
caseENUM_Weapon.Rifle:
m_Weapon.ShowBulletEffect(target.GetPosition(),0.4f,0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue=GetAtkPluginValue(5,20);
break;
caseENUM_Weapon.Rocket:
m_Weapon.ShowBulletEffect(target.GetPosition(),0.5f,0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue=GetAtkPluginValue(5,20);
break;
}
m_Weapon.SetAtkPlusValue(atkPlusValue);
m_Weapon.Fire(target);
}
privateintGetAtkPlusValue(intrate,intatkValue)
{
intrandValue=UnityEngine.Random.Range(0,100);
if(rate>randValue)
returnatkValue;
return0;
}
}


玩家使用武器

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;

publicclassISoldier:ICharacter
{
publicISoldier()
{

}

publicoverridevoidAttack(ICharactertarget)
{
m_Weapon.ShowShootEffect();
switch(m_Weapon.GetWeaponType())
{
caseENUM_Weapon.Gun:
m_Weapon.ShowBulletEffect(target.GetPosition(),0.03f,0.2f);
m_Weapon.ShowSoundEffect("GunShot");
break;
caseENUM_Weapon.Rifle:
m_Weapon.ShowBulletEffect(target.GetPosition(),0.5f,0.2f);
m_Weapon.ShowSoundEffect("RifleShot");
break;
caseENUM_Weapon.Rocket:
m_Weapon.ShowBulletEffect(target.GetPosition(),0.8f,0.2f);
m_Weapon.ShowSoundEffect("RocketShot");
break;
}
m_Weapon.Fire(target);
}
}


3.初步分析

以上实现存在明显两个缺点:

每当添加一个角色时,继承自ICharacter接口的角色重新定义Attack都必须针对不同的武器进行判断,并且要写重复的相同的代码。

每当新增武器时,所有角色的Attack都需要重新改造,这样增加维护成本。
为了解决以上问题,下面我们桥接模式隆重登场了!

4.桥接模式

桥接模式(BridgePattern):桥接模式的用意是将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
以上是桥接模式的官方定义,我们还是通过以上例子来理解分析,”桥接”顾名思义就是这一组跟那一组两组对象通过某种中间关系链接在一起。”当两个群组因为功能上的需求,想要进行链接合作,但又希望两组类可以自行发展互相不受对方变化而影响”,上面角色跟武器的实现案例,武器和角色分别是两组群组,其实上面的实现只是考虑了角色通过子类通过基类继承来实现,然后传入武器对象,这中实现思路有点上一讲介绍的控制反转的味道,那既然角色可以这样设计,为何不把武器也这样设计呢,然后两个群组之间就通过两个群组的接口来实现,就好比两家结婚,双方都有一个媒人来传递信息,这是我对桥接模式的理解,哈哈。基于这种想法,我们来改造一下角色和武器的实现。



说明:

ICharacter:角色的抽象接口拥有一个IWeapon的对象引用,并在接口中申明了一个武器攻击目标WeaponAttackTarget()方法让子类可以调用,同时要求继承子类必须在Attack()中重新实现攻击目标的功能。

ISoldier、IEnemy:双方阵容单位实现攻击目标Attack()时,只需要调用父类的WeaponAttackTarget方法就可以使用当前武器攻击对手。

IWeapon:武器接口,定义游戏中对于武器的操作和使用方法。

WeaponGun、WeaponRifle、WeaponRocket:游戏中可以使用三中武器对象。

5.桥接模式实现武器和角色功能

武器接口

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;

publicabstractclassIWeapon
{
//属性
protectedintm_AtkPlusValue=0;
protectedintm_Atk=0;
protectedfloatm_Range=0;

protectedGameObjectm_GameObject=null;
protectedICharacterm_WeaponOwner=null;//武器拥有者

//发射特效
protectedfloatm_EffectDisplayTime=0;
protectedParticleSystemm_Particles;
protectedAudioSourcem_Audio;

//显示子弹特效
protectedvoidShowBulletEffect(Vector3targetPosition,floatdisPlayTime)
{
m_EffectDisplayTime=disPlayTime;
}

//显示枪口特效
protectedvoidShowShootEffect()
{
if(m_Particles!=null)
{
m_Particles.Stop();
m_Particles.Play();
}
}

//显示音效
protectedvoidShowSoundEffect(stringclipName)
{
if(m_Audio==null)
return;
IAssetFactoryfactory=Factory.GetAssetFactory();
varclip=factory.LoadAudioClip(clipName);
if(clip==null)
return;
m_Audio.clip=clip;
m_Audio.Play();
}

//攻击目标
publicabstractvoidFire(ICharactertarget);
}




手枪武器的实现

publicclassWeaponGun:IWeapon
{
publicWeaponGun()
{

}

publicoverridevoidFire(ICharactertarget)
{
ShowShootEffect();
ShowBulletEffect(target.GetPosition(),0.3f,0.2f);
ShowSoundEffect("GunShot");

target.UnderAttack(m_WeaponOwner);
}
}



其他武器同理…

角色接口

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;

publicabstractclassICharacter
{
protectedWeaponm_Weapon=null;

publicvoidSetWeapon(IWeaponweapon)
{
if(m_Weapon!=null)
m_Weapon.Release();
m_Weapon=weapon;
//设置武器拥有者
m_Weapon.SetOwener(this);
}

//获取武器
publicIWeaponGetWeapon()
{
returnm_Weapon;
}

protectedvoidSetWeaponAtkPlusValue(intvalue)
{
m_Weapon.SetAtkPlusValue(value);
}

protectedvoidWeaponAttackTarget(ICharactertarget)
{
m_Weapon.Fire(target);
}

//获取武器攻击力
publicvoidGetAtkValue()
{
returnm_Weapon.GetAtkValue();
}

///<summary>
///获得攻击距离
///</summary>
///<returns></returns>
publicfloatGetAttackRange()
{
returnm_Weapon.GetAtkRange();
}
///<summary>
///攻击目标
///</summary>
///<paramname="target"></param>
publicabstractvoidAttack(ICharactertarget);
///<summary>
///被其他角色攻击
///</summary>
///<paramname="attacker"></param>
publicabstractvoidUnderAttack(ICharacterattacker);
}




桥接模式角色实现

//角色接口
publicclassISolider:ICharacter
{
publicoverridevoidAttack(ICharactertarget)
{
WeaponAttackTarget(target);
}

publicoverridevoidUnderAttack(ICharacterattacker)
{
...
}
}

//Enemy接口
publicclassIEnemy:ICharacter
{
publicoverridevoidAttack(ICharactertarget)
{
SetWeaponAtkPlusValue(m_Weapon.GetAtkPlusValue);
WeaponAttackTarget(target);
}

publicoverridevoidUnderAttack(ICharacterattacker)
{
...
}
}


4.桥接模式改造后分析

以上设计运用桥接模式后的ICharacter就是群组”抽象类”,它定义了”攻击目标”功能,但实现攻击目标功能的却是群组”IWeapon武器类”,对于ICharacter以及其继承都不会理会IWeapon群组的变化,尤其在新增武器类的时候也不会影响角色类。对于ICharacter来说,它面对的指示IWeapon这个接口类,这让两个群组耦合度降到最低。

5.思考

结合以上案例,可以思考另外一个需求:用不同的图形渲染引擎如OpenGL、DirectX来绘制不同的图形。
提示:渲染引擎RenderEngine和图形属于两大群体,这两大群体都要单独有各自的“抽象类”抽象类RenderEngine和抽象类Shape。

抽象类RenderEngine肯定要有一个抽象功能方法就是Draw(stringshape),这个是被子类具体的渲染引擎重写,因为各自的引擎都有自己独特的渲染方法,所以这个Draw方法就被重写调用各个引擎自己的渲染方法GLRender()和DXRender()。

抽象类Shape肯定也要有一个保护对象RenderEngine和设置这个对象的注入方法SetRenderEngine(RenderEngineengine)以及抽象方法Draw()。Draw方法是要被子类重写实现:调用renderEngine的Draw方法来绘制自己。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: