Wednesday, February 18, 2009

Dictionaries

This isn't really related to XNA, I know but oh well.

I'm a big fan of the generic collections. I use them alot - mainly List(Of T) and Dictionary(Of TKey, TValue). Sometimes I have trouble deciding which to use. Is it going to be easier to access by index or by key. Othertimes I need to use a List (have to supply an IList to a particular interface etc.) but would still like the ease of access of the Dictionary.

As well as XNA, I have also been learning WPF. The DataBinding in WPF relies heavily on INotifyPropertyChanged and INotiftyCollectionChanged. Classes implementing the second interface, INotifyCollectionChanged need to be accessable via index. WPF doesn't come with an ObservableDictionary, probably for this reason.

I was browsing MSDN when I came across the OrderedDictionary. After reading its description "Represents a collection of key/value pairs that are accessible by the key or index.", I thought for a moment that I might have found a solution.

A few problems with this one tho.. First, it may be accessable via index - but it's not a list! i.e. it doesn't implement IList or IList(Of T). Second, it isn't Generic. To make it type safe I would have to inherit it each time I wanted to use it etc.

I decided to write my own DictionaryList. It would be Generic and implement:
  • IDictionary(Of TKey, TValue)
  • IList(Of KeyValuePair(Of TKey, TValue))
  • ICollection(Of KeyValuePair(Of TKey, TValue))
  • IEnumerable(Of KeyValuePair(Of TKey, TValue))
  • IDictionary
  • ICollection
  • IEnumerable
Later I decided to mark it as Serializable and implement IDeserializationCallback.

Internally, my new collection stores items in a Dictionary(Of TKey, TValue) as well as a List(Of KeyValuePair(Of TKey, TValue)). It was fairly easy to create, just make sure both of the internal collections stay in sync.

I haven't tested it thoroughly yet but so far it seems to work well. I can see problems if anyone tried to use it with a Key of type Integer. It wouldn't know which of the (overloaded) Default Properties to call:
  • Default Public Property Item(ByVal index As Integer) As KeyValuePair(Of TKey, TValue)
  • or
  • Default Public Property Item(ByVal key As TKey) As TValue

My second class that I created was ObservableDictionaryList. I inherited my newly created class DictionaryList and implemented INotifyCollectionChanged and INotifyPropertyChanged. This class was straight forward and a lot smaller than the first one!

Sunday, February 1, 2009

On Input and Managers

*Disclamer* I am assuming that you already have a basic knowlege of XNA. I'm not going to explain the code that is generated for you when you create a new XNA project. There enough examples doing that already.


It is a (good) standard practice for the first line of the Update code to call another method called HandleInput. Most of the samples I have seen have HandleInput code along the lines of:
KeyboardState keyboardState = Keyboard.GetState();
GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);

// Exit the game when back is pressed.
if (gamepadState.Buttons.Back == ButtonState.Pressed)
Exit();

bool continuePressed =
keyboardState.IsKeyDown(Keys.Space) ||
gamepadState.IsButtonDown(ContinueButton);
(Taken from the Platformer Starter Kit, included with XNA Games Studio 3.0)

Does this work? Yes. So what are the problems with it..?
  • On the XBox 360 it wont work if if the player's controller is not plugged into the first port (they won't be player 1).
  • It is unlikely that people are going to be using a Keyboard and GamePad at the same time - so why are we checking both?
  • If we want to add support for another input device then we would have to go through every HandleInput method to make the changes (mouse, guitar, Buzz© controller anyone?)
  • Personally, when I'm playing games, I like to be able to configure the controlls to how I like them. When using a Keyboard, what do we have to press to jump? The space bar? Up arrow? Either of them should work?
Thinking back to when I first learnt about the OO Principles, this is clearly going the opposite way from Encapsulation. The HandleInput method of our Spaceship class shouldn't need to worry about what the current key mapping is.

I started by creating an interface IInputManager. I wanted to make it easy to create a manager for the GameController and Keyboard so I gave my interface methods corisponding to each of the XBox 360 controller buttons (with several overloads)


Namespace Input
Public Interface IInputManager

Function MainMovement() As Vector2
Function MainSelectPressed() As Boolean
Function MenuMovement() As Vector2
Function MenuMainMovement() As Vector2
Function SecondaryMovement() As Vector2
Function SecondarySelectPressed() As Boolean
Function LeftTrigger() As Single
Function LeftShoulderPressed() As Boolean
Function BackPressed() As Boolean
Function StartPressed() As Boolean
Function RightTrigger() As Single
Function RightShoulderPressed() As Boolean
Function APressed() As Boolean
Function BPressed() As Boolean
Function XPressed() As Boolean
Function YPressed() As Boolean
Function MainSelectDown() As Boolean
Function SecondarySelectDown() As Boolean
Function LeftShoulderDown() As Boolean
Function BackDown() As Boolean
Function StartDown() As Boolean
Function RightShoulderDown() As Boolean
Function ADown() As Boolean
Function BDown() As Boolean
Function XDown() As Boolean
Function YDown() As Boolean
Sub Update(ByVal elapsedTime As Single)
End Interface ' IInputManager
End Namespace ' Input

From this interface I created two implementations, KeyboardInputManager and GamePadInputManager.

Namespace Input
Public Class KeyboardInputManager
Implements iInputManager

Public Sub New()
' Default Key Mapping
With mKeys
.Add("MainMovementUp", Keys.Up)
.Add("MainMovementDown", Keys.Down)
.Add("MainMovementLeft", Keys.Left)
.Add("MainMovementRight", Keys.Right)
.Add("MainSelect", Keys.Q)

.Add("SecondaryMovementUp", Keys.W)
..... etc.
End With
End Sub

Public Function Back() As Boolean Implements iInputManager.BackPressed
Return IsNewKeypress(mKeys("Back"))
End Function

Public Function LeftShoulder() As Boolean Implements iInputManager.LeftShoulderPressed
Return mCurrentKeyboardState.IsKeyDown(mKeys("LeftShoulder"))
End Function

Public Function MainMovement() As Microsoft.Xna.Framework.Vector2 Implements iInputManager.MainMovement
Dim UpDown As Single = 0.0F
Dim LeftRight As Single = 0.0F

With mCurrentKeyboardState

If .IsKeyDown(mKeys("MainMovementUp")) Then
UpDown = -1.0F
ElseIf .IsKeyDown(mKeys("MainMovementDown")) Then
UpDown = 1.0F
End If
If .IsKeyDown(mKeys("MainMovementLeft")) Then
LeftRight = -1.0F
ElseIf .IsKeyDown(mKeys("MainMovementRight")) Then
LeftRight = 1.0F
End If

End With

Return New Vector2(LeftRight, UpDown)
End Function

Public Sub Update(ByVal elapsedTime As Single) Implements iInputManager.Update
mPreviousKeyboardState = mCurrentKeyboardState
mCurrentKeyboardState = Keyboard.GetState(PlayerIndex.One)
End Sub

Private Function IsNewKeypress(ByVal key As Keys) As Boolean
Return mCurrentKeyboardState.IsKeyDown(key) AndAlso Not mPreviousKeyboardState.IsKeyDown(key)
End Function

This is far from complete but is a good start. All out input code is now in the one place. We can easily change it without disturbing our existing game code. Our HandleInput methods can now follow the format HandleInput(input as Input.IInputManager).

This class needs some default action (return False) if there is no mapping for a particular key string/name. Also a method for Saving and Loading keymapping from XML files.

Some other ideas I had regarding input:
  • KeyboardInputManager & GamePadInputManager could Inherit from GameComponent.
  • Or even better - Inherit from an abstract class InputManager that Inherits from GameComponent.

  • Create an InputDeviceManager that would maintain a list of available input devices based on what devices are connected ("If GamePad.GetState(PlayerIndex.One).IsConnected Then") or other ones manually added.
  • This would allow selection of the currently active device or devices (e.g. keyboard and mouse).
Note: when I created these classes, I had not experimented at all with getting input from the mouse. The interface may need to be tweaked to make it easy to add a MouseInputManager.



The main point I want to get across is the benefits of Encapsulation and Abstraction. Take all the input related code out of your classes and put it into dedicated input classes. Put an interface inbetween so that your classes do not need to know or care about the implementation details.

Of course this does not just relate to Input! The same principles apply to all parts of your game. Look for areas that can be isolated. Create generic Manager classes. It will initially seem like more work but it is worth it in the long run. It makes updates a lot easier (especially when you can use the same set of Manager Classes for multiple games!).

Some examples of Managers that I can think of (and may eventually blog about):
  • Input Manager
  • InputDeviceManager
  • AudioManager
  • ScreenManager
  • AnimationManager (2D or 3D)
  • TexturedQuadManager

Till next time
Happy Coding =)
G

Tuesday, November 18, 2008

The beginnings

I had already picked my papers for 2008, first semester (at MIT). One of the lecturers (Mike Lopez) ah.. 'strongly encouraged' me to switch a paper and enrol in the newly offered paper Games Design Level 6. I wasn't to hard to persuade as it sounded far more interesting than Quality Assurance!

XNA 2.0 had been released only a few months before hand so it was certainly a cutting edge paper. We didn't have a text book as there weren't a lot available. The lecturer had spent his summer studying XNA and putting together a series of modules for us to work through.

The first series (2D) covered:
  • Input Management
  • Audio Management
  • Menus messages and data entry
  • Sprites, 2D camera and scrolling backgrounds

Second series (3D) covered:
  • Introduction to 3D worlds
  • The content pipeline
  • 3D models and animation
  • Movement and collision detection
  • Terrain maps and skydomes

The basic idea was that over the duration of the paper we would each build up the basics of a game engine. The idea of “do it once, do it right, so then you can forget about it” was stressed over and over. Because of this, we ended up creating a lot of Manager classes.

Mentioning Classes.. For anyone considering doing XNA (or any games) programming, I can't stress enough the benefit of Object Oriented methodology!

Next few posts will cover more of my XNA experience over the last year. Hopefully I will be able to share some of what I've learnt.

G