改進(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)行這個(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接口上董瞻,然后將鍵盤寞蚌、鼠標(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接口中,我們要買另一種適配器:
這個(gè)適配器也解決了“接口不兼容”的問題冗锁。當(dāng)“客戶類提供的接口”與“適配者類”不兼容的時(shí)候齐唆,可以實(shí)現(xiàn)一個(gè)適配器,讓適配器實(shí)現(xiàn)“客戶類提供的接口”冻河,并在這個(gè)適配器中調(diào)用“適配者類”的方法箍邮。
如果還想深入學(xué)習(xí)有關(guān)適配器模式的內(nèi)容,可以看看這個(gè)網(wǎng)站叨叙。
謝謝閱讀锭弊!