前言
  到目前为止,我们已经实现了游戏的所有功能。但目前,我们仍然使用键盘和鼠标来操作角色的移动和攻击。为了能让游戏在手机上正常运行,我们需要实现虚拟摇杆和按钮,来替换键盘和鼠标输入。此外,为了减少代码的修改,且同时兼容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接口