Start moving client towards a more refined design

I'm splitting up core pieces of functionality into several smaller
modules. TrueCraftGame will be made smaller and smaller until it's just
a small wrapper around the modules doing all of the work. This should
allow for modders to easily add new modules or replace builtin modules,
and will make the codebase more maintainable in general.
This commit is contained in:
Drew DeVault 2015-09-24 21:20:36 -04:00
parent 915937dbd4
commit 8fe7329135
13 changed files with 716 additions and 613 deletions

View File

@ -8,7 +8,7 @@ namespace TrueCraft.Client.Input
/// <summary> /// <summary>
/// Encapsulates keyboard input in an event-driven manner. /// Encapsulates keyboard input in an event-driven manner.
/// </summary> /// </summary>
public sealed class KeyboardComponent : GameComponent public sealed class KeyboardHandler : GameComponent
{ {
/// <summary> /// <summary>
/// Raised when a key for this keyboard component is pressed. /// Raised when a key for this keyboard component is pressed.
@ -29,7 +29,7 @@ namespace TrueCraft.Client.Input
/// Creates a new keyboard component. /// Creates a new keyboard component.
/// </summary> /// </summary>
/// <param name="game">The parent game for the component.</param> /// <param name="game">The parent game for the component.</param>
public KeyboardComponent(Game game) public KeyboardHandler(Game game)
: base(game) : base(game)
{ {
} }

View File

@ -7,7 +7,7 @@ namespace TrueCraft.Client.Input
/// <summary> /// <summary>
/// Encapsulates mouse input in an event-driven manner. /// Encapsulates mouse input in an event-driven manner.
/// </summary> /// </summary>
public sealed class MouseComponent : GameComponent public sealed class MouseHandler : GameComponent
{ {
/// <summary> /// <summary>
/// Raised when this mouse component is moved. /// Raised when this mouse component is moved.
@ -38,7 +38,7 @@ namespace TrueCraft.Client.Input
/// Creates a new mouse component. /// Creates a new mouse component.
/// </summary> /// </summary>
/// <param name="game">The parent game for the component.</param> /// <param name="game">The parent game for the component.</param>
public MouseComponent(Game game) public MouseHandler(Game game)
: base(game) : base(game)
{ {
} }

View File

@ -103,7 +103,7 @@ namespace TrueCraft.Client.Interface
public bool HasFocus { get; set; } public bool HasFocus { get; set; }
public MultiplayerClient Client { get; set; } public MultiplayerClient Client { get; set; }
public KeyboardComponent Keyboard { get; set; } public KeyboardHandler Keyboard { get; set; }
public FontRenderer Font { get; set; } public FontRenderer Font { get; set; }
private readonly object Lock = new object(); private readonly object Lock = new object();
@ -111,7 +111,7 @@ namespace TrueCraft.Client.Interface
private List<ChatMessage> Messages { get; set; } private List<ChatMessage> Messages { get; set; }
private Texture2D DummyTexture { get; set; } private Texture2D DummyTexture { get; set; }
public ChatInterface(MultiplayerClient client, KeyboardComponent keyboard, FontRenderer font) public ChatInterface(MultiplayerClient client, KeyboardHandler keyboard, FontRenderer font)
{ {
Client = client; Client = client;
Keyboard = keyboard; Keyboard = keyboard;

View File

@ -14,7 +14,6 @@ namespace TrueCraft.Client.Interface
public int Vertices { private get; set; } public int Vertices { private get; set; }
public int Chunks { private get; set; } public int Chunks { private get; set; }
public Coordinates3D HighlightedBlock { private get; set; }
public DebugInterface(MultiplayerClient client, FontRenderer font) public DebugInterface(MultiplayerClient client, FontRenderer font)
{ {
@ -41,7 +40,6 @@ namespace TrueCraft.Client.Interface
Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 1), string.Format("§o{0} vertices", Vertices), scale); Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 1), string.Format("§o{0} vertices", Vertices), scale);
Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 2), string.Format("§o{0} chunks", Chunks), scale); Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 2), string.Format("§o{0} chunks", Chunks), scale);
Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 3), string.Format("§o<{0:N2}, {1:N2}, {2:N2}>", Client.Position.X, Client.Position.Y, Client.Position.Z), scale); Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 3), string.Format("§o<{0:N2}, {1:N2}, {2:N2}>", Client.Position.X, Client.Position.Y, Client.Position.Z), scale);
Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 4), string.Format("§o<{0:N2}, {1:N2}, {2:N2}>", HighlightedBlock.X, HighlightedBlock.Y, HighlightedBlock.Z), scale);
} }
protected override void OnHide() { } protected override void OnHide() { }

View File

@ -0,0 +1,127 @@
using System;
using TrueCraft.Client.Rendering;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using TrueCraft.API;
namespace TrueCraft.Client.Modules
{
public class ChunkModule : IGraphicalModule
{
public TrueCraftGame Game { get; set; }
public ChunkRenderer ChunkRenderer { get; set; }
private List<Mesh> ChunkMeshes { get; set; }
private ConcurrentBag<Mesh> IncomingChunks { get; set; }
private BasicEffect OpaqueEffect { get; set; }
private AlphaTestEffect TransparentEffect { get; set; }
public ChunkModule(TrueCraftGame game)
{
Game = game;
ChunkRenderer = new ChunkRenderer(Game.Client.World, Game, Game.BlockRepository);
Game.Client.ChunkLoaded += (sender, e) => ChunkRenderer.Enqueue(e.Chunk);
//Client.ChunkModified += (sender, e) => ChunkRenderer.Enqueue(e.Chunk, true);
ChunkRenderer.MeshCompleted += MeshCompleted;
ChunkRenderer.Start();
OpaqueEffect = new BasicEffect(Game.GraphicsDevice);
OpaqueEffect.EnableDefaultLighting();
OpaqueEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.TextureEnabled = true;
OpaqueEffect.Texture = Game.TextureMapper.GetTexture("terrain.png");
OpaqueEffect.FogEnabled = true;
OpaqueEffect.FogStart = 512f;
OpaqueEffect.FogEnd = 1000f;
OpaqueEffect.FogColor = Color.CornflowerBlue.ToVector3();
OpaqueEffect.VertexColorEnabled = true;
TransparentEffect = new AlphaTestEffect(Game.GraphicsDevice);
TransparentEffect.AlphaFunction = CompareFunction.Greater;
TransparentEffect.ReferenceAlpha = 127;
TransparentEffect.Texture = Game.TextureMapper.GetTexture("terrain.png");
TransparentEffect.VertexColorEnabled = true;
ChunkMeshes = new List<Mesh>();
IncomingChunks = new ConcurrentBag<Mesh>();
}
void MeshCompleted(object sender, RendererEventArgs<ReadOnlyChunk> e)
{
IncomingChunks.Add(e.Result);
}
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Position":
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
(int)Game.Client.Position.X, 0, (int)Game.Client.Position.Z));
Game.PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
break;
}
}
public void Update(GameTime gameTime)
{
Mesh mesh;
while (IncomingChunks.TryTake(out mesh))
{
ChunkMeshes.Add(mesh);
}
}
private static readonly BlendState ColorWriteDisable = new BlendState()
{
ColorWriteChannels = ColorWriteChannels.None
};
public void Draw(GameTime gameTime)
{
Game.Camera.ApplyTo(OpaqueEffect);
Game.Camera.ApplyTo(TransparentEffect);
int verticies = 0, chunks = 0;
Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox))
{
verticies += ChunkMeshes[i].GetTotalVertices();
chunks++;
ChunkMeshes[i].Draw(OpaqueEffect, 0);
}
}
Game.GraphicsDevice.BlendState = ColorWriteDisable;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox))
{
if (!ChunkMeshes[i].IsDisposed)
verticies += ChunkMeshes[i].GetTotalVertices();
ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
Game.GraphicsDevice.BlendState = BlendState.NonPremultiplied;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox))
{
if (!ChunkMeshes[i].IsDisposed)
verticies += ChunkMeshes[i].GetTotalVertices();
ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
}
}
}

View File

@ -0,0 +1,95 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using TrueCraft.API;
using TrueCraft.Client.Rendering;
using Microsoft.Xna.Framework;
namespace TrueCraft.Client.Modules
{
public class HighlightModule : IGraphicalModule
{
public TrueCraftGame Game { get; set; }
private Texture2D HighlightTexture { get; set; }
private Coordinates3D HighlightedBlock { get; set; }
private Mesh HighlightMesh { get; set; }
private BasicEffect HighlightEffect { get; set; }
public HighlightModule(TrueCraftGame game)
{
Game = game;
const int size = 64;
HighlightedBlock = -Coordinates3D.One;
HighlightTexture = new Texture2D(Game.GraphicsDevice, size, size);
var colors = new Color[size * size];
for (int i = 0; i < colors.Length; i++)
colors[i] = Color.Transparent;
for (int x = 0; x < size; x++)
colors[x] = Color.Black; // Top
for (int x = 0; x < size; x++)
colors[x + (size - 1) * size] = Color.Black; // Bottom
for (int y = 0; y < size; y++)
colors[y * size] = Color.Black; // Left
for (int y = 0; y < size; y++)
colors[y * size + (size - 1)] = Color.Black; // Right
HighlightTexture.SetData<Color>(colors);
var texcoords = new[]
{
Vector2.UnitX + Vector2.UnitY,
Vector2.UnitY,
Vector2.Zero,
Vector2.UnitX
};
int[] indicies;
var verticies = BlockRenderer.CreateUniformCube(Microsoft.Xna.Framework.Vector3.Zero,
texcoords, VisibleFaces.All, 0, out indicies, Color.White);
HighlightMesh = new Mesh(Game, verticies, indicies);
HighlightEffect = new BasicEffect(Game.GraphicsDevice);
HighlightEffect.EnableDefaultLighting();
HighlightEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3();
HighlightEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3();
HighlightEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3();
HighlightEffect.TextureEnabled = true;
HighlightEffect.Texture = HighlightTexture;
HighlightEffect.VertexColorEnabled = true;
}
public void Update(GameTime gameTime)
{
var direction = Microsoft.Xna.Framework.Vector3.Transform(
-Microsoft.Xna.Framework.Vector3.UnitZ,
Matrix.CreateRotationX(MathHelper.ToRadians(Game.Client.Pitch)) *
Matrix.CreateRotationY(MathHelper.ToRadians(Game.Client.Yaw)));
var cast = VoxelCast.Cast(Game.Client.World,
new TrueCraft.API.Ray(Game.Camera.Position,
new TrueCraft.API.Vector3(direction.X, direction.Y, direction.Z)),
Game.BlockRepository, TrueCraftGame.Reach);
if (cast == null)
HighlightedBlock = -Coordinates3D.One;
else
{
HighlightedBlock = cast.Item1;
HighlightEffect.World = Matrix.CreateScale(1.02f) *
Matrix.CreateTranslation(new Microsoft.Xna.Framework.Vector3(
cast.Item1.X, cast.Item1.Y, cast.Item1.Z));
}
}
public void Draw(GameTime gameTime)
{
Game.Camera.ApplyTo(HighlightEffect);
if (HighlightedBlock != -Coordinates3D.One)
{
Game.GraphicsDevice.DepthStencilState = DepthStencilState.None;
HighlightMesh.Draw(HighlightEffect);
}
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using Microsoft.Xna.Framework;
namespace TrueCraft.Client.Modules
{
public interface IGameplayModule
{
void Update(GameTime gameTime);
}
}

View File

@ -0,0 +1,10 @@
using System;
using Microsoft.Xna.Framework;
namespace TrueCraft.Client.Modules
{
public interface IGraphicalModule : IGameplayModule
{
void Draw(GameTime gameTime);
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.Xna.Framework;
using TrueCraft.Client.Input;
namespace TrueCraft.Client.Modules
{
public interface IInputModule : IGameplayModule
{
bool KeyDown(GameTime gameTime, KeyboardKeyEventArgs e);
bool KeyUp(GameTime gameTime, KeyboardKeyEventArgs e);
void MouseMove(GameTime gameTime, MouseMoveEventArgs e);
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using TrueCraft.Client.Input;
using TCVector3 = TrueCraft.API.Vector3;
namespace TrueCraft.Client.Modules
{
public class PlayerControlModule : IInputModule
{
private TrueCraftGame Game { get; set; }
private Vector3 Delta { get; set; }
public PlayerControlModule(TrueCraftGame game)
{
Game = game;
}
public bool KeyDown(GameTime gameTime, KeyboardKeyEventArgs e)
{
switch (e.Key)
{
// Exit game
case Keys.Escape:
Process.GetCurrentProcess().Kill();
return true;
// Take a screenshot.
case Keys.F2:
Game.TakeScreenshot();
return true;
// Move to the left.
case Keys.A:
case Keys.Left:
Delta += Vector3.Left;
return true;
// Move to the right.
case Keys.D:
case Keys.Right:
Delta += Vector3.Right;
return true;
// Move forwards.
case Keys.W:
case Keys.Up:
Delta += Vector3.Forward;
return true;
// Move backwards.
case Keys.S:
case Keys.Down:
Delta += Vector3.Backward;
return true;
case Keys.Space:
if (Math.Floor(Game.Client.Position.Y) == Game.Client.Position.Y)
Game.Client.Velocity += TrueCraft.API.Vector3.Up * 0.3;
return true;
}
return false;
}
public bool KeyUp(GameTime gameTime, KeyboardKeyEventArgs e)
{
switch (e.Key)
{
// Stop moving to the left.
case Keys.A:
case Keys.Left:
Delta -= Vector3.Left;
return true;
// Stop moving to the right.
case Keys.D:
case Keys.Right:
Delta -= Vector3.Right;
return true;
// Stop moving forwards.
case Keys.W:
case Keys.Up:
Delta -= Vector3.Forward;
return true;
// Stop moving backwards.
case Keys.S:
case Keys.Down:
Delta -= Vector3.Backward;
return true;
}
return false;
}
public void MouseMove(GameTime gameTime, MouseMoveEventArgs e)
{
var centerX = Game.GraphicsDevice.Viewport.Width / 2;
var centerY = Game.GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centerX, centerY);
var look = new Vector2((centerX - e.X), (centerY - e.Y))
* (float)(gameTime.ElapsedGameTime.TotalSeconds * 30);
Game.Client.Yaw += look.X;
Game.Client.Pitch += look.Y;
Game.Client.Yaw %= 360;
Game.Client.Pitch = MathHelper.Clamp(Game.Client.Pitch, -89.9f, 89.9f);
}
public void Update(GameTime gameTime)
{
if (Delta != Vector3.Zero)
{
var lookAt = Vector3.Transform(Delta, Matrix.CreateRotationY(MathHelper.ToRadians(Game.Client.Yaw)));
lookAt.X *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
lookAt.Z *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
Game.Bobbing += Math.Max(Math.Abs(lookAt.X), Math.Abs(lookAt.Z));
Game.Client.Velocity = new TCVector3(lookAt.X, Game.Client.Velocity.Y, lookAt.Z);
}
else
Game.Client.Velocity *= new TCVector3(0, 1, 0);
}
}
}

View File

@ -133,19 +133,21 @@ namespace TrueCraft.Client.Rendering
effectMatrices.View = _view; effectMatrices.View = _view;
effectMatrices.Projection = _projection; effectMatrices.Projection = _projection;
effectMatrices.World = Matrix.Identity;
} }
/// <summary> /// <summary>
/// Returns the bounding frustum calculated for this camera. /// Returns the bounding frustum calculated for this camera.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public BoundingFrustum GetFrustum() public BoundingFrustum Frustum
{
get
{ {
if (_isDirty) if (_isDirty)
Recalculate(); Recalculate();
return _frustum; return _frustum;
} }
}
/// <summary> /// <summary>
/// Returns the view matrix calculated for this camera. /// Returns the view matrix calculated for this camera.

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\targets\Client.targets" /> <Import Project="..\targets\Client.targets" />
<PropertyGroup> <PropertyGroup>
@ -72,14 +71,15 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="MonoGame.Framework">
<HintPath>..\packages\MonoGame.Framework.Linux.3.4.0.459\lib\net40\MonoGame.Framework.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Input\KeyboardComponent.cs" />
<Compile Include="Input\KeyboardEventArgs.cs" /> <Compile Include="Input\KeyboardEventArgs.cs" />
<Compile Include="Input\KeyboardKeyEventArgs.cs" /> <Compile Include="Input\KeyboardKeyEventArgs.cs" />
<Compile Include="Input\MouseButton.cs" /> <Compile Include="Input\MouseButton.cs" />
<Compile Include="Input\MouseButtonEventArgs.cs" /> <Compile Include="Input\MouseButtonEventArgs.cs" />
<Compile Include="Input\MouseComponent.cs" />
<Compile Include="Input\MouseEventArgs.cs" /> <Compile Include="Input\MouseEventArgs.cs" />
<Compile Include="Input\MouseMoveEventArgs.cs" /> <Compile Include="Input\MouseMoveEventArgs.cs" />
<Compile Include="Input\MouseScrollEventArgs.cs" /> <Compile Include="Input\MouseScrollEventArgs.cs" />
@ -127,6 +127,14 @@
<Compile Include="Rendering\Blocks\WaterRenderer.cs" /> <Compile Include="Rendering\Blocks\WaterRenderer.cs" />
<Compile Include="Rendering\Blocks\FarmlandRenderer.cs" /> <Compile Include="Rendering\Blocks\FarmlandRenderer.cs" />
<Compile Include="VoxelCast.cs" /> <Compile Include="VoxelCast.cs" />
<Compile Include="Input\KeyboardHandler.cs" />
<Compile Include="Input\MouseHandler.cs" />
<Compile Include="Modules\IGameplayModule.cs" />
<Compile Include="Modules\IInputModule.cs" />
<Compile Include="Modules\IGraphicalModule.cs" />
<Compile Include="Modules\ChunkModule.cs" />
<Compile Include="Modules\HighlightModule.cs" />
<Compile Include="Modules\PlayerControlModule.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -223,4 +231,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Modules\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,60 +1,58 @@
using System; using System;
using Microsoft.Xna.Framework; using System.Collections.Concurrent;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic; using System.Collections.Generic;
using TrueCraft.Client.Interface; using System.ComponentModel;
using System.IO; using System.IO;
using System.Net; using System.Net;
using TrueCraft.API; using Microsoft.Xna.Framework;
using TrueCraft.Client.Rendering; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using Microsoft.Xna.Framework.Input;
using System.ComponentModel;
using TrueCraft.Core.Networking.Packets;
using TrueCraft.API.World;
using System.Collections.Concurrent;
using TrueCraft.Client.Input;
using TrueCraft.Core;
using MonoGame.Utilities.Png; using MonoGame.Utilities.Png;
using System.Diagnostics; using TrueCraft.API;
using TrueCraft.Core.Logic.Blocks; using TrueCraft.API.Logic;
using TrueCraft.API.World;
using TrueCraft.Core;
using TrueCraft.Core.Networking.Packets;
using TrueCraft.Client.Input;
using TrueCraft.Client.Interface;
using TrueCraft.Client.Modules;
using TrueCraft.Client.Rendering;
namespace TrueCraft.Client namespace TrueCraft.Client
{ {
public class TrueCraftGame : Game public class TrueCraftGame : Game
{ {
private MultiplayerClient Client { get; set; } public MultiplayerClient Client { get; private set; }
private GraphicsDeviceManager Graphics { get; set; } public GraphicsDeviceManager Graphics { get; private set; }
private List<IGameInterface> Interfaces { get; set; } public TextureMapper TextureMapper { get; private set; }
private FontRenderer Pixel { get; set; } public Camera Camera { get; private set; }
public ConcurrentBag<Action> PendingMainThreadActions { get; set; }
public double Bobbing { get; set; }
private List<IGameplayModule> Modules { get; set; }
private SpriteBatch SpriteBatch { get; set; } private SpriteBatch SpriteBatch { get; set; }
private KeyboardHandler KeyboardComponent { get; set; }
private MouseHandler MouseComponent { get; set; }
private RenderTarget2D RenderTarget { get; set; }
private FontRenderer Pixel { get; set; }
private IPEndPoint EndPoint { get; set; } private IPEndPoint EndPoint { get; set; }
private ChunkRenderer ChunkConverter { get; set; }
private DateTime LastPhysicsUpdate { get; set; } private DateTime LastPhysicsUpdate { get; set; }
private DateTime NextPhysicsUpdate { get; set; } private DateTime NextPhysicsUpdate { get; set; }
private List<Mesh> ChunkMeshes { get; set; } private bool MouseCaptured { get; set; }
public ConcurrentBag<Action> PendingMainThreadActions { get; set; }
private ConcurrentBag<Mesh> IncomingChunks { get; set; }
public ChatInterface ChatInterface { get; set; }
public DebugInterface DebugInterface { get; set; }
private RenderTarget2D RenderTarget;
private BoundingFrustum CameraView;
private Camera Camera;
private bool MouseCaptured;
private KeyboardComponent KeyboardComponent { get; set; }
private MouseComponent MouseComponent { get; set; }
private GameTime GameTime { get; set; } private GameTime GameTime { get; set; }
private Microsoft.Xna.Framework.Vector3 Delta { get; set; } private Microsoft.Xna.Framework.Vector3 Delta { get; set; }
private TextureMapper TextureMapper { get; set; }
private double Bobbing { get; set; }
private Coordinates3D HighlightedBlock { get; set; }
private Mesh HighlightMesh { get; set; }
private Texture2D HighlightTexture { get; set; }
private BasicEffect OpaqueEffect, HighlightEffect;
private AlphaTestEffect TransparentEffect;
public static readonly int Reach = 5; public static readonly int Reach = 5;
public IBlockRepository BlockRepository
{
get
{
return Client.World.World.BlockRepository;
}
}
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint) public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
{ {
Window.Title = "TrueCraft"; Window.Title = "TrueCraft";
@ -68,75 +66,46 @@ namespace TrueCraft.Client
EndPoint = endPoint; EndPoint = endPoint;
LastPhysicsUpdate = DateTime.MinValue; LastPhysicsUpdate = DateTime.MinValue;
NextPhysicsUpdate = DateTime.MinValue; NextPhysicsUpdate = DateTime.MinValue;
ChunkMeshes = new List<Mesh>();
IncomingChunks = new ConcurrentBag<Mesh>();
PendingMainThreadActions = new ConcurrentBag<Action>(); PendingMainThreadActions = new ConcurrentBag<Action>();
MouseCaptured = true; MouseCaptured = true;
Bobbing = 0; Bobbing = 0;
var keyboardComponent = new KeyboardComponent(this); var keyboardComponent = new KeyboardHandler(this);
KeyboardComponent = keyboardComponent; KeyboardComponent = keyboardComponent;
Components.Add(keyboardComponent); Components.Add(keyboardComponent);
var mouseComponent = new MouseComponent(this); var mouseComponent = new MouseHandler(this);
MouseComponent = mouseComponent; MouseComponent = mouseComponent;
Components.Add(mouseComponent); Components.Add(mouseComponent);
} }
protected override void Initialize() protected override void Initialize()
{ {
Interfaces = new List<IGameInterface>(); Modules = new List<IGameplayModule>();
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize(); // (calls LoadContent) base.Initialize(); // (calls LoadContent)
ChunkConverter = new ChunkRenderer(Client.World, this, Client.World.World.BlockRepository);
Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk); Modules.Add(new ChunkModule(this));
//Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true); Modules.Add(new HighlightModule(this));
ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated; Modules.Add(new PlayerControlModule(this));
ChunkConverter.Start();
Client.PropertyChanged += HandleClientPropertyChanged; Client.PropertyChanged += HandleClientPropertyChanged;
Client.Connect(EndPoint); Client.Connect(EndPoint);
var centerX = GraphicsDevice.Viewport.Width / 2; var centerX = GraphicsDevice.Viewport.Width / 2;
var centerY = GraphicsDevice.Viewport.Height / 2; var centerY = GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centerX, centerY); Mouse.SetPosition(centerX, centerY);
Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 0.1f, 1000.0f); Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 0.1f, 1000.0f);
UpdateCamera(); UpdateCamera();
Window.ClientSizeChanged += (sender, e) => CreateRenderTarget();
MouseComponent.Move += OnMouseComponentMove; MouseComponent.Move += OnMouseComponentMove;
KeyboardComponent.KeyDown += OnKeyboardKeyDown; KeyboardComponent.KeyDown += OnKeyboardKeyDown;
KeyboardComponent.KeyUp += OnKeyboardKeyUp; KeyboardComponent.KeyUp += OnKeyboardKeyUp;
Window.ClientSizeChanged += (sender, e) => CreateRenderTarget();
CreateRenderTarget(); CreateRenderTarget();
} SpriteBatch = new SpriteBatch(GraphicsDevice);
private void InitializeHighlightedBlock()
{
const int size = 64;
HighlightedBlock = -Coordinates3D.One;
HighlightTexture = new Texture2D(GraphicsDevice, size, size);
var colors = new Color[size * size];
for (int i = 0; i < colors.Length; i++)
colors[i] = Color.Transparent;
for (int x = 0; x < size; x++)
colors[x] = Color.Black; // Top
for (int x = 0; x < size; x++)
colors[x + (size - 1) * size] = Color.Black; // Bottom
for (int y = 0; y < size; y++)
colors[y * size] = Color.Black; // Left
for (int y = 0; y < size; y++)
colors[y * size + (size - 1)] = Color.Black; // Right
HighlightTexture.SetData<Color>(colors);
var texcoords = new[]
{
Vector2.UnitX + Vector2.UnitY,
Vector2.UnitY,
Vector2.Zero,
Vector2.UnitX
};
int[] indicies;
var verticies = BlockRenderer.CreateUniformCube(Microsoft.Xna.Framework.Vector3.Zero,
texcoords, VisibleFaces.All, 0, out indicies, Color.White);
HighlightMesh = new Mesh(this, verticies, indicies);
} }
private void CreateRenderTarget() private void CreateRenderTarget()
@ -145,20 +114,12 @@ namespace TrueCraft.Client
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24); false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
} }
void ChunkConverter_MeshGenerated(object sender, RendererEventArgs<ReadOnlyChunk> e)
{
IncomingChunks.Add(e.Result);
}
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e) void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
switch (e.PropertyName) switch (e.PropertyName)
{ {
case "Position": case "Position":
UpdateCamera(); UpdateCamera();
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
(int)Client.Position.X, 0, (int)Client.Position.Z));
PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
break; break;
} }
} }
@ -174,212 +135,52 @@ namespace TrueCraft.Client
TextureMapper.AddTexturePack(TexturePack.FromArchive(Path.Combine(TexturePack.TexturePackPath, UserSettings.Local.SelectedTexturePack))); TextureMapper.AddTexturePack(TexturePack.FromArchive(Path.Combine(TexturePack.TexturePackPath, UserSettings.Local.SelectedTexturePack)));
Pixel = new FontRenderer( Pixel = new FontRenderer(
new Font(Content, "Fonts/Pixel", FontStyle.Regular), new Font(Content, "Fonts/Pixel"),
new Font(Content, "Fonts/Pixel", FontStyle.Bold), new Font(Content, "Fonts/Pixel", FontStyle.Bold),
null, // No support for underlined or strikethrough yet. The FontRenderer will revert to using the regular font style. null, // No support for underlined or strikethrough yet. The FontRenderer will revert to using the regular font style.
null, // (I don't think BMFont has those options?) null, // (I don't think BMFont has those options?)
new Font(Content, "Fonts/Pixel", FontStyle.Italic)); new Font(Content, "Fonts/Pixel", FontStyle.Italic));
Interfaces.Add(ChatInterface = new ChatInterface(Client, KeyboardComponent, Pixel));
Interfaces.Add(DebugInterface = new DebugInterface(Client, Pixel));
ChatInterface.IsVisible = true;
DebugInterface.IsVisible = true;
OpaqueEffect = new BasicEffect(GraphicsDevice);
OpaqueEffect.EnableDefaultLighting();
OpaqueEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3();
OpaqueEffect.TextureEnabled = true;
OpaqueEffect.Texture = TextureMapper.GetTexture("terrain.png");
OpaqueEffect.FogEnabled = true;
OpaqueEffect.FogStart = 512f;
OpaqueEffect.FogEnd = 1000f;
OpaqueEffect.FogColor = Color.CornflowerBlue.ToVector3();
OpaqueEffect.VertexColorEnabled = true;
TransparentEffect = new AlphaTestEffect(GraphicsDevice);
TransparentEffect.AlphaFunction = CompareFunction.Greater;
TransparentEffect.ReferenceAlpha = 127;
TransparentEffect.Texture = TextureMapper.GetTexture("terrain.png");
TransparentEffect.VertexColorEnabled = true;
InitializeHighlightedBlock();
HighlightEffect = new BasicEffect(GraphicsDevice);
HighlightEffect.EnableDefaultLighting();
HighlightEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3();
HighlightEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3();
HighlightEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3();
HighlightEffect.TextureEnabled = true;
HighlightEffect.Texture = HighlightTexture;
HighlightEffect.VertexColorEnabled = true;
base.LoadContent(); base.LoadContent();
} }
protected override void OnExiting(object sender, EventArgs args)
{
ChunkConverter.Stop();
base.OnExiting(sender, args);
}
private void OnKeyboardKeyDown(object sender, KeyboardKeyEventArgs e) private void OnKeyboardKeyDown(object sender, KeyboardKeyEventArgs e)
{ {
// TODO: Rebindable keys foreach (var module in Modules)
// TODO: Horizontal terrain collisions
switch (e.Key)
{ {
// Close game (or chat). var input = module as IInputModule;
case Keys.Escape: if (input != null)
if (ChatInterface.HasFocus)
ChatInterface.HasFocus = false;
else
Process.GetCurrentProcess().Kill();
break;
// Open chat window.
case Keys.T:
if (!ChatInterface.HasFocus && ChatInterface.IsVisible)
ChatInterface.HasFocus = true;
break;
// Open chat window.
case Keys.OemQuestion:
if (!ChatInterface.HasFocus && ChatInterface.IsVisible)
ChatInterface.HasFocus = true;
break;
// Close chat window.
case Keys.Enter:
if (ChatInterface.HasFocus)
ChatInterface.HasFocus = false;
break;
// Take a screenshot.
case Keys.F2:
TakeScreenshot();
break;
// Toggle debug view.
case Keys.F3:
DebugInterface.IsVisible = !DebugInterface.IsVisible;
break;
// Change interface scale.
case Keys.F4:
foreach (var item in Interfaces)
{ {
item.Scale = (InterfaceScale)(item.Scale + 1); if (input.KeyDown(GameTime, e))
if ((int)item.Scale > 2)
item.Scale = InterfaceScale.Small;
}
break;
// Move to the left.
case Keys.A:
case Keys.Left:
if (ChatInterface.HasFocus)
break;
Delta += Microsoft.Xna.Framework.Vector3.Left;
break;
// Move to the right.
case Keys.D:
case Keys.Right:
if (ChatInterface.HasFocus)
break;
Delta += Microsoft.Xna.Framework.Vector3.Right;
break;
// Move forwards.
case Keys.W:
case Keys.Up:
if (ChatInterface.HasFocus)
break;
Delta += Microsoft.Xna.Framework.Vector3.Forward;
break;
// Move backwards.
case Keys.S:
case Keys.Down:
if (ChatInterface.HasFocus)
break;
Delta += Microsoft.Xna.Framework.Vector3.Backward;
break;
case Keys.Space:
if (ChatInterface.HasFocus)
break;
if (Math.Floor(Client.Position.Y) == Client.Position.Y) // Crappy onground substitute
Client.Velocity += TrueCraft.API.Vector3.Up * 0.3;
break;
// Toggle mouse capture.
case Keys.Tab:
MouseCaptured = !MouseCaptured;
break; break;
} }
}
} }
private void OnKeyboardKeyUp(object sender, KeyboardKeyEventArgs e) private void OnKeyboardKeyUp(object sender, KeyboardKeyEventArgs e)
{ {
switch (e.Key) foreach (var module in Modules)
{ {
// Stop moving to the left. var input = module as IInputModule;
case Keys.A: if (input != null)
case Keys.Left: {
if (ChatInterface.HasFocus) break; if (input.KeyUp(GameTime, e))
Delta -= Microsoft.Xna.Framework.Vector3.Left;
break;
// Stop moving to the right.
case Keys.D:
case Keys.Right:
if (ChatInterface.HasFocus) break;
Delta -= Microsoft.Xna.Framework.Vector3.Right;
break;
// Stop moving forwards.
case Keys.W:
case Keys.Up:
if (ChatInterface.HasFocus) break;
Delta -= Microsoft.Xna.Framework.Vector3.Forward;
break;
// Stop moving backwards.
case Keys.S:
case Keys.Down:
if (ChatInterface.HasFocus) break;
Delta -= Microsoft.Xna.Framework.Vector3.Backward;
break; break;
} }
} }
}
private void OnMouseComponentMove(object sender, MouseMoveEventArgs e) private void OnMouseComponentMove(object sender, MouseMoveEventArgs e)
{ {
if (MouseCaptured && IsActive) foreach (var module in Modules)
{ {
var centerX = GraphicsDevice.Viewport.Width / 2; var input = module as IInputModule;
var centerY = GraphicsDevice.Viewport.Height / 2; if (input != null)
Mouse.SetPosition(centerX, centerY); input.MouseMove(GameTime, e);
var look = new Vector2((centerX - e.X), (centerY - e.Y))
* (float)(GameTime.ElapsedGameTime.TotalSeconds * 30);
Client.Yaw += look.X;
Client.Pitch += look.Y;
Client.Yaw %= 360;
Client.Pitch = Microsoft.Xna.Framework.MathHelper.Clamp(Client.Pitch, -89.9f, 89.9f);
if (look != Vector2.Zero)
UpdateCamera();
} }
} }
private void TakeScreenshot() public void TakeScreenshot()
{ {
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png"); ".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png");
@ -387,29 +188,19 @@ namespace TrueCraft.Client
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.OpenWrite(path)) using (var stream = File.OpenWrite(path))
new PngWriter().Write(RenderTarget, stream); new PngWriter().Write(RenderTarget, stream);
ChatInterface.AddMessage(string.Format("Screenshot saved as {0}", Path.GetFileName(path)));
} }
protected override void Update(GameTime gameTime) protected override void Update(GameTime gameTime)
{ {
GameTime = gameTime; GameTime = gameTime;
foreach (var i in Interfaces)
{
i.Update(gameTime);
}
Mesh mesh;
while (IncomingChunks.TryTake(out mesh))
{
ChunkMeshes.Add(mesh);
}
Action action; Action action;
if (PendingMainThreadActions.TryTake(out action)) if (PendingMainThreadActions.TryTake(out action))
action(); action();
IChunk chunk; IChunk chunk;
var adjusted = Client.World.World.FindBlockPosition(new Coordinates3D((int)Client.Position.X, 0, (int)Client.Position.Z), out chunk); var adjusted = Client.World.World.FindBlockPosition(
new Coordinates3D((int)Client.Position.X, 0, (int)Client.Position.Z), out chunk);
if (chunk != null && Client.LoggedIn) if (chunk != null && Client.LoggedIn)
{ {
if (chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) != 0) if (chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) != 0)
@ -426,69 +217,29 @@ namespace TrueCraft.Client
NextPhysicsUpdate = DateTime.UtcNow.AddMilliseconds(1000 / 20); NextPhysicsUpdate = DateTime.UtcNow.AddMilliseconds(1000 / 20);
} }
if (Delta != Microsoft.Xna.Framework.Vector3.Zero) foreach (var module in Modules)
{ module.Update(gameTime);
var lookAt = Microsoft.Xna.Framework.Vector3.Transform(
Delta, Matrix.CreateRotationY(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Yaw)));
lookAt.X *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
lookAt.Z *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717);
Bobbing += Math.Max(Math.Abs(lookAt.X), Math.Abs(lookAt.Z));
Client.Velocity = new TrueCraft.API.Vector3(lookAt.X, Client.Velocity.Y, lookAt.Z);
}
else
Client.Velocity *= new TrueCraft.API.Vector3(0, 1, 0);
UpdateCamera(); UpdateCamera();
base.Update(gameTime); base.Update(gameTime);
} }
private void UpdateCamera() private void UpdateCamera()
{ {
const double bobbingMultiplier = 0.015; const double bobbingMultiplier = 0.015;
var bobbing = Bobbing * 1.5; var bobbing = Bobbing * 1.5;
var xbob = Math.Cos(bobbing + Math.PI / 2) * bobbingMultiplier;
var ybob = Math.Sin(Math.PI / 2 - (2 * bobbing)) * bobbingMultiplier;
Camera.Position = new TrueCraft.API.Vector3( Camera.Position = new TrueCraft.API.Vector3(
Client.Position.X + Math.Cos(bobbing + Math.PI / 2) * bobbingMultiplier Client.Position.X + xbob - (Client.Size.Width / 2),
- (Client.Size.Width / 2), Client.Position.Y + (Client.Size.Height - 0.5) + ybob,
Client.Position.Y + (Client.Size.Height - 0.5)
+ Math.Sin(Math.PI / 2 - (2 * bobbing)) * bobbingMultiplier,
Client.Position.Z - (Client.Size.Depth / 2)); Client.Position.Z - (Client.Size.Depth / 2));
Camera.Pitch = Client.Pitch; Camera.Pitch = Client.Pitch;
Camera.Yaw = Client.Yaw; Camera.Yaw = Client.Yaw;
CameraView = Camera.GetFrustum();
Camera.ApplyTo(HighlightEffect);
Camera.ApplyTo(OpaqueEffect);
Camera.ApplyTo(TransparentEffect);
var direction = Microsoft.Xna.Framework.Vector3.Transform(
-Microsoft.Xna.Framework.Vector3.UnitZ,
Matrix.CreateRotationX(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Pitch)) *
Matrix.CreateRotationY(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Yaw)));
var cast = VoxelCast.Cast(Client.World,
new TrueCraft.API.Ray(Camera.Position, new TrueCraft.API.Vector3(
direction.X, direction.Y, direction.Z)),
Client.World.World.BlockRepository, Reach);
if (cast == null)
HighlightedBlock = -Coordinates3D.One;
else
{
HighlightEffect.World = Matrix.CreateScale(1.02f) *
Matrix.CreateTranslation(new Microsoft.Xna.Framework.Vector3(
cast.Item1.X, cast.Item1.Y, cast.Item1.Z));
} }
}
private static readonly BlendState ColorWriteDisable = new BlendState()
{
ColorWriteChannels = ColorWriteChannels.None
};
protected override void Draw(GameTime gameTime) protected override void Draw(GameTime gameTime)
{ {
@ -499,55 +250,15 @@ namespace TrueCraft.Client
GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp; GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
int verticies = 0, chunks = 0; foreach (var module in Modules)
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
for (int i = 0; i < ChunkMeshes.Count; i++)
{ {
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox)) var drawable = module as IGraphicalModule;
{ if (drawable != null)
verticies += ChunkMeshes[i].GetTotalVertices(); drawable.Draw(gameTime);
chunks++;
ChunkMeshes[i].Draw(OpaqueEffect, 0);
} }
}
HighlightMesh.Draw(HighlightEffect);
GraphicsDevice.BlendState = ColorWriteDisable;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
if (!ChunkMeshes[i].IsDisposed)
verticies += ChunkMeshes[i].GetTotalVertices();
ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
GraphicsDevice.BlendState = BlendState.NonPremultiplied;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
if (!ChunkMeshes[i].IsDisposed)
verticies += ChunkMeshes[i].GetTotalVertices();
ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
DebugInterface.Vertices = verticies;
DebugInterface.Chunks = chunks;
DebugInterface.HighlightedBlock = HighlightedBlock;
HighlightMesh.Draw(HighlightEffect);
SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, null);
for (int i = 0; i < Interfaces.Count; i++)
Interfaces[i].DrawSprites(gameTime, SpriteBatch);
SpriteBatch.End();
GraphicsDevice.SetRenderTarget(null); GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);
SpriteBatch.Begin(); SpriteBatch.Begin();
SpriteBatch.Draw(RenderTarget, new Vector2(0)); SpriteBatch.Draw(RenderTarget, new Vector2(0));
SpriteBatch.End(); SpriteBatch.End();
@ -559,8 +270,6 @@ namespace TrueCraft.Client
{ {
if (disposing) if (disposing)
{ {
ChunkConverter.Dispose();
KeyboardComponent.Dispose(); KeyboardComponent.Dispose();
MouseComponent.Dispose(); MouseComponent.Dispose();
} }