// This file is part of the Noddybox.Emulation C# suite. // // Noddybox.Emulation is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Noddybox.Emulation is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Noddybox.Emulation. If not, see . // // Copyright (c) 2012 Ian Cowburn // using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input.Touch; namespace Noddybox.Emulation.Xna.Input.Joystick { /// /// Displays and drives the joystick. Unlike the keyboard this is not /// not done event driven, but as a clasic Joystick interface. /// public class JoystickDriver { #region Event arguments /// /// Event data when the digital joystick state changes /// public class DigitalJoystickEventArgs : EventArgs { /// /// The joystick state. /// public DigitalJoystickState State {get; private set;} public bool[] Buttons {get; private set;} public DigitalJoystickEventArgs(DigitalJoystickState state, bool[] buttons) { State = state; Buttons = buttons; } } /// /// Event data when the analogue joystick state changes /// public class AnalogueJoystickEventArgs : EventArgs { /// /// The joystick state. /// public Vector2 State {get; private set;} public bool[] Buttons {get; private set;} public AnalogueJoystickEventArgs(Vector2 state, bool[] buttons) { State = state; Buttons = buttons; } } #endregion #region Private data private InputManager manager; private bool subscribed; private JoystickType joystickType; private Texture2D backgroundImage; private Texture2D joystickImage; private Texture2D buttonImage; private DigitalJoystickState digital; private Vector2 analogue; private Vector2 contact; private int stickId; private bool[] button; private int[] buttonId; private Vector2 backgroundPosition; private Vector2 joystickPosition; private Vector2[] buttonPositions; private float deadzone; private bool deadzoneBreached; private float stickSize; private int numButtons; private Vector2 joystickOffset; private Vector2 buttonOffset; private int joystickSize; private int buttonSize; private JoystickLock joylock; private Vector2[] digitalPositions; private DigitalJoystickState[] digitalAngles; #endregion #region Private members /// /// Update the analogue position with the passed state. This also applies the joylock clamp. /// /// The touch screen location. /// True if the state was changed. private bool UpdatePosition(Vector2 v) { v -= contact; switch(joylock) { case JoystickLock.TwoWayVertical: v.X = 0; break; case JoystickLock.TwoWayHorizontal: v.Y = 0; break; case JoystickLock.FourWay: if (Math.Abs(v.Y) > Math.Abs(v.X)) { v.X = 0; } else { v.Y = 0; } break; case JoystickLock.EightWay: break; } if (v.Length() > stickSize) { v.Normalize(); v *= stickSize; } if (v != analogue) { analogue = v; return true; } else { return false; } } /// /// Update the digital position with the new analogue state. /// /// /// True if the state was changed. private bool UpdatePosition() { DigitalJoystickState newState = DigitalJoystickState.Centre; if (analogue.Length() > deadzone) { float angle = MathHelper.ToDegrees((float)Math.Atan2(analogue.Y, analogue.X)) + 22.5f; while (angle < 0) { angle += 360; } System.Diagnostics.Debug.WriteLine("original angle = {0}", angle); angle = Math.Min(angle / 45, 7); newState = digitalAngles[(int)angle]; System.Diagnostics.Debug.WriteLine("calced angle = {0} / {1}", angle, newState); } if (newState != digital) { digital = newState; return true; } else { return false; } } /// /// Updates the joystick. /// private void TouchScreenHandler(object sender, InputManager.TouchLocationEventArgs e) { bool stateChanged = false; // Handle release of stick or buttons // if (e.Location.State == TouchLocationState.Released) { if (stickId == e.Location.Id) { stickId = 0; digital = DigitalJoystickState.Centre; analogue = Vector2.Zero; stateChanged = true; e.Handled = true; } else { for(int f = 0; !e.Handled && f < numButtons; f++) { if (buttonId[f] == e.Location.Id) { buttonId[f] = 0; button[f] = false; stateChanged = true; e.Handled = true; } } } } // Handle hold of stick // if (e.Location.State == TouchLocationState.Moved && e.Location.Id == stickId) { e.Handled = true; if (!deadzoneBreached) { float l = (contact - e.Location.Position).Length(); if (l > deadzone) { deadzoneBreached = true; } } if (joystickType == JoystickType.Digital && deadzoneBreached && UpdatePosition(e.Location.Position)) { stateChanged = UpdatePosition(); } else if (joystickType == JoystickType.Analogue && deadzoneBreached) { stateChanged = UpdatePosition(e.Location.Position); } } // Handle touch of stick or buttons // if (e.Location.State == TouchLocationState.Pressed) { float l = (joystickPosition - e.Location.Position).Length(); if (l < joystickSize) { contact = e.Location.Position; stickId = e.Location.Id; deadzoneBreached = false; e.Handled = true; } else { for(int f = 0; !e.Handled && f < numButtons; f++) { if ((buttonPositions[f] - e.Location.Position).Length() < buttonSize) { e.Handled = true; button[f] = true; buttonId[f] = e.Location.Id; stateChanged = true; } } } } if (stateChanged && joystickType == JoystickType.Digital && DigitalEvent != null) { DigitalEvent(this, new DigitalJoystickEventArgs(digital, button)); } else if (stateChanged && joystickType == JoystickType.Analogue && AnalogueEvent != null) { AnalogueEvent(this, new AnalogueJoystickEventArgs(analogue, button)); } } #endregion #region Public members /// /// Set the type of joystick lock. /// public JoystickLock LockType {set {joylock = value;}} /// /// Stops consuming inputs and resets joystick and fire buttons. /// /// Generates events for cleared state. public void StopJoytstickUpdates(bool generateEvents) { if (subscribed) { manager.TouchEvent -= TouchScreenHandler; subscribed = false; digital = DigitalJoystickState.Centre; analogue = Vector2.Zero; stickId = 0; deadzoneBreached = false; for(int f = 0; f < numButtons; f++) { button[f] = false; buttonId[f] = 0; } if (generateEvents && joystickType == JoystickType.Digital && DigitalEvent != null) { DigitalEvent(this, new DigitalJoystickEventArgs(digital, button)); } else if (generateEvents && joystickType == JoystickType.Analogue && AnalogueEvent != null) { AnalogueEvent(this, new AnalogueJoystickEventArgs(analogue, button)); } } } /// /// Stops consuming inputs and resets joystick and fire buttons. Generates events for the cleared state. /// public void StopJoystickUpdates() { StopJoytstickUpdates(true); } /// /// Starts consuming inputs. /// public void StartJoystickUpdates() { if (!subscribed) { manager.TouchEvent += TouchScreenHandler; subscribed = true; } } /// /// Draw the joystick /// /// public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(backgroundImage, backgroundPosition, Color.White); if (joystickType == JoystickType.Digital) { spriteBatch.Draw(joystickImage, joystickPosition + digitalPositions[(int)digital], null, Color.White, 0f, joystickOffset, 1f, SpriteEffects.None, 0f); } else { spriteBatch.Draw(joystickImage, joystickPosition + analogue, null, Color.White, 0f, joystickOffset, 1f, SpriteEffects.None, 0f); } for(int f = 0; f < numButtons; f++) { spriteBatch.Draw(buttonImage, buttonPositions[f], null, button[f] ? Color.LightGray : Color.White, 0f, buttonOffset, button[f] ? 0.9f : 1f, SpriteEffects.None, 0f); } } #endregion #region Events public EventHandler DigitalEvent; public EventHandler AnalogueEvent; #endregion #region Constructors /// /// Constructor. /// /// The input manager to attach to. /// The graphics device to associate textures with. /// The type of joystick. /// The image for the joystick background. /// The image for the joystick knob. /// The image for a joystick button. /// Where to draw and offset the joystick background to. /// Where to centre the joystick. Note this is absolute. /// Positions for the buttons. /// Deadzone radius where joystick movement is not picked up. /// The maximum radius that the joystick can move. public JoystickDriver(InputManager manager, GraphicsDevice graphics, JoystickType joystickType, Texture2D backgroundImage, Texture2D joystickImage, Texture2D buttonImage, Vector2 backgroundPosition, Vector2 joystickPosition, Vector2[] buttonPositions, float deadzone, float stickSize) { this.manager = manager; this.joystickType = joystickType; this.backgroundImage = backgroundImage; this.joystickImage = joystickImage; this.buttonImage = buttonImage; this.backgroundPosition = backgroundPosition; this.joystickPosition = joystickPosition + backgroundPosition; this.joystickSize = Math.Min(joystickImage.Width, joystickImage.Height) / 2; this.joystickOffset = new Vector2(joystickImage.Width / 2f, joystickImage.Height / 2f); if (buttonImage != null) { this.buttonSize = Math.Min(buttonImage.Width, buttonImage.Height) / 2; this.buttonOffset = new Vector2(buttonImage.Width / 2f, buttonImage.Height / 2f); this.numButtons = buttonPositions.Length; this.button = new bool[numButtons]; this.buttonId = new int[numButtons]; this.buttonPositions = new Vector2[numButtons]; for(int f = 0; f < numButtons; f++) { this.buttonPositions[f] = backgroundPosition + buttonPositions[f] + buttonOffset; } } else { this.buttonSize = 0; this.buttonOffset = Vector2.Zero; this.numButtons = 0; } this.deadzone = deadzone; this.deadzoneBreached = false; this.stickSize = stickSize; this.digital = DigitalJoystickState.Centre; this.joylock = JoystickLock.EightWay; this.analogue = Vector2.Zero; this.digitalPositions = new Vector2[16]; this.digitalPositions[(int)DigitalJoystickState.Centre] = Vector2.Zero; this.digitalPositions[(int)DigitalJoystickState.Up] = new Vector2(0, -stickSize); this.digitalPositions[(int)DigitalJoystickState.Down] = new Vector2(0, stickSize); this.digitalPositions[(int)DigitalJoystickState.Left] = new Vector2(-stickSize, 0); this.digitalPositions[(int)DigitalJoystickState.Right] = new Vector2(stickSize, 0); this.digitalPositions[(int)DigitalJoystickState.UpLeft] = new Vector2(-stickSize, -stickSize); this.digitalPositions[(int)DigitalJoystickState.UpRight] = new Vector2(stickSize, -stickSize); this.digitalPositions[(int)DigitalJoystickState.DownLeft] = new Vector2(-stickSize, stickSize); this.digitalPositions[(int)DigitalJoystickState.DownRight] = new Vector2(stickSize, stickSize); this.digitalAngles = new DigitalJoystickState[8] { //DigitalJoystickState.Up, //DigitalJoystickState.UpRight, //DigitalJoystickState.Right, //DigitalJoystickState.DownRight, //DigitalJoystickState.Down, //DigitalJoystickState.DownLeft, //DigitalJoystickState.Left, //DigitalJoystickState.UpLeft DigitalJoystickState.Right, DigitalJoystickState.DownRight, DigitalJoystickState.Down, DigitalJoystickState.DownLeft, DigitalJoystickState.Left, DigitalJoystickState.UpLeft, DigitalJoystickState.Up, DigitalJoystickState.UpRight }; StartJoystickUpdates(); } #endregion } }