Unity中實(shí)現(xiàn)坦克大戰(zhàn)——90版經(jīng)典游戲復(fù)刻

作者:呆呆的碼農(nóng)
工程難度:??
作為紅白機(jī)時(shí)代的經(jīng)典游戲,90坦克絕對(duì)算是年代表作品之一,今天我想用讀取配置表的方式復(fù)刻一下這兒時(shí)的經(jīng)典。
1.場(chǎng)景建設(shè)
首先將準(zhǔn)備好的場(chǎng)景素材導(dǎo)入工程,將其中的磚墻的Tag設(shè)置為Wall,草地Tag設(shè)置為Wall1,水墻設(shè)置為Wall2,鐵墻Wall3,并在每個(gè)物體身上添加Box Collider2D組件,設(shè)置三個(gè)空節(jié)點(diǎn),用來掛載敵人和玩家和子彈。

上圖中白色圖片為敵人生成點(diǎn)。
2.數(shù)據(jù)配置表
作為可拓展、可配置的坦克大戰(zhàn)當(dāng)然離不開配置表的設(shè)置,你可以在配置表里面添加一切你想擁有的角色屬性或道具屬性等等。
玩家表分析設(shè)計(jì):
1.需要一個(gè)唯一ID來區(qū)別表中的數(shù)據(jù),方便索引;
2.需要等級(jí)來綁定顯示和子彈類型;
3.需要圖片名稱,用來加載圖片用;
4.需要子彈類型,用來顯示不同的子彈效果,及能打穿鐵墻和不能;
敵人表分析設(shè)計(jì):
1.需要一個(gè)唯一ID來區(qū)別表中的數(shù)據(jù),方便索引;
2.需要等級(jí)來綁定顯示和子彈類型;
3.需要圖片名稱,用來加載圖片;
4.需要子彈類型,用來顯示不同的子彈效果,及能打穿鐵墻和不能;
道具分析:
1.添加ID來,方便索引;
2.添加圖片名稱,用來顯示道具外形;
(勤勞的我,會(huì)告訴你就做了一個(gè)道具么?)
我的配置表如下:
1.玩家配置表

2.敵人配置表

3.道具配置表

做好表格之后需要將表格放入U(xiǎn)nity中讀取,這中間我用了一個(gè)小工具將.xlsx格式的文件轉(zhuǎn)化為TXT文本,并放在了Assets根目下,然后用Json插件讀取表格信息并寫入字典,當(dāng)游戲中需要對(duì)應(yīng)數(shù)據(jù)時(shí)new一個(gè)新的物體出來附上對(duì)應(yīng)的值,從而獲取動(dòng)態(tài)數(shù)據(jù)。代碼方式如下:
//構(gòu)造靜態(tài)數(shù)據(jù)類,他們的組成與表格對(duì)應(yīng)
public? class StaticMonsterData
{
??? public readonly string id;
??? public readonly string level;
??? public readonly string pictrue;
??? public readonly string buttle;
??? public float Value(string m)
??? {
??????? float? intA;
???? ???float .TryParse(m, out intA);
??????? return intA;
??? }
}
public class StaticPropData
{
??? public readonly string ID;
??? public readonly string Pictrue;
}
public class StaticCharacterData
{
??? public readonly string id;
??? public readonly string level;
??? public readonly string pictrue;
??? public readonly string buttle;
??? public float Value(string m)
??? {
??????? float intA;
??????? float .TryParse(m, out intA);
??????? return intA;
??? }
}
?
public class StaticData : MonoBehaviour {
??? public static StaticData Instance;
??? public Dictionary<string, StaticMonsterData> monsterDictionary;
??? public Dictionary<string, StaticCharacterData> characterDictionary;
??? public Dictionary<string, StaticPropData> propdictionary;
?
??? void Awake()
??? {
??????? Instance = this;
??????? Init();
??????? LoadSence();
??? }
??? // 初始化,將表格數(shù)據(jù)寫如字典
??? void Init()
??? {
??????? monsterDictionary = Load<Dictionary<string, StaticMonsterData>>("Monster");
??????? characterDictionary = Load<Dictionary<string, StaticCharacterData>>("Character");
??????? propdictionary = Load<Dictionary<string, StaticPropData>>("Prop");
??? }
?
??? // 載入場(chǎng)景,并掛載GameMode腳本
??? void LoadSence()
??? {
??????? var temp = Resources.Load<GameObject>("Sence").gameObject;
??????? var sence = Instantiate(temp);
??????? sence.name = temp.name;
??????? sence.AddComponent<GameMode>();
??? }
??? //獲取TXT文本并賦值給對(duì)應(yīng)的類,并返回T的類型值;
??? public static T Load<T>(string name)
??? {
??????? string rName = name;
??????? string readFilePath=? "Assets/" + rName + ".txt";
??????? string str;
??????? T data = default(T);
??????? if (File.Exists(readFilePath))
??????? {
??????????? StreamReader textData = File.OpenText(readFilePath);
??????????? str = textData.ReadLine();
??????????? textData.Close();
??????????? textData.Dispose();
??????????? data = JsonMapper.ToObject<T>(str);
??????? }
??????? return data;
??? }
?
}
?
?
動(dòng)態(tài)數(shù)據(jù)的獲取:
?
//建立角色類
public class RoleData
{
??? public string id;
??? public float? level;
??? public string picture;
??? public float? speed;
??? public float? bullet;
}
//玩家類繼承角色類
public class CharacterData : RoleData
{
??? ///建立構(gòu)造函數(shù),獲取靜態(tài)數(shù)據(jù)
??? public static CharacterData GetCharacterData(StaticCharacterData data)
??? {
??????? CharacterData characterData = new CharacterData();
??????? characterData.id = data.id;
??????? characterData.level = data.Value(data.level);
??????? characterData.picture = data.pictrue;
??????? characterData.bullet = data.Value(data.buttle);
??????? return characterData;
??? }
}
?
public class MonsterData : RoleData
{
??? public static MonsterData GetMonsterData(StaticMonsterData data)
??? {
??????? MonsterData monsterData =new MonsterData();
??????? monsterData.id = data.id;
??????? monsterData.level = data.Value(data.level);
??????? monsterData.picture = data.pictrue;
??????? monsterData.bullet = data.Value(data.buttle);
??????? return monsterData;
??? }
}
?
public class PropData
{
??? public string id;
??? public string pictrue;
??? public static PropData GetPubicPropData(StaticPropData data)
??? {
?????? ?PropData propData = new PropData();
??????? propData.id = data.ID;
??????? propData.pictrue = data.Pictrue;
??????? return propData;
??? }
}
?
3.GameMode
當(dāng)數(shù)據(jù)獲取完畢,場(chǎng)景載入開始時(shí),需要加載玩家角色,敵人角色,道具信息等,并將他們對(duì)應(yīng)的表信息轉(zhuǎn)換成能隨時(shí)調(diào)用的動(dòng)態(tài)信息賦值給他們。
代碼如下:
void Start()
??? {
??????? home = gameObject.transform.Find("CharacterHome").gameObject;
??????? home1 = gameObject.transform.Find("MonsterHome/Home1").gameObject;
??????? home2 =gameObject .transform .Find("MonsterHome/Home2").gameObject;
??????? home3 = gameObject.transform.Find("MonsterHome/Home3").gameObject;
??????? home4 = gameObject.transform.Find("MonsterHome/Home4").gameObject;
??????? animator1 = home1.GetComponent<Animator>();
??????? animator2 = home2.GetComponent<Animator>();
??????? animator3 = home3.GetComponent<Animator>();
??????? animator4 = home4.GetComponent<Animator>();
??????? Init();
??? }
?
???
??? void CreatMonster(string ID, GameObject Object)
??? {
??????? var Data = StaticData.Instance.monsterDictionary[ID];//獲取字典中相應(yīng)key值的靜態(tài)數(shù)據(jù)
??????? var monsterData = MonsterData.GetMonsterData(Data);//將靜態(tài)數(shù)據(jù)轉(zhuǎn)換成動(dòng)態(tài)數(shù)據(jù)
??????? var temp = Resources.Load<GameObject>("Monster");
??????? var monster = Instantiate(temp, Object.transform);
??????? monster.AddComponent<AiMonster>().data = monsterData;//將動(dòng)態(tài)數(shù)據(jù)賦值給角色。
??? }
??? //獲取KEY值為1的靜態(tài)數(shù)據(jù),轉(zhuǎn)化賦值給生成的玩家。
??? void CreatCharacter()
??? {
??????? var Data = StaticData.Instance.characterDictionary["1"];
??????? var characterData = CharacterData.GetCharacterData(Data);
??????? var temp = Resources.Load<GameObject>("Character");
??????? var character = Instantiate(temp, home.transform);
??????? character.AddComponent<Character>().data = characterData;
??? }
??? //隨機(jī)地點(diǎn)生成道具。并賦值
??? void CreatProp()
??? {
??????? var data = StaticData.Instance.propdictionary["1"];
??????? var propData = PropData.GetPubicPropData(data);
??????? var temp = Resources.Load<GameObject>("Prop");
??????? var prop = Instantiate(temp,gameObject.transform);
??????? prop.transform.position += new Vector3(Random.Range(-480, 200),Random.Range(350,-350));
??????? prop.AddComponent<Prop>().data = propData;
??? }
??? void Init()
??? {
??????? animator1.Play("Start", 0);
??????? animator2.Play("Start", 0);
??????? animator3.Play("Start", 0);
??????? animator4.Play("Start", 0);
???????? //2s后創(chuàng)建敵人
??????? StartCoroutine(OnDelay(2.0f, () =>
???????? {
???????????? CreatMonster("1", home1);
???????? ????CreatMonster("2", home2);
???????????? CreatMonster("3", home3);
???????????? CreatMonster("4", home4);
???????? }));
??????? CreatCharacter();
??? }
??? int times = 0;
??? void Update ()
??? {
??????? if (isGameover == false)
??????? {
??????????? times++;
??????????? CreatProp();
??????????? if (times == 3)
??????????? {
??????????????? isGameover = true;
??????????? }
??????? }
??? }
??? IEnumerator OnDelay(float time, UnityEngine.Events.UnityAction rFunc)
??? {
??????? yield return new WaitForSeconds(time);
??????? rFunc();
??????? yield return null;
??? }
}
?
效果顯示:

4.玩家
簡(jiǎn)單的搭建好玩家模型,設(shè)計(jì)玩家行為:
玩家可以上、下、左、右四個(gè)方向移動(dòng);
每次移動(dòng)距離為場(chǎng)景中一個(gè)墻體的1/2寬度距離;
玩家無法穿越障礙;
玩家可以發(fā)射子彈;
玩家可以獲得道具;
玩家獲取道具升級(jí)之后可以改變外形和字典效果;
玩家可以死亡;
代碼如下:
void Start()
??? {
??????? //獲取玩家模型組件
??????? muzzle = gameObject.transform.Find("MuzzlePiont").gameObject;
??????? rayPiont1 = gameObject.transform.Find("RayPiont1").gameObject;
?? ?????rayPiont2 = gameObject.transform.Find("RayPiont2").gameObject;
??????? bulletPiont = GameObject.Find("BulletPiont");
??????? animator = gameObject.GetComponent<Animator>();
??????? bron = gameObject.transform.Find("Image").GetComponent<Image>();
???? ???//調(diào)用函數(shù)
??????? Init(data);
??????? OnPlayAnimation();
??? }
??? // 播放出生光圈,并在3S后隱藏該組件
??? private void OnPlayAnimation()
??? {
??????? animator.Play("Bron", 0);
??????? StartCoroutine(OnDelay(3, () =>
??????? {
??????????? bron.gameObject.SetActive(false);
??????? }));
??? }
?
??? //初始化玩家,畫出當(dāng)前數(shù)據(jù)下玩家的外形
??? void Init(CharacterData data)
??? {
??????? draw = gameObject.transform.Find("Draw").GetComponent<Image>();
??????? var image_Show = Resources.Load<Sprite>("Image/" + data.picture);
??????? draw.sprite = image_Show;
??? }
?
??? void Update()
??? {
??????? //判斷當(dāng)前是正在移動(dòng)
??????? if (OnMove == false)
??????? {
??????????? if (Input.GetKey(KeyCode.D))
??????????? {
??????????????? OnMove = true;
??????????????? MoveVector = new Vector3(1, 0, 0);
??????????????? endPosition = transform.position + MoveVector * 25;// 設(shè)定每次移動(dòng)25為1/2墻寬
??????????? }
??????????? if (Input.GetKey(KeyCode.S))
??????????? {
??????????????? OnMove = true;
??????????????? MoveVector = new Vector3(0, -1, 0);
??????????????? endPosition = transform.position + MoveVector * 25;
??????????? }
??????????? if (Input.GetKey(KeyCode.A))
??????????? {
??????????????? OnMove = true;
??????????????? MoveVector = new Vector3(-1, 0, 0);
??????????????? endPosition = transform.position + MoveVector * 25;
??????????? }
??????????? if (Input.GetKey(KeyCode.W))
??????????? {
??????????????? OnMove = true;
??????????????? MoveVector = new Vector3(0, 1, 0);
??????????????? endPosition = transform.position + MoveVector * 25;
??????????? }
??????????
??????? }
??????? if (OnMove)
??????? {
??????????? Move();
??????? }
??????? Fire();
??? }
?
??? void Move()
??? {
??????? //當(dāng)初始位置和結(jié)束位置相等時(shí),可以接受重新輸入移動(dòng)方向。
??????? if ((endPosition - transform.position).magnitude ==0)
??????? {
??????????? OnMove = false;
??????????? return;
??????? }
??????? //轉(zhuǎn)向
??????? Trun(MoveVector);
??????? //向移動(dòng)方向打出兩條射線
??????? RaycastHit2D hit1 = Physics2D.Raycast(rayPiont1.transform.position, MoveVector, 0.1f);
??????? RaycastHit2D hit2 = Physics2D.Raycast(rayPiont2.transform.position, MoveVector, 0.1f);
??????? // 射線 碰撞判定
??????? if (hit1.collider != null || hit2.collider != null)
??????? {
??????????? if (hit1.collider == null)
??????????? {
??????????????? if (hit2.collider.tag == "Prop")
??????????????? {
??????????????????? if (data.id == "1")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["2"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
???????? ???????????????Init(data);
??????????????????? }
??????????????????? else if (data.id == "2")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["3"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else if (data.id == "3")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["4"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else
?????????????????? ?{
??????????????????????? return;
??????????????????? }
?
??????????????? }
??????????????? if (hit2.collider.tag == "Wall1")
??????????????? {
??????????????????? transform.position += MoveVector;
??????????????? }
??????????????? else
??????????????? {
??????????????????? OnMove = false;
??????????????? }
??????????? }
??????????? else if (hit2.collider == null)
??????????? {
??????????????? if (hit1.collider.tag == "Prop")
??????????????? {
??????????????????? Destroy(hit1.collider.gameObject);
???????? ???????????if (data.id == "1")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["2"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else if (data.id == "2")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["3"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else if (data.id == "3")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["4"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
?
??????????????? }
??????????????? if (hit1.collider.tag == "Wall1")
??????????????? {
??????????????????? transform.position += MoveVector;
??????????????? }
?
??????????????? else
??????????????? {
??????????????????? OnMove = false;
??????????????? }
??????????? }
???????? ???else
??????????? {
??????????????? if (hit1.collider.tag == "Prop")
??????????????? {
??????????????????? if (data.id == "1")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["2"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else if (data.id == "2")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["3"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????? ????else if (data.id == "3")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["4"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
?
??????????????? }
??????????????? else if (hit2.collider.tag == "Prop")
??????????????? {
??????????????????? if (data.id == "1")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["2"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
???????????????? ???else if (data.id == "2")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["3"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else if (data.id == "3")
??????????????????? {
??????????????????????? var Data = StaticData.Instance.characterDictionary["4"];
??????????????????????? var characterData = CharacterData.GetCharacterData(Data);
??????????????????????? data = characterData;
??????????????????????? Init(data);
??????????????????? }
??????????????????? else
??????????????????? {
??????????????????????? return;
??????????????????? }
?
?? ?????????????}
??????????????? if (hit1.collider.tag == "Wall1" && hit2.collider.tag == "Wall1")
??????????????? {
??????????????????? transform.position += MoveVector;
??????????????? }
??????????????? else
??????????????? {
??????????????????? OnMove = false;
??????????????? }
??????????? }
??????? }
??????? else
??????? {
??????????? transform.position += MoveVector;
??????? }
??? }
?
??? void Trun(Vector3 moveVector)
??? {
??????? var angel = Vector3.Angle(transform.right, moveVector);//計(jì)算當(dāng)前方向和下一個(gè)方向的角度
??????? var normal = Vector3.Cross(transform.right, moveVector);//計(jì)算當(dāng)前向量和下一個(gè)移動(dòng)方向的叉乘得出它們所在平面的垂直向量
??????? angel *= Mathf.Sign(Vector3.Dot(transform.forward, normal));//技算當(dāng)前向量和下個(gè)移動(dòng)方向向量的點(diǎn)積,并返回正負(fù)號(hào)賦值給角度
??????? var vector = transform.right.normalized - moveVector.normalized;//計(jì)算從下一個(gè)方向向量指向當(dāng)前向量的向量
??????? if (vector.magnitude > 0.1f)
??????? {
??????????? transform.Rotate(transform.forward, angel);
??????? }
??? }
?
??? void Fire()
??? {
??????? if (Input.GetKeyDown(KeyCode.Space))
??????? {
??????????? var bulletDemo = Resources.Load<GameObject>("Bullet");
??????????? var bullet = Instantiate(bulletDemo, muzzle.transform);
??????????? bullet.transform.SetParent(bulletPiont.transform);
??????????? bullet.transform.localScale = Vector3.one;
??????????? //如果玩家數(shù)據(jù)的等級(jí)為4則改變自己的子彈效果,并將發(fā)射子彈的母體賦值給子彈
??????????? if (data.level == 4)
??????????? {
??????????????? bullet.AddComponent<Bullet1>().motherObject = gameObject;
??????????? }
??????????? else
??????????? {
??????????????? bullet.AddComponent<Bullet>().motherObject = gameObject;
??????????? }
??????? }
??? }
??? // 死亡時(shí)將顯示晚間外形組件隱藏,播放死亡動(dòng)畫0.3S后摧毀玩家組件
??? public void Die()
??? {
??????? draw.gameObject.SetActive(false);
??????? animator.Play("Die", 0);
??????? StartCoroutine(OnDelay(0.3f, () =>
??????? {
?? ?????????Destroy(gameObject);
??????? }));
??? }
?
??? IEnumerator OnDelay(float time, UnityEngine.Events.UnityAction rFunc)
??? {
??????? yield return new WaitForSeconds(time);
??????? rFunc();
??????? yield return null;
??? }
?
}
?
效果查看:

5.敵人
搭建好敵人模型,設(shè)置敵人Ai:
敵人可以上、下、左、右四個(gè)方向移動(dòng);
每次移動(dòng)距離為場(chǎng)景中墻的1/2寬;
敵人無法穿越障礙;若沒有遇見障礙則隨機(jī)選擇是否繼續(xù)延當(dāng)前方向前進(jìn),若遇見障礙則隨機(jī)重新選擇移動(dòng)方向;
敵人可以發(fā)射子彈,頻率為1.5s一次;
敵人可以死亡;
代碼如下:
因?yàn)閿橙撕屯婕一纠淄皇前淹婕业妮斎霌Q成了隨機(jī)數(shù),所以不貼代碼了。
查看效果:

6.道具
搭建好道具模型,設(shè)置其功能
道具隨機(jī)生成在地圖上;
玩家獲取道具后,道具消失;
因?yàn)橹挥幸粋€(gè)道具,模塊小沒有單獨(dú)劃分模塊出來隨機(jī)生成的體現(xiàn)在GamaMode腳本中體現(xiàn),玩家獲取道具后消失在玩家功能中體現(xiàn)了。
嗯,90坦克的主要玩法差不多也就時(shí)這些了,而且所用的數(shù)據(jù)完全來至配置表,有興趣的朋友可以自己研拓展,可以增加一些道具啊,或者玩家屬性什么的,當(dāng)然也可以加別的那就看你的腦洞了。
相關(guān)工程和插件已經(jīng)上傳github 鏈接:https://github.com/dingchao12/Deom
想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問?http://levelpp.com/? ? ? ? ? ? ? ?
游戲開發(fā)攪基QQ群:869551769? ? ? ? ? ? ? ?
微信公眾號(hào):皮皮關(guān)