Skip to content

Commit

Permalink
Merge pull request #15 from blocktest-game/camera
Browse files Browse the repository at this point in the history
Adds cameras
  • Loading branch information
MarkSuckerberg authored Sep 27, 2023
2 parents 9701ae4 + 79f9a3a commit 22da09c
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 138 deletions.
3 changes: 2 additions & 1 deletion Blocktest/BlocktestGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public BlocktestGame()
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
Window.AllowUserResizing = true;
}

/// <inheritdoc />
Expand All @@ -35,7 +36,7 @@ protected override void LoadContent()
protected override void Update(GameTime gameTime)
{
_currentScene?.Update(gameTime);

base.Update(gameTime);
}

Expand Down
90 changes: 17 additions & 73 deletions Blocktest/Code/Block System/Tilemap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,25 @@ public class Tilemap
/// </summary>
public readonly Vector2Int tilemapSize;
/// <summary>
/// The size of each cell (in pixels) in the tilemap's grid.
/// </summary>
public readonly Vector2Int gridSize = new(8, 8);
/// <summary>
/// A list of <see cref="Vector2Int"/>s that specify which blocks should be refreshed when a tile is placed/destroyed. Defaults to the changed block and all cardinal directions.
/// </summary>
private readonly List<Vector2Int> adjacencies = new() { Vector2Int.Zero, Vector2Int.Up, Vector2Int.Down, Vector2Int.Left, Vector2Int.Right };
private static readonly List<Vector2Int> adjacencies = new() { Vector2Int.Zero, Vector2Int.Up, Vector2Int.Down, Vector2Int.Left, Vector2Int.Right };

private readonly Camera _camera;

/// <summary>
/// Creates a <see cref="Tilemap"/>.
/// </summary>
/// <param name="sizeX">The width of the tilemap in tiles.</param>
/// <param name="sizeY">The height of the tilemap in tiles.</param>
public Tilemap(int sizeX, int sizeY)
/// <param name="camera">The camera to render tiles on</param>
public Tilemap(int sizeX, int sizeY, Camera camera)
{
_camera = camera;
tilemapSize = new(sizeX, sizeY);
tileGrid = new Tile[sizeX, sizeY];
}

/// <summary>
/// Called from the main draw loop, calls <see cref="Tile.Draw(SpriteBatch)"/> on each tile in the tilemap.
/// </summary>
/// <param name="spriteBatch">The spritebatch to draw the tilemap tiles' sprite on.</param>
public void Draw(SpriteBatch spriteBatch)
{
foreach (Tile tile in allTiles) {
tile.Draw(spriteBatch);
}
}

/// <summary>
/// Sets a Tile at the given XYZ coordinates of a cell in the tile map to a specific <see cref="Block"/> type.
/// </summary>
/// <param name="location">Location the new Block will be placed.</param>
/// <param name="newBlock">Block type to be placed in the cell.</param>
public Tile SetBlock(Vector2Int location, Block newBlock) => SetTile(location, new Tile(newBlock, location));
/// <summary>
/// Sets a Tile at the given XYZ coordinates of a cell in the tile map to a specific <see cref="Block"/> type.
/// </summary>
Expand All @@ -66,12 +48,14 @@ public Tile SetTile(Vector2Int location, Tile newTile)
Tile oldTile = GetTile(location);
if (oldTile != null) {
allTiles.Remove(oldTile);
_camera.RenderedComponents.Remove(oldTile.Renderable);
}

tileGrid[location.X, location.Y] = newTile;

if (newTile != null) {
allTiles.Add(newTile);
_camera.RenderedComponents.Add(newTile.Renderable);
}

foreach (Vector2Int dir in adjacencies) {
Expand All @@ -93,37 +77,13 @@ public Tile SetTile(Vector2Int location, Tile newTile)
/// </summary>
/// <param name="location">Location of the Tile on the Tilemap to check.</param>
/// <returns><see cref="Tile"/> placed at the cell.</returns>
public Tile? GetTile(Vector2Int location) => GetTile(location.X, location.Y);

/// <summary>
/// Gets the <see cref="Tile"/> at a specific location on a <see cref="Tilemap"/>.
/// </summary>
/// <param name="x">X position of the Tile on the Tilemap to check.</param>
/// <param name="y">Y position of the Tile on the Tilemap to check.</param>
/// <returns><see cref="Tile"/> placed at the cell.</returns>
public Tile? GetTile(int x, int y) {
if (x < 0 || y < 0 || x >= tilemapSize.X || y >= tilemapSize.Y) {
public Tile? GetTile(Vector2Int location) {
if (location.X < 0 || location.Y < 0 || location.X >= tilemapSize.X || location.Y >= tilemapSize.Y) {
return null;
}
return tileGrid[x, y];
return tileGrid[location.X, location.Y];
}

/// <summary>
/// Gets the <see cref="Tile"/> at a specific location on a <see cref="Tilemap"/>.
/// </summary>
/// <typeparam name="T">The subtype of Tile to return.</typeparam>
/// <param name="location">Location of the Tile on the Tilemap to check.</param>
/// <returns><see cref="Tile"/> of type T placed at the cell.</returns>
public T? GetTile<T>(Vector2Int location) where T : Tile => (T?)GetTile(location.X, location.Y);
/// <summary>
/// Gets the <see cref="Tile"/> at a specific location on a <see cref="Tilemap"/>.
/// </summary>
/// <typeparam name="T">The subtype of Tile to return.</typeparam>
/// <param name="x">X position of the Tile on the Tilemap to check.</param>
/// <param name="y">Y position of the Tile on the Tilemap to check.</param>
/// <returns><see cref="Tile"/> of type T placed at the cell.</returns>
public T? GetTile<T>(int x, int y) where T : Tile => (T?)GetTile(x, y);

/// <summary>
/// Returns whether there is a <see cref="Tile"/> at the location specified.
/// </summary>
Expand All @@ -150,25 +110,18 @@ public class Tile
/// The size of the tile square's edges, in pixels (Default 8)
/// </summary>
protected int size = 8;
/// <summary>
/// Color of the tile.
/// </summary>
public Color color = Color.White;
/// <summary>
/// The rectangle of the tile, used for sprite rendering and collisions.
/// </summary>
public Rectangle rectangle;

public Renderable Renderable;

/// <summary>
/// Creates a <see cref="Tile"/>.
/// </summary>
/// <param name="newBlock">The type of block the new tile should be.</param>
/// <param name="position">The position in a tilemap the tile will be.</param>
public Tile(Block newBlock, Vector2Int position)
public Tile(Block newBlock, Vector2Int position, Layer layer = Layer.ForegroundBlocks)
{
SourceBlock = newBlock;
sprite = SourceBlock.blockSprite;
rectangle = new Rectangle(Globals.gridSize.X * position.X, Globals.gridSize.Y * position.Y, size, size); // HACK: This can probably be done better
Renderable = new Renderable(new Transform(new Vector2(Globals.gridSize.X * position.X, Globals.gridSize.Y * position.Y)), layer, SourceBlock.blockSprite);
}

/// <summary>
Expand All @@ -183,10 +136,10 @@ public void UpdateAdjacencies(Vector2Int position, Tilemap tilemap)
int bitmask = 0; // Using bitmask smoothing, look it up

if (HasSmoothableTile(position + Vector2Int.Up, tilemap)) {
bitmask += 2;
bitmask += 1;
}
if (HasSmoothableTile(position + Vector2Int.Down, tilemap)) {
bitmask += 1;
bitmask += 2;
}
if (HasSmoothableTile(position + Vector2Int.Right, tilemap)) {
bitmask += 4;
Expand All @@ -195,7 +148,7 @@ public void UpdateAdjacencies(Vector2Int position, Tilemap tilemap)
bitmask += 8;
}

sprite = SourceBlock.spriteSheet.OrderedSprites[bitmask];
Renderable.Appearance = SourceBlock.spriteSheet.OrderedSprites[bitmask];
}

/// <summary>
Expand All @@ -211,15 +164,6 @@ private bool HasSmoothableTile(Vector2Int position, Tilemap tilemap)
return otherTile != null;
}

/// <summary>
/// Called from the main draw loop.
/// </summary>
/// <param name="spriteBatch">The spritebatch to draw the tile's sprite on.</param>
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(sprite.Texture, new Vector2(rectangle.X, rectangle.Y), sprite.Bounds, Color.White);
}

/// <summary>
/// If the tile provided is the same type (references the same block) as the current tile.
/// </summary>
Expand Down
30 changes: 4 additions & 26 deletions Blocktest/Code/BuildSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Blocktest.Rendering;

namespace Blocktest
{
public static class BuildSystem
Expand All @@ -7,13 +9,6 @@ public static class BuildSystem
/// </summary>
private static readonly int[,,] currentWorld = new int[Globals.maxX, Globals.maxY, 2];

/// <summary>
/// The method called whenever an object is removed.
/// </summary>
/// <param name="foreground">Whether or not the block to be destroyed is in the foreground.</param>
/// <param name="position">The position of the block to destroy (world coords)</param>
//public static void BreakBlockWorld(bool foreground, Vector2 position) => BreakBlockCell(foreground, Globals.foreground.WorldToCell(position));

/// <summary>
/// The method called whenever an object is removed.
/// </summary>
Expand Down Expand Up @@ -44,14 +39,6 @@ public static void BreakBlockCell(bool foreground, Vector2Int tilePosition)

}

/// <summary>
/// The method called whenever a block is placed.
/// </summary>
/// <param name="toPlace">The block type to place.</param>
/// <param name="foreground">Whether or not the block should be placed in the foreground.</param>
/// <param name="position">The position of the placed block. (World coords)</param>
//public static void PlaceBlockWorld(Block toPlace, bool foreground, Vector2 position) => PlaceBlockCell(toPlace, foreground, Globals.foreground.WorldToCell(position));

/// <summary>
/// The method called whenever a block is placed.
/// </summary>
Expand All @@ -60,27 +47,18 @@ public static void BreakBlockCell(bool foreground, Vector2Int tilePosition)
/// <param name="tilePosition">The position of the placed block. (Grid coords)</param>
public static void PlaceBlockCell(Block toPlace, bool foreground, Vector2Int tilePosition)
{
Tile newTile = new(toPlace, tilePosition);
Tile newTile = new(toPlace, tilePosition, foreground ? Layer.ForegroundBlocks : Layer.BackgroundBlocks);
toPlace.OnPlace(tilePosition, foreground);

if (foreground) {
//newTile.colliderType = Tile.ColliderType.Grid;
Globals.ForegroundTilemap.SetTile(tilePosition, newTile);
currentWorld[tilePosition.X, tilePosition.Y, 0] = toPlace.blockID + 1;
} else if (toPlace.canPlaceBackground) {
newTile.color = new Color(0.5f, 0.5f, 0.5f, 1f);
newTile.Renderable.RenderColor = new Color(0.5f, 0.5f, 0.5f, 1f);
Globals.BackgroundTilemap.SetTile(tilePosition, newTile);
currentWorld[tilePosition.X, tilePosition.Y, 1] = toPlace.blockID + 1;
}
}

/// <summary>
/// The method called whenever a block is placed.
/// </summary>
/// <param name="toPlace">The block type to place.</param>
/// <param name="foreground">Whether or not the block should be placed in the foreground.</param>
/// <param name="tilePosition">The position of the placed block. (Grid coords)</param>
public static void PlaceBlockCell(Block toPlace, bool foreground, Vector2 tilePosition) => PlaceBlockCell(toPlace, foreground, (Vector2Int)tilePosition);

}
}
62 changes: 62 additions & 0 deletions Blocktest/Code/Rendering/Camera.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections;
namespace Blocktest.Rendering;

public sealed class Camera {
private readonly Color _backgroundColor;
private readonly Vector2 _size;

public readonly List<Renderable> RenderedComponents = new();

public Rectangle RenderLocation;
public readonly RenderTarget2D RenderTarget;
public Vector2 Position;

public Camera(Vector2 position, Vector2 size, GraphicsDevice graphicsDevice, Color? backgroundColor = null) {
Position = position;
_size = size;
_backgroundColor = backgroundColor ?? Color.CornflowerBlue;
RenderTarget = new RenderTarget2D(graphicsDevice, (int)size.X, (int)size.Y, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.DiscardContents);
}

public void Draw(GraphicsDevice graphics, SpriteBatch spriteBatch) {
graphics.SetRenderTarget(RenderTarget);
graphics.Clear(_backgroundColor);

spriteBatch.Begin();

foreach (Renderable component in RenderedComponents) {
if (component.Appearance == null) {
continue;
}

Vector2 worldPosition = component.Transform.Position;
Vector2 cameraPosition = worldPosition - Position;

if (worldPosition.X + component.Appearance.Bounds.Width < Position.X &&
worldPosition.X > Position.X + _size.X &&
worldPosition.Y + component.Appearance.Bounds.Height < Position.Y &&
worldPosition.Y > Position.Y + _size.Y) {
continue;
}

Vector2 flippedPosition = new(cameraPosition.X, RenderTarget.Height - cameraPosition.Y - component.Appearance.Bounds.Height);

Rectangle positionBounds = new((int)flippedPosition.X, (int)flippedPosition.Y, (int)(component.Appearance.Bounds.Width * component.Transform.Scale.X),
(int)(component.Appearance.Bounds.Height * component.Transform.Scale.Y));

spriteBatch.Draw(component.Appearance.Texture, positionBounds, component.Appearance.Bounds,
component.RenderColor, component.Transform.Rotation, component.Transform.Origin, SpriteEffects.None, (float)component.Layer / EnumCount);
}

spriteBatch.End();

graphics.SetRenderTarget(null);
}

private static readonly int EnumCount = Enum.GetValues(typeof(Layer)).Length;

public Vector2 CameraToWorldPos(Vector2 mouseState) {
return new((mouseState.X - RenderLocation.X) / RenderLocation.Width * RenderTarget.Width + Position.X, Position.Y + RenderTarget.Height -
(mouseState.Y - RenderLocation.Y) / RenderLocation.Height * RenderTarget.Height);
}
}
32 changes: 32 additions & 0 deletions Blocktest/Code/Rendering/Renderable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Blocktest.Rendering;

public enum Layer {
Top = 0,
Player = 1,
Default = 2,
ForegroundBlocks = 3,
BackgroundBlocks = 4
}

public sealed class Renderable {
public readonly Transform Transform;
public Drawable? Appearance;
public Color RenderColor;
public Layer Layer;

public Renderable(Transform transform, Layer layer = Layer.Default, Drawable? appearance = null, Color? renderColor = null) {
Transform = transform;
Layer = layer;
Appearance = appearance;
RenderColor = renderColor ?? Color.White;
}

public void Draw(SpriteBatch spriteBatch, Vector2 cameraPosition) {
if (Appearance == null) {
return;
}

spriteBatch.Draw(Appearance.Texture, Transform.Position - cameraPosition, Appearance.Bounds, RenderColor, Transform.Rotation, Transform.Origin, Transform.Scale,
SpriteEffects.None, 0);
}
}
15 changes: 15 additions & 0 deletions Blocktest/Code/Rendering/Transform.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Blocktest.Rendering;

public sealed class Transform {
public Vector2 Position;
public float Rotation;
public Vector2 Scale;
public Vector2 Origin;

public Transform(Vector2 position, Vector2? scale = null, float rotation = 0, Vector2? origin = null) {
Position = position;
Scale = scale ?? Vector2.One;
Rotation = rotation;
Origin = origin ?? Vector2.Zero;
}
}
Loading

0 comments on commit 22da09c

Please sign in to comment.