最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Unity快速上手系列之4:《塔防》

2019-05-15 12:07 作者:皮皮關(guān)做游戲  | 我要投稿

作者:四五二十


大家好。偶爾想起了這個(gè)手把手教學(xué)的、但現(xiàn)已長(zhǎng)滿雜草的坑,還是來(lái)挖幾鏟子。

這一期的游戲是最常見(jiàn)的類(lèi)型之一——塔防。

塔防游戲相信大家并不陌生,幾個(gè)主要元素如下:

1、敵方士兵

2、我方防御塔

3、我方主城

emmmmmmm好像就沒(méi)了。

玩法就是建立防御塔阻擊前往我方主城的敵兵,可以通過(guò)視頻直觀感受下:


人越狠,話越不多。不多說(shuō),接下來(lái)我們一步步把這幾個(gè)功能做完。

素材準(zhǔn)備:

網(wǎng)上隨便找一些資源就行,不一定要和我一樣。這里再次強(qiáng)調(diào):

網(wǎng)上獲取的資源一定不能用作商業(yè)用途?。。。。?!

就本工程而言,資源有一下幾種:

敵人2個(gè),分別擁有移動(dòng),攻擊,待機(jī),死亡四種動(dòng)畫(huà)

防御塔3個(gè),擁有待機(jī),攻擊兩種動(dòng)畫(huà)

人形防御塔可還行

主城1個(gè),主地形 1組(內(nèi)含各種雜草亂石 )

敵人地形(敵人能用來(lái)走的路)1種,防御塔地形(防御塔能放置的地方)1種

箭矢1個(gè)

弓兵模型中自帶

場(chǎng)景搭建:

先從簡(jiǎn)單的功能做起:讓敵人從生成點(diǎn)走到主城,看見(jiàn)主城就攻擊。

搭建一個(gè)簡(jiǎn)單場(chǎng)景:

為了檢測(cè)敵人尋路,最好是能轉(zhuǎn)彎的道路

敵人和主城有一個(gè)都有血量的屬性,都會(huì)被攻擊,這里為它們做能顯示在頭上的血條。

以主城為例,在主城的子節(jié)點(diǎn)層創(chuàng)建一個(gè)Sprite做黃血條,設(shè)為黃色,取名“BloodStrip”,調(diào)整好大?。?/p>

然后在BloodStrip的子節(jié)點(diǎn)層創(chuàng)建一個(gè)空物體,取名“Hp”,在Hp的子節(jié)點(diǎn)層再創(chuàng)建一個(gè)Sprite做紅血條,名字“Red”,設(shè)為紅色,大小和黃血條一樣,把黃血色覆蓋:

接下來(lái)就移動(dòng)紅血條位置,讓它左邊邊緣與父物體Hp的Y軸重合:

然后再將Hp往右移動(dòng),讓Y軸與黃血條左邊緣重合(紅血條剛好覆蓋黃血條):

這樣我們只需要設(shè)置Hp的X軸大小,就可以控制紅血條長(zhǎng)度了:

***這里請(qǐng)初學(xué)者注意,如果你選取的紅血條圖片資源不是純色的、是有其他花紋的,則不能用這個(gè)方法。原因很簡(jiǎn)單,這種方法會(huì)把花紋拉長(zhǎng)或壓扁。大家可以下來(lái)想一下:這種情況下應(yīng)該怎樣來(lái)設(shè)置?

后面在代碼中只需要將當(dāng)前血量與總血量的比值賦給Hp的X軸,就可以將血量信息顯示在界面上了。敵人血條做法一樣。

做好后讓BloodStrip處于禁用狀態(tài),受傷后才顯示(這是游戲UI顯示的一個(gè)約定俗成的規(guī)則)。

代碼編寫(xiě):

為主城與敵人創(chuàng)建一個(gè)基類(lèi)腳本Character:

public class Character : MonoBehaviour

{

??? public float totalHp = 100; //總血量

??? float surHp; //剩余血量

??? protected Transform hpObj; //黃血條

??? protected Transform redHp; //血條紅條

??? protected Transform mainCamera; //主攝像機(jī)

?

??? public virtual void Init() //初始化

??? {

??????? surHp = totalHp;

??????? hpObj = transform.Find("BloodStrip");

??????? redHp = hpObj.Find("Hp");

??????? mainCamera = GameObject.Find("Main Camera").transform;

??? }

??? public void Damage(float damage) //受傷方法,參數(shù)為受到的傷害值

??? {

??????? if (surHp > damage) //當(dāng)前血量大于受傷血量,正??垩?/p>

??????? {

??????????? surHp -= damage;

??????????? //受傷后開(kāi)始顯示血條

??????????? if (surHp < totalHp)

??????????????? hpObj.gameObject.SetActive(true);

??????????? Vector3 hpScale = redHp.localScale;

??????????? hpScale.x = surHp / totalHp;

??????????? redHp.localScale = hpScale;

??????? }

??????? else //當(dāng)前血量不夠,調(diào)用死亡方法?????????

??????????? Death();

??? }

??? public virtual void Death() //死亡方法

??? {

??????? surHp = 0;

??????? hpObj.gameObject.SetActive(false); //血條不再顯示

??? }

}

?

創(chuàng)建主調(diào)腳本:用于游戲初始化和記錄游戲死亡,掛在一個(gè)場(chǎng)景物體上:

public class GameMain : MonoBehaviour

{

??? public static GameMain instance;

??? public bool gameOver;

??? void Start()

??? {

??????? InitGame();

??? }

??? //初始化游戲

??? void InitGame()

??? {

??????? instance = this; //單例

??????? gameOver = false;

??? }

}

?

創(chuàng)建主城腳本,繼承自Character腳本:


public class MainCity : Character

{

??? void Start()

??? {

??????? Init();

??? }

??? private void Update()

??? {

??????? hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭

??? }

??? public override void Death() //重新死亡方法

??? {

??????? base.Death();

????? ??GameMain.instance.gameOver = true; //游戲結(jié)束

??? }

}

?

敵人的腳本也繼承自Charater,除了受傷和死亡之外還能攻擊與移動(dòng):


public class Enemy : Character

{

??? Animator anim;

??? public float damage; //傷害

??? public float speed; //移動(dòng)速度

??? MainCity target; //主城

??? public override void Init()

??? {

??????? base.Init();

??? ????anim = GetComponent<Animator>();

??? }

??? private void Update()

??? {

??????? hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭

??? }

??? //前進(jìn)方法

??? private void EnemyForward()

??? {

??? }

??? //攻擊方法(放在攻擊動(dòng)畫(huà)事件中)

??? private void EnemyAttack()

??? {

?? ?????if (target != null)

??????????? target.Damage(damage);

??? }

??? //死亡方法

??? public override void Death()

??? {

??????? base.Death();

??????? anim.Play("death");

??? }

??? //尸體消失

??? private void DestroySelf()

??? {

??????? Destroy(gameObject);

??? }

}

?

重點(diǎn)在移動(dòng)方法上。因?yàn)閿橙说囊苿?dòng)帶有尋路功能,這里沒(méi)有采取Unity自帶的NavMeshAgent,而是用腳本來(lái)實(shí)現(xiàn),主要思路仿照盲人的行進(jìn)方式,利用射線充當(dāng)導(dǎo)盲棍,發(fā)現(xiàn)前方道路中斷再?gòu)膬蛇呎倚碌男羞M(jìn)路線:

拐杖就是射線

要利用好這個(gè)思路,場(chǎng)景中道路的搭建也有一定要求,道路都要掛上MeshCollider組件,方便射線檢測(cè)。

所有道路的Z軸指向路線前進(jìn)方向

道路的物體層設(shè)置為“Way”,主城也掛上碰撞器,物體層設(shè)為“City”。

在敵人模型身上創(chuàng)建一個(gè)空物體為眼睛,取名為“Eye”,主要作用是從此為射線起始點(diǎn),位置合適即可,注意,因?yàn)樗袛橙硕加玫南嗤_本,所以所有敵人的眼睛高度距離地面相同:

正面看這些模型真特么驚悚

當(dāng)然每個(gè)敵人也請(qǐng)掛上碰撞器和剛體以及Animator組件:

創(chuàng)建一個(gè)敵人狀態(tài)機(jī):

public enum EnemyState //狀態(tài)機(jī)

{

??? forward,

??? attack,

??? death

}

?

重寫(xiě)初始化方法:


??? Animator anim;

??? Rigidbody rigid;

??? public EnemyState state;

??? Transform eye; //眼睛:用于觀測(cè)道路和攻擊目標(biāo)

??? List<Collider> ways; //記錄走過(guò)的路(不走回頭路)

??? //重新初始化方法

??? public override void Init()

??? {

??????? base.Init();

???

??????? anim = GetComponent<Animator>();

??????? rigid = GetComponent<Rigidbody>();

??????? gameObject.layer = LayerMask.NameToLayer("Enemy"); //敵人層設(shè)置為"Enemy"

??????? state = EnemyState.forward;

????? ??eye = transform.Find("Eye");

??????? ways = new List<Collider>();

??? }

?

編寫(xiě)移動(dòng)方法,并在Update中調(diào)用:


private void Update()

??? {

??????? hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭

??????? if (GameMain.instance.gameOver) //游戲結(jié)束播放待機(jī)動(dòng)畫(huà)

??????????? anim.Play("idle");

??????? else if (state == EnemyState.forward)

??????????? EnemyForward();

??? }

??? public int view; //視野

??? Quaternion wayDir; //前進(jìn)方向

??? MainCity target; //主城

??? Transform way; //正在走的路

??? public float speed;

??? //前進(jìn)方法

??? private void EnemyForward()

??? {

??????? RaycastHit hit;

??????? //看見(jiàn)攻擊目標(biāo)則攻擊

??????? if (Physics.Raycast(eye.position, transform.forward, out hit, view, LayerMask.GetMask("City")))

??????? {

??????????? state = EnemyState.attack;

??????????? anim.Play("attack");

??? ????????target = hit.collider.GetComponent<MainCity>();

??????? }

?

??????? //斜下方30°打射線檢測(cè)前方道路

??????? if (Physics.Raycast(eye.position, Quaternion.AngleAxis(30, transform.right)

??????????? * transform.forward, out hit, 50, LayerMask.GetMask("Way")))

???? ???{

??????????? Debug.DrawLine(eye.position, hit.point, Color.blue);

??????????? //發(fā)現(xiàn)未走過(guò)的道路,獲取該道路,朝向該路通往的方向

??????????? if (!ways.Contains(hit.collider))

??????????? {

??????????????? ways.Add(hit.collider);

??????????????? way = hit.transform;

???????? ???????wayDir = Quaternion.LookRotation(way.forward);

??????????? }

??????? }

??????? else //前方?jīng)]路了發(fā)射球形射線檢測(cè)周?chē)欠裼新?/p>

??????? {

??????????? Collider[] colliders = Physics.OverlapSphere(transform.position, 8, LayerMask.GetMask("Way"));

??????????? for (int i = 0; i < colliders.Length; i++)

??????????? {

??????????????? //發(fā)現(xiàn)未走過(guò)的道路,獲取該道路,朝向該路通往的方向

??????????????? if (!ways.Contains(colliders[i]))

??????????????? {

??????????????????? way = colliders[i].transform;

??????????????????? wayDir = Quaternion.LookRotation(way.forward);

??????????????????? break;

??????????????? }

??????????? }

??????? }

??????? //獲取與腳下道路x軸上偏差值,好讓自身走在路中間

??????? float offset = 0;

??????? if (way != null)

??????? {

??????????? Vector3 distance = transform.position - way.position;

???????? ???offset = Vector3.Dot(distance, way.right.normalized);

??????? }

??????? //面向該路指向的方向前進(jìn)

??????? transform.rotation = Quaternion.RotateTowards(transform.rotation, wayDir, speed * 20 * Time.deltaTime);

??????? transform.Translate(-offset * Time.deltaTime, 0, speed * Time.deltaTime);

??? }

?

暫時(shí)把初始化方法放在Start中調(diào)用(后面我們會(huì)在創(chuàng)建的時(shí)候初始化),然后設(shè)置好血量、視野、速度、傷害,主城也設(shè)置好血量:

先來(lái)看下尋路運(yùn)行效果:


尋路沒(méi)有問(wèn)題了,將攻擊動(dòng)畫(huà)設(shè)為循環(huán)播放,然后將攻擊方法放入攻擊動(dòng)畫(huà)事件中,敵人看到主城就會(huì)自動(dòng)攻擊了:

敵人主要功能就已經(jīng)完成。現(xiàn)在我們來(lái)做敵人生成器。

塔防游戲的敵人生成方式一般都是比較有規(guī)律的,比如先生成一組a敵人,跟著生成一組b敵人,每組敵人的生成間隔也恒定(當(dāng)然,讀者也可以自己嘗試更豐富的出兵方法,比如讓“某些特定敵人的血量減到某個(gè)閾值”作為觸發(fā)條件等等):

為了生成方便,我們來(lái)做一個(gè)定時(shí)器,可以重復(fù)并規(guī)律地調(diào)用一個(gè)生成敵人方法:

public class Util : MonoBehaviour

{

??? private static Util _Instance = null;

??? public static Util Instance //單例模式,依附GameObject

??? {

??????? get

??????? {

??????????? if (_Instance == null)

??????????? {

??????????????? GameObject obj = new GameObject("Util");

??????????? ????_Instance = obj.AddComponent<Util>();

??????????? }

??????????? return _Instance;

??????? }

??? }

??? public class TimeTask //定時(shí)事件類(lèi)

??? {

??????? public Action callback; //回調(diào)函數(shù)

??????? public float delayTime; //延遲長(zhǎng)度

??????? public float destTime; //延遲后的目標(biāo)時(shí)間

??????? public int count; //重復(fù)次數(shù)

??? }???????????????

??? List<TimeTask> timeTaskList = new List<TimeTask>(); //保存所有的定時(shí)事件??

??? //增加定時(shí)回調(diào)的方法

??? public void AddTimeTask(Action _callback, float _delayTime, int _count = 1)????

??? {

??????? timeTaskList.Add(new TimeTask()

??????? {

??????????? callback = _callback,

??????????? delayTime = _delayTime,

??????????? destTime = Time.realtimeSinceStartup + _delayTime,

??????????? count = _count

??????? });

??? }

??? private void Update()

??? {

??????? for (int i = 0; i < timeTaskList.Count; i++) //實(shí)時(shí)監(jiān)測(cè)所有定時(shí)事件

??????? {

??????????? TimeTask task = timeTaskList[i];

??????????? if (Time.realtimeSinceStartup >= task.destTime) //時(shí)間到了,則執(zhí)行

??????????? {

??????????????? task.callback?.Invoke();

??????????????? if (task.count == 1) //當(dāng)次數(shù)為1,執(zhí)行完移除該定時(shí)事件

??????????????????? timeTaskList.RemoveAt(i);

??????????????? else if (task.count > 1) //當(dāng)次數(shù)大于1,執(zhí)行完次數(shù)減1

??????????????????? task.count--;

??????????????? task.destTime += task.delayTime; //執(zhí)行完一次后,重新定出下次執(zhí)行時(shí)間

??????????? }

??????? }

??? }

}

?


把所有敵人放入一個(gè)路徑中:

創(chuàng)建一個(gè)空物體做敵人生成器,放在敵人生成點(diǎn),創(chuàng)建腳本掛上去:

public class EnemySystem : MonoBehaviour

{

??? //根據(jù)名稱(chēng)保存所有敵人

Dictionary<string, Enemy> enemyDict = new Dictionary<string, Enemy>();

//初始化,放在主調(diào)腳本GameMain中執(zhí)行

??? public void Init()

??? {

?????? ?//保存所有種類(lèi)敵人,可以根據(jù)名字獲取

??????? Enemy[] enemys = Resources.LoadAll<Enemy>("Prefab/Chara/EnemyChara");

??????? for (int i = 0; i < enemys.Length; i++)

??????? {

??????????? if (!enemyDict.ContainsKey(enemys[i].name))

??????????????? enemyDict.Add(enemys[i].name, enemys[i]);

??????? }

??? }

??? //生成敵人,參數(shù)中設(shè)置敵人種類(lèi),生成間隔,生成數(shù)量(默認(rèn)為1)

??? public void CreateEnemy(string name, float delay, int count = 1)

??? {

??????? if (GameMain.instance.gameOver == false)

??????????? //使用定時(shí)器,生成敵人

??????????? Util.Instance.AddTimeTask(() => Instantiate(

??????????? enemyDict[name], transform.position, transform.rotation).Init(),

??????????? delay, count);

}

??? //點(diǎn)擊按鈕生成敵人(掛在按鈕事件中)

??? public void ClickButtonDispatchTroops()

??? {

??????? //每秒生成一個(gè)敵人,生成5次,第一次生成在1秒后執(zhí)行

??????? CreateEnemy("Zombie1", 1, 5);

??????? //沒(méi)0.5秒生成一個(gè)敵人,生成10次,第一次生成在5.5秒后執(zhí)行

??????? Util.Instance.AddTimeTask(() => CreateEnemy("Zombie2", 0.5f, 10), 5);

??? }

}

?

做到這一步就可以像演示視頻中那樣點(diǎn)擊按鈕出兵了。

放上工程鏈接:https://pan.baidu.com/share/init?surl=T2nZ_FrIk9DaTvem-YH8nQ

提取碼:n61s


下一篇文章我們將做UI界面點(diǎn)擊頭像在場(chǎng)景中生成防御塔,以及不同的防御塔與敵人的交互。


想系統(tǒng)學(xué)習(xí)游戲開(kāi)發(fā)的童鞋,歡迎訪問(wèn):http://levelpp.com/????

另有專(zhuān)業(yè)開(kāi)發(fā)交(gao)流(ji)群等待大家強(qiáng)勢(shì)插入:869551769

Unity快速上手系列之4:《塔防》的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
南江县| 青州市| 阳春市| 若羌县| SHOW| 汽车| 凉城县| 临西县| 综艺| 曲靖市| 黎城县| 成安县| 恭城| 龙里县| 广东省| 左权县| 中西区| 苗栗市| 那坡县| 思茅市| 新乡市| 中江县| 亳州市| 余姚市| 嫩江县| 朝阳市| 元阳县| 三明市| 勃利县| 定州市| 益阳市| 景德镇市| 永新县| 浮梁县| 晴隆县| 双鸭山市| 晋州市| 江永县| 安溪县| 岱山县| 陵水|