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); }