1. 程式人生 > >Unity3D學習筆記(7)—— 擊球遊戲

Unity3D學習筆記(7)—— 擊球遊戲

        在下Simba,有何貴幹!新一期的遊戲又出爐了(明明就是個Demo啊喂),這次的遊戲比以往都簡單(你別騙我),但是比以往的都好看(Excuse me?),沒錯,那就是動畫!這一期的遊戲使用了以往都沒有使用過的動畫系統,而且用了別人模型(不要臉)。

        使用前請將所有模型和動作設定成Legacy動畫, inspector -> Rig -> Animation type -> Legacy -> Apply button。

        先來看看這酷炫的效果吧:(這...這莫非是...蓋倫?)


        遊戲的規則很簡單,玩家控制蓋倫擊打完場上的7個球遊戲結束,並給出最終用時。

        遊戲的類圖:


        (說好的很簡單呢?我怎麼看不懂)不急不急,看上去很複雜,其實只有3個指令碼要寫,話說這圖可能還有錯,剛學的UML作圖。

         步驟說明:

一、場景佈置

        場景佈置總是遊戲開始製作時必須考慮和完善的事情,在打程式碼前得腦補出遊戲正常執行時的情景。我設計的遊戲攝像機是不動的,玩家可以在固定區域內通過WASD移動,點選滑鼠左鍵進行攻擊。當區域內所有小球都被擊打消失後,遊戲結束,顯示玩家用時。

        首先把玩家Garen拖入場景中,reset位置。新建4個正方體和1個平面,通過拉伸和位移組成一個方塊區域:


        給玩家新增剛體元件和碰撞盒,碰撞盒採用膠囊體:



        新建1個UI Text物件,並將其定位在螢幕中央,用於顯示玩家的最終用時:


        場景中的所有物件:


二、玩家動作

        OK,可以開始寫程式碼了。想從最直觀的寫起,那麼就先寫玩家的控制吧。玩家有3個動作,分別是Idle、Run、Attack。Idle在Start時就播放,Run需要在Update中檢測鍵盤輸入,可以使用GetAxisRaw獲得WASD的方向。Attack是比較複雜的動作,通過在動作過程中註冊回撥函式,可以使得在攻擊動畫播放過程中觸發這些函式,比如AttackHit函式,以及StopAttack函式。前者判斷有沒有擊中物體,並廣播擊中訊息。後者使玩家播放站立動畫(攻擊動畫結束後站立)。

using UnityEngine;
using System.Collections;

public class GarenMovement : MonoBehaviour {
    Animation ani;
    AnimationState idle;
    AnimationState run;
    AnimationState attack;

    public float speed = 5f;
    Vector3 movement;
    Rigidbody playerRigidbody;
    bool isAttacking = false;
    float rayLength = 1.8f;

    public delegate void AttackHitHandler(GameObject obj);
    public static event AttackHitHandler OnAttackHit;

    void Start()
    {
        playerRigidbody = this.GetComponent<Rigidbody>();
        ani = this.GetComponent<Animation>();
        idle = ani["Idle"];
        run = ani["Run"];
        attack = ani["Attack1"];

        // 預設播放站立動畫
        idle.wrapMode = WrapMode.Loop;
        ani.Play(idle.clip.name);
    }

	void FixedUpdate ()
    {
        if (!isAttacking)
        {
            float h = Input.GetAxisRaw("Horizontal");
            float v = Input.GetAxisRaw("Vertical");
            Move(h, v);
        }
        if (Input.GetMouseButtonDown(0))
        {
            Attack();
        }
	}

    void Move(float h, float v)
    {
        if (h != 0 || v != 0)
        {
            movement.Set(h, 0f, v);

            // 移動玩家位置
            movement = movement.normalized * speed * Time.deltaTime;
            playerRigidbody.MovePosition(transform.position + movement);

            // 旋轉玩家角度
            Quaternion newRotation = Quaternion.LookRotation(movement);
            playerRigidbody.MoveRotation(newRotation);

            // 播放跑步動畫
            run.wrapMode = WrapMode.Loop;
            ani.CrossFade(run.clip.name, 0.1f);
        }
        else 
        {
            ani.CrossFade(idle.clip.name, 0.1f);
        }
    }

    void Attack()
    {
        isAttacking = true;

        if (attack.clip.events.Length == 0)
        {
            // 新增攻擊動畫結束後的回撥函式
            AnimationEvent endEvent = new AnimationEvent();
            endEvent.functionName = "StopAttack";
            endEvent.time = attack.clip.length - 0.2f;
            attack.clip.AddEvent(endEvent);

            // 新增攻擊動畫中的回撥函式
            AnimationEvent hitEvent = new AnimationEvent();
            hitEvent.functionName = "AttackHit";
            hitEvent.time = 0.5f;
            attack.clip.AddEvent(hitEvent);
        }
        ani.Play(attack.clip.name);
    }

    void StopAttack()
    {
        isAttacking = false;
    }

    void AttackHit()
    {
        // 射線判斷打擊物
        GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC");
        Ray ray = new Ray(obj.transform.position, movement);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, rayLength))
        {
            Debug.DrawLine(ray.origin, hit.point);
            OnAttackHit(hit.collider.gameObject);
        }
    }
}

        射線碰撞檢測非常有意思,如果單純的靠網格碰撞器來檢測擊打物的話,至少我是沒想到什麼好方法,貌似網格碰撞檢測都是被動的,主動的只有射線檢測。這裡的Trick是把射線的長度設定為刀的長度,然後發出點設為Garen的腰部,這就可以很好地檢測Garen的刀是否“砍”中了物體。

        另外,Delegate物件完成了UML圖中的AttackHitHandler和HitEvent類的工作,因此實際上程式碼並沒有那麼複雜。

        什麼是Delegate呢?這好比是一個公眾號,任何人都可以關注它。突然某一天,公眾號宣佈科比退役了!如果是關注了這個公眾號的人就可以立馬知道這個新聞,但是每個人都可以做出不同的反應,或傷心或開心,因人而因,和公眾號就沒有關係了。Delegate只負責廣播新聞,卻不去追究新聞釋出後的結果。

三、裁判類

        現在玩家可以執行各種動作了,可是我還不知道我的AttackHitHandler是否能正常工作,於是趕忙寫了裁判Judge類來驗證一下:

using UnityEngine;
using System.Collections;
using Com.mygame;

public class Judge : MonoBehaviour {
    public int count = 7;

	void Start () {
        GarenMovement.OnAttackHit += HitEvent;
	}

    void HitEvent(GameObject obj)
    {
        if(obj.tag.Contains("Ball"))
        {
            obj.SetActive(false);
            if (--count == 0)
            {
                MyUI.GetInstance().Display(Time.time.ToString());
            }
        }
    }
}

        其中,UI是後來改的,沒寫時可以用print或Debug來測試。記得新增Tag。


四、工廠類

        現在得考慮球體了,建立一個工廠來管理這些球體是個不錯的方法,新建BaseCode指令碼用來寫單例類吧,順便定義一個名稱空間:

using UnityEngine;
using UnityEngine.UI;  
using System.Collections;
using System.Collections.Generic;
using Com.mygame;

namespace Com.mygame
{
    public class BallFactory : System.Object
    {
        static BallFactory instance;
        static List<GameObject> ballList;

        public static BallFactory GetInstance()
        {
            if (instance == null)
            {
                instance = new BallFactory();
                ballList = new List<GameObject>();
            }
            return instance;
        }

        public GameObject GetBall()
        {
            for (int i = 0; i < ballList.Count; ++i)
            {
                if (!ballList[i].activeInHierarchy)
                {
                    return ballList[i];
                }
            }
            GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newObj.GetComponent<Renderer>().material.color = Color.green;
            newObj.tag = "Ball";
            ballList.Add(newObj);
            return newObj;
        }
    }
}
        考慮到回收完全可以通過ball.SetActive(false)完成,就讓Judge來完成了。

五、UI

        UI這麼重要的東西這麼可以忘掉,在名稱空間內加上:

    public class MyUI : System.Object
    {
        static MyUI instance;
        public Text mainText;
        
        public static MyUI GetInstance()
        {
            if (instance == null)
            {
                instance = new MyUI();
            }
            return instance;
        }

        public void Display(string info)
        {
            mainText.text = info;
        }
    }

六、場景初始化

        萬事俱備,只欠初始了。BaseCode剛好可以用來初始化場景。Start在區域內隨機位置生成7個球,並把MyUI的mainText物件賦值:

public class BaseCode : MonoBehaviour {
    public int balls = 7;
    public Text text;

    void Start()
    {
        MyUI.GetInstance().mainText = text;
        for (int i = 0; i < balls; ++i)
        {
            GameObject ball = BallFactory.GetInstance().GetBall();
            ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f));
        }
    }
}

        程式碼寫完了(簡單吧,才200行),得把它們掛載在遊戲物體上,我把GarenMovement指令碼掛載在了玩家Garen上,把BallFactory和BaseCode掛載在了MainCamera上,執行一下,OK!

全部程式碼

GarenMovement.cs

using UnityEngine;
using System.Collections;

public class GarenMovement : MonoBehaviour {
    Animation ani;
    AnimationState idle;
    AnimationState run;
    AnimationState attack;

    public float speed = 5f;
    Vector3 movement;
    Rigidbody playerRigidbody;
    bool isAttacking = false;
    float rayLength = 1.8f;

    public delegate void AttackHitHandler(GameObject obj);
    public static event AttackHitHandler OnAttackHit;

    void Start()
    {
        playerRigidbody = this.GetComponent<Rigidbody>();
        ani = this.GetComponent<Animation>();
        idle = ani["Idle"];
        run = ani["Run"];
        attack = ani["Attack1"];

        // 預設播放站立動畫
        idle.wrapMode = WrapMode.Loop;
        ani.Play(idle.clip.name);
    }

	void FixedUpdate ()
    {
        if (!isAttacking)
        {
            float h = Input.GetAxisRaw("Horizontal");
            float v = Input.GetAxisRaw("Vertical");
            Move(h, v);
        }
        if (Input.GetMouseButtonDown(0))
        {
            Attack();
        }
	}

    void Move(float h, float v)
    {
        if (h != 0 || v != 0)
        {
            movement.Set(h, 0f, v);

            // 移動玩家位置
            movement = movement.normalized * speed * Time.deltaTime;
            playerRigidbody.MovePosition(transform.position + movement);

            // 旋轉玩家角度
            Quaternion newRotation = Quaternion.LookRotation(movement);
            playerRigidbody.MoveRotation(newRotation);

            // 播放跑步動畫
            run.wrapMode = WrapMode.Loop;
            ani.CrossFade(run.clip.name, 0.1f);
        }
        else 
        {
            ani.CrossFade(idle.clip.name, 0.1f);
        }
    }

    void Attack()
    {
        isAttacking = true;

        if (attack.clip.events.Length == 0)
        {
            // 新增攻擊動畫結束後的回撥函式
            AnimationEvent endEvent = new AnimationEvent();
            endEvent.functionName = "StopAttack";
            endEvent.time = attack.clip.length - 0.2f;
            attack.clip.AddEvent(endEvent);

            // 新增攻擊動畫中的回撥函式
            AnimationEvent hitEvent = new AnimationEvent();
            hitEvent.functionName = "AttackHit";
            hitEvent.time = 0.5f;
            attack.clip.AddEvent(hitEvent);
        }
        ani.Play(attack.clip.name);
    }

    void StopAttack()
    {
        isAttacking = false;
    }

    void AttackHit()
    {
        // 射線判斷打擊物
        GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC");
        Ray ray = new Ray(obj.transform.position, movement);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, rayLength))
        {
            Debug.DrawLine(ray.origin, hit.point);
            OnAttackHit(hit.collider.gameObject);
        }
    }
}

Judge.cs

using UnityEngine;
using System.Collections;
using Com.mygame;

public class Judge : MonoBehaviour {
    public int count = 7;

	void Start () {
        GarenMovement.OnAttackHit += HitEvent;
	}

    void HitEvent(GameObject obj)
    {
        if(obj.tag.Contains("Ball"))
        {
            obj.SetActive(false);
            if (--count == 0)
            {
                MyUI.GetInstance().Display(Time.time.ToString());
            }
        }
    }
}

BaseCode.cs

using UnityEngine;
using UnityEngine.UI;  
using System.Collections;
using System.Collections.Generic;
using Com.mygame;

namespace Com.mygame
{
    public class MyUI : System.Object
    {
        static MyUI instance;
        public Text mainText;
        
        public static MyUI GetInstance()
        {
            if (instance == null)
            {
                instance = new MyUI();
            }
            return instance;
        }

        public void Display(string info)
        {
            mainText.text = info;
        }
    }

    public class BallFactory : System.Object
    {
        static BallFactory instance;
        static List<GameObject> ballList;

        public static BallFactory GetInstance()
        {
            if (instance == null)
            {
                instance = new BallFactory();
                ballList = new List<GameObject>();
            }
            return instance;
        }

        public GameObject GetBall()
        {
            for (int i = 0; i < ballList.Count; ++i)
            {
                if (!ballList[i].activeInHierarchy)
                {
                    return ballList[i];
                }
            }
            GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newObj.GetComponent<Renderer>().material.color = Color.green;
            newObj.tag = "Ball";
            ballList.Add(newObj);
            return newObj;
        }
    }

}

public class BaseCode : MonoBehaviour {
    public int balls = 7;
    public Text text;

    void Start()
    {
        MyUI.GetInstance().mainText = text;
        for (int i = 0; i < balls; ++i)
        {
            GameObject ball = BallFactory.GetInstance().GetBall();
            ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f));
        }
    }
}

相關推薦

Unity3D學習筆記7—— 擊球遊戲

        在下Simba,有何貴幹!新一期的遊戲又出爐了(明明就是個Demo啊喂),這次的遊戲比以往都簡單(你別騙我),但是比以往的都好看(Excuse me?),沒錯,那就是動畫!這一期的遊戲使用了以往都沒有使用過的動畫系統,而且用了別人模型(不要臉)。    

unity3d學習筆記--NGUI製作遊戲介面

本系列文章由Aimar_Johnny編寫,歡迎轉載,轉載請標明出處,謝謝。 http://blog.csdn.net/lzhq1982/article/details/12706199 有關NGUI的介紹我這裡就不多說了,由於unity3d自帶的介面繪製工具GUI效率低下

Unity3D學習筆記10—— 遊戲序列化

        這期內容有關遊戲的序列化,什麼是序列化呢?額...就是遊戲的內容可以輸出成文字格式,或者遊戲的內容可以從文字中解析獲得。現在的遊戲幾乎離不開序列化,只要有存檔機制的遊戲必然會序列化,並且遊戲的每次啟動都會讀取序列文字。另外遊戲的更新也和序列化緊密相關,比如L

Unity3D學習筆記4-牧師與魔鬼遊戲改進

這次實現的還是牧師與魔鬼遊戲,不過在此基礎上添加了一個動作管理器 把動作管理和遊戲場景分離出來,上一個版本的連結在這裡http://blog.csdn.net/x2_yt/article/details/61912680,有興趣的朋友可以 去看看。 以下是

樹莓派3學習筆記77分辨率800 480顯示器配置

樹莓派、顯示器配置樹莓派3學習筆記(7):7寸(分辨率800 480)顯示器配置 樹莓派搭載分辨率為800X480的顯示器在顯示的時候可能會遇到無法全屏顯示的問題, 顯示器只有部分能夠顯示,有一部分是黑邊,對於這一種情況,我們只需進入系統的boot目錄,找到config.txt文件,或者直接在命

C#學習筆記7——委托

() namespace test task cnblogs [] string 命名空間 program 說明(2017-5-29 22:22:50): 1. 語法:public delegate void mydel();這一句在類外面,命名空間裏面。 2. 專門新建一

Linux學習筆記7

7一、PATH環境變量PATH 環境變量用which可以查看到一個命令的所在路徑,包括它的alias,實際是從當前環境的目錄下去找的。echo $PATH 查看當前命令 rm = /usr/bin/rm舉例:cp /usr/bin/ls /tmp/bin/ls2如果想直接使用ls2,有以下兩種方法:(

Linux第二周學習筆記7

詳解 顯示 one per mes ctr 方向鍵 post sage Linux第二周學習筆記(7)2.13 文檔查看cat_more_less_head_tail(1). cat命令cat命令:用於查看一個文件的內容並將其顯示在屏幕上cat-A命令:顯示所有的內容,包括

STM32學習筆記7——USART串口的使用

工作 清除 ESS 界面 默認 支持 oat channels 函數 1、 串口的基本概念 在STM32的參考手冊中,串口被描述成通用同步異步收發器(USART),它提供了一種靈活的方法與使用工業標準NRZ異步串行數據格式的外部設備之間進行全雙工數據交換。U

STM32學習筆記7——通用定時器PWM輸出

nbsp 錯誤 buffer put inter def internal reset 有效 1、TIMER輸出PWM基本概念 脈沖寬度調制(PWM),是英文“Pulse Width Modulation”的縮寫,簡稱脈寬調制,是利

Rust語言學習筆記7

src mod 四個文件 lib clas nec rust語言 () connect 模塊 // 兄弟模塊 mod network { fn connect() { } } mod client { fn connect() { } } /

ActiveMQ學習筆記7----ActiveMQ支援的傳輸協議

1. 連線到ActiveMQ   Connector: Active提供的,用來實現連線通訊的功能,包括:client-to-broker,broker-to-broker.ActiveMQ允許客戶端使用多種協議來連線。   1.1 配置Transport Connecto   在conf/active

cesium 學習筆記72018.7.9

1.材質 可以在建立時賦值材質,也可以構造後賦值材質 //方法一,構造時賦材質 var entity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(-103.0, 40.0), ellipse : {

shiro學習筆記7--cacheManager、sessionManager、rememberMe配置

1、授權:在自定義realm的doGetAuthorizationInfo方法中讀取使用者許可權並授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pri

MongoDB學習筆記7--- 條件操作符

描述 條件操作符用於比較兩個表示式並從mongoDB集合中獲取資料。 在本章節中,我們將討論如何在MongoDB中使用條件操作符。 MongoDB中條件操作符有: (>) 大於 - $gt (<) 小於 - $lt (>=) 大

【原創】pygame學習筆記2----pie遊戲需優化

  測試程式碼情況 (1)做到了弧形可以按出來 (2)數字的顯示正確 (3)出的一些低階錯誤         temp:\\pygame2.txt 這樣的錯誤, temp\\pygame2.txt 導致 這樣的錯誤,

微信小程序學習筆記7--------布局基礎

all 程序 read 參照物 tracking 占滿 art 文字 決定 ui布局基礎 一、flex布局 1、flex的容器和元素 2、flex容器屬性詳解 1>flex-direction

吳恩達深度學習筆記7--邏輯迴歸的代價函式Cost Function

邏輯迴歸的代價函式(Logistic Regression Cost Function) 在上一篇文章中,我們講了邏輯迴歸模型,這裡,我們講邏輯迴歸的代價函式(也翻譯作成本函式)。 吳恩達讓我轉達大家:這一篇有很多公式,做好準備,睜大眼睛!代價函式很重要! 為什麼需要代價函式: 為

solidity 學習筆記7內聯彙編

為什麼要有內聯彙編?   //普通迴圈和內斂彙編迴圈比較 pragma solidity ^0.4.25; contract Assembly{ function nativeLoop() public view returns(uint _r){ for(uint i=0;i<

Kafka學習筆記7----Kafka使用Cosumer接收訊息

1. 什麼是KafkaConsumer?   應用程式使用KafkaConsul'le 「向Kafka 訂閱主題,並從訂閱的主題上接收訊息。Kafka的訊息讀取不同於從其他訊息系統讀取資料,它涉及了一些獨特的概念和想法。   1.1 消費者和消費者群組   單個的消費者就跟前面的訊息系統的消費者一樣,建