找回密碼
 注冊帳號

掃一掃,訪問微社區

士郎 用Unity開發一款塔防游戲(一):攻擊方設計

27
回復
2396
查看
打印 上一主題 下一主題
[ 復制鏈接 ]
9以壇為家
33145/50000
排名
1
昨日變化

8252

主題

8810

帖子

3萬

積分

Rank: 9Rank: 9Rank: 9

UID
1231
好友
186
蠻牛幣
227
威望
30
注冊時間
2013-7-29
在線時間
4217 小時
最后登錄
2019-11-20

活力之星原創精華達人突出貢獻獎財富之證游戲蠻牛QQ群會員蠻牛妹VIP

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

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

1、敵方士兵

2、我方防御塔

3、我方主城

emmmmmmm好像就沒了。


人越狠,話越不多。不多說,接下來我們一步步把這幾個功能做完。

素材準備:

網上隨便找一些資源就行,不一定要和我一樣。這里再次強調:

網上獲取的資源一定不能用作商業用途!!!!!!

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

敵人2個,分別擁有移動,攻擊,待機,死亡四種動畫



防御塔3個,擁有待機,攻擊兩種動畫


人形防御塔可還行

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



敵人地形(敵人能用來走的路)1種,防御塔地形(防御塔能放置的地方)1種,箭矢1個


場景搭建:

先從簡單的功能做起:讓敵人從生成點走到主城,看見主城就攻擊。

搭建一個簡單場景:


為了檢測敵人尋路,最好是能轉彎的道路

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

以主城為例,在主城的子節點層創建一個Sprite做黃血條,設為黃色,取名“BloodStrip”,調整好大小:



然后在BloodStrip的子節點層創建一個空物體,取名“Hp”,在Hp的子節點層再創建一個Sprite做紅血條,名字“Red”,設為紅色,大小和黃血條一樣,把黃血色覆蓋:



接下來就移動紅血條位置,讓它左邊邊緣與父物體Hp的Y軸重合:



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



這樣我們只需要設置H的X軸大小,就可以控制紅血條長度了


***這里請初學者注意,如果你選取的紅血條圖片資源不是純色的、是有其他花紋的,則不能用這個方法。原因很簡單,這種方法會把花紋拉長或壓扁。大家可以下來想一下:這種情況下應該怎樣來設置?

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

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

代碼編寫:

為主城與敵人創建一個基類腳本Character:

[AppleScript] 純文本查看 復制代碼
public class Character : MonoBehaviour
{
    public float totalHp = 100; //總血量
    float surHp; //剩余血量
    protected Transform hpObj; //黃血條
    protected Transform redHp; //血條紅條
    protected Transform mainCamera; //主攝像機

    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) //受傷方法,參數為受到的傷害值
    {
        if (surHp > damage) //當前血量大于受傷血量,正常扣血
        {
            surHp -= damage;
            //受傷后開始顯示血條
            if (surHp < totalHp)
                hpObj.gameObject.SetActive(true);
            Vector3 hpScale = redHp.localScale;
            hpScale.x = surHp / totalHp;
            redHp.localScale = hpScale;
        }
        else //當前血量不夠,調用死亡方法          
            Death();
    }
    public virtual void Death() //死亡方法
    {
        surHp = 0;
        hpObj.gameObject.SetActive(false); //血條不再顯示
    }
}


創建主調腳本:用于游戲初始化和記錄游戲死亡,掛在一個場景物體上:


[AppleScript] 純文本查看 復制代碼
public class GameMain : MonoBehaviour
{
    public static GameMain instance;
    public bool gameOver;
    void Start()
    {
        InitGame();
    }
    //初始化游戲
    void InitGame()
    {
        instance = this; //單例
        gameOver = false;
    }
}


創建主城腳本,繼承自Character腳本:
[AppleScript] 純文本查看 復制代碼
public class MainCity : Character
{
    void Start()
    {
        Init();
    }
    private void Update()
    {
        hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭
    }
    public override void Death() //重新死亡方法
    {
        base.Death();
        GameMain.instance.gameOver = true; //游戲結束
    }
}


敵人的腳本也繼承自Charater,除了受傷和死亡之外還能攻擊與移動:
[AppleScript] 純文本查看 復制代碼
public class Enemy : Character
{
    Animator anim;
    public float damage; //傷害
    public float speed; //移動速度
    MainCity target; //主城
    public override void Init()
    {
        base.Init();
        anim = GetComponent<Animator>();
    }
    private void Update()
    {
        hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭
    }
    //前進方法
    private void EnemyForward()
    {
    }
    //攻擊方法(放在攻擊動畫事件中)
    private void EnemyAttack()
    {
        if (target != null)
            target.Damage(damage);
    }
    //死亡方法
    public override void Death()
    {
        base.Death();
        anim.Play("death");
    }
    //尸體消失
    private void DestroySelf()
    {
        Destroy(gameObject);
    }
}


重點在移動方法上。因為敵人的移動帶有尋路功能,這里沒有采取Unity自帶的NavMeshAgent,而是用腳本來實現,主要思路仿照盲人的行進方式,利用射線充當導盲棍,發現前方道路中斷再從兩邊找新的行進路線:


拐杖就是射線

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


所有道路的Z軸指向路線前進方向

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



在敵人模型身上創建一個空物體為眼睛,取名為“Eye”,主要作用是從此為射線起始點,位置合適即可,注意,因為所有敵人都用的相同腳本,所以所有敵人的眼睛高度距離地面相同:


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

當然每個敵人也請掛上碰撞器和剛體以及Animator組件:



創建一個敵人狀態機:
[AppleScript] 純文本查看 復制代碼
public enum EnemyState //狀態機
{
    forward,
    attack,
    death
}


重寫初始化方法:
[AppleScript] 純文本查看 復制代碼
 Animator anim;
    Rigidbody rigid;
    public EnemyState state;
    Transform eye; //眼睛:用于觀測道路和攻擊目標
    List<Collider> ways; //記錄走過的路(不走回頭路)
    //重新初始化方法
    public override void Init() 
    {
        base.Init();
    
        anim = GetComponent<Animator>();
        rigid = GetComponent<Rigidbody>();
        gameObject.layer = LayerMask.NameToLayer("Enemy"); //敵人層設置為"Enemy"
        state = EnemyState.forward;
        eye = transform.Find("Eye");
        ways = new List<Collider>();
    }


編寫移動方法,并在Update中調用:
[AppleScript] 純文本查看 復制代碼
private void Update()
    {
        hpObj.rotation = mainCamera.rotation; //血條始終面向鏡頭
        if (GameMain.instance.gameOver) //游戲結束播放待機動畫
            anim.Play("idle");
        else if (state == EnemyState.forward)
            EnemyForward();
    }
    public int view; //視野
    Quaternion wayDir; //前進方向
    MainCity target; //主城
    Transform way; //正在走的路
    public float speed; 
    //前進方法
    private void EnemyForward()
    {
        RaycastHit hit;
        //看見攻擊目標則攻擊
        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°打射線檢測前方道路
        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);
            //發現未走過的道路,獲取該道路,朝向該路通往的方向
            if (!ways.Contains(hit.collider))
            {
                ways.Add(hit.collider);
                way = hit.transform;
                wayDir = Quaternion.LookRotation(way.forward);
            }
        }
        else //前方沒路了發射球形射線檢測周圍是否有路
        {
            Collider[] colliders = Physics.OverlapSphere(transform.position, 8, LayerMask.GetMask("Way"));
            for (int i = 0; i < colliders.Length; i++)
            {
                //發現未走過的道路,獲取該道路,朝向該路通往的方向
                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);
        }
        //面向該路指向的方向前進
        transform.rotation = Quaternion.RotateTowards(transform.rotation, wayDir, speed * 20 * Time.deltaTime);
        transform.Translate(-offset * Time.deltaTime, 0, speed * Time.deltaTime);
    }


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



先來看下尋路運行效果:


藍線檢測前方道路,紅圈檢測周圍道路

尋路沒有問題了,將攻擊動畫設為循環播放,然后將攻擊方法放入攻擊動畫事件中,敵人看到主城就會自動攻擊了:

敵人主要功能就已經完成。現在我們來做敵人生成器。

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



為了生成方便,我們來做一個定時器,可以重復并規律地調用一個生成敵人方法:
[AppleScript] 純文本查看 復制代碼
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 //定時事件類
    {
        public Action callback; //回調函數
        public float delayTime; //延遲長度
        public float destTime; //延遲后的目標時間
        public int count; //重復次數
    }                
    List<TimeTask> timeTaskList = new List<TimeTask>(); //保存所有的定時事件   
    //增加定時回調的方法 
    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++) //實時監測所有定時事件
        {
            TimeTask task = timeTaskList[i];
            if (Time.realtimeSinceStartup >= task.destTime) //時間到了,則執行
            {
                task.callback?.Invoke(); 
                if (task.count == 1) //當次數為1,執行完移除該定時事件
                    timeTaskList.RemoveAt(i);
                else if (task.count > 1) //當次數大于1,執行完次數減1
                    task.count--;
                task.destTime += task.delayTime; //執行完一次后,重新定出下次執行時間
            }
        }
    }
}


把所有敵人放入一個路徑中,創建一個空物體做敵人生成器,放在敵人生成點,創建腳本掛上去:
[AppleScript] 純文本查看 復制代碼
public class EnemySystem : MonoBehaviour
{
    //根據名稱保存所有敵人
Dictionary<string, Enemy> enemyDict = new Dictionary<string, Enemy>();
//初始化,放在主調腳本GameMain中執行
    public void Init()
    {
        //保存所有種類敵人,可以根據名字獲取
        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]);
        }
    }
    //生成敵人,參數中設置敵人種類,生成間隔,生成數量(默認為1)
    public void CreateEnemy(string name, float delay, int count = 1)
    {
        if (GameMain.instance.gameOver == false)
            //使用定時器,生成敵人
            Util.Instance.AddTimeTask(() => Instantiate(
            enemyDict[name], transform.position, transform.rotation).Init(),
            delay, count);
}
    //點擊按鈕生成敵人(掛在按鈕事件中)
    public void ClickButtonDispatchTroops()
    {
        //每秒生成一個敵人,生成5次,第一次生成在1秒后執行
        CreateEnemy("Zombie1", 1, 5); 
        //沒0.5秒生成一個敵人,生成10次,第一次生成在5.5秒后執行
        Util.Instance.AddTimeTask(() => CreateEnemy("Zombie2", 0.5f, 10), 5);
    }
}


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

放上工程鏈接:

https://pan.baidu.com/s/1T2nZ_FrIk9DaTvem-YH8nQ提取碼:n61s

知乎@四五二十
回復

使用道具 舉報

0

主題

14

帖子

57

積分

Rank: 2Rank: 2

UID
299963
好友
0
蠻牛幣
82
威望
0
注冊時間
2018-10-14
在線時間
43 小時
最后登錄
2019-11-14
沙發
2019-6-12 21:20:04 只看該作者
非常感謝樓主,剛好缺這方面的姿勢
回復 支持 反對

使用道具 舉報

7日久生情
2534/5000
排名
4094
昨日變化

0

主題

1753

帖子

2534

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蠻牛幣
2274
威望
0
注冊時間
2017-11-16
在線時間
419 小時
最后登錄
2019-12-10
板凳
2019-6-13 08:23:17 只看該作者
66666666666666666666666666666666
回復 支持 反對

使用道具 舉報

4四處流浪
407/500
排名
8825
昨日變化

0

主題

39

帖子

407

積分

Rank: 4

UID
307305
好友
0
蠻牛幣
1025
威望
0
注冊時間
2018-12-6
在線時間
234 小時
最后登錄
2019-12-10
地板
2019-6-13 08:38:06 只看該作者
感謝分享
回復

使用道具 舉報

7日久生情
1936/5000
排名
1395
昨日變化

16

主題

285

帖子

1936

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119664
好友
3
蠻牛幣
8871
威望
0
注冊時間
2015-8-25
在線時間
758 小時
最后登錄
2019-12-9
QQ
5#
2019-6-13 09:01:41 只看該作者
學習學習~~~~~~~~~~~~~~~
回復

使用道具 舉報

6蠻牛粉絲
1024/1500
排名
16957
昨日變化

1

主題

795

帖子

1024

積分

Rank: 6Rank: 6Rank: 6

UID
175005
好友
0
蠻牛幣
1072
威望
0
注冊時間
2016-10-12
在線時間
188 小時
最后登錄
2019-12-6
6#
2019-6-13 09:11:50 只看該作者
不錯
回復

使用道具 舉報

4四處流浪
488/500
排名
7196
昨日變化

0

主題

143

帖子

488

積分

Rank: 4

UID
267358
好友
0
蠻牛幣
659
威望
0
注冊時間
2018-2-1
在線時間
165 小時
最后登錄
2019-9-29
7#
2019-6-13 09:53:00 只看該作者
學習大佬操作
回復

使用道具 舉報

4四處流浪
471/500
排名
16595
昨日變化

1

主題

144

帖子

471

積分

Rank: 4

UID
281418
好友
0
蠻牛幣
815
威望
0
注冊時間
2018-5-16
在線時間
284 小時
最后登錄
2019-12-10
8#
2019-6-13 10:17:34 只看該作者
很有用        
回復 支持 反對

使用道具 舉報

3偶爾光臨
212/300

0

主題

94

帖子

212

積分

Rank: 3Rank: 3Rank: 3

UID
318682
好友
0
蠻牛幣
330
威望
0
注冊時間
2019-4-4
在線時間
119 小時
最后登錄
2019-11-8
9#
2019-6-13 10:22:15 只看該作者
感謝分享
回復

使用道具 舉報

排名
64938
昨日變化

0

主題

7

帖子

16

積分

Rank: 1

UID
180527
好友
1
蠻牛幣
37
威望
0
注冊時間
2016-11-16
在線時間
7 小時
最后登錄
2019-7-2
10#
2019-6-13 10:49:00 只看該作者
6666666666666666
回復 支持 反對

使用道具 舉報

2初來乍到
106/150
排名
13721
昨日變化

0

主題

8

帖子

106

積分

Rank: 2Rank: 2

UID
180322
好友
0
蠻牛幣
346
威望
0
注冊時間
2016-11-3
在線時間
36 小時
最后登錄
2019-12-5
11#
2019-6-13 11:53:52 只看該作者
感謝分享啊!!!
回復

使用道具 舉報

6蠻牛粉絲
1055/1500
排名
10708
昨日變化

0

主題

761

帖子

1055

積分

Rank: 6Rank: 6Rank: 6

UID
301976
好友
1
蠻牛幣
1562
威望
0
注冊時間
2018-10-31
在線時間
196 小時
最后登錄
2019-12-10
12#
2019-6-13 13:48:39 只看該作者
感謝分享...
回復

使用道具 舉報

排名
64938
昨日變化

0

主題

11

帖子

22

積分

Rank: 1

UID
271058
好友
0
蠻牛幣
24
威望
0
注冊時間
2018-3-7
在線時間
9 小時
最后登錄
2019-6-17
13#
2019-6-13 13:56:57 只看該作者
366666666666666
回復 支持 反對

使用道具 舉報

2初來乍到
114/150
排名
13986
昨日變化

0

主題

16

帖子

114

積分

Rank: 2Rank: 2

UID
306015
好友
0
蠻牛幣
106
威望
0
注冊時間
2018-11-26
在線時間
38 小時
最后登錄
2019-8-23
14#
2019-6-13 15:29:07 只看該作者
感謝分享
回復

使用道具 舉報

7日久生情
1588/5000
排名
1338
昨日變化

8

主題

167

帖子

1588

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
215462
好友
1
蠻牛幣
4021
威望
0
注冊時間
2017-3-30
在線時間
501 小時
最后登錄
2019-12-6
15#
2019-6-13 17:12:23 只看該作者
跟大佬學習操作
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

女校游泳队彩金 新69棋牌 河南481开奖结果 去日本还能赚钱吗 现在投资0元赚钱买卖 乐胡麻将下载免费 甘肃 3d基本走势图 181137星彩开奖号码 金平县有什么赚钱的 快乐12开奖走势图四川走势图 老时时彩杀号网站 淘宝网上面什么东西赚钱 梦幻175跑宝宝环真赚钱 万客彩票苹果 山西快乐10分前三走势图 浙江大乐透走势图 学围棋