// 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
}
}