前言
  到目前为止,我们已经实现了游戏的所有功能。但目前,我们仍然使用键盘和鼠标来操作角色的移动和攻击。为了能让游戏在手机上正常运行,我们需要实现虚拟摇杆
和按钮
,来替换键盘和鼠标输入。此外,为了减少代码的修改,且同时兼容PC端和手机端的使用,我们希望虚拟摇杆
和按钮
的输入值的获取方式和Unity提供的Input类
类似。
  注意到Unity提供的Standard Asstes下面有一个名为CrossPlatformInput
的插件,这个插件的作用是进行跨平台输入适配
,因此我们可以参考CrossPlatformInput
插件来实现我们的虚拟摇杆和按钮。
框架设计
  Unity提供的Input
类为静态类,因此我们也需要提供一个用于管理虚拟摇杆
和按钮
输入的静态类,我们不妨将这个静态类命名为InputManager
。此外,因为虚拟摇杆
和按钮
需要根据玩家对UI进行的操作
来获得输入
,所以我们需要先实现只用于处理输入的VirtualAxis
和VirtualButton
,然后在VirtualAxis
和VirtualButton
的基础上,加上UI操作
来实现虚拟摇杆
和按钮
。
  此外,为了方便脚本的寻找,我们在Assets\Scripts
文件夹下新建一个名为Input
的文件夹,用于存放所有和实现虚拟摇杆和按钮有关的脚本。
  在清楚了框架的设计之后,我们先来实现用于处理输入的VirtualAxis
和VirtualButton
类。在实现VirtualAxis
和VirtualButton
类之前,我们需要了解一下VirtualAxis
和VirtualButton
类需要管理哪些值。
  通过前面对Unity提供的Input
类的使用,我们知道,当我们使用Axis
时,我们可以获得一个在(-1, 1)
内连续变化的float类型
值。而当我们使用Button
时,我们可以获取三个分别代表按钮刚刚被按下
、按钮被持续按下
和按钮刚刚被松开
的bool 类型
值。
  也就是说,VirtualAxis
需要管理一个float类型
的变量,而VirtualButton
需要管理三个bool类型
的变量。
  了解了VirtualAxis
和VirtualButton
类需要管理的值之后,我们先来实现较为简单的VirtualAxis
类。首先,我们在Assets\Scripts\Input
文件夹下新建一个名为VirtualAxis
的C#脚本,然后编辑VirtualAxis.cs
如下:
VirtualAxis.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class VirtualAxis { public string Name { get; private set; } private float m_Value;
public VirtualAxis(string name) { this.Name = name; }
public void Update(float value) { m_Value = value; }
public float GetValue() { return m_Value; }
public float GetValueRaw() { return m_Value; } }
|
  接着,我们来实现较为复杂的VirtualButton
类,我们在Assets\Scripts\Input
文件夹下新建一个名为VirtualButton
的C#脚本,然后编辑VirtualButton.cs
如下:
VirtualButton.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class VirtualButton { public string Name { get; private set; }
private int m_LastPressedFrame = -5; private int m_ReleasedFrame = -5; private bool m_Pressed;
public VirtualButton(string name) { this.Name = name; }
public void Pressed() { if (m_Pressed) { return; }
m_Pressed = true; m_LastPressedFrame = Time.frameCount; }
public void Released() { m_Pressed = false; m_ReleasedFrame = Time.frameCount; }
public bool GetButton() { return m_Pressed; }
public bool GetButtonDown() { return (m_LastPressedFrame == Time.frameCount - 1); }
public bool GetButtonUp() { return (m_ReleasedFrame == Time.frameCount - 1); } }
|
代码说明:
- 这里我们使用类型为
bool
的GetButtonDown
、GetButton
和GetBottonUp
函数来获取按钮当前是否处于按钮刚刚被按下
、按钮被持续按下
和按钮刚刚被松开
状态
- 我们知道,
按钮刚刚被按下
和按钮刚刚被松开
状态是一个短暂的状态,当按钮被按下时,按钮刚刚被按下
状态只会持续一帧的时间,按钮刚刚被松开
状态也一样。因此,我们使用了m_LastPressedFrame
和m_ReleasedFrame
来保存按钮刚刚被按下
和按钮刚刚被松开
的帧数,并通过分别比较m_LastPressedFrame
、m_ReleasedFrame
和Time.frameCount
的帧数差来判断按钮当前是否为按钮刚刚被按下
或者按钮刚刚被松开
状态。
  实现完VirtualAxis
和VirtualButton
类之后,我们还需要实现用于管理VirtualAxis
和VirtualButton
类的静态类InputManager
。我们在Assets\Scripts\Input
文件夹下新建一个名为InputManager
的C#脚本,然后编辑InputManager.cs
如下:
InputManager.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| using System; using System.Collections; using System.Collections.Generic; using UnityEngine;
public static class InputManager { private static Dictionary<string, VirtualAxis> m_VirtualAxes; private static Dictionary<string, VirtualButton> m_VirtualButtons;
static InputManager() { m_VirtualAxes = new Dictionary<string, VirtualAxis>(); m_VirtualButtons = new Dictionary<string, VirtualButton>(); }
#region 用于管理的API public static bool AxisExists(string name) { return m_VirtualAxes.ContainsKey(name); }
public static bool ButtonExists(string name) { return m_VirtualButtons.ContainsKey(name); }
public static void RegisterVirtualAxis(VirtualAxis axis) { if (m_VirtualAxes.ContainsKey(axis.Name)) { Debug.LogError("There is already a virtual axis named " + axis.Name + " registered."); } else { m_VirtualAxes.Add(axis.Name, axis); } }
public static void RegisterVirtualButton(VirtualButton button) { if (m_VirtualButtons.ContainsKey(button.Name)) { Debug.LogError("There is already a virtual button named " + button.Name + " registered."); } else { m_VirtualButtons.Add(button.Name, button); } }
public static void UnRegisterVirtualAxis(VirtualAxis axis) { if (m_VirtualAxes.ContainsKey(axis.Name)) { m_VirtualAxes.Remove(axis.Name); } }
public static void UnRegisterVirtualButton(VirtualButton button) { if (m_VirtualButtons.ContainsKey(button.Name)) { m_VirtualButtons.Remove(button.Name); } }
public static void SetButtonDown(VirtualButton button) { if(InputManager.ButtonExists(button.Name)) { button.Pressed(); } else { Debug.LogError("There is not a virtual button named " + button.Name + " registered."); } }
public static void SetButtonUp(VirtualButton button) { if(InputManager.ButtonExists(button.Name)) { button.Released(); } else { Debug.LogError("There is not a virtual button named " + button.Name + " registered."); } } #endregion
#region 用于获取输入的API public static float GetAxis(string name) { if(m_VirtualAxes.ContainsKey(name)) { return m_VirtualAxes[name].GetValue(); } else { Debug.LogError("There is not axis named " + name + " registered."); return 0f; } }
public static float GetAxisRaw(string name) { if(m_VirtualAxes.ContainsKey(name)) { return m_VirtualAxes[name].GetValueRaw(); } else { Debug.LogError("There is not axis named " + name + " registered."); return 0f; } }
public static bool GetButton(string name) { if(m_VirtualButtons.ContainsKey(name)) { return m_VirtualButtons[name].GetButton(); } else { Debug.LogError("There is not button named " + name + " registered."); return false; } }
public static bool GetButtonDown(string name) { if(m_VirtualButtons.ContainsKey(name)) { return m_VirtualButtons[name].GetButtonDown(); } else { Debug.LogError("There is not button named " + name + " registered."); return false; } }
public static bool GetButtonUp(string name) { if(m_VirtualButtons.ContainsKey(name)) { return m_VirtualButtons[name].GetButtonUp(); } else { Debug.LogError("There is not button named " + name + " registered."); return false; } } #endregion }
|
代码说明:
- 我们知道,我们通常会使用多个
Axis
和Button
,且我们通过Name
来获取Axis
和Button
的输入值,因此我们需要使用Dictionary
来管理VirtualAxis
和VirtualButton
,并将它们的Name
设置为Key
InputManager
是一个静态类,因此我们需要提供一个不带访问符
的静态构造器
,且所有函数和变量都应为Static
,具体详情可以查阅C# 静态类
InputManager
提供了两类API,一类用于管理VirtualAxis
和VirtualButton
的API,另外一类则是用于在Game Play
中获取输入的API
实现虚拟摇杆
  实现了静态类InputManager
之后,我们开始实现控制虚拟摇杆的JoyStickHandler
。我们在Assets\Scripts\Input
文件夹下新建一个名为JoyStickHandler
的C#脚本,然后编辑JoyStickHandler.cs
如下:
JoyStickHandler.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;
public class JoyStickHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler { public enum AxisOption { Both, Horizontal, Vertical }
[Tooltip("虚拟摇杆的最大活动范围")] public float Range = 100; [Tooltip("是否根据屏幕的尺寸对虚拟摇杆的最大获得范围进行缩放")] public bool ScaleRange = true; [Tooltip("使用哪个轴")] public AxisOption AxisToUse = AxisOption.Both; [Tooltip("水平轴的名称")] public string HorizontalAxisName = "Horizontal"; [Tooltip("数值轴的名称")] public string VerticalAxisName = "Vertical";
Vector3 m_StartPos; bool m_UseHorizontalAxis; bool m_UseVerticalAxis; VirtualAxis m_HorizontalVirtualAxis; VirtualAxis m_VerticalVirtualAxis;
private void Awake() { if(ScaleRange){ CanvasScaler scaler = transform.root.GetComponent<CanvasScaler>();
float scaleX = Screen.width / scaler.referenceResolution.x; float scaleY = Screen.height / scaler.referenceResolution.y; if(scaleX > scaleY) { Range *= scaleY; } else { Range *= scaleX; } } m_UseHorizontalAxis = (AxisToUse == AxisOption.Both || AxisToUse == AxisOption.Horizontal); m_UseVerticalAxis = (AxisToUse == AxisOption.Both || AxisToUse == AxisOption.Vertical);
if (m_UseHorizontalAxis) { m_HorizontalVirtualAxis = new VirtualAxis(HorizontalAxisName); }
if (m_UseVerticalAxis) { m_VerticalVirtualAxis = new VirtualAxis(VerticalAxisName); } }
private void OnEnable() { if (m_UseHorizontalAxis) { InputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis); }
if (m_UseVerticalAxis) { InputManager.RegisterVirtualAxis(m_VerticalVirtualAxis); } }
private void Start() { m_StartPos = transform.position; }
private void OnDisable() { if (m_UseHorizontalAxis) { InputManager.UnRegisterVirtualAxis(m_HorizontalVirtualAxis); }
if (m_UseVerticalAxis) { InputManager.UnRegisterVirtualAxis(m_VerticalVirtualAxis); } }
private void UpdateVirtualAxes(Vector3 delta) { transform.position = new Vector3( m_StartPos.x + delta.x, m_StartPos.y + delta.y, m_StartPos.z + delta.z );
delta /= Range;
if (m_UseHorizontalAxis) { m_HorizontalVirtualAxis.Update(delta.x); }
if (m_UseVerticalAxis) { m_VerticalVirtualAxis.Update(delta.y); } }
#region 接口函数 public void OnDrag(PointerEventData data) { Vector3 newPos = Vector3.zero;
if (m_UseHorizontalAxis) { float delta = data.position.x - m_StartPos.x; newPos.x = delta; }
if (m_UseVerticalAxis) { float delta = data.position.y - m_StartPos.y; newPos.y = delta; }
if(newPos.magnitude > Range) { newPos = newPos.normalized * Range; }
UpdateVirtualAxes(newPos); }
public void OnPointerUp(PointerEventData data) { UpdateVirtualAxes(Vector3.zero); }
public void OnPointerDown(PointerEventData data) { } #endregion }
|
代码说明:
JoyStickHandler.cs
脚本需要附加到Game Object
上,因此JoyStickHandler
类需要继承MonoBehaviour
- 根据前面的框架设计,
JoyStickHandler
类除了要使用VirtualAxis
,还需要根据用户对UI的操作来获取输入,因此我们需要让JoyStickHandler
实现位于UnityEngine.EventSystems
命名空间下的IPointerDownHandler
、IPointerUpHandler
和IDragHandler
接口,从而获取UI的按下
、松开
和拖拽
事件
- 当我们勾选
ScaleRange
时,我们需要根据屏幕尺寸对Range
进行缩放,从而保证在不同尺寸的屏幕上,摇杆移动的最大距离一致
- 为了减少
InputManager
的管理量,当虚拟摇杆被禁用时,我们需要注销对应的Axis
,当虚拟摇杆被启用时,我们在重新注册对应的Axis
实现按钮
  实现了虚拟摇杆之后,我们接着实现控制按钮的ButtonHandler
。我们在Assets\Scripts\Input
文件夹下新建一个名为ButtonHandler
的C#脚本,然后编辑ButtonHandler.cs
如下:
ButtonHandler.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;
[RequireComponent(typeof(Image))] public class ButtonHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { [Tooltip("激活按钮使用的名字")] public string Name; [Tooltip("按钮松开时显示的图片")] public Sprite NormalImage; [Tooltip("按钮被按下时显示的图片")] public Sprite ActiveImage; [Tooltip("按钮被禁用时显示的图片")] public Sprite DisableImage;
private Image m_CurrentImage; private VirtualButton m_Button;
private void Awake() { m_CurrentImage = GetComponent<Image>(); m_Button = new VirtualButton(Name); }
private void OnEnable() { InputManager.RegisterVirtualButton(m_Button); if(NormalImage != null) { m_CurrentImage.sprite = NormalImage; } }
private void OnDisable() { InputManager.UnRegisterVirtualButton(m_Button); if(DisableImage != null) { m_CurrentImage.sprite = DisableImage; } } #region 接口函数 public void OnPointerUp(PointerEventData data) { InputManager.SetButtonUp(m_Button);
if(NormalImage != null) { m_CurrentImage.sprite = NormalImage; } }
public void OnPointerDown(PointerEventData data) { InputManager.SetButtonDown(m_Button);
if(ActiveImage != null) { m_CurrentImage.sprite = ActiveImage; } } #endregion }
|
代码说明:
ButtonHandler.cs
脚本需要附加到Game Object
上,因此ButtonHandler
类需要继承MonoBehaviour
- 因为按钮不可以被拖拽,因此我们只需要实现位于
UnityEngine.EventSystems
命名空间下的IPointerDownHandler
和IPointerUpHandler
接口
- 为了减少
InputManager
的管理量,当按钮被禁用时,我们需要注销对应的Button
,当按钮被启用时,我们在重新注册对应的Button
- 在这里,我们默认
Button
都需要使用Image
,可以根据自己的具体需求来拓展
为游戏场景添加虚拟摇杆和按钮
  按钮和虚拟摇杆都实现了之后,我们开始为游戏场景添加虚拟摇杆和按钮。为了和游戏场景的UI区分开来,我们需要另外创建一个用于绘制虚拟摇杆和按钮的Canvas。
创建绘制虚拟摇杆和按钮的Canvas的步骤:
- 在游戏场景中新建一个Canvas,并将其重命名为
InputCanvas
- 接着修改
InputCanvas
下Canvas Scaler
组件
Canvas Scaler
组件的属性
UI Scale Mode
: Scale With Screen Size
Reference Resolution
: X(1920), Y(1080)
Screen Match Mode
: Match Width or Height
Match
: 0.5
- 最后,我们需要确保原先的
UICanvas
绘制在InputCanvas
之上,因此我们需要修改UICanvas
上Canvas
组件的Sort Order
属性为1
  创建好绘制虚拟摇杆和按钮的Canvas之后,我们先来添加虚拟摇杆。
添加虚拟摇杆的步骤如下:
- 在
InputCanvas
下创建一个Image
,然后将其重命名为JoyStickBackground
JoyStickBackground
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择bottom-left
PosX
: 250
PosY
: 210
Width
: 300
Height
: 300
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的RadialJoy_Area
图片
Color
: (255, 255, 255, 255)
- 在
JoyStickBackground
下创建一个Image
,然后将其重命名为JoyStick
,并在JoyStick
物体上添加JoyStickHandler.cs
脚本
JoyStick
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: 0
Width
: 150
Height
: 150
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的RadialJoy_Touch
图片
Color
: (255, 255, 255, 255)
JoyStickHandler (Script)
Range
: 100
Scale Range
: true
Axis To Use
: Both
Horizontal Axis Name
: Horizontal
Vertical Axis Name
: Vertical
  添加完虚拟摇杆之后,我们继续添加按钮
添加按钮的步骤:
- 在
InputCanvas
下创建一个Image
,然后将其重命名为Fire1Button
,并在Fire1Button
物体上添加ButtonHandler.cs
脚本
Fire1Button
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择bottom-right
PosX
: -160
PosY
: 140
Width
: 200
Height
: 200
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的Button_normal
图片
Color
: (255, 255, 255, 255)
ButtonHandler (Script)
Name
: Fire1
Normal Image
: Assets\Sprites\UI
文件夹下的Button_normal
图片
Active Image
: Assets\Sprites\UI
文件夹下的Button_active
图片
Diasble Image
: Assets\Sprites\UI
文件夹下的Button_normal
图片
- 在
Fire1Button
物体上创建一个Image
Image
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: 0
Width
: 140
Height
: 60
Image
:
Source Image
: Assets\Sprites\Props
文件夹下的part_rocket
图片
Color
: (255, 255, 255, 255)
- 复制三次
Fire1Button
物体得到Fire1Button (1)
、Fire1Button (2)
和Fire1Button (3)
物体
- 将
Fire1Button (1)
重命名为JumpButton
JumpButton
物体需要修改的组件属性:
Rect Transform
:
PosX
: -380
PosY
: 140
Width
: 150
Height
: 150
ButtonHandler (Script)
- 在
JumpButton
物体上创建一个Text
,并删除JumpButton
物体下的Image
子物体
Text
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择strentch-strentch
Left
: 0
Top
: 0
Right
: 0
Bottom
: 0
Text
:
Text
: Jump
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 58
Alignment
: 水平居中,垂直居中
Color
: (50, 50, 50, 255)
- 将
Fire1Button (2)
重命名为Fire2Button
Fire2Button
物体需要修改的组件属性:
Rect Transform
:
PosX
: -351.6
PosY
: 295.6
Width
: 150
Height
: 150
ButtonHandler (Script)
- 接着,我们修改
Fire2Button
物体下的Image
子物体
Image
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: 0
Width
: 100
Height
: 100
Image
:
Source Image
: Assets\Sprites\Props
文件夹下的prop_bomb
图片
- 将
Fire1Button (3)
重命名为Fire3Button
Fire3Button
物体需要修改的组件属性:
Rect Transform
:
PosX
: -160
PosY
: 360
Width
: 150
Height
: 150
ButtonHandler (Script)
- 最后,我们修改
Fire1Button (3)
物体下的Image
子物体
Image
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: 0
Width
: 110
Height
: 60
Image
:
Source Image
: Assets\Sprites\Character
文件夹下char_hero_beanMan
图集切割出来的bazooka
图片
  添加完虚拟摇杆和按钮之后的效果图如下所示:
使用虚拟摇杆和按钮
  添加完虚拟摇杆和按钮之后,我们需要将之前使用Unity提供Input类
来获取输入的脚本改为使用InputManager
类来获取输入。首先,我们修改Assets\Scripts\Player
文件夹下的PlayerAttack.cs
:
PlayerAttack.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[RequireComponent(typeof(PlayerController))] public class PlayerAttack : MonoBehaviour { [Tooltip("导弹Prefab")] public Missile MissilePrefab; [Tooltip("导弹发射点")] public Transform ShootingPoint; [Tooltip("发射导弹的音效")] public AudioClip ShootEffect; [Tooltip("炸弹Prefab")] public Rigidbody2D BombPrefab; [Tooltip("使用火箭筒抛射炸弹的力")] public float ProjectileBombForce = 1000f;
private PlayerController m_PlayerCtrl;
private void Awake() { m_PlayerCtrl = GetComponent<PlayerController>();
if(MissilePrefab == null) { Debug.LogError("请设置MissilePrefab"); }
if(ShootingPoint == null) { Debug.LogError("请设置ShootingPoint"); }
if(BombPrefab == null) { Debug.LogError("请设置BombPrefab"); } }
private void Update() { #if UNITY_STANDALONE //PC端使用Input来获取输入 if (Input.GetButtonDown("Fire1")) { Fire(); }
if (Input.GetButtonDown("Fire2")) { LayBomb(); }
if (Input.GetButtonDown("Fire3")) { ProjectileBomb(); } #elif UNITY_IOS || UNITY_ANDROID //移动端使用InputManager来获取输入 if (InputManager.GetButtonDown("Fire1")) { Fire(); }
if (InputManager.GetButtonDown("Fire2")) { LayBomb(); }
if (InputManager.GetButtonDown("Fire3")) { ProjectileBomb(); } #endif }
private void Fire() {
AudioSource.PlayClipAtPoint(ShootEffect, ShootingPoint.position);
Missile instance = Instantiate(MissilePrefab, ShootingPoint.position, Quaternion.identity) as Missile;
if(m_PlayerCtrl.FacingRight ^ instance.FacingRight) { instance.Flip(); } }
private void LayBomb() { if(GameStateManager.Instance.BombManagerInstance.ReleaseBomb(1) == false) { return; }
Instantiate(BombPrefab, this.transform.position, Quaternion.identity); }
private void ProjectileBomb() { if(GameStateManager.Instance.BombManagerInstance.ReleaseBomb(1) == false) { return; }
Rigidbody2D body = Instantiate(BombPrefab, ShootingPoint.position, Quaternion.identity) as Rigidbody2D; if(m_PlayerCtrl.FacingRight) { body.AddForce(Vector2.right * ProjectileBombForce); } else { body.AddForce(Vector2.left * ProjectileBombForce); } } }
|
  首先,我们修改Assets\Scripts\Player
文件夹下的PlayerController.cs
:
PlayerController.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(Animator))] public class PlayerController : MonoBehaviour { [Tooltip("角色初始朝向是否朝向右边")] public bool FacingRight = true; [Tooltip("移动时角色加速的力大小")] public float MoveForce = 365f; [Tooltip("角色移动的最大速度")] public float MaxSpeed = 5f; [Tooltip("跳跃时向上加速的力大小")] public float JumpForce = 1000f; [Tooltip("检测角色是否落地")] public Transform GroundCheck;
[Tooltip("跳跃音效")] public AudioClip[] JumpClips;
[Tooltip("显示血量条的物体")] public Transform HealthBarDisplay;
private Vector2 m_Input; private bool m_IsReadyToJump; private bool m_IsJumping; private bool m_GroundedStatus;
private Rigidbody2D m_Rigidbody2D; private Animator m_Animator;
private void Awake() { m_Rigidbody2D = GetComponent<Rigidbody2D>(); m_Animator = GetComponent<Animator>(); }
private void Start() { if(GroundCheck == null) { Debug.LogError("请先设置GroundCheck"); }
m_Input = new Vector2(); m_IsReadyToJump = false; m_IsJumping = false; m_GroundedStatus = false; }
private void Update() { m_GroundedStatus = Physics2D.Linecast( transform.position, GroundCheck.position, LayerMask.GetMask("Obstacle") ); m_Animator.SetBool("Grounded", m_GroundedStatus);
#if UNITY_STANDALONE //PC端使用Input来获取输入 if(m_GroundedStatus && !m_IsJumping && Input.GetButtonDown("Jump")) { m_IsReadyToJump = true; }
m_Input.x = Input.GetAxis("Horizontal"); #elif UNITY_IOS || UNITY_ANDROID //移动端使用InputManager来获取输入 if(m_GroundedStatus && !m_IsJumping && InputManager.GetButtonDown("Jump")) { m_IsReadyToJump = true; }
m_Input.x = InputManager.GetAxis("Horizontal"); #endif
if(m_GroundedStatus && m_IsJumping) { m_IsJumping = false; } }
private void FixedUpdate() { float h = m_Input.x;
m_Animator.SetFloat("Speed", Mathf.Abs(h));
if(h * m_Rigidbody2D.velocity.x < MaxSpeed) { m_Rigidbody2D.AddForce(Vector2.right * h * MoveForce); }
if(Mathf.Abs(m_Rigidbody2D.velocity.x) > MaxSpeed) { m_Rigidbody2D.velocity = new Vector2( Mathf.Sign(m_Rigidbody2D.velocity.x) * MaxSpeed, m_Rigidbody2D.velocity.y ); }
if(h > 0 && !FacingRight) { Flip(); }else if(h < 0 && FacingRight) { Flip(); }
if(m_IsReadyToJump) { Jump(); } }
private void Jump() { m_IsJumping = true;
m_Rigidbody2D.AddForce(new Vector2(0f, JumpForce));
m_Animator.SetTrigger("Jump");
m_IsReadyToJump = false;
if(JumpClips.Length > 0) { int i = Random.Range(0, JumpClips.Length); AudioSource.PlayClipAtPoint(JumpClips[i], transform.position); } }
private void Flip() { FacingRight = !FacingRight;
this.transform.localScale = Vector3.Scale( new Vector3(-1, 1, 1), this.transform.localScale );
if(HealthBarDisplay != null) { HealthBarDisplay.localScale = Vector3.Scale( new Vector3(-1, 1, 1), HealthBarDisplay.localScale ); } else { Debug.LogWarning("请设置HealthBarDisplay"); } } }
|
代码说明:
- 因为
PlayController
没有使用到m_AduioScoure
成员变量,因此我们将和这个变量有关的代码全部删除
- 之前我们在
FixedUpdate
中获取Horizontal Axis
的输入,通常来说,获取输入这一操作应该在Update
函数中执行,因此我们新增一个m_Input
在Update
中获取输入,然后在FixedUpdate
函数中使用m_Input
  修改完毕之后,运行游戏,发现当我们拖拽虚拟摇杆的时候,角色可以左右移动。当我们点击按钮的时候,角色会根据我们点击的按钮执行相应的操作。
后言
  至此,我们就已经完成了实现虚拟摇杆和按钮的全部工作。需要说明的是,我们InputManager
并没有根据当前的平台自动来判断是否需要显示虚拟摇杆和按钮
的功能,我们可以根据自己的具体需求自行进行拓展。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay20
分支下看到,读者可以clone这个仓库到本地进行查看。
参考链接
- Standard Asstes
- C# 静态类
- IPointerDownHandler接口
- IPointerUpHandler接口
- IDragHandler接口