《土豆榮耀》重構(gòu)筆記(九)實(shí)現(xiàn)角色的血量控制功能

前言

??本篇文章的內(nèi)容是實(shí)現(xiàn)實(shí)現(xiàn)角色的血量控制功能涨岁,在開(kāi)始實(shí)現(xiàn)之前漠酿,我們需要知道角色血量控制功能的需求是什么冯凹。

角色血量控制功能的需求

  1. 角色頭上需要顯示一個(gè)跟隨角色移動(dòng)的血量條,實(shí)時(shí)顯示角色當(dāng)前的血量
  2. 角色的最大血量可以任意修改
  3. 角色接觸怪物時(shí)會(huì)受傷炒嘲,并播放受傷音效
  4. 角色受傷時(shí)宇姚,除了減少相應(yīng)的血量,還需要有一個(gè)向后擊退的效果
  5. 為了避免角色被怪物卡住時(shí)摸吠,出現(xiàn)不斷受傷的問(wèn)題空凸,角色在受傷后,將在短暫時(shí)間內(nèi)獲得免傷效果
  6. 當(dāng)角色血量為0時(shí)寸痢,角色死亡呀洲,播放死亡動(dòng)畫(huà),游戲結(jié)束

在弄清楚并整理好需求之后啼止,我們開(kāi)始一一實(shí)現(xiàn)這些功能道逗。


制作血量條

??首先,我們來(lái)制作血量條献烦。因?yàn)?code>血量條要一直跟隨移動(dòng)滓窍,所以我們不妨將血量條作為Player的子物體。在Player下新建一個(gè)名為HealthBarDisplay的空物體巩那,然后將Assets\Sprites\UI下的Health以及Health-bg拖拽到HealthBarDisplay下面吏夯。

制作血量條

它們的具體屬性如下:

  • HealthBarDisplay:
    • Position: (0, 0, 0)
  • Health:
    • Position: (-0.8, 1.5, 0)
    • Color: (0, 255, 0, 255)
    • Sorting Layer: Character, Order In Layer: 4
  • Health-bg:
    • Position: (0, 1.5, 0)
    • Sorting Layer: Character, Order In Layer: 4

??此時(shí),將HealthScale屬性的X分量緩慢從1減少至0即横,我們可以看到血量條逐漸變短噪生。但為了避免角色在轉(zhuǎn)向時(shí),血量條跟著翻轉(zhuǎn)东囚,我們還需要在PlayerController.cs中加入以下代碼:

public class PlayerController : MonoBehaviour {
    ...
    [Tooltip("顯示血量條的物體")]
    public Transform HealthBarDisplay;

    ...

    private void Flip() {
        ...

        if(HealthBarDisplay != null) {
            // 在角色轉(zhuǎn)向時(shí)翻轉(zhuǎn)HealthBarDisplay跺嗽,確保HealthBarDisplay不隨角色轉(zhuǎn)向而翻轉(zhuǎn)
            HealthBarDisplay.localScale = Vector3.Scale(
                new Vector3(-1, 1, 1),
                HealthBarDisplay.localScale
            );
        } else {
            Debug.LogWarning("請(qǐng)?jiān)O(shè)置HealthBarDisplay");
        }
    }
}

??接著,我們將HealthBarDisplay拖拽到PlayerController.csHealthBarDisplay屬性的賦值框,然后HealthScale設(shè)置為(0.5, 1, 1)桨嫁,運(yùn)行游戲植兰,讓角色左右翻轉(zhuǎn),可以看到血量條不隨著角色轉(zhuǎn)向而翻轉(zhuǎn)璃吧。停止運(yùn)行游戲楣导,將HealthScale設(shè)置為(1, 1, 1),然后保存游戲畜挨,將我們所做的修改應(yīng)用至Player對(duì)于的Prefab爷辙。


創(chuàng)建血量控制腳本

??我們?cè)?code>Assets\Scripts\Player下創(chuàng)建一個(gè)名為PlayerHealth.cs的腳本。因?yàn)榻巧?code>最大血量需要能被修改朦促,角色受傷時(shí)不僅要播放受傷音效,還要有向后擊退的效果栓始,因此我們需要在PlayerHealth.cs腳本中添加以下代碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerHealth : MonoBehaviour {
    [Tooltip("角色的最大生命值")]
    public float MaxHP = 100f;
    [Tooltip("角色被怪物傷害時(shí)受到的擊退力大小")]
    public float HurtForce = 100f;
    [Tooltip("角色受傷后的免傷時(shí)間")]
    public float FreeDamagePeriod = 0.35f;
    [Tooltip("角色的受傷音效")]
    public AudioClip[] OuchClips;
}

??添加完畢之后务冕,將PlayerHealth.cs添加到物體Player上,并將Assets\Audio\Player\Ouch下的四個(gè)音頻文件拖動(dòng)到OuchClips的賦值框幻赚,然后保存修改禀忆。

設(shè)置音效

實(shí)現(xiàn)接觸怪物時(shí)受傷

??在Unity中,當(dāng)一個(gè)帶Collider2D的物體和其他帶有Collider2D的物體發(fā)生了碰撞時(shí)落恼,將會(huì)觸發(fā)OnCollisionEnter2D箩退,我們可以通過(guò)OnCollisionEnter2D這個(gè)函數(shù)來(lái)獲取物體的碰撞信息。那我們?nèi)绾闻袛嗯鲎驳奈矬w是怪物呢佳谦?答案是利用Unity提供的Tag戴涝,通過(guò)設(shè)置Tag這一屬性,我們可以方便地對(duì)物體進(jìn)行標(biāo)識(shí)钻蔑。選中AlienSlug啥刻,點(diǎn)擊Tag下拉框,然后點(diǎn)擊Add Tag咪笑,創(chuàng)建一個(gè)名為Enemy的Tag并將AlienSlugAlienShip的Tag都設(shè)置為Enemy可帽。最后,將AlienSlugAlienShip的修改應(yīng)用至Prefab窗怒。

創(chuàng)建Tag

??添加完成之后映跟,我們?cè)?code>PlayerHealth.cs腳本中添加以下代碼:

private void OnCollisionEnter2D(Collision2D collision) {
    //假如撞到怪物
    if(collision.gameObject.tag == "Enemy") {
        Debug.Log("Enemy");
    }
}

??運(yùn)行游戲,控制人物移動(dòng)去接觸怪物扬虚,可以看到Console輸出Enemy字符串努隙,說(shuō)明已經(jīng)檢測(cè)到了角色和怪物發(fā)生碰撞。接下來(lái)孔轴,我們?cè)?code>PlayerHealth.cs加入角色受傷的代碼:

public class PlayerHealth : MonoBehaviour {
    ...
    [Tooltip("角色受傷時(shí)減少的血量")]
    public float DamageAmount = 10f;
    [Tooltip("角色受傷后的免傷時(shí)間")]
    public float FreeDamagePeriod = 0.35f;

    // 角色當(dāng)前的血量
    private float m_CurrentHP;
    // 上一次受到傷害的時(shí)間
    private float m_LastFreeDamageTime;

    private void Start() {
        // 初始化變量
        m_CurrentHP = MaxHP;
        m_LastFreeDamageTime = 0f;
    }

    private void OnCollisionEnter2D(Collision2D collision) {
        // 判斷此時(shí)是否處于免傷狀態(tài)
        if(Time.time > m_LastFreeDamageTime + FreeDamagePeriod) {
            // 假如撞到怪物
            if(collision.gameObject.tag == "Enemy") {
                // 檢測(cè)當(dāng)前血量
                if(m_CurrentHP > 0f) {
                    // 調(diào)用受傷函數(shù)
                    TakeDamage(collision.transform);
                    
                    // 更新上次受傷害的時(shí)間
                    m_LastFreeDamageTime = Time.time;
                } else {
                    // 角色死亡
                }
            }
        }
    }

    // 受傷函數(shù)
    public void TakeDamage(Transform enemy) {
        // 給角色加上后退的力剃法,制造擊退效果
        Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;
        GetComponent<Rigidbody2D>().AddForce(hurtVector * HurtForce);

        // 更新角色的生命值
        m_CurrentHP -= DamageAmount;

        // 更新生命條
        Debug.Log(m_CurrentHP);

        // 隨機(jī)播放音頻
        int i = Random.Range(0, OuchClips.Length);
        AudioSource.PlayClipAtPoint(OuchClips[i], transform.position);
    }
}

??運(yùn)行游戲,控制人物移動(dòng)去接觸怪物,可以看到角色在觸碰怪物時(shí)贷洲,角色會(huì)受到一個(gè)擊退力的作用收厨,同時(shí)Console窗口輸出當(dāng)前的生命值。


更新血量條的顯示

??接下來(lái)优构,我們要根據(jù)角色當(dāng)前的生命值來(lái)實(shí)時(shí)更新血量條的顯示诵叁,也就是我們需要根據(jù)角色當(dāng)前的生命值,來(lái)更新HealthBarDisplay的子物體HealthScaleColor钦椭。我們?cè)?code>PlayerHealth.cs中加入以下代碼:

public class PlayerHealth : MonoBehaviour {
    ...
    [Tooltip("血量條")]
    public SpriteRenderer HealthSprite;

    ...
    // 血量條的初始長(zhǎng)度
    private Vector3 m_InitHealthScale;

    private void Start() {
        // 初始化變量
        ...
        m_InitHealthScale = HealthSprite.transform.localScale;
    }

    //受傷函數(shù)
    public void TakeDamage(Transform enemy) {
        ...

        // 更新生命條
        UpdateHealthBar();

        ...
    }

    private void UpdateHealthBar() {
        if(HealthSprite != null) {
            // 更新血量條顏色
            HealthSprite.color = Color.Lerp(Color.green, Color.red, 1 - m_CurrentHP * 0.01f);
            // 更新血量條長(zhǎng)度
            HealthSprite.transform.localScale = Vector3.Scale(m_InitHealthScale, new Vector3(m_CurrentHP * 0.01f, 1, 1));
        } else {
            Debug.LogError("請(qǐng)?jiān)O(shè)置HealthSprite");
        }
    }
}

??將HealthBarDisplay的子物體Health拖動(dòng)到HealthSprite的賦值框拧额,運(yùn)行游戲,控制人物移動(dòng)去接觸怪物彪腔,可以看到當(dāng)角色的生命值變化時(shí)侥锦,血量條也隨之更新。


控制角色的死亡

??最后德挣,我們還需要控制角色的死亡恭垦。我們知道,當(dāng)角色死亡時(shí)格嗅,不能再和場(chǎng)景中的任何物體發(fā)生交互番挺,玩家也不能再控制角色。因此屯掖,我們?cè)?code>PlayerHealth.cs中加入以下代碼:

public class PlayerHealth : MonoBehaviour {
    ...

    //受傷函數(shù)
    public void TakeDamage(Transform enemy) {
        ...

        // 檢測(cè)當(dāng)前血量
        if(m_CurrentHP > 0f) {
            ...
        } else {
            // 角色死亡
            Death();
        }

        ...
    }

    private void Death() {
        // 禁用碰撞體
        Collider2D[] cols = GetComponents<Collider2D>();
        foreach(Collider2D c in cols) {
            c.enabled = false;
        }

        // 禁用腳本
        GetComponent<PlayerController>().enabled = false;

        // 播放死亡動(dòng)畫(huà)
        GetComponent<Animator>().SetTrigger("Death");
    }
}

??運(yùn)行游戲玄柏,控制人物移動(dòng)去接觸怪物,可以看到當(dāng)角色的生命值減少至0時(shí)贴铜,角色播放死亡動(dòng)畫(huà)粪摘,且不與場(chǎng)景中的其他物體發(fā)生交互,玩家也不能再控制角色绍坝。將Player的修改應(yīng)用至Prefab赶熟,并保存場(chǎng)景產(chǎn)生的修改。


PlayerHealth.cs的完整代碼

??PlayerHealth.cs的完整代碼如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerHealth : MonoBehaviour {
    [Tooltip("角色的最大生命值")]
    public float MaxHP = 100f;
    [Tooltip("角色被怪物傷害時(shí)受到的擊退力大小")]
    public float HurtForce = 100f;
    [Tooltip("角色的受傷音效")]
    public AudioClip[] OuchClips;
    [Tooltip("角色受傷時(shí)減少的血量")]
    public float DamageAmount = 10f;
    [Tooltip("角色受傷后的免傷時(shí)間")]
    public float FreeDamagePeriod = 0.35f;
    [Tooltip("血量條")]
    public SpriteRenderer HealthSprite;

    // 角色當(dāng)前的血量
    private float m_CurrentHP;
    // 上一次受到傷害的時(shí)間
    private float m_LastFreeDamageTime;
    // 血量條的初始長(zhǎng)度
    private Vector3 m_InitHealthScale;


    private void Start() {
        // 初始化變量
        m_CurrentHP = MaxHP;
        m_LastFreeDamageTime = 0f;
        m_InitHealthScale = HealthSprite.transform.localScale;
    }

    private void OnCollisionEnter2D(Collision2D collision) {
        // 判斷此時(shí)是否處于免傷狀態(tài)
        if(Time.time > m_LastFreeDamageTime + FreeDamagePeriod) {
            // 假如撞到怪物
            if(collision.gameObject.tag == "Enemy") {
                // 檢測(cè)當(dāng)前血量
                if(m_CurrentHP > 0f) {
                    // 調(diào)用受傷函數(shù)
                    TakeDamage(collision.transform);
                    
                    // 更新上次受傷害的時(shí)間
                    m_LastFreeDamageTime = Time.time;
                } else {
                    // 角色死亡
                    Death();
                }
            }
        }
    }

    // 受傷函數(shù)
    public void TakeDamage(Transform enemy) {
        // 給角色加上后退的力陷嘴,制造擊退效果
        Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;
        GetComponent<Rigidbody2D>().AddForce(hurtVector * HurtForce);

        // 更新角色的生命值
        m_CurrentHP -= DamageAmount;

        // 更新生命條
        UpdateHealthBar();

        // 隨機(jī)播放音頻
        int i = Random.Range(0, OuchClips.Length);
        AudioSource.PlayClipAtPoint(OuchClips[i], transform.position);
    }

    private void UpdateHealthBar() {
        if(HealthSprite != null) {
            // 更新血量條顏色
            HealthSprite.color = Color.Lerp(Color.green, Color.red, 1 - m_CurrentHP * 0.01f);
            // 更新血量條長(zhǎng)度
            HealthSprite.transform.localScale = Vector3.Scale(m_InitHealthScale, new Vector3(m_CurrentHP * 0.01f, 1, 1));
        } else {
            Debug.LogError("請(qǐng)?jiān)O(shè)置HealthSprite");
        }
    }

    private void Death() {
        // 禁用碰撞體
        Collider2D[] cols = GetComponents<Collider2D>();
        foreach(Collider2D c in cols) {
            c.enabled = false;
        }

        // 禁用腳本
        GetComponent<PlayerController>().enabled = false;

        // 播放死亡動(dòng)畫(huà)
        GetComponent<Animator>().SetTrigger("Death");
    }
}

后言

??至此映砖,我們已經(jīng)完成了角色的血量控制功能,本篇文章提到的數(shù)值參數(shù)都可以根據(jù)自己的喜好進(jìn)行調(diào)整灾挨。最后邑退,本篇文章所做的修改,可以在PotatoGloryTutorial這個(gè)倉(cāng)庫(kù)的essay7分支下看到劳澄,讀者可以clone這個(gè)倉(cāng)庫(kù)到本地進(jìn)行查看地技。


參考鏈接

  1. Tags
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秒拔,隨后出現(xiàn)的幾起案子莫矗,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件作谚,死亡現(xiàn)場(chǎng)離奇詭異三娩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)妹懒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)雀监,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人眨唬,你說(shuō)我怎么就攤上這事会前。” “怎么了匾竿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瓦宜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我岭妖,道長(zhǎng)歉提,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任区转,我火速辦了婚禮,結(jié)果婚禮上版扩,老公的妹妹穿的比我還像新娘废离。我一直安慰自己,他們只是感情好礁芦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蜻韭。 她就那樣靜靜地躺著,像睡著了一般柿扣。 火紅的嫁衣襯著肌膚如雪肖方。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天未状,我揣著相機(jī)與錄音俯画,去河邊找鬼。 笑死司草,一個(gè)胖子當(dāng)著我的面吹牛艰垂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埋虹,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼猜憎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了搔课?” 一聲冷哼從身側(cè)響起胰柑,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后柬讨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體崩瓤,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年姐浮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谷遂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卖鲤,死狀恐怖肾扰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛋逾,我是刑警寧澤集晚,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站区匣,受9級(jí)特大地震影響偷拔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亏钩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一莲绰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姑丑,春花似錦蛤签、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至留拾,卻和暖如春戳晌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痴柔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工沦偎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咳蔚。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓扛施,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屹篓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疙渣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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