Joystick在手遊開發中非常常見,也就是在手機屏幕上的虛擬操縱桿,但是Unity3D自帶的Joystick貼圖比較原始,所以經常有使用自定義貼圖的需求。
下面就來演示一下如何實現自定義JoyStick貼圖。更多精彩請關注【狗刨學習網】
首先導入貼圖,注意要把默認的Texture改爲GUI要不然尺寸會發生改變:
在Inspector面板中點擊Texture選項可以實現簡單的貼圖切換:
選中後便會發現場景中的Joystick已經發生了改變:
同理,可以對右邊的Joystick做同樣的修改:
當然很多時候這樣簡單的修改很難滿足我們的需求。
下面來說說對Joystick的常見調整。
首先是座標的調整,一般把Postition歸零而在GUITexture中調整Pixel Inset:
但是這樣依舊會出問題,全屏的時候因爲採用了絕對座標所以會出現這種情況:
所以我們還需要在腳本中稍作調整。
先來給Joystick加個背景圖片。
創建一個JS腳本JoystickBackgroundGUI:
-
<font face="新宋體" size="2">
-
@script RequireComponent(Joystick)
-
@script ExecuteInEditMode ()
-
var background = new SwitchGUI();
-
var location = new Location();
-
private var GUIalpha:float = 1;
-
private var joystick : Joystick;
-
joystick = GetComponent (Joystick);
-
var noGuiStyle : GUIStyle;
-
function Update() {
-
if (joystick.IsFingerDown()) {
-
background.up();
-
} else {
-
background.down();
-
}
-
if (background.texture != null){
-
location.updateLocation();
-
}
-
}
-
function OnGUI () {
-
GUI.color.a = GUIalpha;
-
GUI.Box(Rect(location.offset.x + background.offset.x - background.texture.width/2,location.offset.y + background.offset.y - background.texture.height/2,background.texture.width,background.texture.height),background.texture,noGuiStyle);
-
}
- </font>
joystick是Unity自己封裝好的對象,其中有IsFingerDown等函數有需要的同學可以查閱一下Unity官網的說明文檔。
腳本中用到了Location和SwitchGUI,這兩個函數在另一個腳本 _GUIClasses 中定義:
-
<font face="新宋體" size="2">[javascript]
-
-
-
import System.Collections.Generic;
-
// TextureGUI Class: create a basic class for creating and placing GUI elements
-
// texture = the texture to display
-
// offset = pixel offset from top left corner, can be modified for easy positioning
-
class TextureGUI {
-
var texture:Texture; //useful: texture.width, texture.height
-
var offset:Vector2; // .x and .y
-
private var originalOffset:Vector2; //store the original to correctly reset anchor point
-
enum Point { TopLeft, TopRight, BottomLeft, BottomRight, Center} //what part of texture to position around?
-
var anchorPoint = Point.TopLeft; // Unity default is from top left corner of texture
-
function setAnchor() { // meant to be run ONCE at Start.
-
originalOffset = offset;
-
if (texture) { // check for null texture
-
switch(anchorPoint) { //depending on where we want to center our offsets
-
case anchorPoint.TopLeft: // Unity default, do nothing
-
break;
-
case anchorPoint.TopRight: // Take the offset and go to the top right corner
-
offset.x = originalOffset.x - texture.width;
-
break;
-
case anchorPoint.BottomLeft: // bottom left corner of texture
-
offset.y = originalOffset.y - texture.height;
-
break;
-
case anchorPoint.BottomRight: //bottom right corner of texture
-
offset.x = originalOffset.x - texture.width;
-
offset.y = originalOffset.y - texture.height;
-
break;
-
case anchorPoint.Center: //and the center of the texture (useful for screen center textures)
-
offset.x = originalOffset.x - texture.width/2;
-
offset.y = originalOffset.y - texture.height/2;
-
break;
-
}
-
}
-
}
-
}
-
//Timer Class:
-
class TimerGUI extends TextureGUI { // Extend functionality from TextureGUI for a depreciating timer graphic
-
var textureLEnd:Texture; // left side of full texture (non stretching part)
-
var offsetLEnd:Vector2; // left side of full texture (non stretching part) start position
-
var textureCenter:Texture; // center of timer (will be stretched across width)
-
var offsetCenter:Vector2;
-
var textureREnd:Texture;
-
var offsetREnd:Vector2;
-
var timerPerct:float = 1; // percentage (0 to 1) this stretches the center
-
var desiredWidth:float = 403; // max width of the timer in pixels
-
function setTime(newTime:float) {
-
timerPerct = newTime; // sets the percent based on value
-
}
-
}
-
// SwitchGUI Class: Extends the TextureGUI to be able to load in multiple textures and switch between them
-
class SwitchGUI extends TextureGUI {
-
var switchableTextures = new List.();
-
var currentTexture:int = 0;
-
function Start() {
-
if (switchableTextures.Count > 0) {
-
texture = switchableTextures[currentTexture];
-
}
-
}
-
function changeTexture(switchTo:int) {
-
if (switchTo < switchableTextures.Count && switchTo >= 0) {
-
texture = switchableTextures[switchTo];
-
currentTexture = switchTo;
-
} else {
-
//Debug.Log( this + ": tried to call invalid part of switchTextures array!");
-
}
-
}
-
function up() {
-
if ((currentTexture+1) < switchableTextures.Count) {
-
++currentTexture;
-
texture = switchableTextures[currentTexture];
-
} else {
-
//Debug.Log( this + ": at the top!");
-
}
-
}
-
function nextTexture() {
-
if ((currentTexture+1) < switchableTextures.Count) { // if we are at the end of the array
-
++currentTexture;
-
texture = switchableTextures[currentTexture];
-
} else {// loop to the beginning
-
currentTexture = 0;
-
texture = switchableTextures[currentTexture];
-
}
-
}
-
function down() {
-
if ((currentTexture-1) >= 0) {
-
--currentTexture;
-
texture = switchableTextures[currentTexture];
-
} else {
-
//Debug.Log( this + ": at the bottom!");
-
}
-
}
-
}
-
// Location class:
-
class Location {
-
enum Point { TopLeft, TopRight, BottomLeft, BottomRight, Center}
-
var pointLocation = Point.TopLeft;
-
var offset:Vector2;
-
function updateLocation() {
-
switch(pointLocation) {
-
case pointLocation.TopLeft:
-
offset = Vector2(0,0);
-
break;
-
case pointLocation.TopRight:
-
offset = Vector2(Screen.width,0);
-
break;
-
case pointLocation.BottomLeft:
-
offset = Vector2(0,Screen.height);
-
break;
-
case pointLocation.BottomRight:
-
offset = Vector2(Screen.width,Screen.height);
-
break;
-
case pointLocation.Center:
-
offset = Vector2(Screen.width/2,Screen.height/2);
-
break;
-
}
-
}
-
}
-
class TextureAnchor {
-
enum Point { TopLeft, TopRight, BottomLeft, BottomRight, Center}
-
var anchorPoint = Point.TopLeft;
-
var offset:Vector2;
-
function update() {
-
switch(anchorPoint) {
-
case anchorPoint.TopLeft:
-
offset = Vector2(0,0);
-
break;
-
case anchorPoint.TopRight:
-
offset = Vector2(Screen.width,0);
-
break;
-
case anchorPoint.BottomLeft:
-
offset = Vector2(0,Screen.height);
-
break;
-
case anchorPoint.BottomRight:
-
offset = Vector2(Screen.width,Screen.height);
-
break;
-
case anchorPoint.Center:
-
offset = Vector2(Screen.width/2,Screen.height/2);
-
break;
-
}
-
}
-
}
- </font>
將腳本拖拽到Joystick上面並且部署好貼圖,運行可見Joystick的背景貼圖,當然座標還有點問題:
我們在腳本中將其設置爲BottomLeft,並且設置好SwitchTexture:
配置好了之後點擊運行,會發現Joystick 的貼圖出現在了左下角:
通過腳本中的Pixel設置可以調整兩個紋理貼圖的座標並使他們趨於一致:
調整之後的結果如圖:
同時將Joystick的腳本換成下面的腳本,可以實現隱藏操縱桿而只在碰到搖桿區域才顯示Joystick的效果:
-
<font face="新宋體" size="2">[javascript] view plaincopy
-
-
-
//////////////////////////////////////////////////////////////
-
// 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
-
//////////////////////////////////////////////////////////////
-
#pragma strict
-
@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
-
private var alphaOff:float = 0.0;
-
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;
-
gui.color.a = alphaOff;
-
defaultRect.x += transform.position.x * Screen.width; // + gui.pixelInset.x; // - Screen.width * 0.5;
-
defaultRect.y += transform.position.y * Screen.height; //+ gui.pixelInset.y; // - 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;
-
fingerDownPos = Vector2.zero;
-
gui.color.a = alphaOff;
-
}
-
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) as 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;
-
gui.color.a = .5;
-
}
-
// 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 );
-
}
- }</font>
運行以下項目可以發現Joystick不見了:
但是點擊屏幕就會出現了: