diff --git a/TrueCraft.Client/Input/KeyboardComponent.cs b/TrueCraft.Client/Input/KeyboardComponent.cs
new file mode 100644
index 0000000..06d92c9
--- /dev/null
+++ b/TrueCraft.Client/Input/KeyboardComponent.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+namespace TrueCraft.Client.Input
+{
+ ///
+ /// Encapsulates keyboard input in an event-driven manner.
+ ///
+ public sealed class KeyboardComponent : GameComponent
+ {
+ ///
+ /// Raised when a key for this keyboard component is pressed.
+ ///
+ public event EventHandler KeyDown;
+
+ ///
+ /// Raised when a key for this keyboard component is released.
+ ///
+ public event EventHandler KeyUp;
+
+ ///
+ /// Gets the state for this keyboard component.
+ ///
+ public KeyboardState State { get; private set; }
+
+ ///
+ /// Creates a new keyboard component.
+ ///
+ /// The parent game for the component.
+ public KeyboardComponent(Game game)
+ : base(game)
+ {
+ }
+
+ ///
+ /// Initializes this keyboard component.
+ ///
+ public override void Initialize()
+ {
+ State = Keyboard.GetState();
+
+ base.Initialize();
+ }
+
+ ///
+ /// Updates this keyboard component.
+ ///
+ /// The game time for the update.
+ public override void Update(GameTime gameTime)
+ {
+ var newState = Keyboard.GetState();
+ Process(newState, State);
+ State = newState;
+
+ base.Update(gameTime);
+ }
+
+ ///
+ /// Processes a change between two states.
+ ///
+ /// The new state.
+ /// The old state.
+ private void Process(KeyboardState newState, KeyboardState oldState)
+ {
+ var currentKeys = newState.GetPressedKeys();
+ var lastKeys = oldState.GetPressedKeys();
+
+ // LINQ was a saviour here.
+ var pressed = currentKeys.Except(lastKeys);
+ var unpressed = lastKeys.Except(currentKeys);
+
+ foreach (var key in pressed)
+ {
+ var args = new KeyboardKeyEventArgs(key, true);
+ if (KeyDown != null)
+ KeyDown(this, args);
+ }
+
+ foreach (var key in unpressed)
+ {
+ var args = new KeyboardKeyEventArgs(key, false);
+ if (KeyUp != null)
+ KeyUp(this, args);
+ }
+ }
+
+ ///
+ /// Called when this keyboard component is being disposed of.
+ ///
+ /// Whether Dispose() called this method.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ KeyDown = null;
+ KeyUp = null;
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/TrueCraft.Client/Input/KeyboardEventArgs.cs b/TrueCraft.Client/Input/KeyboardEventArgs.cs
new file mode 100644
index 0000000..3a02a7f
--- /dev/null
+++ b/TrueCraft.Client/Input/KeyboardEventArgs.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace TrueCraft.Client.Input
+{
+ ///
+ /// Provides the event data for keyboard events.
+ ///
+ public class KeyboardEventArgs : EventArgs
+ {
+ }
+}
diff --git a/TrueCraft.Client/Input/KeyboardKeyEventArgs.cs b/TrueCraft.Client/Input/KeyboardKeyEventArgs.cs
new file mode 100644
index 0000000..3e2e49d
--- /dev/null
+++ b/TrueCraft.Client/Input/KeyboardKeyEventArgs.cs
@@ -0,0 +1,32 @@
+using System;
+using Microsoft.Xna.Framework.Input;
+
+namespace TrueCraft.Client.Input
+{
+ ///
+ ///
+ ///
+ public class KeyboardKeyEventArgs : KeyboardEventArgs
+ {
+ ///
+ ///
+ ///
+ public Keys Key { get; private set; }
+
+ ///
+ ///
+ ///
+ public bool IsPressed { get; private set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public KeyboardKeyEventArgs(Keys key, bool isPressed)
+ {
+ Key = key;
+ IsPressed = isPressed;
+ }
+ }
+}
diff --git a/TrueCraft.Client/TrueCraft.Client.csproj b/TrueCraft.Client/TrueCraft.Client.csproj
index 00a45b7..f93bfce 100644
--- a/TrueCraft.Client/TrueCraft.Client.csproj
+++ b/TrueCraft.Client/TrueCraft.Client.csproj
@@ -94,6 +94,9 @@
+
+
+
diff --git a/TrueCraft.Client/TrueCraftGame.cs b/TrueCraft.Client/TrueCraftGame.cs
index f9fe9bd..0d01b20 100644
--- a/TrueCraft.Client/TrueCraftGame.cs
+++ b/TrueCraft.Client/TrueCraftGame.cs
@@ -37,9 +37,10 @@ namespace TrueCraft.Client
private BoundingFrustum CameraView;
private Camera Camera;
private bool MouseCaptured;
- private KeyboardState PreviousKeyboardState;
+ private KeyboardComponent KeyboardComponent { get; set; }
private MouseComponent MouseComponent { get; set; }
private GameTime GameTime { get; set; }
+ private Microsoft.Xna.Framework.Vector3 Delta { get; set; }
private BasicEffect OpaqueEffect, TransparentEffect;
@@ -61,6 +62,10 @@ namespace TrueCraft.Client
PendingMainThreadActions = new ConcurrentBag();
MouseCaptured = true;
+ var keyboardComponent = new KeyboardComponent(this);
+ KeyboardComponent = keyboardComponent;
+ Components.Add(keyboardComponent);
+
var mouseComponent = new MouseComponent(this);
MouseComponent = mouseComponent;
Components.Add(mouseComponent);
@@ -83,9 +88,10 @@ namespace TrueCraft.Client
Mouse.SetPosition(centerX, centerY);
Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 0.1f, 1000.0f);
UpdateCamera();
- PreviousKeyboardState = Keyboard.GetState();
Window.ClientSizeChanged += (sender, e) => CreateRenderTarget();
MouseComponent.Move += OnMouseComponentMove;
+ KeyboardComponent.KeyDown += OnKeyboardKeyDown;
+ KeyboardComponent.KeyUp += OnKeyboardKeyUp;
CreateRenderTarget();
}
@@ -151,46 +157,83 @@ namespace TrueCraft.Client
base.OnExiting(sender, args);
}
- protected virtual void UpdateKeyboard(GameTime gameTime, KeyboardState state, KeyboardState oldState)
+ private void OnKeyboardKeyDown(object sender, KeyboardKeyEventArgs e)
{
- if (state.IsKeyDown(Keys.Escape))
- Exit();
-
// TODO: Rebindable keys
// TODO: Horizontal terrain collisions
- if (state.IsKeyDown(Keys.F2) && oldState.IsKeyUp(Keys.F2)) // Take a screenshot
+ switch (e.Key)
{
- var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
- ".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png");
- if (!Directory.Exists(Path.GetDirectoryName(path)))
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- using (var stream = File.OpenWrite(path))
- RenderTarget.SaveAsPng(stream, RenderTarget.Width, RenderTarget.Height);
- ChatInterface.AddMessage(string.Format("Screenshot saved as {0}", Path.GetFileName(path)));
+ // Quit the game.
+ case Keys.Escape:
+ Exit();
+ break;
+
+ // Take a screenshot.
+ case Keys.F2:
+ TakeScreenshot();
+ break;
+
+ // Move to the left.
+ case Keys.A:
+ case Keys.Left:
+ Delta += Microsoft.Xna.Framework.Vector3.Left;
+ break;
+
+ // Move to the right.
+ case Keys.D:
+ case Keys.Right:
+ Delta += Microsoft.Xna.Framework.Vector3.Right;
+ break;
+
+ // Move forwards.
+ case Keys.W:
+ case Keys.Up:
+ Delta += Microsoft.Xna.Framework.Vector3.Forward;
+ break;
+
+ // Move backwards.
+ case Keys.S:
+ case Keys.Down:
+ Delta += Microsoft.Xna.Framework.Vector3.Backward;
+ break;
+
+ // Toggle mouse capture.
+ case Keys.Tab:
+ MouseCaptured = !MouseCaptured;
+ break;
}
- Microsoft.Xna.Framework.Vector3 delta = Microsoft.Xna.Framework.Vector3.Zero;
+ }
- if (state.IsKeyDown(Keys.Left) || state.IsKeyDown(Keys.A))
- delta += Microsoft.Xna.Framework.Vector3.Left;
- if (state.IsKeyDown(Keys.Right) || state.IsKeyDown(Keys.D))
- delta += Microsoft.Xna.Framework.Vector3.Right;
- if (state.IsKeyDown(Keys.Up) || state.IsKeyDown(Keys.W))
- delta += Microsoft.Xna.Framework.Vector3.Forward;
- if (state.IsKeyDown(Keys.Down) || state.IsKeyDown(Keys.S))
- delta += Microsoft.Xna.Framework.Vector3.Backward;
-
- if (delta != Microsoft.Xna.Framework.Vector3.Zero)
+ private void OnKeyboardKeyUp(object sender, KeyboardKeyEventArgs e)
+ {
+ switch (e.Key)
{
- var lookAt = Microsoft.Xna.Framework.Vector3.Transform(
- delta, Matrix.CreateRotationY(MathHelper.ToRadians(Client.Yaw)));
+ // Stop moving to the left.
+ case Keys.A:
+ case Keys.Left:
+ Delta -= Microsoft.Xna.Framework.Vector3.Left;
+ break;
- Client.Position += new TrueCraft.API.Vector3(lookAt.X, lookAt.Y, lookAt.Z) * (gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
+ // Stop moving to the right.
+ case Keys.D:
+ case Keys.Right:
+ Delta -= Microsoft.Xna.Framework.Vector3.Right;
+ break;
+
+ // Stop moving forwards.
+ case Keys.W:
+ case Keys.Up:
+ Delta -= Microsoft.Xna.Framework.Vector3.Forward;
+ break;
+
+ // Stop moving backwards.
+ case Keys.S:
+ case Keys.Down:
+ Delta -= Microsoft.Xna.Framework.Vector3.Backward;
+ break;
}
-
- if (state.IsKeyUp(Keys.Tab) && oldState.IsKeyDown(Keys.Tab))
- MouseCaptured = !MouseCaptured;
}
private void OnMouseComponentMove(object sender, MouseMoveEventArgs e)
@@ -214,6 +257,17 @@ namespace TrueCraft.Client
}
}
+ private void TakeScreenshot()
+ {
+ var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ ".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png");
+ if (!Directory.Exists(Path.GetDirectoryName(path)))
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ using (var stream = File.OpenWrite(path))
+ RenderTarget.SaveAsPng(stream, RenderTarget.Width, RenderTarget.Height);
+ ChatInterface.AddMessage(string.Format("Screenshot saved as {0}", Path.GetFileName(path)));
+ }
+
protected override void Update(GameTime gameTime)
{
GameTime = gameTime;
@@ -254,9 +308,16 @@ namespace TrueCraft.Client
Client.Position.Y + MultiplayerClient.Height, Client.Position.Z, Client.Yaw, Client.Pitch, false));
NextPhysicsUpdate = DateTime.Now.AddMilliseconds(1000 / 20);
}
- var state = Keyboard.GetState();
- UpdateKeyboard(gameTime, state, PreviousKeyboardState);
- PreviousKeyboardState = state;
+
+ if (Delta != Microsoft.Xna.Framework.Vector3.Zero)
+ {
+ var lookAt = Microsoft.Xna.Framework.Vector3.Transform(
+ Delta, Matrix.CreateRotationY(MathHelper.ToRadians(Client.Yaw)));
+
+ Client.Position += new TrueCraft.API.Vector3(lookAt.X, lookAt.Y, lookAt.Z)
+ * (gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
+ }
+
base.Update(gameTime);
}