Unity實戰(zhàn)之FlyPin(見縫插針)小游戲的實現(xiàn)
一、簡單介紹
Unity 游戲實例開發(fā)集合,使用簡單易懂的方式,講解常見游戲的開發(fā)實現(xiàn)過程,方便后期類似游戲開發(fā)的借鑒和復用。
本節(jié)介紹,F(xiàn)lyPin (見縫插針) 休閑小游戲快速實現(xiàn)的方法,希望能幫到你,若有不對,請留言。
二、FlyPin (見縫插針)游戲內(nèi)容與操作
1、游戲開始,針 Pin 自動準備好,
2、鼠標點擊左鍵發(fā)射 Pin,飛向目標,同時自動準備下一根針 Pin,并增加分數(shù)
3、針 Pin 插入目標后,會隨之一起轉動
4、當兩個針 Pin 發(fā)生碰撞,則游戲結束
5、游戲結束動畫完成后,自動重新開始游戲
三、游戲代碼框架

四、知識點
1、MonoBehaviour 生命周期函數(shù):Awake,Start,Update,OnGUI
2、Input 按鍵的監(jiān)控
3、GameObject.Instantiate 物體的生成,GameObject.Destroy 物體的銷毀
4、Camera.orthographicSize 正交相機視口大小修改,Camera.backgroundColor 相機 SolidColor 背景色的修改
5、Rigidbody2D 取消重力效果,添加 Collider ,進行 Trigger 碰撞檢測
6、GUIStyle GUI樣式,GUI.Label 添加文字的功能使用
7、Vector3.Lerp 位移向量插值使用,Vector3.Distance 位置距離函數(shù)的使用
8、Mathf.Lerp ,Color.Lerp 數(shù)值和顏色插值計算的使用
9、Transform.Rotate 旋轉使用
10、IEnumerator 協(xié)程 , StartCoroutine 開始協(xié)程 和 StopAllCoroutines 停止所有協(xié)程的使用
11、SimpleMessageCenter 簡單的消息中心使用
12、Action<int> OnChangeValue 屬性變化中委托的使用
13、Resources.Load<GameObject>() 代碼加載預制體的使用
14、OnTriggerEnter2D 2D 碰撞檢測的使用
15、 SceneManager.LoadScene 加載,和 SceneManager.GetActiveScene() 當前場景的獲取
16、等等
五、游戲效果預覽

六、實現(xiàn)步驟
這是一個 2D 游戲,主要用到 SpriteRenderer 、2D Collider,2D Rigidbody,以及 TextMesh 等資源組件,所有資源都是Unity自帶,沒有導入其他外部貼圖模型資源。
1、打開 Unity,創(chuàng)建工程

2、構建場景,添加 Pin 初始生成,準備和飛向的地方,以及旋轉的目標圓,和顯示分數(shù)的 3DText

3、ScoreText,創(chuàng)建是 3D Object - 3D Text,其中 根據(jù) TextMesh的字體大小,根據(jù) Character Size 和 Font Size 共同控制


4、如果 TextMesh 看起來很模糊,可以改小 Character Size ,放大 Font Size ,如圖


5、TargetCircle 是 SpriteRenderer ,根據(jù)需要設置顏色,Sprite 是 Unity 自帶的 Knob
說明,同時 TargetCircle 也是 Pin 飛向的目標體


6、PinSpawnPos 、PinReadyPos 是 針 Pin 生成的初始位置,和 準備好位置,PinSpawnPos 、PinReadyPos 下的 Cube 是便于觀察位置設定的,確定好位置后,可以刪掉或者隱藏 Cube


7、注意 ScoreText 、TargetCircle、PinSpawnPos、PinReadyPos,Position.z 都是 0,為了保證實在同一個平面上,滿足2D 游戲要求

8、Main Camera,設置如圖,ClearFlags 為 Solid Color ,Background 顏色根據(jù)需要設定,Projection 為 Orthographic 正交(2D游戲設置相機),Size 為 5 ,你可以根據(jù)自己需要設置

9、最后,效果如圖

10、Pin 預制體,包含PinHead 和 PinBody

11、Pin 的 PinHead ,SpriteRenderer 的 Sprite 自帶的 Knob, 顏色根據(jù)自己需要設定,添加 CircleCollider2D 碰撞體,并且勾選 IsTrigger(避免發(fā)生碰撞,產(chǎn)生碰撞效果,不勾選則會有碰撞的物理效果),CircleCollider2D的大小可以根據(jù)實際情況調整(一般Unity會自動根據(jù)Renderer 大小設置默認大小);添加 Rigidbody2D (發(fā)生碰撞的必需條件之一),并且BodyType設置為 Kinematic 可以不受重力下墜(或者 BodyType 為 Dynamic ,把 Gravity Scale 設置 為 0 ,也可以不受重力影響)

12、Pin 中的 PinBody,SpriteRenderer 中 Sprite 自帶的 Background ,顏色自選,Order in Layer 默認是 0 ,設置為 -1,是為針插入 TargetCircle 效果,Scale 設置為 (1,15,1),是為了細長如針的效果


13、至于 Pin ,放置在 Resources 文件夾下的Prefabs 文件夾,是為了使用 Unity 中 Resources.Load 代碼加載預制體,不需要手動掛載預制體,而 Resources.Load 加載就要求把預制體放在 Resources 文件夾下

14、PinHead 類 監(jiān)聽兩個針是否插在靠近位置相撞,相撞則發(fā)送游戲結束消息

15、PinHead中的 OnTriggerEnter2D(Collider2D collision) 見監(jiān)聽碰撞進入,碰撞則發(fā)送游戲借宿消息
/// <summary>
/// 監(jiān)聽兩個針是否插在靠近位置相撞
/// 相撞則發(fā)送游戲結束消息
/// </summary>
/// <param name="collision"></param>
private void OnTriggerEnter2D(Collider2D collision)
{
// 兩個 PinHead 是否碰撞
if (collision.name.Equals(ConstStr.PIN_HEAD_NAME))
{
// 相撞則發(fā)送游戲結束消息
SimpleMessageCenter.Instance.SendMsg(MsgType.GameOver);
}
}16、Pin 類,管理 針 Pin 的狀態(tài)和運動

17、Pin 類,Init 初始化函數(shù),得到相關位置和 PinHead 腳本的掛載到 Pin 的 PinHead 物體上
參數(shù)說明:
// Pin 準備好的位置
private Vector3 m_PinReadyPos;
// Pin 飛行的目標位置
private Vector3 m_PinFlyTargetPos;
// Pin 插入目標位置的距離間隔
private float m_PinFlyTargetPosDistance;
// Pin 的移動速度
private float m_PinMoveSpeed;
// Pin 飛到目標位置的 Transfrorm
private Transform m_PinTargetParentTrans;
// PinHead
private PinHead m_PinHead; /// <summary>
/// 初始化函數(shù),得到相關位置和 PinHead
/// </summary>
/// <param name="pinsManager"></param>
/// <param name="pinReadyPos"></param>
/// <param name="flyTargetPos"></param>
/// <param name="flyTargetPosDistance"></param>
/// <param name="pinMoveSpeed"></param>
/// <param name="pinTargetParentTrans"></param>
public void Init(PinsManager pinsManager, Vector3 pinReadyPos, Vector3 flyTargetPos,
float flyTargetPosDistance, float pinMoveSpeed, Transform pinTargetParentTrans) {
m_PinsManager = pinsManager;
m_PinReadyPos = pinReadyPos;
m_PinFlyTargetPos = flyTargetPos;
m_PinFlyTargetPosDistance = flyTargetPosDistance;
m_PinMoveSpeed = pinMoveSpeed;
m_PinTargetParentTrans = pinTargetParentTrans;
// PinHead 子物體添加PinHead腳本
m_PinHead = transform.Find(ConstStr.PIN_HEAD_NAME).gameObject.AddComponent<PinHead>() ;
// 設置狀態(tài)為準備狀態(tài)
m_CurPinState = PinState.Readying;
}18、Pin 類,UpdatePinState(),Pin 的狀態(tài)監(jiān)聽
/// <summary>
/// 狀態(tài)監(jiān)聽
/// </summary>
void UpdatePinState() {
switch (CurPinState)
{
case PinState.Idle:
break;
case PinState.Readying:
Readying();
break;
case PinState.ReadyOK:
m_PinsManager.CurPin = this;
break;
case PinState.Fly:
Fly();
m_PinsManager.CurPin = null;
break;
default:
break;
}
}19、Pin 類,Readying() 正在準備的狀態(tài)函數(shù),F(xiàn)ly() 飛行目標位置狀態(tài),包含 Pin真正的運動(Vector3.Lerp),和滿足條件(Vector3.Distance)對應的狀態(tài)切換(注意:把 Pin 實體至于 TargetCircle 即可以 使 Pin 隨 TargetCircle 一起轉動 (this.transform.SetParent(m_PinTargetParentTrans);))
/// <summary>
/// 正在準備的狀態(tài)函數(shù)
/// </summary>
void Readying() {
// 移動到準備位置
transform.position = Vector3.Lerp(transform.position, m_PinReadyPos, Time.deltaTime * m_PinMoveSpeed);
// 判斷是否到達準備位置
if (Vector3.Distance(transform.position, m_PinReadyPos)<=0.1f)
{
transform.position = m_PinReadyPos;
// 到達后切換狀態(tài)
CurPinState = PinState.ReadyOK;
}
}
/// <summary>
/// 飛行目標位置狀態(tài)
/// </summary>
void Fly()
{
// 移動到目標位置
transform.position = Vector3.Lerp(transform.position, m_PinFlyTargetPos, Time.deltaTime * m_PinMoveSpeed);
if (Vector3.Distance(transform.position, m_PinFlyTargetPos) <= m_PinFlyTargetPosDistance)
{
// 到達后切換狀態(tài),并且置于 飛到的目標下,使之隨目標一起轉動
CurPinState = PinState.Idle;
this.transform.SetParent(m_PinTargetParentTrans);
}
}20、PinsManager Pins 管理類,主要管理 Pin 的生成和 發(fā)射 Fly

21、PinsManager Pins 管理類,構造函數(shù) PinsManager(),Resources.Load 獲取預制體 Pin 預制體,和參數(shù)賦值
參數(shù)說明:
// Pin 預制體
private GameObject m_PinPrefab;
// Pin 生成位置
private Vector3 m_PinSpawnPos;
// Pin 準備位置
private Vector3 m_PinReadyPos;
// Pin 飛向目標位置
private Vector3 m_PinFlyTargetPos;
// Pin 飛向目標插入間距
private float m_PinFlyTargetPosDistance;
// Pin 移動速度
private float m_PinMoveSpeed;
// Pin 飛向目標實體
private Transform m_PinTargetParentTrans; /// <summary>
/// 構造函數(shù)
/// </summary>
/// <param name="pinSpawnPos"></param>
/// <param name="pinReadyPos"></param>
/// <param name="flyTargetPos"></param>
/// <param name="flyTargetPosDistance"></param>
/// <param name="pinMoveSpeed"></param>
/// <param name="pinTargetParentTrans"></param>
public PinsManager(Vector3 pinSpawnPos, Vector3 pinReadyPos,Vector3 flyTargetPos,
float flyTargetPosDistance, float pinMoveSpeed,Transform pinTargetParentTrans) {
// 獲取預制體
m_PinPrefab = Resources.Load<GameObject>(ConstStr.RESOURCES_PREFABS_PIN_PATH);
// 參數(shù)賦值
m_PinSpawnPos = pinSpawnPos;
m_PinReadyPos = pinReadyPos;
m_PinFlyTargetPos = flyTargetPos;
m_PinFlyTargetPosDistance = flyTargetPosDistance;
m_PinMoveSpeed = pinMoveSpeed;
m_PinTargetParentTrans = pinTargetParentTrans;
}22、PinsManager Pins 管理類,SpawnPin() 根據(jù)條件 生成準備的 Pin
/// <summary>
/// 生成準備的 Pin
/// </summary>
public void SpawnPin() {
// 生成準備的 Pin
if (m_PinPrefab!=null && m_ReadyPin == null)
{
GameObject pinGo = GameObject.Instantiate(m_PinPrefab);
// 設置生成位置
pinGo.transform.position = m_PinSpawnPos;
// 添加 Pin 腳本
m_ReadyPin = pinGo.AddComponent<Pin>();
// Pin 腳本 初始化
m_ReadyPin.Init(this,m_PinReadyPos,m_PinFlyTargetPos,
m_PinFlyTargetPosDistance,m_PinMoveSpeed,m_PinTargetParentTrans);
// 添加到集合中
m_PinsList.Add(pinGo);
}
}23、PinsManager Pins 管理類,F(xiàn)lyPin() 根據(jù)條件 讓當前 Pin 飛向目標位置,返回是否有可飛行的 Pin,有則 true,DestroyAllPins() 銷毀清空 Pins 集合
/// <summary>
/// 讓當前 Pin 飛向目標位置
/// 返回是否有可飛行的 Pin
/// </summary>
/// <returns>true : 有可飛行 Pin </returns>
public bool FlyPin() {
if (CurPin!=null)
{
m_ReadyPin = null;
if (CurPin.CurPinState==PinState.ReadyOK)
{
CurPin.CurPinState = PinState.Fly;
}
return true;
}
return false;
}
/// <summary>
/// 銷毀清空 Pins 集合
/// </summary>
public void DestroyAllPins() {
for (int i=m_PinsList.Count-1; i >= 0 ; i--)
{
GameObject.Destroy(m_PinsList[i]);
}
m_PinsList.Clear();
}24、TargetCircleManager Pin 飛向目標圓管理類

25、TargetCircleManager Pin 飛向目標圓管理,TargetCircleManager()構造函數(shù) 獲取設置 旋轉的實體和旋轉速度
參數(shù)說明:
// 實體Transform
Transform m_TargetCircleTrans;
// 轉動速度
float m_Speed;
// 是否開始轉動
bool m_IsRotated = false; /// <summary>
/// 構造函數(shù)
/// </summary>
/// <param name="target">目標實體</param>
/// <param name="rotSpeed">旋轉速度</param>
public TargetCircleManager(Transform target, float rotSpeed) {
m_TargetCircleTrans = target;
m_Speed = rotSpeed;
m_IsRotated = false;
}26、TargetCircleManager Pin 飛向目標圓管理, void UpdateRotateSelf() 更新自身旋轉,StartRotateSelf() 開始旋轉,StopRotateSelf() 停止旋轉
/// <summary>
/// 更新自身旋轉
/// </summary>
public void UpdateRotateSelf() {
if (m_TargetCircleTrans != null && m_IsRotated==true)
{
m_TargetCircleTrans.Rotate(Vector3.forward,Time.deltaTime * m_Speed * -1); // -1 是讓其反向旋轉
}
}
/// <summary>
/// 開始旋轉
/// </summary>
public void StartRotateSelf()
{
m_IsRotated = true;
}
/// <summary>
/// 停止旋轉
/// </summary>
public void StopRotateSelf() {
m_IsRotated = false;
}27、ScoreManager 分數(shù)管理類,管理分數(shù),和分數(shù)變化更新的委托事件

public int Score { get { return m_Scroe; }
set {
// 判斷分數(shù)是否更新,更新則觸發(fā)更新事件
if (m_Scroe!=value)
{
m_Scroe = value;
if (OnChangeValue!=null)
{
OnChangeValue.Invoke(value);
}
}
}
}
// 分數(shù)變化委托
public Action<int> OnChangeValue;28、SimpleMessageCenter 簡單消息中心,管理消息的注冊,觸發(fā),和清空,并且設置靜態(tài)單例(static SimpleMessageCenter Instance),方便被訪問使用

29、SimpleMessageCenter 簡單消息中心, RegisterMsg()注冊消息,SendMsg()發(fā)送消息,ClearAllMsg() 清空消息
/// <summary>
/// 注冊消息
/// </summary>
/// <param name="msgType"></param>
/// <param name="action"></param>
public void RegisterMsg(MsgType msgType, Action action) {
if (m_MsgDict.ContainsKey(msgType) == true)
{
m_MsgDict[msgType] += action;
}
else {
m_MsgDict.Add(msgType,action);
}
}
/// <summary>
/// 發(fā)送消息
/// </summary>
/// <param name="msgType"></param>
public void SendMsg(MsgType msgType)
{
if (m_MsgDict.ContainsKey(msgType) == true)
{
m_MsgDict[msgType].Invoke();
}
}
/// <summary>
/// 清空消息
/// </summary>
public void ClearAllMsg() {
m_MsgDict.Clear();
}30、ConstStr 統(tǒng)一管理一些不可變的常量字符串

/// <summary>
/// 不可變字符串
/// </summary>
public class ConstStr
{
// Pin 飛行目標圓 名字路徑
public const string WORLD_TARGETCIRCLE_NAME_PATH = "World/TargetCircle";
// Pin Resources 預制體路徑
public const string RESOURCES_PREFABS_PIN_PATH = "Prefabs/Pin";
// Pin Head 名字
public const string PIN_HEAD_NAME = "PinHead";
}31、Enum 統(tǒng)一管理枚舉類型

/// <summary>
/// Pin 狀態(tài)
/// </summary>
public enum PinState
{
Idle = 0, // 閑置狀態(tài)
Readying, // 正在準備狀態(tài)
ReadyOK, // 裝備好狀態(tài)
Fly, // 飛向目標狀態(tài)
}
/// <summary>
/// 消息類型
/// </summary>
public enum MsgType
{
GameOver = 0, // 游戲結束
}32、GameManager 游戲管理類 ,直接掛載到場景中,獲取一些場景游戲實體,并管理一些游戲邏輯的初始化等

33、GameManager 游戲管理類 ,Awake() 和Start() Unity自帶函數(shù),初始化變量和類,以及啟動游戲,TargetCircle 開始旋轉,Pin 開始準備,在簡單消息中心注冊監(jiān)聽游戲結束事件,設置 分數(shù)變化更新到 TextMesh 上,等等
private void Awake()
{
// 初始化參數(shù)值
m_MainCamera = Camera.main;
m_IsGameOver = false;
Transform targetCircleTrans = GameObject.Find(ConstStr.WORLD_TARGETCIRCLE_NAME_PATH).transform;
m_TargetCircleManager = new TargetCircleManager(targetCircleTrans, RotateSpeed);
m_PinsManager = new PinsManager(m_PinSpawnPos.position,m_PinReadyPos.position, targetCircleTrans.position,
m_PinFlyTargetPosDistance,m_PinMoveSpeed,targetCircleTrans);
m_ScoreManager = new ScoreManager();
}
private void Start()
{
// 開始TargetCircle旋轉
m_TargetCircleManager.StartRotateSelf();
// 生成第一個 Pin
m_PinsManager.SpawnPin();
// 注冊監(jiān)聽游戲結束消息
SimpleMessageCenter.Instance.RegisterMsg(MsgType.GameOver,ToGameOver);
// 初始化分數(shù) 0
m_ScoreManager.Score = 0;
// 分數(shù)更新事件,更新 UI
m_ScoreManager.OnChangeValue += (score)=> { ScoreText.text = score.ToString(); };
}34、GameManager 游戲管理類 ,Update() Unity自帶函數(shù),游戲未結束,TargetCircle 每幀轉動,并監(jiān)聽鼠標左鍵按下,F(xiàn)ly Pin 和生成下一個 Pin 準備
private void Update()
{
// 游戲結束
if (m_IsGameOver == true)
{
return;
}
// TargetCircle 更新旋轉
m_TargetCircleManager.UpdateRotateSelf();
// 監(jiān)聽鼠標左鍵按下
if (Input.GetMouseButtonDown(0))
{
// Pin 飛向目標,則增加分數(shù),并且生成下一個 Pin
bool isFly = m_PinsManager.FlyPin();
if (isFly==true)
{
m_ScoreManager.Score++;
m_PinsManager.SpawnPin(); // 生成下一個
}
}
}35、GameManager 游戲管理類 ,OnDestroy() 在游戲重新加載銷毀時,進行一些數(shù)據(jù)清理置空,并停止可能的所有協(xié)程(不阻礙主程運行的小程序),OnGUI() 做一些游戲操作說明
private void OnDestroy()
{
// 銷毀所有 Pin
m_PinsManager.DestroyAllPins();
// 清空消息中心
SimpleMessageCenter.Instance.ClearAllMsg();
// 置空分數(shù)更新事件
m_ScoreManager.OnChangeValue = null;
// 置空相關參數(shù)
m_PinsManager = null;
m_TargetCircleManager = null;
m_ScoreManager = null;
// 停止所有協(xié)程
StopAllCoroutines();
}
// Unity 周期函數(shù) 每幀調用
private void OnGUI()
{
// 游戲操作說明
GUIStyle fontStyle = new GUIStyle();
fontStyle.normal.background = null; //設置背景填充
fontStyle.normal.textColor = new Color(1, 0, 0); //設置字體顏色
fontStyle.fontSize = 40; //字體大小
GUI.Label(new Rect(10, 10, 200, 200),
"操作說明:\n1、點擊鼠標左鍵發(fā)射球體;\n2、兩針 Pin 碰撞會自動觸發(fā)重新開始游戲;",
fontStyle);
}36、GameManager 游戲管理類 ,ToGameOver()游戲結束事件,停止旋轉,開啟協(xié)程,進行游戲結束特效處理,然后自動重新加載當前場景,重新開始游戲
/// <summary>
/// 游戲結束
/// </summary>
void ToGameOver() {
// 游戲結束
if (m_IsGameOver==true)
{
return;
}
// 游戲結束
m_IsGameOver = true;
// 停止目標旋轉
m_TargetCircleManager.StopRotateSelf();
// 開始結束協(xié)程
StartCoroutine(GameOver());
}
/// <summary>
/// 游戲結束協(xié)程
/// </summary>
/// <returns></returns>
IEnumerator GameOver() {
while (true)
{
// 等待幀最后
yield return new WaitForEndOfFrame();
// 更新主Camera 視口
m_MainCamera.orthographicSize = Mathf.Lerp(m_MainCamera.orthographicSize, m_OrthographicSize,Time.deltaTime * 10);
// 更新主Camera 背景色
m_MainCamera.backgroundColor = Color.Lerp(m_MainCamera.backgroundColor, Color.red,Time.deltaTime * 5);
// 更新主Camera 視口 到位,跳出循環(huán)
if ((m_MainCamera.orthographicSize - m_OrthographicSize)<0.01f)
{
break;
}
}
// 加載當前場景
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}37、把 GameManager 掛載到場景中,根據(jù) GameManager 參數(shù)說明賦值,一些速度等可以根據(jù)實際需要修改

38、運行場景, Pin 自動準備好,游戲就開始了

39、點擊鼠標左鍵,即可發(fā)射Fly,如圖

七、工程源碼地址
github 地址:GitHub - XANkui/UnityMiniGameParadise: Unity 游戲開發(fā)集合代碼集
八、延伸擴展
游戲的好不好玩,趣味性,視覺化等諸多因素影響,下面簡單介紹幾個方面拓展游戲的方向,僅做參考
1、可以根據(jù)自己需要修改游戲資源,換膚什么的等
2、可以根據(jù)需要添加撞擊特效,音效等
3、添加 UI 面板等,美化游戲
4、等等
以上就是Unity實戰(zhàn)之FlyPin(見縫插針)小游戲的實現(xiàn)的詳細內(nèi)容,更多關于Unity見縫插針游戲的資料請關注腳本之家其它相關文章!
相關文章
C#實現(xiàn)在Excel中添加篩選器并執(zhí)行篩選的操作
自動篩選器是 Excel 中的一個基本但極其有用的功能,它可以讓你根據(jù)特定的條件來自動隱藏和顯示你的數(shù)據(jù),當有大量的數(shù)據(jù)需要處理時,這個功能可以幫你快速找到你需要的信息,下面將介紹如何使用免費.NET Excel庫在Excel中添加、應用和刪除自動篩選器,需要的朋友可以參考下2024-05-05
DevExpress實現(xiàn)GridControl同步列頭checkbox與列中checkbox狀態(tài)
這篇文章主要介紹了DevExpress實現(xiàn)GridControl同步列頭checkbox與列中checkbox狀態(tài),需要的朋友可以參考下2014-08-08
c#中Empty()和DefalutIfEmpty()用法分析
這篇文章主要介紹了c#中Empty()和DefalutIfEmpty()用法,以實例形式分析了針對不同情況下Empty()和DefalutIfEmpty()用法區(qū)別,需要的朋友可以參考下2014-11-11

