This commit is contained in:
MasterGordon 2022-10-09 22:58:45 +02:00
commit dbbe203db8
31 changed files with 684 additions and 0 deletions

32
.github/workflows/dotnet.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: .NET
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Publish
run: dotnet publish -r linux-x64
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.0
with:
# Artifact name
name: Linux build
# A file, directory or wildcard pattern that describes what to upload
path: bin/Debug/net6.0/linux-x64/publish/asdlteroids

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
obj/

2
Program.cs Normal file
View File

@ -0,0 +1,2 @@
var boulderDash = new BoulderDash();
boulderDash.Run();

BIN
assets/explode.wav Normal file

Binary file not shown.

BIN
assets/font.ttf Normal file

Binary file not shown.

18
assets/level1.json Normal file
View File

@ -0,0 +1,18 @@
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

BIN
assets/shot.wav Normal file

Binary file not shown.

BIN
assets/sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

22
boulder-dash.csproj Normal file
View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>boulder-dash</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishSingleFile>true</PublishSingleFile>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.SDL2-CS" Version="1.0.596-alpha" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="assets/*" />
</ItemGroup>
</Project>

2
readme.md Normal file
View File

@ -0,0 +1,2 @@
# Boulder Dash SDL

60
src/BoulderDash.cs Normal file
View File

@ -0,0 +1,60 @@
using static SDL2.SDL;
class BoulderDash : Game
{
private Context context;
private TileSet tileSet;
public BoulderDash()
{
var window = new Window("Boulder Dash", 800, 600);
var renderer = new Renderer(window);
this.registerScenes();
this.context = new Context(window, renderer);
GameState.sceneManager.ChangeScene("main", context);
this.tileSet = new TileSet(context, "assets.sprites.png", 32);
}
private void registerScenes()
{
GameState.sceneManager.AddScene("main", new MainMenu());
}
protected override void update(double dt)
{
SDL_Event e;
while (SDL_PollEvent(out e) != 0)
{
if (e.type == SDL_EventType.SDL_QUIT)
{
System.Environment.Exit(0);
}
if (e.type == SDL_EventType.SDL_KEYDOWN)
{
if (e.key.keysym.sym == SDL_Keycode.SDLK_ESCAPE)
{
System.Environment.Exit(0);
}
}
}
GameState.keyState = new KeyState();
GameState.sceneManager.GetCurrentScene()?.Update(dt, context);
}
protected override void draw()
{
#if (DEBUG)
SDL_SetWindowTitle(this.context.window.GetWindow(), "Boulder Dash - " + GameState.fps + "FPS");
#endif
this.context.renderer.Clear();
GameState.sceneManager.GetCurrentScene()?.Draw(this.context);
this.context.renderer.DrawTileSet(this.tileSet, 0, 0, 1, 6);
this.context.renderer.DrawTileSet(this.tileSet, 32, 0, 1, 6);
this.context.renderer.DrawTileSet(this.tileSet, 64, 0, 1, 6);
this.context.renderer.DrawTileSet(this.tileSet, 96, 0, 1, 6);
this.context.renderer.DrawTileSet(this.tileSet, 128, 0, 1, 6);
this.context.renderer.DrawTileSet(this.tileSet, 160, 0, 1, 6);
this.context.renderer.Present();
SDL_Delay(1);
}
}

32
src/Controls.cs Normal file
View File

@ -0,0 +1,32 @@
using static SDL2.SDL;
enum Control
{
UP,
DOWN,
LEFT,
RIGHT,
STAY,
}
static class ControlKeyExtension
{
public static SDL_Keycode Key(this Control c)
{
switch (c)
{
case Control.UP:
return SDL_Keycode.SDLK_w;
case Control.DOWN:
return SDL_Keycode.SDLK_s;
case Control.LEFT:
return SDL_Keycode.SDLK_a;
case Control.RIGHT:
return SDL_Keycode.SDLK_d;
case Control.STAY:
return SDL_Keycode.SDLK_LCTRL;
default:
throw new ArgumentException("Invalid control");
}
}
}

25
src/actors/Map.cs Normal file
View File

@ -0,0 +1,25 @@
using Newtonsoft.Json;
class Map : Actor
{
private int[][] map;
public Map(uint width, uint height)
{
this.map = new int[height][];
for (uint i = 0; i < height; i++)
{
this.map[i] = new int[width];
}
}
public Map(string json)
{
var map = JsonConvert.DeserializeObject<int[][]>(json);
if (map == null)
{
throw new Exception("Invalid map");
}
this.map = map;
}
}

12
src/engine/Actor.cs Normal file
View File

@ -0,0 +1,12 @@
abstract class Actor
{
public virtual void Update(double dt, Context context)
{
}
public virtual void Draw(Context context)
{
}
}

27
src/engine/AudioPlayer.cs Normal file
View File

@ -0,0 +1,27 @@
enum Sound
{
}
class AudioPlayer
{
private Dictionary<Sound, byte[]> audioFiles = new();
private ResourceLoader resourceLoader = new();
public AudioPlayer()
{
SDL2.SDL_mixer.Mix_OpenAudio(44100, SDL2.SDL_mixer.MIX_DEFAULT_FORMAT, 2, 2048);
}
public void Register(Sound name, string path)
{
var buffer = resourceLoader.LoadBytes(path);
this.audioFiles.Add(name, buffer);
}
public void Play(Sound name)
{
var buffer = this.audioFiles[name];
var sound = SDL2.SDL_mixer.Mix_QuickLoad_WAV(buffer);
SDL2.SDL_mixer.Mix_PlayChannel((int)name, sound, 0);
}
}

15
src/engine/Context.cs Normal file
View File

@ -0,0 +1,15 @@
struct Context
{
public Context(Window window, Renderer renderer)
{
this.window = window;
this.renderer = renderer;
this.resourceLoader = new ResourceLoader();
this.fontManager = new FontManager(resourceLoader);
}
public Window window { get; }
public Renderer renderer { get; }
public ResourceLoader resourceLoader { get; }
public FontManager fontManager { get; }
}

35
src/engine/FontManager.cs Normal file
View File

@ -0,0 +1,35 @@
using static SDL2.SDL_ttf;
using static SDL2.SDL;
class FontManager
{
private Dictionary<string, IntPtr> fonts = new();
private ResourceLoader resourceLoader;
public FontManager(ResourceLoader resourceLoader)
{
this.resourceLoader = resourceLoader;
if (TTF_Init() != 0)
{
throw new Exception("TTF_Init failed");
}
}
public void RegisterFont(string name, string path, int fontSize)
{
if (fonts.ContainsKey(name)) return;
var res = resourceLoader.LoadToIntPtr(path);
var sdlBuffer = SDL_RWFromConstMem(res.ptr, res.size);
var font = TTF_OpenFontRW(sdlBuffer, 1, fontSize);
if (font == IntPtr.Zero)
{
throw new Exception("TTF_OpenFont failed");
}
fonts.Add(name, font);
}
public IntPtr GetFont(string name)
{
return fonts[name];
}
}

31
src/engine/Game.cs Normal file
View File

@ -0,0 +1,31 @@
abstract class Game
{
public const int TPS = 64;
private Queue<int> fpsQueue = new Queue<int>();
protected abstract void update(double dt);
protected abstract void draw();
public void Run()
{
var tLast = DateTime.Now;
var tAcc = TimeSpan.Zero;
while (true)
{
var dt = DateTime.Now - tLast;
tLast = DateTime.Now;
tAcc += dt;
var fps = (int)(1 / dt.TotalSeconds);
fpsQueue.Enqueue(fps);
while (fpsQueue.Count > fps)
fpsQueue.Dequeue();
GameState.fps = (int)fpsQueue.Average();
while (tAcc >= TimeSpan.FromSeconds(1.0 / TPS))
{
update(dt.TotalSeconds);
tAcc -= TimeSpan.FromSeconds(1.0 / TPS);
}
draw();
}
}
}

6
src/engine/GameState.cs Normal file
View File

@ -0,0 +1,6 @@
static class GameState
{
public static KeyState keyState = new KeyState();
public static SceneManager sceneManager = new SceneManager();
public static int fps = 0;
}

25
src/engine/KeyState.cs Normal file
View File

@ -0,0 +1,25 @@
using System.Runtime.InteropServices;
using static SDL2.SDL;
class KeyState
{
private byte[] keys;
public KeyState()
{
var origArray = SDL_GetKeyboardState(out var arraySize);
this.keys = new byte[arraySize];
Marshal.Copy(origArray, this.keys, 0, arraySize);
}
public bool isPressed(SDL_Keycode keycode)
{
byte scanCode = (byte)SDL_GetScancodeFromKey(keycode);
return (this.keys[scanCode] == 1);
}
public bool isPressed(Control c)
{
return this.isPressed(c.Key());
}
}

107
src/engine/Renderer.cs Normal file
View File

@ -0,0 +1,107 @@
using static SDL2.SDL;
using static SDL2.SDL_ttf;
class Renderer
{
private IntPtr renderer;
private IntPtr font;
private SDL_Color color;
public Renderer(Window window)
{
this.renderer = SDL_CreateRenderer(window.GetWindow(), -1, SDL_RendererFlags.SDL_RENDERER_ACCELERATED);
}
public void Clear()
{
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
}
public void Present()
{
SDL_RenderPresent(renderer);
}
public void DrawRect(double x, double y, int w, int h)
{
this.DrawRect((int)x, (int)y, w, h);
}
public void DrawRect(int x, int y, int w, int h)
{
SDL_Rect rect = new SDL_Rect();
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_RenderFillRect(renderer, ref rect);
}
public void DrawLines(double[][] points)
{
SDL_Point[] sdlPoints = new SDL_Point[points.Length];
for (int i = 0; i < points.Length; i++)
{
sdlPoints[i].x = (int)points[i][0];
sdlPoints[i].y = (int)points[i][1];
}
SDL_RenderDrawLines(renderer, sdlPoints, points.Length);
}
public void SetColor(int r, int g, int b, int a = 255)
{
SDL_SetRenderDrawColor(renderer, (byte)r, (byte)g, (byte)b, (byte)a);
}
public void SetFont(IntPtr font, SDL_Color color)
{
this.font = font;
this.color = color;
}
public void SetFont(IntPtr font, Color color)
{
this.font = font;
this.color = color.toSDLColor();
}
public void DrawText(string text, int x, int y, bool center = false)
{
var surfaceMessage = TTF_RenderText_Solid(this.font, text, this.color);
var texture = SDL_CreateTextureFromSurface(this.renderer, surfaceMessage);
int width;
int height;
SDL_QueryTexture(texture, out _, out _, out width, out height);
SDL_Rect rect = new SDL_Rect();
rect.x = x;
rect.y = y;
rect.w = width;
rect.h = height;
if (center)
{
rect.x -= width / 2;
rect.y -= height / 2;
}
SDL_RenderCopy(this.renderer, texture, IntPtr.Zero, ref rect);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surfaceMessage);
}
public IntPtr GetRaw()
{
return renderer;
}
public IntPtr CreateTextureFromSurface(IntPtr surface)
{
return SDL_CreateTextureFromSurface(renderer, surface);
}
}

View File

@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
class ResourceLoader
{
private string assemblyName;
public ResourceLoader()
{
this.assemblyName = this.GetType().Assembly.GetName().Name!;
}
public string LoadString(string resourceName)
{
using var stream = this.GetType().Assembly.GetManifestResourceStream($"{this.assemblyName}.{resourceName}");
using var reader = new StreamReader(stream!);
return reader.ReadToEnd();
}
public byte[] LoadBytes(string resourceName)
{
using var stream = this.GetType().Assembly.GetManifestResourceStream($"{this.assemblyName}.{resourceName}");
using var memoryStream = new MemoryStream();
stream!.CopyTo(memoryStream);
return memoryStream.ToArray();
}
public (IntPtr ptr, int size) LoadToIntPtr(string resourceName)
{
var bytes = this.LoadBytes(resourceName);
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
var ptr = handle.AddrOfPinnedObject();
return (ptr, bytes.Length);
}
}

12
src/engine/Scene.cs Normal file
View File

@ -0,0 +1,12 @@
abstract class Scene : Actor
{
public virtual void Create(Context context)
{
}
public virtual void Destroy(Context context)
{
}
}

View File

@ -0,0 +1,26 @@
class SceneManager
{
private Dictionary<string, Scene> scenes = new Dictionary<string, Scene>();
private Scene? currentScene;
public void AddScene(string name, Scene scene)
{
scenes.Add(name, scene);
}
public void ChangeScene(string name, Context context)
{
if (currentScene != null)
{
currentScene.Destroy(context);
}
currentScene = scenes[name];
currentScene.Create(context);
}
public Scene? GetCurrentScene()
{
return currentScene;
}
}

46
src/engine/TileSet.cs Normal file
View File

@ -0,0 +1,46 @@
using static SDL2.SDL_image;
using static SDL2.SDL;
class TileSet
{
public readonly IntPtr Texture;
public readonly int Width, Height, Resolution;
public TileSet(Context context, String tileSetPath, int resolution)
{
var res = context.resourceLoader.LoadToIntPtr(tileSetPath);
var sdlBuffer = SDL_RWFromMem(res.ptr, res.size);
var surface = IMG_Load_RW(sdlBuffer, 1);
this.Texture = context.renderer.CreateTextureFromSurface(surface);
SDL_QueryTexture(Texture, out _, out _, out Width, out Height);
SDL_FreeSurface(surface);
this.Resolution = resolution;
}
~TileSet()
{
SDL_DestroyTexture(Texture);
}
}
static class TileSetRendererExtension
{
public static void DrawTileSet(this Renderer renderer, TileSet tileSet, int x, int y, int tileX, int tileY)
{
var src = new SDL_Rect
{
x = tileX * tileSet.Resolution,
y = tileY * tileSet.Resolution,
w = tileSet.Resolution,
h = tileSet.Resolution
};
var dst = new SDL_Rect
{
x = x,
y = y,
w = tileSet.Resolution,
h = tileSet.Resolution
};
SDL_RenderCopy(renderer.GetRaw(), tileSet.Texture, ref src, ref dst);
}
}

33
src/engine/Window.cs Normal file
View File

@ -0,0 +1,33 @@
using static SDL2.SDL;
class Window
{
IntPtr window;
public Window(string title, int w, int h)
{
window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, SDL_WindowFlags.SDL_WINDOW_VULKAN | SDL_WindowFlags.SDL_WINDOW_RESIZABLE);
}
public Window(string title, int x, int y, int w, int h, SDL_WindowFlags flags)
{
this.window = SDL_CreateWindow(title, x, y, w, h, flags);
}
public void Dispose()
{
SDL_DestroyWindow(this.window);
}
public IntPtr GetWindow()
{
return this.window;
}
public (int width, int height) GetSize()
{
int w, h;
SDL_GetWindowSize(this.window, out w, out h);
return (w, h);
}
}

View File

@ -0,0 +1,4 @@
interface Logic
{
public void Update(KeyState keyState, double dx);
}

View File

@ -0,0 +1,4 @@
interface Renderable
{
public void Render(Renderer renderer, double dx);
}

32
src/engine/utils/Color.cs Normal file
View File

@ -0,0 +1,32 @@
using static SDL2.SDL;
class Color
{
int r, g, b, a;
public Color(int r, int g, int b, int a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color(int r, int g, int b)
{
this.r = r;
this.g = g;
this.b = b;
this.a = 255;
}
public SDL_Color toSDLColor()
{
SDL_Color color = new SDL_Color();
color.r = (byte)r;
color.g = (byte)g;
color.b = (byte)b;
color.a = (byte)a;
return color;
}
}

View File

@ -0,0 +1,7 @@
class Point
{
public static double Distance(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
}
}

33
src/scenes/MainMenu.cs Normal file
View File

@ -0,0 +1,33 @@
class MainMenu : Scene
{
double x = 0;
bool dir = true;
public override void Create(Context context)
{
context.fontManager.RegisterFont("MainMenu", "assets.font.ttf", 24);
}
public override void Update(double dt, Context context)
{
x += dir ? 10 : -10;
var windowSize = context.window.GetSize();
if (x + 30 > windowSize.width)
{
dir = false;
}
if (x < 0)
{
dir = true;
}
}
public override void Draw(Context context)
{
var windowSize = context.window.GetSize();
var font = context.fontManager.GetFont("MainMenu");
context.renderer.SetFont(font, new Color(255, 255, 255));
context.renderer.DrawText("Main Menu", windowSize.width / 2, windowSize.height / 2, true);
context.renderer.SetColor(255, 255, 255);
context.renderer.DrawRect(x, 0, 30, 30);
}
}