一、前言
嗨,大家好,我是新發(fā)。
有同學(xué)私信我,問我能不能寫一篇Unity手游第一人稱視角控制的教程,
那么,今天就來做個(gè)Demo
吧~
注:
Demo
工程源碼見文章末尾
最終效果如下:
二、實(shí)現(xiàn)方案
1、無主之地,第一人稱視角
第一人稱視角的游戲大家應(yīng)該不陌生,比如《無主之地》,
不過它是PC
平臺(tái)的,使用WASD
控制移動(dòng),使用鼠標(biāo)來控制鏡頭角度,單擊鼠標(biāo)左鍵開槍。注:你也可以接手柄來操作~
那么,如果我們想做移動(dòng)端(手機(jī)端)的第一人稱視角,如何做角色控制呢?
2、我之前做的搖桿控制
手機(jī)端比較常見的就是搖桿控制了,我之前在幾篇博客中都有做過搖桿控制,
《【游戲開發(fā)創(chuàng)新】用Unity等比例制作廣州地鐵,廣州加油,早日戰(zhàn)勝疫情(Unity | 地鐵地圖 | 第三人稱視角)》
《【游戲開發(fā)創(chuàng)新】上班通勤時(shí)間太長,做一個(gè)任意門,告別地鐵與塞車(Unity | 建模 | ShaderGraph | 搖桿 | 角色控制)》
《【游戲開發(fā)實(shí)戰(zhàn)】新發(fā)教你做游戲(六):教你2個(gè)步驟實(shí)現(xiàn)搖桿功能》
3、第一人稱視角 + 搖桿控制
我上面做的都是第三人稱視角的搖桿控制,我們改成第一人稱視角即可,也就是第一人稱視角+搖桿控制
,像這樣子,(圖片說明:下圖是我在《無主之地》游戲截圖中P
了搖桿的UI
)
三、開始實(shí)戰(zhàn)
1、資源獲?。篣nity AssetStore
我沒有《無主之地》的資源,沒關(guān)系,我們?nèi)?code>Unity的AssetStore
上找一下FPS
射擊游戲的資源,
注:
Unity AssetStore
地址:https://assetstore.unity.com/
關(guān)于資源的搜索,我之前寫過一篇文章:《Unity游戲開發(fā)——新發(fā)教你做游戲(二):60個(gè)Unity免費(fèi)資源獲取網(wǎng)站》
搜索關(guān)鍵字FPS Pack
,馬上就搜到了一個(gè)免費(fèi)的資源Low Poly FPS Pack
,
我們點(diǎn)擊添加至我的資源
(注意需要先登錄你的Unity
賬號(hào)),
然后回到Unity
編輯器中,點(diǎn)擊菜單Windows / Package Manager
,打開PackageManager
窗口,就可以看到我們剛剛在AssetStore
中添加的資源啦,我們把資源包下載并導(dǎo)入我們的工程即可。
注:你得先創(chuàng)建一個(gè)空工程,然后再導(dǎo)入資源包。我之前寫過《學(xué)Unity的貓》系列教程,其中第三章有講創(chuàng)建工程的步驟,
《【學(xué)Unity的貓】——第三章:第一個(gè)Unity工程,你好喵星人》)
2、Low Poly FPS Pack資源運(yùn)行效果
Low Poly FPS Pack
資源包中已經(jīng)幫我們做好了一個(gè)簡單的第一人稱FPS
游戲Demo
,我們打開Assault_Rifle_01_Demo
場景,如下
運(yùn)行,測試效果如下
如你所見,經(jīng)典的PC
平臺(tái)FPS
射擊游戲玩法,使用WASD
控制移動(dòng),使用鼠標(biāo)來控制鏡頭角度,單擊鼠標(biāo)左鍵開槍。
接下來,我們要給它做下手術(shù),改成 搖桿 和 按鈕 控制。
3、制作UI界面
3.1、UI素材獲取
搖桿圖片簡單處理,用一個(gè)圓就可以了,然后我們還需要一些按鈕圖標(biāo),比如開槍、丟手雷、跳躍、裝子彈等,這里推薦我平時(shí)經(jīng)常用的一個(gè)查找圖標(biāo)資源的網(wǎng)站,阿里圖標(biāo)庫:https://www.iconfont.cn/
比如我搜關(guān)鍵字:槍,就可以看到槍的圖標(biāo)啦~
可以直接免費(fèi)下載,而且還可以事先修改圖片顏色,建議改成白色,這樣方便在Unity
中設(shè)置其他顏色,
根據(jù)你自身的需要下載一些圖標(biāo)資源,我下載的圖標(biāo)如下,
注意,因?yàn)槲覀円?code>UGUI中顯示這些圖標(biāo),需要將它們的Texture Type
設(shè)置為Sprite (2D and UI)
,如下
3.2、創(chuàng)建UI攝像機(jī):UICamera
建議UI
的顯示使用一個(gè)單獨(dú)的攝像機(jī)來渲染,我們?cè)趫鼍爸袆?chuàng)建一個(gè)Camera
,重命名為UICamera
,
注:創(chuàng)建攝像機(jī)的操作步驟:在
Hierarchy
視圖中鼠標(biāo)右鍵,然后點(diǎn)擊菜單Camera
即可。
設(shè)置攝像機(jī)的Clear Flags
為Depth only
,設(shè)置Culling Mask
只渲染UI
層,設(shè)置Projection
為Orthographic
(正交模式),設(shè)置Depth
為1
(確保UI
攝像機(jī)的比3D
攝像機(jī)后渲染),
3.3、創(chuàng)建UI畫布:Canvas
接下來,我們?cè)?code>Hierarchy視圖中鼠標(biāo)右鍵,點(diǎn)擊菜單UI / Canvas
,創(chuàng)建一個(gè)Canvas
,
設(shè)置一下參數(shù),如下,目的是讓UICamera
來渲染Canvas
的內(nèi)容,并設(shè)置分辨率適配規(guī)則,
3.4、創(chuàng)建Panel:GamePanel
我們?cè)?code>Canvas子節(jié)點(diǎn)下創(chuàng)建一個(gè)Panel
,重命名為GamePanel
,并把Image
組件禁用,
下面我們?cè)僭?code>GamePanel下去創(chuàng)建UI
對(duì)象。
3.5、制作搖桿
我們先做移動(dòng)控制的搖桿,在GamePanel
子節(jié)點(diǎn)下創(chuàng)建一個(gè)Image
,重命名為moveJointedArm
,
設(shè)置左下角對(duì)齊,并調(diào)整坐標(biāo)和尺寸,
像這樣,它就是我們搖桿的檢測區(qū)域,
我們把它的Image
組件的顏色的Alpha
通道設(shè)置為0
,這樣我們就看不見它了,
我們?cè)?code>moveJointedArm子節(jié)點(diǎn)下再創(chuàng)建兩個(gè)Image
,分別命名為bg
和center
,
分別設(shè)置一下尺寸,圖片,顏色,如下
效果
同理,做一下右搖桿,
效果
3.6、制作操作按鈕
除了移動(dòng)和旋轉(zhuǎn),我們還有開槍、丟手雷、跳躍、裝子彈的操作,配套需要制作對(duì)應(yīng)的按鈕。安排上,
效果
到這里,我們的UI
界面就基本做好啦,下面就是寫代碼的環(huán)節(jié)了~
4、搖桿控制腳本:JointedArm.cs
搖桿的邏輯實(shí)現(xiàn),我之前寫過一篇文章講過原理:《Unity使用ScrollRect制作搖桿(UGUI)》,這里我就不過對(duì)贅述,直接說下操作流程。
4.1、JointedArm.cs腳本代碼
創(chuàng)建一個(gè)C#
腳本,重命名為JointedArm.cs
,代碼如下:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System;
// 搖桿邏輯
public class JointedArm : ScrollRect, IPointerDownHandler
{
public Action<Vector2> onDragCb;
public Action onStopCb;
protected float mRadius = 0f;
private Transform trans;
private RectTransform bgTrans;
private Camera uiCam;
private Vector3 originalPos;
protected override void Awake()
{
base.Awake();
trans = transform;
bgTrans = trans.Find("bg") as RectTransform;
uiCam = GameObject.Find("UICamera").GetComponent<Camera>();
originalPos = trans.localPosition;
}
void Update()
{
if (Input.GetMouseButtonUp(0))
{
//松手時(shí),搖桿復(fù)位
trans.localPosition = originalPos;
this.content.localPosition = Vector3.zero;
}
}
protected override void Start()
{
base.Start();
//計(jì)算搖桿塊的半徑
mRadius = bgTrans.sizeDelta.x * 0.5f;
}
public override void OnDrag(PointerEventData eventData)
{
base.OnDrag(eventData);
var contentPostion = this.content.anchoredPosition;
if (contentPostion.magnitude > mRadius)
{
contentPostion = contentPostion.normalized * mRadius;
SetContentAnchoredPosition(contentPostion);
}
//Debug.Log("搖桿滑動(dòng),方向:" + contentPostion);
if(null != onDragCb)
onDragCb(contentPostion);
}
public override void OnEndDrag(PointerEventData eventData)
{
base.OnEndDrag(eventData);
//Debug.Log("搖桿拖動(dòng)結(jié)束");
if (null != onStopCb)
onStopCb();
}
public void OnPointerDown(PointerEventData eventData)
{
//點(diǎn)擊到搖桿的區(qū)域,搖桿移動(dòng)到點(diǎn)擊的位置
trans.position = uiCam.ScreenToWorldPoint(eventData.position);
trans.localPosition = new Vector3(trans.localPosition.x, trans.localPosition.y, 0);
}
}
4.2、掛搖桿腳本,設(shè)置成員對(duì)象
給moveJointedArm
節(jié)點(diǎn)掛JointedArm
腳本,并設(shè)置Content
為center
節(jié)點(diǎn),如下,
同理設(shè)置右搖桿rotateJointedArm
。到此,我們的搖桿就有交互效果了,如下
5、關(guān)聯(lián)UI交互事件
5.1、定義UI成員:GamePanel.cs
我們創(chuàng)建一個(gè)GamePanel.cs
腳本,聲明UI
對(duì)象,如下
using UnityEngine;
public class GamePanel : MonoBehaviour
{
/// <summary>
/// 移動(dòng)搖桿
/// </summary>
public JointedArm moveJointedArm;
/// <summary>
/// 旋轉(zhuǎn)搖桿
/// </summary>
public JointedArm rotateJointedArm;
/// <summary>
/// 開槍按鈕
/// </summary>
public GameObject fireBtn;
/// <summary>
/// 丟手雷按鈕
/// </summary>
public GameObject bombBtn;
/// <summary>
/// 跳躍按鈕
/// </summary>
public GameObject jumpBtn;
/// <summary>
/// 裝子彈按鈕
/// </summary>
public GameObject bulletBtn;
void Start()
{
// TODO 關(guān)聯(lián)UI交互事件
}
}
5.2、設(shè)置UI對(duì)象
把它掛到GamePanel
節(jié)點(diǎn)上,并設(shè)置變量對(duì)象,如下
5.3、設(shè)置搖桿委托
在Start
函數(shù)中添加搖桿的委托,如下
// GamePanel.cs
void Start()
{
// 移動(dòng)控制搖桿
moveJointedArm.onDragCb = (direction) =>
{
// TODO 拋出事件
};
moveJointedArm.onStopCb = () =>
{
// TODO 拋出事件
};
// 旋轉(zhuǎn)控制搖桿
rotateJointedArm.onDragCb = (direction) =>
{
// TODO 拋出事件
};
rotateJointedArm.onStopCb = () =>
{
// TODO 拋出事件
};
// ...
}
我們要拋出一些事件,這里要封裝一個(gè)事件管理器。
6、事件管理:訂閱、注銷、拋出
6.1、封裝事件管理器:EventDispatcher.cs
我在之前的多篇文章中都有到和用到事件管理器,歡迎閱讀我之前寫的這些文章,里面都有用到事件管理器,
《【游戲開發(fā)框架】自制Unity通用游戲框架UnityXFramework,詳細(xì)教程(Unity3D技能樹 | tolua | 框架 | 熱更新)》
《【游戲開發(fā)創(chuàng)新】用Unity等比例制作廣州地鐵,廣州加油,早日戰(zhàn)勝疫情(Unity | 地鐵地圖 | 第三人稱視角)》
《【游戲開發(fā)實(shí)戰(zhàn)】使用Unity 2019制作仿微信小游戲飛機(jī)大戰(zhàn)(二):搭建基礎(chǔ)游戲框架》
《【游戲開發(fā)實(shí)戰(zhàn)】使用Unity制作水果消消樂游戲教程(三):水果拖動(dòng)與交換邏輯》
《【游戲開發(fā)實(shí)戰(zhàn)】使用Unity制作像天天酷跑一樣的跑酷游戲——第七篇:游戲界面的基礎(chǔ)UI》
《【學(xué)Unity的貓】第十二章:使用Unity制作背包,皮皮的夢(mèng)想背包》EventDispatcher
腳本代碼:
using UnityEngine;
using System.Collections.Generic;
public delegate void MyEventHandler(params object[] objs);
/// <summary>
/// 游戲事件管理器
/// </summary>
public class EventDispatcher
{
/// <summary>
/// 注冊(cè)事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="handler">響應(yīng)函數(shù)</param>
public void Regist(string evt, MyEventHandler handler)
{
if (handler == null)
return;
if (listeners.ContainsKey(evt))
{
//這里涉及到Dispath過程中反注冊(cè)問題,必須使用listeners[type]+=..
listeners[evt] += handler;
}
else
{
listeners.Add(evt, handler);
}
}
/// <summary>
/// 注銷事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="handler">響應(yīng)函數(shù)</param>
public void UnRegist(string evt, MyEventHandler handler)
{
if (handler == null)
return;
if (listeners.ContainsKey(evt))
{
//這里涉及到Dispath過程中反注冊(cè)問題,必須使用listeners[type]-=..
listeners[evt] -= handler;
if (listeners[evt] == null)
{
//已經(jīng)沒有監(jiān)聽者了,移除.
listeners.Remove(evt);
}
}
}
/// <summary>
/// 拋出事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="objs">參數(shù)</param>
public void DispatchEvent(string evt, params object[] objs)
{
try
{
if (listeners.ContainsKey(evt))
{
MyEventHandler handler = listeners[evt];
if (handler != null)
handler(objs);
}
}
catch (System.Exception ex)
{
Debug.LogErrorFormat(szErrorMessage, evt, ex.Message, ex.StackTrace);
}
}
public void ClearEvents(string key)
{
if (listeners.ContainsKey(key))
{
listeners.Remove(key);
}
}
private Dictionary<string, MyEventHandler> listeners = new Dictionary<string, MyEventHandler>();
private readonly string szErrorMessage = "DispatchEvent Error, Event:{0}, Error:{1}, {2}";
private static EventDispatcher s_instance;
public static EventDispatcher instance
{
get
{
if (null == s_instance)
s_instance = new EventDispatcher();
return s_instance;
}
}
}
6.2、定義事件名:EventNameDef.cs
我們?cè)賱?chuàng)建一個(gè)EventNameDef.cs
腳本,用于定義事件名,如下
/// <summary>
/// 事件名定義
/// </summary>
public class EventNameDef
{
/// <summary>
/// 移動(dòng)
/// </summary>
public const string MOVE = "MOVE";
/// <summary>
/// 旋轉(zhuǎn)
/// </summary>
public const string ROTATE = "ROTATE";
/// <summary>
/// 開槍
/// </summary>
public const string FIRE = "FIRE";
/// <summary>
/// 丟手榴彈
/// </summary>
public const string BOMB = "BOMB";
/// <summary>
/// 跳躍
/// </summary>
public const string JUMP = "JUMP";
/// <summary>
/// 裝子彈
/// </summary>
public const string BULLET = "BULLET";
}
6.3、拋出事件
我們回到GamePanel.cs
腳本,在搖桿的委托中拋出事件,
// GamePanel.cs
void Start()
{
// 移動(dòng)控制搖桿
moveJointedArm.onDragCb = (direction) =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.MOVE, new Vector3(direction.x, 0, direction.y).normalized, true);
};
moveJointedArm.onStopCb = () =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.MOVE, Vector3.zero, false);
};
// 旋轉(zhuǎn)控制搖桿
rotateJointedArm.onDragCb = (direction) =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.ROTATE, new Vector3(direction.x, 0, direction.y).normalized);
};
rotateJointedArm.onStopCb = () =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.ROTATE, Vector3.zero);
};
// ...
}
6.4、訂閱事件和注銷事件
搖桿拋出的事件,最終的響應(yīng)邏輯就是角色移動(dòng)和旋轉(zhuǎn),那么我們就要在原來控制角色移動(dòng)和旋轉(zhuǎn)的腳本中添加事件訂閱。
邏輯在哪里呢?邏輯在FPSControllerLPFP.cs
腳本和AutomaticGunScriptLPFP.cs
腳本中。
畫個(gè)圖,方便大家理解,
我們分別在FPSControllerLPFP.cs
腳本和AutomaticGunScriptLPFP.cs
腳本中添加事件訂閱和注銷,如下
// FPSControllerLPFP.cs
private void Start()
{
// ...
// 訂閱事件
EventDispatcher.instance.Regist(EventNameDef.MOVE, OnEventMove);
EventDispatcher.instance.Regist(EventNameDef.ROTATE, OnEventRotate);
// ...
}
private void OnDestroy()
{
// 注銷事件
EventDispatcher.instance.UnRegist(EventNameDef.MOVE, OnEventMove);
EventDispatcher.instance.UnRegist(EventNameDef.ROTATE, OnEventRotate);
// ...
}
// AutomaticGunScriptLPFP.cs
private void Start()
{
// ...
// 訂閱事件
EventDispatcher.instance.Regist(EventNameDef.MOVE, OnEventMove);
// ...
}
private void OnDestroy()
{
// 注銷事件
EventDispatcher.instance.UnRegist(EventNameDef.MOVE, OnEventMove);
// ...
}
7、移動(dòng)控制
7.1、流程
流程如下,
7.2、代碼實(shí)現(xiàn)
搖桿通過MOVE
事件傳遞了移動(dòng)方向過來,我們?cè)?code>FPSControllerLPFP.cs腳本中把它緩存到m_moveDirection
變量中,如下
// FPSControllerLPFP.cs
Vector3 _moveDirection;
private void OnEventMove(params object[] args)
{
_moveDirection = (Vector3)args[0];
}
在FixedUpdate
函數(shù)中執(zhí)行MoveCharacter
方法,在MoveCharacter
方法中根據(jù)m_moveDirection
去計(jì)算移動(dòng),邏輯如下,(部分函數(shù)此處沒有列出,可下載工程源碼進(jìn)行查看)
// FPSControllerLPFP.cs
/// <summary>
/// 移動(dòng)角色
/// </summary>
private void MoveCharacter()
{
// 轉(zhuǎn)為世界坐標(biāo)系下的方向
var worldDirection = transform.TransformDirection(_moveDirection);
// 移動(dòng)速度
var velocity = worldDirection * (input.Run ? runningSpeed : walkingSpeed);
// 檢查碰撞,以便角色在跳墻時(shí)不會(huì)卡住
var intersectsWall = CheckCollisionsWithWalls(velocity);
if (intersectsWall)
{
_velocityX.Current = _velocityZ.Current = 0f;
return;
}
// 平滑運(yùn)算
var smoothX = _velocityX.Update(velocity.x, movementSmoothness);
var smoothZ = _velocityZ.Update(velocity.z, movementSmoothness);
// 獲取當(dāng)前剛體速度
var rigidbodyVelocity = _rigidbody.velocity;
// 計(jì)算速度差
var force = new Vector3(smoothX - rigidbodyVelocity.x, 0f, smoothZ - rigidbodyVelocity.z);
// 給剛體施加一個(gè)力
_rigidbody.AddForce(force, ForceMode.VelocityChange);
}
移動(dòng)的同時(shí),還需要播放走路動(dòng)畫,邏輯在AutomaticGunScriptLPFP.cs
腳本中,
// AutomaticGunScriptLPFP.cs
private Animator anim;
private bool isWalking;
private void OnEventMove(params object[] args)
{
isWalking = (bool)args[1];
}
private void Update()
{
// ...
if (isWalking && !isRunning)
{
anim.SetBool("Walk", true);
}
else
{
anim.SetBool("Walk", false);
}
// ...
}
7.3、運(yùn)行效果
此時(shí)效果
8、旋轉(zhuǎn)控制
8.1、流程
同理,旋轉(zhuǎn)控制也是通過事件的響應(yīng)函數(shù)來觸發(fā),流程如下,
8.2、代碼實(shí)現(xiàn)
// FpsControllerLPFP.cs
/// <summary>
/// 旋轉(zhuǎn)角色
/// </summary>
private void RotateCameraAndCharacter()
{
// 平滑運(yùn)算
var rotationX = _rotationX.Update(RotationXRaw, rotationSmoothness);
var rotationY = _rotationY.Update(RotationYRaw, rotationSmoothness);
// 限制豎直方向的旋轉(zhuǎn)角度:
var clampedY = RestrictVerticalRotation(rotationY);
_rotationY.Current = clampedY;
// 將世界坐標(biāo)系下的up方向轉(zhuǎn)為相對(duì)手臂的局部坐標(biāo)系下的方向
var worldUp = arms.InverseTransformDirection(Vector3.up);
// 計(jì)算最終角度(四元數(shù))
var rotation = arms.rotation *
Quaternion.AngleAxis(rotationX, worldUp) *
Quaternion.AngleAxis(clampedY, Vector3.left);
// 父節(jié)點(diǎn)只沿著y軸旋轉(zhuǎn),容易漏掉此步,如果沒有此步,計(jì)算移動(dòng)的時(shí)候會(huì)出問題
transform.eulerAngles = new Vector3(0f, rotation.eulerAngles.y, 0f);
// 手臂自由旋轉(zhuǎn)
arms.rotation = rotation;
}
8.3、運(yùn)行效果
此時(shí)效果
9、開槍控制
9.1、流程
9.2、封裝EventTrigger,監(jiān)聽長按事件
因?yàn)殚_槍是一個(gè)連續(xù)過程,我們要檢測是否長按了開槍按鈕,而UGUI
的Button
的onClick
只能監(jiān)聽點(diǎn)擊事件,所以我們需要另外實(shí)現(xiàn)長按事件的監(jiān)聽。UnityEngine.EventSystems
命名空間下有個(gè)EventTrigger
類,它基本提供了所有UI
事件,
我們封裝一個(gè)EventTriggerListener.cs
,繼承EventTrigger
,如下,
using UnityEngine;
using UnityEngine.EventSystems;
/// <summary>
/// UI事件觸發(fā)器
/// </summary>
public class EventTriggerListener : UnityEngine.EventSystems.EventTrigger
{
public delegate void VoidDelegate(GameObject go);
public delegate void BoolDelegate(GameObject go, bool state);
public delegate void FloatDelegate(GameObject go, float delta);
public delegate void VectorDelegate(GameObject go, Vector2 delta);
public delegate void ObjectDelegate(GameObject go, GameObject obj);
public delegate void KeyCodeDelegate(GameObject go, KeyCode key);
public VoidDelegate onClick;
public VoidDelegate onDown;
public VoidDelegate onEnter;
public VoidDelegate onExit;
public VoidDelegate onUp;
public VoidDelegate onSelect;
public VoidDelegate onUpdateSelect;
static public EventTriggerListener Get(GameObject go)
{
EventTriggerListener listener = go.GetComponent<EventTriggerListener>();
if (listener == null) listener = go.AddComponent<EventTriggerListener>();
return listener;
}
static public EventTriggerListener Get(Transform transform)
{
EventTriggerListener listener = transform.GetComponent<EventTriggerListener>();
if (listener == null) listener = transform.gameObject.AddComponent<EventTriggerListener>();
return listener;
}
public override void OnPointerClick(PointerEventData eventData)
{
if (onClick != null) onClick(gameObject);
}
public override void OnPointerDown(PointerEventData eventData)
{
if (onDown != null) onDown(gameObject);
}
public override void OnPointerEnter(PointerEventData eventData)
{
if (onEnter != null) onEnter(gameObject);
}
public override void OnPointerExit(PointerEventData eventData)
{
if (onExit != null) onExit(gameObject);
}
public override void OnPointerUp(PointerEventData eventData)
{
if (onUp != null) onUp(gameObject);
}
public override void OnSelect(BaseEventData eventData)
{
if (onSelect != null) onSelect(gameObject);
}
public override void OnUpdateSelected(BaseEventData eventData)
{
if (onUpdateSelect != null) onUpdateSelect(gameObject);
}
}
9.3、開槍按鈕,長按與抬起事件
在GamePanel.cs
中添加按鈕長按onDown
和按鈕抬起onUp
的監(jiān)聽并拋出FIRE
事件,如下
// GamePanel.cs
// 開炮
void Start()
{
// ...
EventTriggerListener.Get(fireBtn).onDown += (btn) =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.FIRE, true);
};
EventTriggerListener.Get(fireBtn).onUp += (btn) =>
{
EventDispatcher.instance.DispatchEvent(EventNameDef.FIRE, false);
};
// ...
}
9.4、響應(yīng)FIRE事件
開槍的邏輯在AutomaticGunScriptLPFP.cs
腳本中,流程如下
// AutomaticGunScriptLPFP.cs
bool _fire;
private void OnEventFire(params object[] args)
{
_fire = (bool)args[0];
}
private void Update()
{
// ...
if (_fire && !outOfAmmo && !isReloading && !isInspecting && !isRunning)
{
if (Time.time - lastFired > 1 / fireRate)
{
lastFired = Time.time;
// 執(zhí)行開槍
DoFire();
}
}
// ...
}
void DoFire()
{
// 以下具體代碼見工程代碼,此處不展開了
// 播放開槍音效
// 播放開炮動(dòng)畫
// 播放槍口粒子
// 實(shí)例化子彈并給子彈一個(gè)力
// 實(shí)例化彈殼
}
9.5、運(yùn)行效果
此時(shí)效果,
10、丟手雷、跳躍、裝子彈
同理,通過事件訂閱觸發(fā)丟手雷、跳躍、裝子彈等邏輯。
10.1、丟手雷
10.2、跳躍
10.3、裝子彈
11、加個(gè)彩蛋,同學(xué)你上電視了
給這位提問的同學(xué)一次上電視的機(jī)會(huì),我把他貼到墻上,
初始的時(shí)候圖片半透明,角色靠近的時(shí)候圖片完全不透明,用到的是觸發(fā)器,
關(guān)于觸發(fā)器的教程,我之前寫過一些文章,《【學(xué)Unity的貓】第十章:Unity的物理碰撞,流浪喵星計(jì)劃》
這里的檢測邏輯如下,
using UnityEngine;
using UnityEngine.UI;
public class TipsBoard : MonoBehaviour
{
public Image board;
private void Start() {
board.color = new Color(1, 1, 1, 0.3f);
}
private void OnTriggerEnter(Collider other)
{
if ("Player" != other.tag) return;
board.color = new Color(1, 1, 1, 1);
}
private void OnTriggerExit(Collider other)
{
if ("Player" != other.tag) return;
board.color = new Color(1, 1, 1, 0.3f);
}
}
四、工程源碼
本文工程源碼我已上傳到CODE CHINA
,感興趣的同學(xué)可自行下載學(xué)習(xí),
地址:https://codechina.csdn.net/linxinfa/FirstPersonGame
注:我使用的Unity
版本是2021.1.7f1c1
,如果你使用的版本與我的不同,可能會(huì)有一些兼容問題。文章來源:http://www.zghlxwxcb.cn/news/detail-404460.html
五、完畢
好啦,就到這里吧~
我是林新發(fā):https://blog.csdn.net/linxinfa
原創(chuàng)不易,若轉(zhuǎn)載請(qǐng)注明出處,感謝大家~
喜歡我的可以點(diǎn)贊、關(guān)注、收藏,如果有什么技術(shù)上的疑問,歡迎留言或私信~文章來源地址http://www.zghlxwxcb.cn/news/detail-404460.html
到了這里,關(guān)于【游戲開發(fā)實(shí)戰(zhàn)】Unity手游第一人稱視角,雙搖桿控制,F(xiàn)PS射擊游戲Demo(教程 | 含Demo工程源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!