移动开发游戏中使用到的触摸游戏摇杆在iPhone上是非常普遍的,毕竟是全触摸屏手机,今天MOMO 通过一个小例子和大家讨论Unity3D 中如何自定义一个漂亮的全触摸游戏摇杆。
值得高兴的是,Unity3D 游戏引擎的标准资源中已经帮助我们封装了一个游戏摇杆脚本,所以实现部分的代码可以完全借助它的,具体调用需要我们自己来。
Joystick.js是官方提供的脚本,具体代码如下,有兴趣的朋友可以仔细研究研究,MOMO就不多说啦。哇咔咔~
1 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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
////////////////////////////////////////////////////////////// // Joystick.js // Penelope iPhone Tutorial // // Joystick creates a movable joystick (via GUITexture) that // handles touch input, taps, and phases. Dead zones can control // where the joystick input gets picked up and can be normalized. // // Optionally, you can enable the touchPad property from the editor // to treat this Joystick as a TouchPad. A TouchPad allows the finger // to touch down at any point and it tracks the movement relatively // without moving the graphic ////////////////////////////////////////////////////////////// @script RequireComponent( GUITexture ) // A simple class for bounding how far the GUITexture will move class Boundary { var min : Vector2 = Vector2.zero; var max : Vector2 = Vector2.zero; } static private var joysticks : Joystick[]; // A static collection of all joysticks static private var enumeratedJoysticks : boolean = false; static private var tapTimeDelta : float = 0.3; // Time allowed between taps var touchPad : boolean; // Is this a TouchPad? var touchZone : Rect; var deadZone : Vector2 = Vector2.zero; // Control when position is output var normalize : boolean = false; // Normalize output after the dead-zone? var position : Vector2; // [-1, 1] in x,y var tapCount : int; // Current tap count private var lastFingerId = -1; // Finger last used for this joystick private var tapTimeWindow : float; // How much time there is left for a tap to occur private var fingerDownPos : Vector2; private var fingerDownTime : float; private var firstDeltaTime : float = 0.5; private var gui : GUITexture; // Joystick graphic private var defaultRect : Rect; // Default position / extents of the joystick graphic private var guiBoundary : Boundary = Boundary(); // Boundary for joystick graphic private var guiTouchOffset : Vector2; // Offset to apply to touch input private var guiCenter : Vector2; // Center of joystick function Start() { // Cache this component at startup instead of looking up every frame gui = GetComponent( GUITexture ); // Store the default rect for the gui, so we can snap back to it defaultRect = gui.pixelInset; defaultRect.x += transform.position.x * Screen.width;// + gui.pixelInset.x; // - Screen.width * 0.5; defaultRect.y += transform.position.y * Screen.height;// - Screen.height * 0.5; transform.position.x = 0.0; transform.position.y = 0.0; if ( touchPad ) { // If a texture has been assigned, then use the rect ferom the gui as our touchZone if ( gui.texture ) touchZone = defaultRect; } else { // This is an offset for touch input to match with the top left // corner of the GUI guiTouchOffset.x = defaultRect.width * 0.5; guiTouchOffset.y = defaultRect.height * 0.5; // Cache the center of the GUI, since it doesn‘t change guiCenter.x = defaultRect.x + guiTouchOffset.x; guiCenter.y = defaultRect.y + guiTouchOffset.y; // Let‘s build the GUI boundary, so we can clamp joystick movement guiBoundary.min.x = defaultRect.x - guiTouchOffset.x; guiBoundary.max.x = defaultRect.x + guiTouchOffset.x; guiBoundary.min.y = defaultRect.y - guiTouchOffset.y; guiBoundary.max.y = defaultRect.y + guiTouchOffset.y; } } function Disable() { gameObject.active = false; enumeratedJoysticks = false; } function ResetJoystick() { // Release the finger control and set the joystick back to the default position gui.pixelInset = defaultRect; lastFingerId = -1; position = Vector2.zero; fingerDownPosition = Vector2.zero; if ( touchPad ) gui.color.a = 0.025; } function IsFingerDown() : boolean { return (lastFingerId != -1); } function LatchedFinger( fingerId : int ) { // If another joystick has latched this finger, then we must release it if ( lastFingerId == fingerId ) ResetJoystick(); } function Update() { if ( !enumeratedJoysticks ) { // Collect all joysticks in the game, so we can relay finger latching messages joysticks = FindObjectsOfType( Joystick ); enumeratedJoysticks = true; } var count = Input.touchCount; // Adjust the tap time window while it still available if ( tapTimeWindow > 0 ) tapTimeWindow -= Time.deltaTime; else tapCount = 0; if ( count == 0 ) ResetJoystick(); else { for(var i : int = 0;i < count; i++) { var touch : Touch = Input.GetTouch(i); var guiTouchPos : Vector2 = touch.position - guiTouchOffset; var shouldLatchFinger = false; if ( touchPad ) { if ( touchZone.Contains( touch.position ) ) shouldLatchFinger = true; } else if ( gui.HitTest( touch.position ) ) { shouldLatchFinger = true; } // Latch the finger if this is a new touch if ( shouldLatchFinger && ( lastFingerId == -1 ¦¦ lastFingerId != touch.fingerId ) ) { if ( touchPad ) { gui.color.a = 0.15; lastFingerId = touch.fingerId; fingerDownPos = touch.position; fingerDownTime = Time.time; } lastFingerId = touch.fingerId; // Accumulate taps if it is within the time window if ( tapTimeWindow > 0 ) tapCount++; else { tapCount = 1; tapTimeWindow = tapTimeDelta; } // Tell other joysticks we‘ve latched this finger for ( var j : Joystick in joysticks ) { if ( j != this ) j.LatchedFinger( touch.fingerId ); } } if ( lastFingerId == touch.fingerId ) { // Override the tap count with what the iPhone SDK reports if it is greater // This is a workaround, since the iPhone SDK does not currently track taps // for multiple touches if ( touch.tapCount > tapCount ) tapCount = touch.tapCount; if ( touchPad ) { // For a touchpad, let‘s just set the position directly based on distance from initial touchdown position.x = Mathf.Clamp( ( touch.position.x - fingerDownPos.x ) / ( touchZone.width / 2 ), -1, 1 ); position.y = Mathf.Clamp( ( touch.position.y - fingerDownPos.y ) / ( touchZone.height / 2 ), -1, 1 ); } else { // Change the location of the joystick graphic to match where the touch is gui.pixelInset.x = Mathf.Clamp( guiTouchPos.x, guiBoundary.min.x, guiBoundary.max.x ); gui.pixelInset.y = Mathf.Clamp( guiTouchPos.y, guiBoundary.min.y, guiBoundary.max.y ); } if ( touch.phase == TouchPhase.Ended ¦¦ touch.phase == TouchPhase.Canceled ) ResetJoystick(); } } } if ( !touchPad ) { // Get a value between -1 and 1 based on the joystick graphic location position.x = ( gui.pixelInset.x + guiTouchOffset.x - guiCenter.x ) / guiTouchOffset.x; position.y = ( gui.pixelInset.y + guiTouchOffset.y - guiCenter.y ) / guiTouchOffset.y; } // Adjust for dead zone var absoluteX = Mathf.Abs( position.x ); var absoluteY = Mathf.Abs( position.y ); if ( absoluteX < deadZone.x ) { // Report the joystick as being at the center if it is within the dead zone position.x = 0; } else if ( normalize ) { // Rescale the output after taking the dead zone into account position.x = Mathf.Sign( position.x ) * ( absoluteX - deadZone.x ) / ( 1 - deadZone.x ); } if ( absoluteY < deadZone.y ) { // Report the joystick as being at the center if it is within the dead zone position.y = 0; } else if ( normalize ) { // Rescale the output after taking the dead zone into account position.y = Mathf.Sign( position.y ) * ( absoluteY - deadZone.y ) / ( 1 - deadZone.y ); } } |
单击Create 创建一个GUI Texture,命名为Joy ,它用来显示游戏摇杆,如下图所示将摇杆的图片资源,与摇杆的脚本连线赋值给Joy. Pixel Inset 中可以设置摇杆的显示位置与显示宽高。
到这一步 build and run 就可以在iPhone上看到这个游戏摇杆,并且可以通过触摸它,360度平滑过度。
在屏幕中绘制一个飞机,通过游戏摇杆去控制飞机的移动。
创建一个脚本,命名为Main.js 如下图所示 将 Main.js 、joy、plan 分别 绑定在Main Camera 上。
moveJoystick.position.x;
moveJoystick.position.y;
这两个值是非常重要的两个信息,它们的取值范围是 -1 到 +1 ,表示 用户触摸摇杆的位置, 上 下 左 右 的信息。
1 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 |
//游戏摇杆对象 var moveJoystick : Joystick; //飞机的贴图 var plan : Texture; //飞机在屏幕中的坐标 var x = 0; var y = 0; //避免飞机飞出屏幕,分别是X、Y最大坐标,最小坐标是0、0 var cross_x = 0; var cross_y = 0; //飞机移动的速度 var planSpeed = 20; function Start() { //初始化赋值 x = 100; y = 100; cross_x = Screen.width - plan.width; cross_y = Screen.height - plan.height; } function Update () { //得到游戏摇杆的反馈信息,得到的值是 -1 到 +1 之间 var touchKey_x = moveJoystick.position.x; var touchKey_y = moveJoystick.position.y; //摇杆向左 if(touchKey_x == -1){ x -= planSpeed; } //摇杆向右 else if(touchKey_x == 1){ x += planSpeed; } //摇杆向上 if(touchKey_y == -1){ y += planSpeed; } //摇杆向下 else if(touchKey_y == 1){ y -= planSpeed; } //防止飞机飞出屏幕,出界检测 if(x < 0){ x = 0; }else if(x > cross_x){ x = cross_x; } if(y < 0){ y = 0; }else if(y > cross_y){ y = cross_y; } } function OnGUI () { //将飞机绘制在屏幕中 GUI.DrawTexture(Rect(x,y,128,128),plan); } |
导出 build and run 看看在iPhone 上的效果,通过触摸游戏摇杆可以控制飞机的移动啦,不错吧,哇咔咔~~
最后欢迎各位盆友可以和MOMO一起讨论Unity3D游戏开发,最近感冒的盆友越来越多,大家要多多注意身体健康噢。哇咔咔~~~ 附上Unity3D工程的下载地址,Xcode项目我就不上传了,须要的自己导出。
下载地址:http://vdisk.weibo.com/s/abtVb
- 本文固定链接: http://www.xuanyusong.com/archives/526
- 转载请注明: 雨松MOMO 2012年05月01日 于 雨松MOMO程序研究院 发表
Joystick.js的C#版:
[RequireComponent(typeof(GUITexture))] public class MyStick : MonoBehaviour { class Boundary { public Vector2 min = Vector2.zero; public Vector2 max = Vector2.zero; } private static MyStick[] joysticks; // A static collection of all joysticks private static bool enumeratedJoysticks = false; private static float tapTimeDelta = 0.3f; // Time allowed between taps public bool touchPad; public Vector2 position = Vector2.zero; public Rect touchZone; public Vector2 deadZone = Vector2.zero; // Control when position is output public bool normalize = false; // Normalize output after the dead-zone? public int tapCount; private int lastFingerId = -1; // Finger last used for this joystick private float tapTimeWindow; // How much time there is left for a tap to occur private Vector2 fingerDownPos; //private float fingerDownTime; //private float firstDeltaTime = 0.5f; private GUITexture gui; private Rect defaultRect; // Default position / extents of the joystick graphic private Boundary guiBoundary = new Boundary(); // Boundary for joystick graphic private Vector2 guiTouchOffset; // Offset to apply to touch input private Vector2 guiCenter; // Center of joystick void Start() { gui = (GUITexture)GetComponent(typeof(GUITexture)); defaultRect = gui.pixelInset; defaultRect.x += transform.position.x * Screen.width;// + gui.pixelInset.x; // - Screen.width * 0.5; defaultRect.y += transform.position.y * Screen.height;// - Screen.height * 0.5; transform.position = Vector3.zero; if (touchPad) { // If a texture has been assigned, then use the rect ferom the gui as our touchZone if (gui.texture) touchZone = defaultRect; } else { guiTouchOffset.x = defaultRect.width * 0.5f; guiTouchOffset.y = defaultRect.height * 0.5f; // Cache the center of the GUI, since it doesn‘t change guiCenter.x = defaultRect.x + guiTouchOffset.x; guiCenter.y = defaultRect.y + guiTouchOffset.y; // Let‘s build the GUI boundary, so we can clamp joystick movement guiBoundary.min.x = defaultRect.x - guiTouchOffset.x; guiBoundary.max.x = defaultRect.x + guiTouchOffset.x; guiBoundary.min.y = defaultRect.y - guiTouchOffset.y; guiBoundary.max.y = defaultRect.y + guiTouchOffset.y; } } public Vector2 getGUICenter() { return guiCenter; } void Disable() { gameObject.SetActive(false); //enumeratedJoysticks = false; } private void ResetJoystick() { gui.pixelInset = defaultRect; lastFingerId = -1; position = Vector2.zero; fingerDownPos = Vector2.zero; } private bool IsFingerDown() { return (lastFingerId != -1); } public void LatchedFinger(int fingerId) { // If another joystick has latched this finger, then we must release it if (lastFingerId == fingerId) ResetJoystick(); } void Update() { if (!enumeratedJoysticks) { // Collect all joysticks in the game, so we can relay finger latching messages joysticks = (MyStick[])FindObjectsOfType(typeof(MyStick)); enumeratedJoysticks = true; } int count = Input.touchCount; if (tapTimeWindow > 0) tapTimeWindow -= Time.deltaTime; else tapCount = 0; if (count == 0) ResetJoystick(); else { for (int i = 0; i < count; i++) { Touch touch = Input.GetTouch(i); Vector2 guiTouchPos = touch.position - guiTouchOffset; bool shouldLatchFinger = false; if (touchPad) { if (touchZone.Contains(touch.position)) shouldLatchFinger = true; } else if (gui.HitTest(touch.position)) { shouldLatchFinger = true; } // Latch the finger if this is a new touch if (shouldLatchFinger && (lastFingerId == -1 || lastFingerId != touch.fingerId)) { if (touchPad) { //gui.color.a = 0.15; lastFingerId = touch.fingerId; //fingerDownPos = touch.position; //fingerDownTime = Time.time; } lastFingerId = touch.fingerId; // Accumulate taps if it is within the time window if (tapTimeWindow > 0) tapCount++; else { tapCount = 1; tapTimeWindow = tapTimeDelta; } // Tell other joysticks we‘ve latched this finger //for ( j : Joystick in joysticks ) foreach (MyStick j in joysticks) { if (j != this) j.LatchedFinger(touch.fingerId); } } if (lastFingerId == touch.fingerId) { // Override the tap count with what the iPhone SDK reports if it is greater // This is a workaround, since the iPhone SDK does not currently track taps // for multiple touches if (touch.tapCount > tapCount) tapCount = touch.tapCount; if (touchPad) { // For a touchpad, let‘s just set the position directly based on distance from initial touchdown position.x = Mathf.Clamp((touch.position.x - fingerDownPos.x) / (touchZone.width / 2), -1, 1); position.y = Mathf.Clamp((touch.position.y - fingerDownPos.y) / (touchZone.height / 2), -1, 1); } else { // Change the location of the joystick graphic to match where the touch is Rect r = gui.pixelInset; r.x = Mathf.Clamp(guiTouchPos.x, guiBoundary.min.x, guiBoundary.max.x); r.y = Mathf.Clamp(guiTouchPos.y, guiBoundary.min.y, guiBoundary.max.y); gui.pixelInset = r; } if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled) ResetJoystick(); } } } if (!touchPad) { // Get a value between -1 and 1 based on the joystick graphic location position.x = (gui.pixelInset.x + guiTouchOffset.x - guiCenter.x) / guiTouchOffset.x; position.y = (gui.pixelInset.y + guiTouchOffset.y - guiCenter.y) / guiTouchOffset.y; } // Adjust for dead zone var absoluteX = Mathf.Abs(position.x); var absoluteY = Mathf.Abs(position.y); if (absoluteX < deadZone.x) { // Report the joystick as being at the center if it is within the dead zone position.x = 0; } else if (normalize) { // Rescale the output after taking the dead zone into account position.x = Mathf.Sign(position.x) * (absoluteX - deadZone.x) / (1 - deadZone.x); } if (absoluteY < deadZone.y) { // Report the joystick as being at the center if it is within the dead zone position.y = 0; } else if (normalize) { // Rescale the output after taking the dead zone into account position.y = Mathf.Sign(position.y) * (absoluteY - deadZone.y) / (1 - deadZone.y); } } }
C#版
Main.js的C#版:
public class MyPlane : MonoBehaviour { //游戏摇杆对象 public MyStick moveJoystick; //飞机的贴图 public Texture plan; //飞机在屏幕中的坐标 float x = 0; float y = 0; //避免飞机飞出屏幕,分别是X、Y最大坐标,最小坐标是0、0 float cross_x = 0; float cross_y = 0; //飞机移动的速度 float planSpeed = 20; void Start() { //初始化赋值 x = 50; y = 50; } void Update() { cross_x = Screen.width - plan.width; cross_y = Screen.height - plan.height; //得到游戏摇杆的反馈信息,得到的值是 -1 到 +1 之间 var touchKey_x = moveJoystick.position.x; var touchKey_y = moveJoystick.position.y; //摇杆向左 if (touchKey_x == -1) { x -= planSpeed; } //摇杆向右 else if (touchKey_x == 1) { x += planSpeed; } //摇杆向上 if (touchKey_y == -1) { y += planSpeed; } //摇杆向下 else if (touchKey_y == 1) { y -= planSpeed; } //防止飞机飞出屏幕,出界检测 if (x < 0) { x = 0; } else if (x > cross_x) { x = cross_x; } if (y < 0) { y = 0; } else if (y > cross_y) { y = cross_y; } } void OnGUI() { //将飞机绘制在屏幕中 GUI.DrawTexture(new Rect(x, y, 128, 128), plan); } }
C#版