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學習筆記(7):7寸(分辨率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 學習筆記(7)2018.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 消費者和消費者群組 單個的消費者就跟前面的訊息系統的消費者一樣,建