學(xué)習(xí)Unity(9)打飛碟小游戲改進(jìn)——使用適配器模式

改進(jìn)描述

我們之前完成的飛碟游戲中真慢,UFO是在兩點(diǎn)之間來回飛行齿风,我們是通過修改position來使得飛碟運(yùn)動(dòng)起來的杜恰。

現(xiàn)在,為了練習(xí)對(duì)Unity物理引擎的使用和適配器模式的使用敞贡,我們想要加入另一種飛碟運(yùn)動(dòng)模式:物理運(yùn)動(dòng)模式泵琳,飛碟受到向下的力,向地面撞去誊役。玩家要在飛碟撞到地面之前擊中飛碟才能得分获列,飛碟撞上地面則不得分。

并且蛔垢,我們不僅要實(shí)現(xiàn)物理運(yùn)動(dòng)模式击孩,還要保留著原本的普通運(yùn)動(dòng)模式,通過鼠標(biāo)右鍵鹏漆,用戶可以在兩種模式之間切換巩梢。

游戲截圖

正常運(yùn)動(dòng)模式下飛碟來回飛行
鼠標(biāo)右鍵可以切換運(yùn)動(dòng)模式
物理模式下飛碟會(huì)緩緩掉落地面

在自己的電腦上運(yùn)行這個(gè)游戲!

我的github下載項(xiàng)目資源甫男,將所有文件放進(jìn)你的項(xiàng)目的Assets文件夾(如果有重復(fù)則覆蓋)且改,然后在U3D中雙擊“hw5”,就可以運(yùn)行了板驳!

實(shí)現(xiàn)物理模式的動(dòng)作管理器

這次的改進(jìn)有一點(diǎn)特別又跛。正常運(yùn)動(dòng)模式不能刪除,而是與新的物理運(yùn)動(dòng)模式共存若治,我們要在游戲運(yùn)行的時(shí)候來決定使用哪種運(yùn)動(dòng)模式慨蓝。也就是說感混,原本的動(dòng)作管理器類不能刪除,它們是管理正常運(yùn)動(dòng)模式的礼烈。我們還要再實(shí)現(xiàn)一個(gè)動(dòng)作管理器弧满,用來管理物理運(yùn)動(dòng)模式。最后想一種辦法將兩個(gè)動(dòng)作管理器結(jié)合起來此熬。
首先我們實(shí)現(xiàn)物理模式動(dòng)作管理器:

public class PhysicsActionManager : MonoBehaviour {

    public void addForce(GameObject gameObj, Vector3 force) {
        ConstantForce originalForce = gameObj.GetComponent<ConstantForce>();
        if (originalForce) {
            originalForce.enabled = true;
            originalForce.force = force;
        } else {
            gameObj.AddComponent<Rigidbody>().useGravity = false;
            gameObj.AddComponent<ConstantForce>().force = force;
        }
    }

    public void removeForce(GameObject gameObj) {
        gameObj.GetComponent<ConstantForce>().enabled = false;
    }
}

這個(gè)管理器的實(shí)現(xiàn)非常簡單庭呜,只需要負(fù)責(zé)增加\移除ConstantForce組件就可以了。

要使物體受到力的影響犀忱,必須先讓他具有Rigidbody(剛體)組件募谎。對(duì)物理引擎的使用,網(wǎng)上有很多教程阴汇。你可以查看官方文檔學(xué)習(xí)其他作者的博客数冬。


適配器模式

如何將兩種動(dòng)作管理器有機(jī)地結(jié)合起來呢?讓FirstController(場景控制器)同時(shí)擁有兩個(gè)變量搀庶,分別指向這兩個(gè)動(dòng)作管理器嗎拐纱?這樣不好,如果我們以后又要增加新的動(dòng)作管理器呢哥倔?如果我們要增加新的飛碟工廠類呢秸架?這樣的話FirstController就需要管理太多功能相同的部件了,F(xiàn)irstController會(huì)越來越臃腫咆蒿,可擴(kuò)展性很差咕宿。

我們希望FirstController只需要為同一個(gè)用途的所有組件保存1個(gè)變量

這就是為什么我們需要適配器模式蜡秽。
讓我通過一個(gè)生活中的例子來解釋適配器模式:現(xiàn)在大部分的的平板電腦只有一個(gè)USB接口,現(xiàn)在我想在我的平板電腦上同時(shí)使用鍵盤和鼠標(biāo)缆镣,怎么辦芽突?很簡單,買一個(gè)這樣的USB擴(kuò)展器:


USB擴(kuò)展器就是一種適配器

將USB擴(kuò)展器插在平板的USB接口上董瞻,然后將鍵盤寞蚌、鼠標(biāo)插在USB擴(kuò)展器的USB接口上,你就可以同時(shí)使用鍵盤和鼠標(biāo)了钠糊!

在這個(gè)例子中挟秤,我們的平板就像是FirstController,兩個(gè)輸入設(shè)備就像是兩個(gè)動(dòng)作管理器抄伍。要將兩個(gè)動(dòng)作管理器同時(shí)接入FirstController艘刚,我們要實(shí)現(xiàn)一個(gè)適配器,讓FirstController連接適配器截珍,然后讓適配器連接兩個(gè)動(dòng)作管理器攀甚。

我們先將FirstController中原本保存ActionManager的變量刪掉箩朴,然后添加這一行:

ActionManagerTarget actionManagerTarget;

ActionManagerTarget是一個(gè)接口,它就相當(dāng)于平板電腦上的USB接口:

public interface ActionManagerTarget {
    void switchActionMode();
    
    void addAction(GameObject gameObj, Dictionary<string, object> option);

    void addActionForArr(GameObject[] Arr, Dictionary<string, object> option);

    void addActionForArr(UFOController[] Arr, Dictionary<string, object> option);

    void removeActionOf(GameObject obj, Dictionary<string, object> option);
}

然后實(shí)現(xiàn)一個(gè)適配器類ActionManagerAdapter秋度,這個(gè)類要實(shí)現(xiàn)這個(gè)接口:

public class ActionManagerAdapter: ActionManagerTarget {
    FirstSceneActionManager normalAM;
    PhysicsActionManager PhysicsAM;

    int whichActionManager = 0; // 0->normal, 1->physics

    public ActionManagerAdapter(GameObject main) {
        normalAM = main.AddComponent<FirstSceneActionManager>();
        PhysicsAM = main.AddComponent<PhysicsActionManager>();
        whichActionManager = 0;
    }

    public void switchActionMode() {
        whichActionManager = 1-whichActionManager;
    }

    public void addAction(GameObject gameObj, Dictionary<string, object> option) {
        if (whichActionManager == 0)
        //  use normalAM
        {
            Debug.Log("use normalAM");
            normalAM.addRandomAction(gameObj, (float)option["speed"]);
        }

        else
        //  use PhysicsAM
        {
            Debug.Log("use PhysicsAM");
            PhysicsAM.addForce(gameObj, (Vector3)option["force"]);
        }
    }

    public void addActionForArr(GameObject[] Arr, Dictionary<string, object> option) {
        if (whichActionManager == 0)
        //  use normalAM
        {
            Debug.Log("use normalAM");
            float speed = (float)option["speed"];
            foreach (GameObject gameObj in Arr) {
                normalAM.addRandomAction(gameObj, speed);
            }
        }

        else
        //  use PhysicsAM
        {
            Debug.Log("use PhysicsAM");
            Vector3 force = (Vector3)option["force"];
            foreach (GameObject gameObj in Arr) {
                PhysicsAM.addForce(gameObj, force);
            }
        }
    }

    public void addActionForArr(UFOController[] Arr, Dictionary<string, object> option) {
        if (whichActionManager == 0)
        //  use normalAM
        {
            Debug.Log("use normalAM");
            float speed = (float)option["speed"];
            foreach (UFOController ctrl in Arr) {
                normalAM.addRandomAction(ctrl.getObj(), speed);
            }
        }

        else
        //  use PhysicsAM
        {
            Debug.Log("use PhysicsAM");
            Vector3 force = (Vector3)option["force"];
            foreach (UFOController ctrl in Arr) {
                PhysicsAM.addForce(ctrl.getObj(), force);
            }
        }
    }

    public void removeActionOf(GameObject gameObj, Dictionary<string, object> option){
        if (whichActionManager == 0)
        //  use normalAM
        {
            Debug.Log("use normalAM");
            normalAM.removeActionOf(gameObj);
        }

        else
        //  use PhysicsAM
        {
            Debug.Log("use PhysicsAM");
            PhysicsAM.removeForce(gameObj);
        }
    }
}

可以看出炸庞,我們?cè)趯?shí)現(xiàn)適配器的時(shí)候,將兩個(gè)動(dòng)作管理器“焊死”在適配器上了荚斯,你還可以自己嘗試埠居,實(shí)現(xiàn)一個(gè)可以“自由插拔”的適配器:)。

然后我們?cè)贔irstController的構(gòu)造函數(shù)中實(shí)例化一個(gè)適配器(相當(dāng)于將USB擴(kuò)展器插在平板電腦上):

actionManagerTarget = new ActionManagerAdapter(gameObject);

最后不要忘了在Update中監(jiān)測用戶鼠標(biāo)的右鍵輸入事期,切換動(dòng)作管理模式滥壕。最終的FirstController是這樣的:

public class FirstController : MonoBehaviour, SceneController
{
    Director director;

    UFOFactory UFOfactory;

    ExplosionFactory explosionFactory;

    ActionManagerTarget actionManagerTarget;

    bool switchAMInNextRound = false;

    Scorer scorer;

    DifficultyManager difficultyManager;

    float timeAfterRoundStart = 10;

    bool roundHasStarted = false;

    FirstCharacterController firstCharacterController;

    Text hint;

    void Awake()
    {
        // 掛載各種控制組件

        director = Director.getInstance();
        director.currentSceneController = this;

        // actionManager = gameObject.AddComponent<FirstSceneActionManager>();
        actionManagerTarget = new ActionManagerAdapter(gameObject);

        UFOfactory = gameObject.AddComponent<UFOFactory>();

        explosionFactory = gameObject.AddComponent<ExplosionFactory>();

        scorer = Scorer.getInstance();
        difficultyManager = DifficultyManager.getInstance();


        loadResources();
        Physics.IgnoreLayerCollision(LayerMask.NameToLayer("Shootable"), LayerMask.NameToLayer("Shootable"), true);
    }
    public void loadResources()
    {
        // 初始化場景中的物體
        firstCharacterController = new FirstCharacterController();
        Instantiate(Resources.Load("Terrain"));
        hint = (Instantiate(Resources.Load("ShowResult")) as GameObject).GetComponentInChildren<Text>();
        hint.text = "";
    }

    public void Start()
    {
        roundStart();
    }

    void Update()
    {
        if (roundHasStarted) {
            timeAfterRoundStart += Time.deltaTime;
        }

        if (roundHasStarted && checkAllUFOIsShot()) // 檢查是否所有UFO都已經(jīng)被擊落
        {
            hint.text = "All UFO has crashed in this round! Next round in 3 sec";
            roundHasStarted = false;
            Invoke("roundStart", 3);
            difficultyManager.setDifficultyByScore(scorer.getScore());
        }
        else if (roundHasStarted && checkTimeOut()) // 檢查這一輪是否已經(jīng)超時(shí)
        {
            hint.text = "Time out! Next round in 3 sec";
            roundHasStarted = false;
            foreach (UFOController ufo in UFOfactory.getUsingList())
            {
                actionManagerTarget.removeActionOf(ufo.getObj(), new Dictionary<string, object>());
            }
            UFOfactory.recycleAll();
            Invoke("roundStart", 3);
            difficultyManager.setDifficultyByScore(scorer.getScore());
        }
        if (Input.GetButtonDown("Fire2")) {
            hint.text = "Action of UFOs will change in the next round!";
            switchAMInNextRound = true;
        }
    }

    void roundStart()
    {   
        // 開始新的一輪
        if (switchAMInNextRound) {
            switchAMInNextRound = false;
            actionManagerTarget.switchActionMode();
        }

        roundHasStarted = true;
        timeAfterRoundStart = 0;
        UFOController[] ufoCtrlArr = UFOfactory.produceUFOs(difficultyManager.getUFOAttributes(), difficultyManager.UFONumber);
        for (int i = 0; i < ufoCtrlArr.Length; i++)
        {
            ufoCtrlArr[i].appear();
            ufoCtrlArr[i].setPosition(getRandomUFOPosition());
        }

        actionManagerTarget.addActionForArr(ufoCtrlArr, new Dictionary<string, object>() {
            {"speed", ufoCtrlArr[0].attr.speed},
            {"force", difficultyManager.getGravity()}
        });
        hint.text = "";
    }

    bool checkTimeOut()
    {
        if (timeAfterRoundStart > difficultyManager.currentSendInterval)
        {
            return true;
        }
        return false;
    }

    bool checkAllUFOIsShot()
    {
        return UFOfactory.getUsingList().Count == 0;
    }

    public void UFOIsShot(UFOController UFOCtrl)
    {
        // 響應(yīng)UFO被擊中的事件
        scorer.record(difficultyManager.getDifficulty());
        actionManagerTarget.removeActionOf(UFOCtrl.getObj(), new Dictionary<string, object>());
        UFOfactory.recycle(UFOCtrl);
        explosionFactory.explodeAt(UFOCtrl.getObj().transform.position);
    }

    public void GroundIsShot(Vector3 pos) {
        // 響應(yīng)地面被擊中的事件(直接產(chǎn)生一個(gè)爆炸)
        explosionFactory.explodeAt(pos);
    }

    public void UFOCrash(UFOController UFOCtrl) {
        actionManagerTarget.removeActionOf(UFOCtrl.getObj(), new Dictionary<string, object>());
        UFOfactory.recycle(UFOCtrl);
        explosionFactory.explodeAt(UFOCtrl.getObj().transform.position);
    }

    public Vector3 getRandomUFOPosition() {
        Vector3 relativeToCharacter = new Vector3(Random.Range(-10, 10), Random.Range(10, 15), Random.Range(-10, 10));
        return firstCharacterController.getPosition()+relativeToCharacter;
    }
}

注意我沒有在監(jiān)測到鼠標(biāo)右鍵輸入以后馬上切換動(dòng)作管理模式,而是通過一點(diǎn)小技巧刑赶,延遲到下一輪開始的時(shí)候再切換捏浊。這是因?yàn)槿绻⒖糖袚Q,這一輪的“動(dòng)作取消”會(huì)出很大的問題撞叨。你仔細(xì)想想金踪,這一輪開始的時(shí)候,我們使用正常動(dòng)作管理器給每個(gè)飛碟添加了一個(gè)普通的動(dòng)作牵敷,而取消動(dòng)作的時(shí)候卻使用物理動(dòng)作管理器胡岔!這樣,飛碟上的普通動(dòng)作就無法被回收枷餐,下一輪開始的時(shí)候飛碟依然在來回移動(dòng)靶瘸。

ActionManagerAdapter使用了一個(gè)非常靈活的方式來接收參數(shù):Dictionary<string, object> option 其中的object可以傳遞任何類型的值,甚至是int毛肋、float原始類型怨咪。因?yàn)镕irstController不知道當(dāng)前的運(yùn)動(dòng)模式是什么,不知道應(yīng)該給ActionManagerAdapter傳遞speed參數(shù)還是force參數(shù)润匙,于是干脆兩個(gè)都傳進(jìn)去诗眨,讓ActionManagerAdapter自己選擇:

actionManagerTarget.addActionForArr(ufoCtrlArr, new Dictionary<string, object>() {
            {"speed", ufoCtrlArr[0].attr.speed},
            {"force", difficultyManager.getGravity()}
        });

適配器模式補(bǔ)充說明

適配器模式定義

適配器模式(Adapter Pattern) :將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,適配器模式使接口不兼容的那些類可以一起工作孕讳,其別名為包裝器(Wrapper)匠楚。

需要接入2個(gè)類,而客戶類只提供1個(gè)接口厂财,這也是一種“接口不兼容”芋簿。

適配器模式的組成

  • Target:目標(biāo)抽象類(USB接口)
  • Adapter:適配器類(USB擴(kuò)展器)
  • Adaptee:適配者類(鼠標(biāo)、鍵盤璃饱、U盤)
  • Client:客戶類(平板電腦)

適配器的作用与斤,除了我們剛才所說的,將多個(gè)類接入同一個(gè)接口以外,還有轉(zhuǎn)接“不兼容”接口的作用幽告。比如說梅鹦,如果我們想將U盤插入U(xiǎn)SB-typeC接口中,我們要買另一種適配器:

USB轉(zhuǎn)接器也是一種適配器

這個(gè)適配器也解決了“接口不兼容”的問題冗锁。當(dāng)“客戶類提供的接口”與“適配者類”不兼容的時(shí)候齐唆,可以實(shí)現(xiàn)一個(gè)適配器,讓適配器實(shí)現(xiàn)“客戶類提供的接口”冻河,并在這個(gè)適配器中調(diào)用“適配者類”的方法箍邮。

如果還想深入學(xué)習(xí)有關(guān)適配器模式的內(nèi)容,可以看看這個(gè)網(wǎng)站叨叙。


謝謝閱讀锭弊!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市擂错,隨后出現(xiàn)的幾起案子味滞,更是在濱河造成了極大的恐慌,老刑警劉巖钮呀,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剑鞍,死亡現(xiàn)場離奇詭異,居然都是意外死亡爽醋,警方通過查閱死者的電腦和手機(jī)蚁署,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚂四,“玉大人光戈,你說我怎么就攤上這事∷煸” “怎么了久妆?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跷睦。 經(jīng)常有香客問我镇饺,道長,這世上最難降的妖魔是什么送讲? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮惋啃,結(jié)果婚禮上哼鬓,老公的妹妹穿的比我還像新娘。我一直安慰自己边灭,他們只是感情好异希,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绒瘦,像睡著了一般称簿。 火紅的嫁衣襯著肌膚如雪扣癣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天憨降,我揣著相機(jī)與錄音父虑,去河邊找鬼。 笑死授药,一個(gè)胖子當(dāng)著我的面吹牛士嚎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悔叽,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼莱衩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娇澎?” 一聲冷哼從身側(cè)響起笨蚁,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趟庄,沒想到半個(gè)月后括细,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岔激,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年勒极,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虑鼎。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辱匿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炫彩,到底是詐尸還是另有隱情匾七,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布江兢,位于F島的核電站昨忆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杉允。R本人自食惡果不足惜邑贴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叔磷。 院中可真熱鬧拢驾,春花似錦、人聲如沸改基。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稠腊,卻和暖如春躁染,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背架忌。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工吞彤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳖昌。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓备畦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親许昨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懂盐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容