diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ff30ec5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/SkinnerBox.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..d8d64ba --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/SkinnerBox.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/SkinnerBox.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/SkinnerBox.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Entities/Entity.cs b/Entities/Entity.cs new file mode 100644 index 0000000..310ec39 --- /dev/null +++ b/Entities/Entity.cs @@ -0,0 +1,76 @@ +using System.Drawing; +using System.Numerics; +using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Textures; + +namespace SkinnerBox.Entities +{ + public abstract class Entity : IMesh, IPositionInterpolable + { + public RectangleMesh mesh; + public virtual float CenterX { + get { + return X + Width / 2f; + } + set { + X = value - Width / 2f; + } + } + public float X { get; set; } + + public float Y { get; set; } + + public float Width { + get { + return mesh.Width; + } + + set { + mesh.Width = value; + } + } + + public float Height { + get { + return mesh.Height; + } + + set { + mesh.Height = value; + } + } + + public virtual RectangleF HitBox + { + get { + return mesh.Bounds; + } + } + + public Entity(ITexture texture) + { + this.mesh = new RectangleMesh(texture, Color.White); + } + + public (Vector3, Vector2)[] Vertices => mesh.Vertices; + + public uint[] Elements => mesh.Elements; + + public ITexture Texture => mesh.Texture; + + public Color Color { + get { + return this.mesh.Color; + } + set { + this.mesh.Color = value; + } + } + + public virtual void InterpolatePosition(float delta) + { + mesh.X += delta * (X - mesh.X); + mesh.Y += delta * (Y - mesh.Y); + } + } +} \ No newline at end of file diff --git a/Entities/PacketEntity.cs b/Entities/PacketEntity.cs new file mode 100644 index 0000000..e2b158f --- /dev/null +++ b/Entities/PacketEntity.cs @@ -0,0 +1,33 @@ +using System; +using System.Numerics; +using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Textures; +using SlatedGameToolkit.Framework.Utilities.Collections.Pooling; + +namespace SkinnerBox.Entities +{ + public class PacketEntity : Entity, IPositionInterpolable, IPoolable + { + public float velocity; + public PacketEntity(ITexture texture) : base(texture) + { + this.Width = 0.5f; + this.Height = 1f; + Reset(); + } + + public void Reset() + { + this.Y = Game.HEIGHT_UNITS; + this.mesh.Y = this.Y; + this.velocity = 0; + this.X = 0; + this.mesh.X = this.X; + } + + public void Update(double delta) { + this.Y -= (float)(velocity * delta); + } + + } +} \ No newline at end of file diff --git a/Entities/ServerEntity.cs b/Entities/ServerEntity.cs new file mode 100644 index 0000000..f8d93f8 --- /dev/null +++ b/Entities/ServerEntity.cs @@ -0,0 +1,58 @@ +using System.Drawing; +using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Textures; +using SlatedGameToolkit.Framework.Graphics.Window; + +namespace SkinnerBox.Entities +{ + public class ServerEntity : Entity + { + private readonly float length = 4/8f; + public float Speed { get; set; } + public override float CenterX { + get { + return base.X + (length * size) / 2f; + } + + set { + base.X = value - (length * size) / 2f; + } + } + private int size; + public int Size { + get { + return size; + } + + set { + if (this.size != value) { + this.size = value; + this.mesh.TextureBounds = new RectangleF(0, 0, size, 1); + this.Width = value * length; + } + } + } + public override RectangleF HitBox { + get { + RectangleF hitbox = base.HitBox; + hitbox.Width = hitbox.Width * size; + return hitbox; + } + } + public ServerEntity(Texture texture, float initialX, float Y) : base(texture) + { + this.X = (Game.WIDTH_UNITS - this.Width) / 2f; + this.Height = length; + + Size = 1; + + this.Speed = 2f; + + CenterX = initialX; + mesh.X = X; + this.Y = Y; + mesh.Y = this.Y; + } + + } +} \ No newline at end of file diff --git a/Entities/WarningEntity.cs b/Entities/WarningEntity.cs new file mode 100644 index 0000000..8cfc0f5 --- /dev/null +++ b/Entities/WarningEntity.cs @@ -0,0 +1,38 @@ +using SkinnerBox.Utilities.Gameplay; +using SlatedGameToolkit.Framework.Graphics.Textures; +using SlatedGameToolkit.Framework.Utilities.Collections.Pooling; +using System; +using System.Drawing; + +namespace SkinnerBox.Entities +{ + public class WarningEntity : Entity, IPoolable + { + public float LifeTime { get; set; } + public TransitionValue aliveTime; + + public WarningEntity(ITexture texture) : base(texture) { + this.Width = 2; + this.Height = 2; + Reset(); + } + public void Reset() + { + LifeTime = 0; + X = 0 - Width; + mesh.X = X; + Y = Game.HEIGHT_UNITS - Height; + mesh.Y = Y; + aliveTime.HardSet(0); + this.Color = Color.Red; + } + + public override void InterpolatePosition(float delta) { + aliveTime.InterpolatePosition(delta); + float prog = (aliveTime.Value / LifeTime); + if (prog > 1) prog = 1; + this.Color = Color.FromArgb((int)(byte.MaxValue * (1f - prog)), this.Color); + base.InterpolatePosition(delta); + } + } +} \ No newline at end of file diff --git a/Game.cs b/Game.cs new file mode 100644 index 0000000..e196f8d --- /dev/null +++ b/Game.cs @@ -0,0 +1,24 @@ +using System; +using SkinnerBox.States.Gameplay; +using SkinnerBox.States.Main; +using SlatedGameToolkit.Framework; +using SlatedGameToolkit.Framework.AssetSystem; +using SlatedGameToolkit.Framework.Graphics; +using SlatedGameToolkit.Framework.Graphics.Window; +using SlatedGameToolkit.Framework.Loaders; + +namespace SkinnerBox +{ + class Game + { + public const int WIDTH_UNITS = 8; + public const int HEIGHT_UNITS = 8; + static void Main(string[] args) + { + GameEngine.targetFPS = 0; + GameEngine.UpdatesPerSecond = 20; + GameEngine.Ignite(new MenuState()); + } + + } +} diff --git a/SkinnerBox.csproj b/SkinnerBox.csproj new file mode 100644 index 0000000..aa71130 --- /dev/null +++ b/SkinnerBox.csproj @@ -0,0 +1,12 @@ + + + + + + + + Exe + netcoreapp3.1 + + + diff --git a/States/Gameplay/GamePlayState.cs b/States/Gameplay/GamePlayState.cs new file mode 100644 index 0000000..515843f --- /dev/null +++ b/States/Gameplay/GamePlayState.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Numerics; +using SDL2; +using SkinnerBox.Entities; +using SlatedGameToolkit.Framework.AssetSystem; +using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Textures; +using SlatedGameToolkit.Framework.Graphics.Window; +using SlatedGameToolkit.Framework.Input.Devices; +using SlatedGameToolkit.Framework.StateSystem; +using SlatedGameToolkit.Framework.StateSystem.States; +using SlatedGameToolkit.Framework.Utilities.Collections.Pooling; + +namespace SkinnerBox.States.Gameplay +{ + public class GamePlayState : IState + { + private MeshBatchRenderer renderer; + private AssetManager assets; + private StateManager stateManager; + private Random random; + + //Cursor information + private float widthFactor, heightFactor; + private float leftXPos; //Last left click position + + + //Entities + private ServerEntity server; + + private ObjectPool warningPool; + private List activeWarnings = new List(); + + private ObjectPool packetPool; + private List activePackets = new List(); + private SpawnInfo packetSpawnInfo; + + public GamePlayState(MeshBatchRenderer renderer, AssetManager asset) + { + this.assets = asset; + this.renderer = renderer; + packetPool = new ObjectPool(CreatePacket); + warningPool = new ObjectPool(createWarning); + } + + public bool Activate() + { + Keyboard.keyboardUpdateEvent += KeyInputListener; + Mouse.mouseUpdateEvent += MouseInput; + leftXPos = 0.5f * Game.WIDTH_UNITS; + server = new ServerEntity((Texture)assets["serverunit.png"], leftXPos, 0.1f); + random = new Random(); + + packetSpawnInfo = new SpawnInfo(2, 1, (float)(random.NextDouble() * Game.WIDTH_UNITS), 1f, 0.2f); + return true; + } + + public PacketEntity CreatePacket() { + return new PacketEntity((Texture)assets["packet.png"]); + } + + public WarningEntity createWarning() { + return new WarningEntity((Texture)assets["warning.png"]); + } + + public bool Deactivate() + { + Keyboard.keyboardUpdateEvent -= KeyInputListener; + Mouse.mouseUpdateEvent -= MouseInput; + return true; + } + + public void Deinitialize() + { + + } + + public string getName() + { + return "GamePlayState"; + } + + public void Initialize(StateManager manager) + { + this.stateManager = manager; + int vw, vh, vx, vy; + WindowContextsManager.CurrentGL.GetViewport(out vx, out vy, out vw, out vh); + CalculateScaleFactors(vw, vh); + + } + + public void Render(double delta) + { + renderer.Begin(Matrix4x4.Identity, delta); + foreach (WarningEntity warn in activeWarnings) + { + renderer.Draw(warn); + } + foreach (PacketEntity packet in activePackets) + { + renderer.Draw(packet); + } + renderer.Draw(server); + renderer.End(); + } + + public void Update(double timeStep) + { + #region ServerUpdate + if (Mouse.LeftButtonPressed) { + leftXPos = widthFactor * Mouse.X; + } + + if (leftXPos < server.CenterX) + { + server.CenterX -= ((float)timeStep * server.Speed); + if (server.CenterX < leftXPos) server.CenterX = leftXPos; + } else if (leftXPos > server.CenterX) + { + server.CenterX += ((float)timeStep * server.Speed); + if (server.X > leftXPos) server.CenterX = leftXPos; + } + #endregion + #region PacketUpdate + packetSpawnInfo.timeElapsed += (float) timeStep; + if (packetSpawnInfo.timeElapsed >= packetSpawnInfo.period) { + packetSpawnInfo.timeElapsed = 0; + //do spawning + for(int i = 0; i < packetSpawnInfo.perSpawn; i++) { + PacketEntity packet = packetPool.Retrieve(); + packet.CenterX = packetSpawnInfo.batchLocation; + packet.Y = i * packet.Height + packetSpawnInfo.distanceBetween + Game.HEIGHT_UNITS + packetSpawnInfo.speed * (2/3f); + packet.velocity = packetSpawnInfo.speed; + packet.Color = Color.Blue; + activePackets.Add(packet); + } + + //Spawn Warning + WarningEntity warning = warningPool.Retrieve(); + warning.CenterX = packetSpawnInfo.batchLocation; + warning.LifeTime = packetSpawnInfo.period * (2/3f); + warning.Y = Game.HEIGHT_UNITS - warning.Height; + activeWarnings.Add(warning); + + //Prepare next batch + packetSpawnInfo.batchLocation = (float)(random.NextDouble() * Game.WIDTH_UNITS); + } + + for (int i = 0; i < activePackets.Count; i++) + { + PacketEntity packet = activePackets[i]; + packet.Update(timeStep); + if (packet.HitBox.IntersectsWith(server.HitBox) && packet.velocity > 0) { + packet.velocity *= -2f; + packet.Color = Color.Cyan; + } + if (packet.Y <= 0 - packet.Height) { + packetPool.Release(packet); + activePackets.RemoveAt(i); + i--; + continue; + } + if (packet.Y >= Game.HEIGHT_UNITS && packet.velocity < 0) { + packetPool.Release(packet); + activePackets.RemoveAt(i); + i--; + } + } + #endregion + #region WarningCleanup + for (int i = 0; i < activeWarnings.Count; i++) + { + WarningEntity warn = activeWarnings[i]; + warn.aliveTime.Value += (float) timeStep; + if (warn.aliveTime.Value >= warn.LifeTime) { + warningPool.Release(warn); + activeWarnings.RemoveAt(i); + i--; + } + } + #endregion + } + + public void KeyInputListener(SDL.SDL_Keycode keycode, bool down) { + } + + public void MouseInput(bool leftDown, bool rightDown, bool middle, int x, int y, int scrollX, int scrollY) { + + } + + public void WindowResize(int width, int height) { + WindowContextsManager.CurrentWindowContext.GetDrawableDimensions(); + int vw, vh, vx, vy; + WindowContextsManager.CurrentGL.GetViewport(out vx, out vy, out vw, out vh); + CalculateScaleFactors(vw, vh); + } + + private void CalculateScaleFactors(float width, float height) { + this.widthFactor = Game.WIDTH_UNITS * (1f / width); + this.heightFactor = Game.HEIGHT_UNITS * (1f / height); + } + } +} \ No newline at end of file diff --git a/States/Gameplay/SpawnInfo.cs b/States/Gameplay/SpawnInfo.cs new file mode 100644 index 0000000..336dae0 --- /dev/null +++ b/States/Gameplay/SpawnInfo.cs @@ -0,0 +1,21 @@ +namespace SkinnerBox.States.Gameplay +{ + public struct SpawnInfo + { + public float timeElapsed; + public int period; + public int perSpawn; + public float batchLocation; + public float distanceBetween; + public float speed; + + public SpawnInfo(int period, int perSpawn, float location, float speed, float distance) { + timeElapsed = 0; + this.period = period; + this.perSpawn = perSpawn; + this.batchLocation = location; + this.distanceBetween = distance; + this.speed = speed; + } + } +} \ No newline at end of file diff --git a/States/Main/MenuState.cs b/States/Main/MenuState.cs new file mode 100644 index 0000000..ad1f610 --- /dev/null +++ b/States/Main/MenuState.cs @@ -0,0 +1,124 @@ +using System; +using System.Drawing; +using System.Numerics; +using SDL2; +using SkinnerBox.States.Gameplay; +using SlatedGameToolkit.Framework.AssetSystem; +using SlatedGameToolkit.Framework.Graphics; +using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Text; +using SlatedGameToolkit.Framework.Graphics.Textures; +using SlatedGameToolkit.Framework.Graphics.Window; +using SlatedGameToolkit.Framework.Input.Devices; +using SlatedGameToolkit.Framework.Loaders; +using SlatedGameToolkit.Framework.StateSystem; +using SlatedGameToolkit.Framework.StateSystem.States; + +namespace SkinnerBox.States.Main +{ + public class MenuState : IState + { + private StateManager manager; + WindowContext context; + AssetManager assets; + Camera2D camera; + MeshBatchRenderer renderer; + BitmapFont titleFont, boldFont; + RectangleMesh serverUnit; + + public bool Activate() + { + Keyboard.keyboardUpdateEvent += KeyInput; + return true; + } + + public bool Deactivate() + { + Keyboard.keyboardUpdateEvent -= KeyInput; + return true; + } + + public void Deinitialize() + { + this.renderer.Dispose(); + this.assets.UnloadAll(); + } + + public string getName() + { + return "Main"; + } + + public void Initialize(StateManager manager) + { + this.manager = manager; + this.manager.backgroundColour = Color.White; + this.context = new WindowContext("You Are the Website", width: 640, height: 640, options: SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN); // Creates the window context. + this.assets = new AssetManager(); + this.assets.DefaultPathModifier = (p) => "resources/" + p; + this.assets.Loaders.TryAdd("png", TextureLoader.Load2DTexture); + this.camera = new Camera2D(Game.WIDTH_UNITS, Game.HEIGHT_UNITS); + this.camera.Position = new Vector2(Game.WIDTH_UNITS / 2, Game.HEIGHT_UNITS / 2); + this.camera.MoveTo = this.camera.Position; + this.renderer = new MeshBatchRenderer(camera); + + //Add additional states + manager.AddState(new GamePlayState(renderer, this.assets)); + + //Load assets + this.assets.Load("serverunit.png"); + this.assets.Load("packet.png"); + this.assets.Load("warning.png"); + + //Set up title TTF + this.titleFont = new BitmapFont("resources/BigShouldersDisplay-Regular.ttf", textureSizes: 512); + this.titleFont.PixelHeight = 120; + this.titleFont.PixelsPerUnitHeight = 80; + this.titleFont.PixelsPerUnitWidth = 80; + this.titleFont.PrepareCharacterGroup("You Are the Website.".ToCharArray()); + this.titleFont.PixelHeight = 40; + this.titleFont.PrepareCharacterGroup("By: Reslate".ToCharArray()); + + //Set up bold TTF + boldFont = new BitmapFont("resources/BigShouldersDisplay-Black.ttf", textureSizes: 512); + boldFont.PixelHeight = 60; + boldFont.PixelsPerUnitWidth = 80; + boldFont.PixelsPerUnitHeight = 80; + boldFont.PrepareCharacterGroup("Press any key to start...".ToCharArray()); + + //Set up icon + Texture serverUnitTex = (Texture)assets["serverunit.png"]; + serverUnitTex.SetNearestFilter(true, true); + this.serverUnit = new RectangleMesh(new RectangleF(Game.WIDTH_UNITS/2 - 0.75f, Game.HEIGHT_UNITS * 0.75f - 0.75f, 1.5f, 1.5f), serverUnitTex, Color.White); + + this.context.Shown = true; + } + + public void Render(double delta) + { + renderer.Begin(Matrix4x4.Identity, delta); + this.titleFont.PixelHeight = 120; + this.titleFont.WriteLine(renderer, 0.02f, 0.02f, "You Are the Website.", Color.Black); + + this.titleFont.PixelHeight = 40; + this.titleFont.WriteLine(renderer, 0, 1.2f, "By: Reslate", Color.Gray); + + renderer.Draw(serverUnit); + + this.boldFont.WriteLine(renderer, 1.15f, Game.HEIGHT_UNITS / 2, "Press any key to start...", Color.Black); + renderer.End(); + } + + public void Update(double timeStep) + { + } + + public void KeyInput(SDL.SDL_Keycode keys, bool pressed) + { + if (pressed) + { + manager.ChangeState("GamePlayState"); + } + } + } +} \ No newline at end of file diff --git a/Utilities/TransitionValue.cs b/Utilities/TransitionValue.cs new file mode 100644 index 0000000..68a1e5d --- /dev/null +++ b/Utilities/TransitionValue.cs @@ -0,0 +1,31 @@ +using SlatedGameToolkit.Framework.Graphics.Render; + +namespace SkinnerBox.Utilities.Gameplay +{ + public struct TransitionValue : IPositionInterpolable + { + private float current; + private float value; + + public float Value + { + get + { + return current; + } + set + { + this.value = value; + } + } + public void InterpolatePosition(float delta) + { + this.current += (value - current) * delta; + } + + public void HardSet(float value) { + current = value; + this.value = value; + } + } +} \ No newline at end of file diff --git a/resources/BigShouldersDisplay-Black.ttf b/resources/BigShouldersDisplay-Black.ttf new file mode 100644 index 0000000..7047626 Binary files /dev/null and b/resources/BigShouldersDisplay-Black.ttf differ diff --git a/resources/BigShouldersDisplay-Regular.ttf b/resources/BigShouldersDisplay-Regular.ttf new file mode 100644 index 0000000..708fff5 Binary files /dev/null and b/resources/BigShouldersDisplay-Regular.ttf differ diff --git a/resources/packet.png b/resources/packet.png new file mode 100644 index 0000000..d86f0d6 Binary files /dev/null and b/resources/packet.png differ diff --git a/resources/serverunit.png b/resources/serverunit.png new file mode 100644 index 0000000..01a4ad8 Binary files /dev/null and b/resources/serverunit.png differ diff --git a/resources/warning.png b/resources/warning.png new file mode 100644 index 0000000..36cd499 Binary files /dev/null and b/resources/warning.png differ