Change folder structure, add solution to the root.

This commit is contained in:
2023-06-18 17:19:03 +02:00
parent d5f5fb5614
commit 15747d7e9b
51 changed files with 33 additions and 6 deletions

View File

@@ -0,0 +1,40 @@
namespace DaggerFramework.Audio
{
public abstract class AudioBackend : IDisposable
{
public abstract void Initialize();
public abstract void Update();
public abstract void Shutdown();
// BUS
public abstract void CreateBus(string busName);
public abstract void SetBusVolume(string busName, float volume);
public abstract float GetBusVolume(string busName);
// SOUND
public abstract void PlaySound(Sound sound, string bus = "Master", float pitch = 1.0f, float volume = 1.0f);
public void PlaySound(Sound sound, string bus = "Master") => PlaySound(sound, bus, default, default);
public void PlaySoundVariation(Sound sound, string bus = "Master", float pitchVariation = 0.1f, float volume = 1.0f)
{
var maxPitch = 1.0f + pitchVariation;
var minPitch = 1.0f - pitchVariation;
var pitch = (float)_random.NextDouble() * (maxPitch - minPitch) + minPitch;
PlaySound(sound, bus, pitch, volume);
}
// EFFECTS
public abstract void AddBusEffect<T>(T effect, string bus = "Master") where T : AudioEffect;
public void Dispose()
{
Shutdown();
}
private LehmerRandom _random = new LehmerRandom();
}
public struct SoundInstance
{
}
}

View File

@@ -0,0 +1,7 @@
namespace DaggerFramework
{
public class AudioBus
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DaggerFramework
{
public class AudioEffect { }
public class AudioEffectReverb : AudioEffect
{
}
}

View File

@@ -0,0 +1,45 @@
namespace DaggerFramework.Audio
{
public class DummyAudioBackend : AudioBackend
{
public override void AddBusEffect<T>(T effect, string bus = "Master")
{
return;
}
public override void CreateBus(string busName)
{
return;
}
public override float GetBusVolume(string busName)
{
return 0.0f;
}
public override void Initialize()
{
return;
}
public override void SetBusVolume(string busName, float volume)
{
return;
}
public override void Shutdown()
{
return;
}
public override void Update()
{
return;
}
public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1)
{
return;
}
}
}

View File

@@ -0,0 +1,136 @@
using FMOD;
using System.Runtime.InteropServices;
namespace DaggerFramework.Audio
{
public class FmodAudioBackend : AudioBackend
{
public override void Initialize()
{
CreateSystem();
_loadedSounds = new Dictionary<string, FMOD.Sound>();
_channelGroups = new Dictionary<string, ChannelGroup>();
CreateBus("Master");
}
public override void Update() => _system.update();
public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1)
{
int channels = 0;
if (sound.Format == SoundFormat.Mono)
{
channels = 1;
}
else if (sound.Format == SoundFormat.Stereo)
{
channels = 2;
}
var channel = PlaySoundFromBuffer(sound.Path, sound.BufferSize, channels, sound.SampleRate, sound.Buffer, GetChannelGroup(bus));
channel.setVolume(volume);
channel.setPitch(pitch);
}
public override void CreateBus(string busName)
{
ChannelGroup channelGroup;
_system.createChannelGroup(busName, out channelGroup);
_channelGroups.Add(busName, channelGroup);
}
public override void SetBusVolume(string busName, float volume)
{
var channel = GetChannelGroup(busName);
channel.setVolume(volume);
}
public override float GetBusVolume(string busName)
{
float volume;
GetChannelGroup(busName).getVolume(out volume);
return volume;
}
public override void AddBusEffect<T>(T effect, string bus = "Master")
{
var channelGroup = GetChannelGroup(bus);
DSP dsp;
switch (effect)
{
case AudioEffectReverb:
dsp = CreateReverbDsp(effect as AudioEffectReverb);
break;
default:
_system.createDSPByType(DSP_TYPE.UNKNOWN, out dsp);
break;
}
channelGroup.addDSP(0, dsp);
}
private DSP CreateReverbDsp(AudioEffectReverb effectReverb)
{
DSP dsp;
_system.createDSPByType(DSP_TYPE.SFXREVERB, out dsp);
return dsp;
}
private void CreateSystem()
{
var result = FMOD.Factory.System_Create(out _system);
_system.init(128, INITFLAGS.NORMAL, 0);
}
private Channel PlaySoundFromBuffer(string path, int length, int channels, int sampleRate, byte[] buffer, ChannelGroup channelGroup)
{
Channel fmodChannel;
FMOD.Sound fmodSound = IsLoaded(path) ? GetSoundFromLoaded(path) : CreateSound(length, channels, sampleRate, path, buffer);
_system.playSound(fmodSound, channelGroup, false, out fmodChannel);
return fmodChannel;
}
private FMOD.Sound GetSoundFromLoaded(string path) => _loadedSounds[path];
private bool IsLoaded(string path) => _loadedSounds.ContainsKey(path);
private FMOD.Sound CreateSound(int length, int channels, int sampleRate, string path, byte[] buffer)
{
FMOD.Sound sound;
CREATESOUNDEXINFO info = new CREATESOUNDEXINFO()
{
numchannels = channels,
defaultfrequency = sampleRate,
format = SOUND_FORMAT.PCM16,
length = (uint)length,
cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO))
};
var result = _system.createSound(buffer, FMOD.MODE.OPENMEMORY | FMOD.MODE.OPENRAW | FMOD.MODE.CREATESAMPLE, ref info, out sound);
AddToLoaded(path, sound);
return sound;
}
private void AddToLoaded(string path, FMOD.Sound sound) => _loadedSounds.Add(path, sound);
private ChannelGroup GetChannelGroup(string busName)
{
return _channelGroups[busName];
}
public override void Shutdown()
{
throw new NotImplementedException();
}
private FMOD.System _system;
// TODO: use a different key for the dictionary, paths are not good :( (waste of memory lol)
private Dictionary<string, FMOD.Sound> _loadedSounds;
private Dictionary<string, ChannelGroup> _channelGroups;
}
}

View File

@@ -0,0 +1,110 @@
namespace DaggerFramework
{
// Based on https://github.com/ppr-game/PPR/blob/engine/PER.Util/src/Color.cs
/// <summary>
/// A record struct representing a color.
/// </summary>
public record struct Color
{
// TODO: add more HTML colors.
public static Color AliceBlue = new(0xF0F8FF);
public static Color AntiqueWhite = new(0xFAEBD7);
public static Color Aqua = new(0x00FFFF);
public static Color Aquamarine = new(0x7FFFD4);
public static Color Azure = new(0xF0FFFF);
public static Color Beige = new(0xF5F5DC);
public static Color Bisque = new(0xFFE4C4);
public static Color Black = new(0x000000);
public static Color BlanchedAlmond = new(0xFFEBCD);
public static Color Blue = new(0x0000FF);
public static Color BlueViolet = new(0x8A2BE2);
public static Color Brown = new(0xA52A2A);
public static Color BurlyWood = new(0xDEB887);
public static Color CadetBlue = new(0x5F9EA0);
public static Color Chartreuse = new(0x7FFF00);
public static Color Chocolate = new(0xD2691E);
public static Color Coral = new(0xFF7F50);
public static Color CornflowerBlue = new(0x6495ED);
public static Color Cornsilk = new(0xFFF8DC);
public static Color Crimson = new(0xDC143C);
public static Color Cyan = new(0x00FFFF);
public static Color DarkBlue = new(0x00008B);
public static Color DarkCyan = new(0x008B8B);
public static Color White = new(0xFFFFFF);
public static Color Green = new(0x00FF00);
public static Color Red = new(0xFF0000);
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
public int Argb
{
get
{
int c = (ushort)Math.Round(A * 255f);
c <<= 8;
c |= (ushort)Math.Round(R * 255f);
c <<= 8;
c |= (ushort)Math.Round(G * 255f);
c <<= 8;
c |= (ushort)Math.Round(B * 255f);
return c;
}
}
public Color(float r, float g, float b, float a)
{
R = r;
G = g;
B = b;
A = a;
}
public Color(byte r, byte g, byte b, byte a)
{
R = r / 255f;
G = g / 255f;
B = b / 255f;
A = a / 255f;
}
public Color(int hex)
{
A = 1.0f;
B = (hex & 0xFF) / 255.0f;
hex >>= 8;
G = (hex & 0xFF) / 255.0f;
hex >>= 8;
R = (hex & 0xFF) / 255.0f;
}
public Color Lightened(float amount)
{
var result = this;
result.R = result.R + (1.0f - result.R) * amount;
result.G = result.G + (1.0f - result.G) * amount;
result.B = result.B + (1.0f - result.B) * amount;
return result;
}
public Color Darkened(float amount)
{
var result = this;
result.R = result.R * (1.0f - amount);
result.G = result.G * (1.0f - amount);
result.B = result.B * (1.0f - amount);
return result;
}
public System.Drawing.Color ToSystemColor()
{
var result = System.Drawing.Color.FromArgb(Argb);
return result;
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Numerics;
namespace DaggerFramework.Extensions
{
public static class Mat4Extensions
{
public static Matrix4x4 Scale(this Matrix4x4 mat, Vector2 scale)
{
return mat;
}
}
}

2506
DaggerFramework/Source/External/FastNoiseLite.cs vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
/// <summary>
/// https://github.com/YourRobotOverlord/LehmerRandom/blob/master/Lehmer/Random.cs
/// Alternative to System.Random based on the Lehmer algorithm.
/// </summary>
public class LehmerRandom
{
// Stores the seed for the Next methods.
private uint _seed;
/// <summary>
/// Initializes a new instance of the Random class, using a time-dependent default seed value.
/// </summary>
public LehmerRandom()
{
_seed = (uint)System.Environment.TickCount;
}
/// <summary>
/// Initializes a new instance of the Random class, using the specified seed value.
/// </summary>
/// <param name="seed"></param>
public LehmerRandom(int seed)
{
_seed = (uint)seed;
}
/// <summary>
/// Initializes a new instance of the Random class, using the specified seed value.
/// </summary>
/// <param name="seed"></param>
public LehmerRandom(uint seed)
{
_seed = seed;
}
/// <summary>
/// Returns a non-negative random integer.
/// </summary>
/// <returns>Int32</returns>
public int Next()
{
return GetNextInt(0, int.MaxValue);
}
/// <summary>
/// Returns a non-negative random integer that is less than the specified maximum.
/// </summary>
/// <param name="maxValue">Int32</param>
/// <returns>Int32</returns>
public int Next(int maxValue)
{
return GetNextInt(0, maxValue);
}
/// <summary>
/// Returns a random integer that is within a specified range.
/// </summary>
/// <param name="minValue">Int32</param>
/// <param name="maxValue">Int32</param>
/// <returns>Int32</returns>
public int Next(int minValue, int maxValue)
{
return GetNextInt(minValue, maxValue);
}
/// <summary>
/// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0
/// </summary>
/// <returns></returns>
public double NextDouble()
{
return GetNextDouble(0.0, 1.0);
}
/// <summary>
/// Returns a non-negative random floating-point number that is less than the specified maximum.
/// </summary>
/// <param name="maxValue"></param>
/// <returns></returns>
public double NextDouble(double maxValue)
{
return GetNextDouble(0.0, maxValue);
}
/// <summary>
/// Returns a random floating-point number that is within a specified range.
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
public double NextDouble(double minValue, double maxValue)
{
return GetNextDouble(minValue, maxValue);
}
/// <summary>
/// Fills the elements of a specified array of bytes with random numbers.
/// </summary>
/// <param name="buffer">The array to be filled with random numbers.</param>
public void NextBytes(byte[] buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)Next();
}
}
// Returns a random int and sets the seed for the next pass.
internal int GetNextInt(int minValue, int maxValue)
{
_seed = Rnd(_seed);
return ConvertToIntRange(_seed, minValue, maxValue);
}
// Returns a random double and sets the seed for the next pass.
internal double GetNextDouble(double minValue, double maxValue)
{
_seed = Rnd(_seed);
return ConvertToDoubleRange(_seed, minValue, maxValue);
}
/// <summary>
/// Returns a random integer that is within a specified range, using the specified seed value.
/// </summary>
/// <param name="seed"></param>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
public static int RndInt(uint seed, int minValue, int maxValue)
{
return GetInt(seed, minValue, maxValue);
}
/// <summary>
/// Returns a random integer that is within a specified range, using a time-dependent default seed value.
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
public static int RndInt(int minValue, int maxValue)
{
return GetInt((uint)System.Environment.TickCount, minValue, maxValue);
}
/// <summary>
/// Returns a random double that is within a specified range, using the specified seed value.
/// </summary>
/// <param name="seed"></param>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
public static double RndDouble(uint seed, double minValue, double maxValue)
{
return GetDouble(seed, minValue, maxValue);
}
/// <summary>
/// Returns a random double that is within a specified range, using a time-dependent default seed value.
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <returns></returns>
public static double RndDouble(double minValue, double maxValue)
{
return GetDouble((uint)System.Environment.TickCount, minValue, maxValue);
}
internal static int GetInt(uint seed, int minValue, int maxValue)
{
return ConvertToIntRange(Rnd(seed), minValue, maxValue);
}
internal static double GetDouble(uint seed, double minValue, double maxValue)
{
return ConvertToDoubleRange(Rnd(seed), minValue, maxValue);
}
// Converts uint to integer within the given range.
internal static int ConvertToIntRange(uint val, int minValue, int maxValue)
{
return (int)(val % (maxValue - minValue) + minValue);
}
// Converts uint to double within the given range.
internal static double ConvertToDoubleRange(uint val, double minValue, double maxValue)
{
return (double)val / uint.MaxValue * (maxValue - minValue) + minValue;
}
// Pseudo-random number generator based on the Lehmer Algorithm
// and javidx9's implementation:
// https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/OneLoneCoder_PGE_ProcGen_Universe.cpp
internal static uint Rnd(uint seed)
{
seed += 0xe120fc15;
ulong tmp = (ulong)seed * 0x4a39b70d;
uint m1 = (uint)((tmp >> 32) ^ tmp);
tmp = (ulong)m1 * 0x12fad5c9;
return (uint)((tmp >> 32) ^ tmp);
}
}

40
DaggerFramework/Source/Game.cs Executable file
View File

@@ -0,0 +1,40 @@
namespace DaggerFramework
{
public abstract class Game
{
public abstract string ResourceRoot { get; }
/// <summary>
/// Starts the game application.
/// This involves initializing the required subsystems, loading resources from the disk, and then preparing the subsystems for running in the main loop.
/// </summary>
public void Start()
{
Initialize();
LoadResources();
Ready();
Run();
}
/// <summary>
/// Called when it's time to initialize the subsystems.
/// </summary>
public abstract void Initialize();
/// <summary>
/// Called when it's time to load the application's resources, such as images or sounds.
/// </summary>
protected abstract void LoadResources();
/// <summary>
/// Called when it's safe to manipulate with the resources or/and systems.
/// </summary>
protected abstract void Ready();
/// <summary>
/// Called when everything has been readied to start the main loop.
/// </summary>
protected abstract void Run();
/// <summary>
/// Called when the application quits and it's safe to clean up.
/// </summary>
public abstract void Shutdown();
}
}

View File

@@ -0,0 +1,29 @@
namespace DaggerFramework
{
public abstract class InputAction
{
public abstract bool IsDown(InputHandler inputHandler);
public abstract bool IsPressed(InputHandler inputHandler);
}
public class KeyInputAction : InputAction
{
public KeyboardKey Key => _keyboardKey;
public KeyInputAction(KeyboardKey keyboardKey)
{
_keyboardKey = keyboardKey;
}
public override bool IsDown(InputHandler inputHandler)
{
return inputHandler.IsKeyboardKeyDown(_keyboardKey);
}
public override bool IsPressed(InputHandler inputHandler)
{
return inputHandler.KeyboardKeyJustPressed(_keyboardKey);
}
private KeyboardKey _keyboardKey;
}
}

View File

@@ -0,0 +1,159 @@
using System.Numerics;
namespace DaggerFramework
{
public abstract class InputHandler
{
public InputHandler()
{
inputMappings = new Dictionary<string, IEnumerable<InputAction>>();
}
public Action OnInput;
public bool Handled { get => _handled; set => _handled = value; }
public abstract bool IsKeyboardKeyDown(KeyboardKey key);
public abstract bool KeyboardKeyJustPressed(KeyboardKey key);
public abstract Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey);
public abstract int GetCharPressed();
public abstract bool IsActionDown(string action);
public abstract bool IsActionJustPressed(string action);
public abstract bool IsMouseButtonDown(MouseButton button);
public abstract float GetMouseWheelMovement();
public abstract void SetMousePosition(Vector2 position);
public abstract Vector2 GetMousePosition();
public abstract void HideCursor();
public abstract void ShowCursor();
public abstract bool IsCursorHidden();
public void SetAsHandled() => _handled = true;
public void AddInputMapping(string actionName, IEnumerable<InputAction> inputActions)
{
inputMappings.Add(actionName, inputActions);
}
private bool _handled;
protected Dictionary<string, IEnumerable<InputAction>> inputMappings;
}
public enum KeyboardKey
{
Null = 0,
Back = 4,
VolumeUp = 24,
VolumeDown = 25,
Spacebar = 32,
Apostrophe = 39,
Comma = 44,
Minus = 45,
Period = 46,
Slash = 47,
Zero = 48,
One = 49,
Two = 50,
Three = 51,
Four = 52,
Five = 53,
Six = 54,
Seven = 55,
Eight = 56,
Nine = 57,
Semicolon = 59,
Equal = 61,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
Menu = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
LeftBracket = 91,
Backslash = 92,
RightBracket = 93,
Grave = 96,
Escape = 256,
Enter = 257,
Tab = 258,
Backspace = 259,
Insert = 260,
Delete = 261,
Right = 262,
Left = 263,
Down = 264,
Up = 265,
PageUp = 266,
PageDown = 267,
Home = 268,
End = 269,
CapsLock = 280,
ScrollLock = 281,
NumLock = 282,
PrintScreen = 283,
Pause = 284,
F1 = 290,
F2 = 291,
F3 = 292,
F4 = 293,
F5 = 294,
F6 = 295,
F7 = 296,
F8 = 297,
F9 = 298,
F10 = 299,
F11 = 300,
F12 = 301,
KP0 = 320,
KP1 = 321,
KP2 = 322,
KP3 = 323,
KP4 = 324,
KP5 = 325,
KP6 = 326,
KP7 = 327,
KP8 = 328,
KP9 = 329,
KPDecimal = 330,
KPDivide = 331,
KPMultiply = 332,
KPSubstract = 333,
KPAdd = 334,
KPEnter = 335,
KPEqual = 336,
LeftShift = 340,
LeftControl = 341,
LeftAlt = 342,
LeftSuper = 343,
RightShift = 344,
RightControl = 345,
RightAlt = 346,
RightSuper = 347,
KBMenu = 348
}
public enum MouseButton
{
Left = 0,
Right = 1,
Middle = 2,
}
}

View File

@@ -0,0 +1,87 @@
using System.Numerics;
using Raylib_cs;
namespace DaggerFramework
{
public class RaylibInputHandler : InputHandler
{
public override int GetCharPressed()
{
return Raylib.GetCharPressed();
}
public override Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey)
{
Vector2 dir = Vector2.Zero;
if (IsKeyboardKeyDown(leftKey))
dir += new Vector2(-1, 0);
if (IsKeyboardKeyDown(rightKey))
dir += new Vector2(1, 0);
if (IsKeyboardKeyDown(upKey))
dir += new Vector2(0, -1);
if (IsKeyboardKeyDown(downKey))
dir += new Vector2(0, 1);
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
}
public override Vector2 GetMousePosition()
{
return Raylib.GetMousePosition();
}
public override float GetMouseWheelMovement()
{
return Raylib.GetMouseWheelMove();
}
public override void HideCursor()
{
Raylib.HideCursor();
}
public override bool IsActionDown(string action)
{
// throw new NotImplementedException();
// return inputMappings[action].IsPressed();
foreach (InputAction inputAction in inputMappings[action])
if (inputAction.IsDown(this)) return true;
return false;
}
public override bool IsActionJustPressed(string action)
{
foreach (InputAction inputAction in inputMappings[action])
if (inputAction.IsPressed(this)) return true;
return false;
}
public override bool IsKeyboardKeyDown(DaggerFramework.KeyboardKey key)
{
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
OnInput?.Invoke();
return Raylib.IsKeyDown(rayKey);
}
public override bool IsMouseButtonDown(MouseButton button)
{
return Raylib.IsMouseButtonDown((Raylib_cs.MouseButton)button);
}
public override bool KeyboardKeyJustPressed(KeyboardKey key)
{
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
return Raylib.IsKeyPressed(rayKey);
}
public override void SetMousePosition(Vector2 position)
{
Raylib.SetMousePosition((int)position.X, (int)position.Y);
}
public override void ShowCursor() => Raylib.ShowCursor();
public override bool IsCursorHidden() => Raylib.IsCursorHidden();
}
}

View File

@@ -0,0 +1,49 @@
namespace DaggerFramework.Rendering
{
public class ColorRectShader : Shader
{
public override string FragmentSource => @"
#version 450
layout(location = 0) out vec4 fsout_Color;
layout (set = 0, binding = 0) uniform Uniforms
{
vec4 color;
};
void main()
{
fsout_Color = color;
}
";
public override string VertexSource => @"
#version 450
layout(location = 0) in vec2 Position;
// uniform MatrixBlock
// {
// // mat4 model = mat4();
// // mat4 view;
// // mat4 proj;
// mat4 transform = mat4(1.0, 1.0, 1.0, 1.0,
// 1.0, 1.0, 1.0, 1.0,
// 1.0, 1.0, 1.0, 1.0,
// 1.0, 1.0, 1.0, 1.0);
// };
// mat4 transform = mat4(1.0, 1.0, 0.0, 1.0,
// 0.0, 1.0, 1.0, 1.0,
// 0.0, 0.0, 1.0, 1.0,
// 0.0, 0.0, 0.0, 1.0);
mat4 transform = mat4(1.0);
void main()
{
// gl_Position = proj * view * model * vec4(Position, 0, 1);
// gl_Position = vec4(Position, 0, 1);
gl_Position = transform * vec4(Position, 0, 1);
}";
}
}

View File

@@ -0,0 +1,164 @@
using System.Drawing;
using System.Numerics;
using Silk.NET.GLFW;
using Silk.NET.OpenGL;
namespace DaggerFramework.Rendering
{
/// <summary>
/// A standard, OpenGL-based renderer.
/// </summary>
public class StandardRenderer : Renderer
{
/// <inheritdoc />
public override Vector2 WindowSize => throw new NotImplementedException();
/// <inheritdoc />
public override bool ShouldRun => throw new NotImplementedException();
/// <inheritdoc />
public override void Initialize(RendererSettings settings)
{
}
/// <inheritdoc />
public override void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings)
{
CreateWindow(windowSettings);
Initialize(renderSettings);
}
public override void BeginFrame()
{
_glfw.PollEvents();
_gl.Viewport(new Size((int)_windowSize.X, (int)_windowSize.Y));
}
/// <inheritdoc />
public override void EndFrame()
{
// throw new NotImplementedException();
EndFrameUnsafe();
}
/// <inheritdoc />
public override void ClearBackground(Color color)
{
_gl.ClearColor(color.ToSystemColor());
_gl.Clear((uint)ClearBufferMask.ColorBufferBit);
}
/// <inheritdoc />
public override void CreateWindow(string title, Vector2 size) => CreateWindowUnsafe(title, size);
/// <inheritdoc />
public override void Shutdown()
{
CloseWindowUnsafe();
_glfw.Terminate();
}
/// <inheritdoc />
public override void DrawCircle(float radius, Color color)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void DrawDebugText(string text, int fontSize, Color color)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void DrawRectangle(Vector2 size, Color color)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void DrawTexture(int id, Color tint)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override double GetFrameTime()
{
return 0.0;
}
/// <inheritdoc />
public override int LoadTexture(Texture2d texture)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetTargetFps(int fps)
{
return;
}
/// <inheritdoc />
public override void SetTransform(Vector2 position, float rotation = 0)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetTransform(Matrix4x4 transform)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetWindowTitle(string title)
{
SetWindowTitleUnsafe(title);
}
/// <inheritdoc />
public override void SetWindowVSync(bool value)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override bool WindowShouldClose() => WindowShouldCloseUnsafe();
private unsafe void CreateWindowUnsafe(string title, Vector2 size)
{
_glfw = GlfwProvider.GLFW.Value;
_glfw.Init();
_glfw.WindowHint(WindowHintInt.ContextVersionMajor, 4);
_glfw.WindowHint(WindowHintInt.ContextVersionMinor, 6);
_windowHandle = _glfw.CreateWindow((int)size.X, (int)size.Y, title, null, null);
_glfw.MakeContextCurrent(_windowHandle);
_gl = GL.GetApi(_glfw.GetProcAddress);
_glfw.SwapInterval(1);
_windowSize = size;
}
private unsafe void CloseWindowUnsafe() => _glfw.DestroyWindow(_windowHandle);
private unsafe bool WindowShouldCloseUnsafe() => _glfw.WindowShouldClose(_windowHandle);
private unsafe void SetWindowTitleUnsafe(string title) => _glfw.SetWindowTitle(_windowHandle, title);
private unsafe void EndFrameUnsafe()
{
_glfw.SwapBuffers(_windowHandle);
}
public override void DrawSdfText(string text, int fontSize, Color color)
{
throw new NotImplementedException();
}
private GL _gl;
private Glfw _glfw;
private unsafe WindowHandle* _windowHandle;
private Vector2 _windowSize;
}
}

View File

@@ -0,0 +1,162 @@
using System.Numerics;
using System.Text;
using Raylib_cs;
namespace DaggerFramework.Rendering
{
public class RaylibRenderer : Renderer
{
public override Vector2 WindowSize => _windowSize;
public override bool ShouldRun => !WindowShouldClose();
public override void CreateWindow(string title, Vector2 size)
{
Raylib.SetTraceLogLevel(TraceLogLevel.LOG_NONE);
_windowSize = size;
Raylib.InitWindow((int)_windowSize.X, (int)_windowSize.Y, title);
}
public override void SetWindowTitle(string title)
{
Raylib.SetWindowTitle(title);
}
public override void SetWindowVSync(bool value)
{
}
public override void SetTargetFps(int fps)
{
Raylib.SetTargetFPS(fps);
}
public override bool WindowShouldClose()
{
return Raylib.WindowShouldClose();
}
public override void Shutdown()
{
Raylib.CloseWindow();
}
public override void BeginFrame()
{
Raylib.BeginDrawing();
}
public override void EndFrame()
{
Raylib.EndDrawing();
}
public override void ClearBackground(Color color)
{
Raylib.ClearBackground(DaggerColorToRaylibColor(color));
}
public override double GetFrameTime()
{
return (double)Raylib.GetFrameTime();
}
public override void DrawCircle(float radius, Color color)
{
Raylib.DrawCircle((int)_position.X, (int)_position.Y, radius, DaggerColorToRaylibColor(color));
}
public override void SetTransform(Vector2 position, float rotation)
{
_position = position;
_rotation = rotation;
}
public override int LoadTexture(Texture2d texture)
{
Image image = new Image();
unsafe
{
fixed (void* dataPtr = texture.Buffer)
{
image.data = dataPtr;
}
}
image.width = texture.Width;
image.height = texture.Height;
image.mipmaps = 1;
image.format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
Texture2D rayTexture;
rayTexture = Raylib.LoadTextureFromImage(image);
_texturePool.Add(rayTexture);
return _texturePool.Count - 1;
}
public override void DrawTexture(int id, Color tint)
{
Raylib.DrawTextureV(_texturePool[id], _position, DaggerColorToRaylibColor(tint));
}
public override void DrawRectangle(Vector2 size, Color color)
{
// Raylib.DrawRectangleV(_position, size, SystemColorToRaylibColor(color));
Raylib.DrawRectanglePro(new Rectangle()
{
x = _position.X,
y = _position.Y,
width = size.X,
height = size.Y
}, Vector2.Zero, _rotation, DaggerColorToRaylibColor(color));
}
public override void DrawDebugText(string text, int fontSize, Color color)
{
Raylib.DrawText(text, (int)_position.X, (int)_position.Y, fontSize, DaggerColorToRaylibColor(color));
}
public override void DrawSdfText(string text, int fontSize, Color color)
{
throw new NotImplementedException();
}
public override void Initialize(RendererSettings settings)
{
ConfigFlags flags = 0;
// MSAA
flags |= settings.Msaa == Msaa.Msaa4x ? ConfigFlags.FLAG_MSAA_4X_HINT : 0;
// VSync
flags |= settings.UseVSync ? ConfigFlags.FLAG_VSYNC_HINT : 0;
// Fullscreen
flags |= settings.Fullscreen ? ConfigFlags.FLAG_FULLSCREEN_MODE : 0;
Raylib.SetConfigFlags(flags);
}
// TODO
public override void SetTransform(Matrix4x4 transform) { }
public override void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings)
{
CreateWindow(windowSettings);
Initialize(renderSettings);
}
private Raylib_cs.Color DaggerColorToRaylibColor(Color color)
{
return new Raylib_cs.Color { r = (byte)Math.Round(color.R * 255f), g = (byte)Math.Round(color.G * 255f), b = (byte)Math.Round(color.B * 255f), a = (byte)Math.Round(color.A * 255f) };
}
private List<Texture2D> _texturePool = new List<Texture2D>();
private Vector2 _position;
private float _rotation;
private Vector2 _windowSize;
}
}

View File

@@ -0,0 +1,150 @@
using System.Numerics;
namespace DaggerFramework.Rendering
{
/// <summary>
/// An abstract class representing the graphics renderer.
/// </summary>
public abstract class Renderer
{
// INIT
/// <summary>
/// Creates the renderer window and initializes internal resources.
/// </summary>
/// <param name="windowSettings">Settings for the rendering window.</param>
/// <param name="renderSettings">Rendering settings.</param>
public abstract void CreateAndInitialize(WindowSettings windowSettings, RendererSettings renderSettings);
/// <summary>
/// Initializes internal resources. Should be called before other methods.
/// </summary>
/// <param name="settings">Rendering settings.</param>
public abstract void Initialize(RendererSettings settings);
// UTIL
/// <summary>
/// Indicates if the renderer will render the next frame.
/// </summary>
public abstract bool ShouldRun { get; }
// WINDOW
/// <summary>
/// The size of the render window.
/// </summary>
public abstract Vector2 WindowSize { get; }
/// <summary>
/// Creates the window with a given title and size.
/// </summary>
/// <param name="title">Title of the window.</param>
/// <param name="size">Vector2 representing size.</param>
public abstract void CreateWindow(string title, Vector2 size);
public void CreateWindow(WindowSettings windowSettings)
{
CreateWindow(windowSettings.Title, windowSettings.Size);
}
// TODO: use properties for these.
public abstract void SetWindowTitle(string title);
public abstract void SetWindowVSync(bool value);
public abstract void SetTargetFps(int fps);
public abstract bool WindowShouldClose();
public abstract void Shutdown();
// DRAWING
/// <summary>
/// Prepares the renderer for drawing the next frame.
/// </summary>
public abstract void BeginFrame();
/// <summary>
/// Ends rendering of the frame.
/// </summary>
public abstract void EndFrame();
/// <summary>
/// Clears the render canvas and sets a background color.
/// </summary>
/// <param name="color">Background color.</param>
public abstract void ClearBackground(Color color);
public abstract double GetFrameTime();
/// <summary>
/// Loads the texture onto the GPU for later use in DrawTexture or other Texture related methods.
/// </summary>
/// <param name="texture">Texture to load.</param>
/// <returns>A texture handler on the GPU.</returns>
public abstract int LoadTexture(Texture2d texture);
/// <summary>
/// Sets transforms for the next draw operation.
/// </summary>
/// <param name="position">Global transform position.</param>
/// <param name="rotation">Rotation.</param>
public abstract void SetTransform(Vector2 position, float rotation = 0.0f);
/// <summary>
/// Sets the transform for the next draw operation.
/// </summary>
/// <param name="transform">Transform matrix.</param>
public abstract void SetTransform(Matrix4x4 transform);
/// <summary>
/// Draws a filled circle.
/// </summary>
/// <param name="radius">Radius of a circle.</param>
/// <param name="color">Fill color.</param>
public abstract void DrawCircle(float radius, Color color);
/// <summary>
/// Draws a filled rectangle.
/// </summary>
/// <param name="size">Rectangle size.</param>
/// <param name="color">Fill color.</param>
public abstract void DrawRectangle(Vector2 size, Color color);
/// <summary>
/// Draws a debug text with a default font.
/// </summary>
/// <param name="position"></param>
/// <param name="text"></param>
/// <param name="fontSize"></param>
/// <param name="color"></param>
public abstract void DrawDebugText(string text, int fontSize, Color color);
/// <summary>
/// Draws text using a signed distance field font atlas.
/// </summary>
/// <param name="text">Text to draw.</param>
/// <param name="fontSize">Size of the font.</param>
/// <param name="color">Color of the text.</param>
public abstract void DrawSdfText(string text, int fontSize, Color color);
/// <summary>
/// Draws the texture.
/// </summary>
/// <param name="id">Texture handle.</param>
/// <param name="tint">Texture tint.</param>
public abstract void DrawTexture(int id, Color tint);
}
public enum Msaa
{
None,
Msaa2x,
Msaa4x,
Msaa8x
}
public struct RendererSettings
{
public Msaa Msaa;
public bool UseVSync;
public bool Fullscreen;
public static RendererSettings Default => new RendererSettings()
{
UseVSync = true,
};
}
public struct WindowSettings
{
public string Title;
public Vector2 Size = new Vector2(640, 480);
public WindowSettings(string title, Vector2 size)
{
Title = title;
Size = size;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace DaggerFramework.Rendering
{
public abstract class Shader
{
public abstract string FragmentSource { get; }
public abstract string VertexSource { get; }
}
}

View File

@@ -0,0 +1,9 @@
namespace DaggerFramework;
public class Font : Resource
{
public int Size = 16;
public Font(string path, byte[] buffer) : base(path, buffer)
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DaggerFramework;
public class FontLoader : ResourceLoader<Font>
{
public override Font Load(string path)
{
return default;
}
}

View File

@@ -0,0 +1,8 @@
namespace DaggerFramework
{
public abstract class ResourceLoader<T> : IDisposable where T : Resource
{
public void Dispose() { }
public abstract T Load(string path);
}
}

View File

@@ -0,0 +1,47 @@
using StbVorbisSharp;
namespace DaggerFramework
{
public class SoundLoader : ResourceLoader<Sound>
{
public override Sound Load(string path)
{
Vorbis vorbis;
Sound result;
var fileBuffer = File.ReadAllBytes(path);
vorbis = Vorbis.FromMemory(fileBuffer);
vorbis.SubmitBuffer();
if (vorbis.Decoded == 0)
{
vorbis.Restart();
vorbis.SubmitBuffer();
}
var audioShort = vorbis.SongBuffer;
int length = vorbis.Decoded * vorbis.Channels;
byte[] audioData = new byte[length * 2];
for (int i = 0; i < length; i++)
{
if (i * 2 >= audioData.Length) break;
var b1 = (byte)(audioShort[i] >> 8);
var b2 = (byte)(audioShort[i] & 256);
audioData[i * 2] = b2;
audioData[i * 2 + 1] = b1;
}
result = new Sound(path, audioData);
result.Format = (SoundFormat)vorbis.Channels - 1;
result.SampleRate = vorbis.SampleRate;
result.BufferSize = length;
vorbis.Dispose();
return result;
}
}
}

View File

@@ -0,0 +1,22 @@
using StbImageSharp;
namespace DaggerFramework
{
public class Texture2dLoader : ResourceLoader<Texture2d>
{
public override Texture2d Load(string path)
{
ImageResult image;
using (var stream = File.OpenRead(path))
{
image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
}
Texture2d result = new Texture2d(path, image.Data);
result.Width = image.Width;
result.Height = image.Height;
return result;
}
}
}

View File

@@ -0,0 +1,23 @@
namespace DaggerFramework
{
public abstract class Resource : IDisposable
{
public string? Path { get => _path; set => _path = value; }
public byte[]? Buffer { get => _buffer; set => _buffer = value; }
public Resource(string path, byte[] buffer)
{
_path = path;
_buffer = buffer;
}
public void Dispose()
{
Buffer = null;
Path = null;
}
private string? _path;
private byte[]? _buffer;
}
}

View File

@@ -0,0 +1,19 @@
namespace DaggerFramework
{
public class Sound : Resource
{
public SoundFormat Format { get; set; }
public int SampleRate { get; set; }
public int BufferSize { get; set; }
public Sound(string path, byte[] buffer) : base(path, buffer)
{
}
}
public enum SoundFormat
{
Mono,
Stereo
}
}

View File

@@ -0,0 +1,11 @@
namespace DaggerFramework
{
public class Texture2d : Resource
{
public int Width { get; set; }
public int Height { get; set; }
public Texture2d(string path, byte[] buffer) : base(path, buffer)
{
}
}
}

View File

@@ -0,0 +1,18 @@
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public class CircleShape2d : Drawable2d
{
public float Radius { get => _radius; set => _radius = value; }
public Color Color { get => _color; set => _color = value; }
public override void OnDraw(Renderer renderer)
{
renderer.DrawCircle(_radius, _color);
}
private float _radius;
private Color _color;
}
}

View File

@@ -0,0 +1,15 @@
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public abstract class Drawable2d : Entity2d, IDrawable
{
public void Draw(Renderer renderer)
{
renderer.SetTransform(Position);
OnDraw(renderer);
}
public abstract void OnDraw(Renderer renderer);
}
}

View File

@@ -0,0 +1,27 @@
using DaggerFramework.Audio;
namespace DaggerFramework.SceneGraph
{
public class Entity
{
public EntityLayer Layer { get; set; }
public InputHandler Input => Layer.Scene.Input;
public AudioBackend Audio => Layer.Scene.Audio;
public int Id { get; set; }
public void Start() => OnStart();
public void Update(double dt) => OnUpdate(dt);
public void ReceiveInput(InputHandler input) => OnInput(input);
protected virtual void OnStart() { }
protected virtual void OnDestroy() { }
protected virtual void OnUpdate(double dt) { }
protected virtual void OnInput(InputHandler input) { }
public void Destroy()
{
OnDestroy();
Layer?.DestroyEntity(Id);
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Numerics;
namespace DaggerFramework.SceneGraph
{
public class Entity2d : Entity
{
public Vector2 Position { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public interface IDrawable
{
public void Draw(Renderer renderer);
}
}

View File

@@ -0,0 +1,158 @@
using System.Drawing;
using System.Numerics;
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
// TODO: add oneshot parameter.
public class Particles2d : Drawable2d
{
public int MaxParticles => _maxParticles;
public void BeginEmit(ParticleSettings settings)
{
_settings = settings;
_maxParticles = _settings.MaxParticles;
_particleIndex = _maxParticles - 1;
InitializeParticles();
}
public void Restart()
{
CleanupParticles();
InitializeParticles();
}
public override void OnDraw(Renderer renderer)
{
foreach (var particle in _particles)
{
if (!particle.Alive) continue;
var t = particle.LifeTimeRemaining / particle.LifeTime;
var scale = MathUtils.Lerp(_settings.ScaleEnd, _settings.ScaleBegin, t);
var color = MathUtils.LerpColor(_settings.ColorEnd, _settings.ColorBegin, t);
renderer.SetTransform(particle.Position, particle.Rotation);
renderer.DrawRectangle(Vector2.One * scale, color);
}
}
protected override void OnStart()
{
base.OnStart();
// Emit();
}
protected override void OnUpdate(double dt)
{
base.OnUpdate(dt);
var rate = (int)MathUtils.Lerp(1, _maxParticles, _settings.Explosiveness);
for (int i = 0; i < rate; i++)
{
Emit();
}
for (int i = 0; i < _maxParticles; i++)
{
var particle = _particles[i];
if (!particle.Alive) continue;
if (particle.LifeTimeRemaining <= 0.0f)
{
particle.Alive = false;
continue;
}
particle.LifeTimeRemaining -= (float)dt;
particle.Velocity += _settings.Gravity * (float)dt;
particle.Position += particle.Velocity * (float)dt;
particle.Rotation += particle.AngularVelocity * (float)dt;
particle.Velocity -= particle.Velocity * _settings.Damping * (float)dt;
_particles[i] = particle;
}
}
private void InitializeParticles()
{
_particles = new Particle[_maxParticles];
}
private void Emit()
{
Particle particle = _particles[_particleIndex];
if (!(particle.LifeTimeRemaining <= 0)) return;
particle.Alive = true;
particle.Position = GetEmitPosition();
particle.Velocity = _settings.Direction * _settings.LinearVelocity;
particle.Velocity += Vector2.One * _settings.LinearVelocityRandom * ((float)_random.NextDouble() - 0.5f);
particle.AngularVelocity = _settings.AngularVelocity;
particle.AngularVelocity += 1f * _settings.AngularVelocityRandom * ((float)_random.NextDouble() - 0.5f);
particle.LifeTime = _settings.LifeTime;
particle.LifeTimeRemaining = particle.LifeTime;
_particles[_particleIndex] = particle;
_particleIndex = --_particleIndex <= 0 ? _maxParticles - 1 : --_particleIndex;
}
private void CleanupParticles() => Array.Clear(_particles);
private Vector2 GetEmitPosition()
{
// https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image
var angle = _random.NextDouble() * Math.PI * 2;
float radius = (float)Math.Sqrt(_random.NextDouble()) * _settings.EmitRadius;
float x = Position.X + radius * (float)Math.Cos(angle);
float y = Position.Y + radius * (float)Math.Sin(angle);
return new Vector2(x, y);
}
private ParticleSettings _settings;
private int _maxParticles;
private Particle[] _particles;
private int _particleIndex;
// TODO: replace a random function for better distribution and performance.
private LehmerRandom _random = new LehmerRandom();
}
public struct ParticleSettings
{
public ParticleSettings()
{
}
public float EmitRadius;
public float LifeTime;
public float Explosiveness;
public int MaxParticles;
public Vector2 Direction;
public float LinearVelocity;
public float AngularVelocity = 0.0f;
public float AngularVelocityRandom;
public float LinearVelocityRandom;
public Vector2 Gravity;
public float ScaleBegin = 16f;
public float ScaleEnd = 0.0f;
public Color ColorBegin = Color.White;
public Color ColorEnd = Color.Black;
public float Damping = 0.0f;
}
public struct Particle
{
public Particle()
{
}
public Vector2 Position;
public Vector2 Velocity;
public float AngularVelocity;
public float LifeTime;
public float LifeTimeRemaining;
public float Scale;
public float Rotation;
public bool Alive = true;
}
}

View File

@@ -0,0 +1,25 @@
using System.Drawing;
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public class Sprite2d : Drawable2d
{
public Texture2d Texture { get => _texture; set => _texture = value; }
protected override void OnStart()
{
var renderer = Layer.Scene.Renderer;
_texId = renderer.LoadTexture(_texture);
}
public override void OnDraw(Renderer renderer)
{
renderer.DrawTexture(_texId, Color.White);
}
private Texture2d _texture;
private int _texId;
}
}

View File

@@ -0,0 +1,21 @@
using DaggerFramework.Rendering;
using System.Drawing;
namespace DaggerFramework.SceneGraph
{
public class Text2d : Drawable2d
{
public string Contents { get => _contents; set => _contents = value; }
public int FontSize { get => _fontSize; set => _fontSize = value; }
public Color FontColor { get => _fontColor; set => _fontColor = value; }
public override void OnDraw(Renderer renderer)
{
renderer.DrawDebugText(_contents, _fontSize, _fontColor);
}
private string _contents = string.Empty;
private int _fontSize = 16;
private Color _fontColor = Color.White;
}
}

View File

@@ -0,0 +1,58 @@
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public class EntityLayer : Layer
{
public List<Entity> Entities { get => _entities; }
public EntityLayer(List<Entity> entities)
{
_entities = entities;
}
public EntityLayer()
{
_entities = new List<Entity>();
}
public bool AddEntity(Entity entity)
{
entity.Id = Entities.Count;
entity.Layer = this;
Entities.Add(entity);
return true;
}
public void DestroyEntity(int at)
{
_entities.RemoveAt(at);
}
protected override void OnStart()
{
foreach (var entity in _entities)
{
entity.Layer = this;
entity.Start();
}
}
protected override void OnUpdate(double dt)
{
foreach (var entity in _entities)
{
entity.Update(dt);
}
}
protected override void OnDraw(Renderer renderer)
{
foreach (IDrawable drawable in _entities)
{
drawable.Draw(renderer);
}
}
private List<Entity> _entities;
}
}

View File

@@ -0,0 +1,11 @@
namespace DaggerFramework.SceneGraph
{
public interface IMainLoop
{
public void Init();
public void Start();
public bool ShouldStop();
public void Update();
public double DeltaTime { get; }
}
}

View File

@@ -0,0 +1,21 @@
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public abstract class Layer : IDrawable
{
public Scene Scene { get; set; }
public InputHandler Input { get; set; }
public void Draw(Renderer renderer) => OnDraw(renderer);
public void Start() => OnStart();
public void Update(double dt) => OnUpdate(dt);
public void ReceiveInput(InputHandler input) => OnInput(input);
protected virtual void OnStart() { }
protected virtual void OnUpdate(double dt) { }
protected virtual void OnInput(InputHandler input) { }
protected abstract void OnDraw(Renderer renderer);
}
}

View File

@@ -0,0 +1,82 @@
using System.Drawing;
using System.Numerics;
using DaggerFramework.Audio;
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public class Scene : IMainLoop
{
public Renderer Renderer { get => _renderer; set => _renderer = value; }
public InputHandler Input { get => _input; set => _input = value; }
public AudioBackend Audio => _audioBackend;
public double DeltaTime => Renderer.GetFrameTime();
public Scene(Renderer renderer, InputHandler input, AudioBackend audioBackend)
{
_renderer = renderer;
_input = input;
_audioBackend = audioBackend;
_layers = new Dictionary<string, Layer>();
}
public Scene(Renderer renderer)
{
_renderer = renderer;
}
public void Init() => SetupRenderer();
public void Start()
{
foreach (var layer in _layers.Values)
{
layer.Input = _input;
layer.Start();
}
}
public void Update()
{
foreach (var layer in _layers)
{
layer.Value.Update(DeltaTime);
}
Draw();
}
public void AddLayer(string name, Layer layer)
{
layer.Scene = this;
_layers.Add(name, layer);
}
public bool ShouldStop() => Renderer.WindowShouldClose();
private void Draw()
{
Renderer.BeginFrame();
Renderer.ClearBackground(Color.Black);
foreach (var layer in _layers.Values)
{
layer.Draw(_renderer);
}
Renderer.EndFrame();
Audio.Update();
}
private void SetupRenderer()
{
Renderer.CreateWindow("Game", new Vector2(1280, 720));
Renderer.Initialize(new RendererSettings { Msaa = Msaa.Msaa4x, UseVSync = true });
Renderer.SetTargetFps(60);
}
private Dictionary<string, Layer> _layers;
private Renderer _renderer;
private AudioBackend _audioBackend;
private InputHandler _input;
}
}

View File

@@ -0,0 +1,320 @@
using System.Numerics;
using ImGuiNET;
using Raylib_cs;
using DaggerFramework.Rendering;
namespace DaggerFramework.SceneGraph
{
public class ImGuiRenderLayer : Layer
{
protected override void OnDraw(Renderer renderer)
{
Layout();
_controller.Draw(renderer);
}
protected virtual void Layout() { }
protected override void OnStart()
{
_controller = new ImGuiController();
_controller.Load(Scene.Renderer.WindowSize);
}
protected override void OnUpdate(double dt)
{
_controller.Update(dt, Input);
}
private ImGuiController _controller;
}
public class ImGuiController : IDisposable, IDrawable
{
public ImGuiController()
{
_context = ImGui.CreateContext();
ImGui.SetCurrentContext(_context);
}
public void Dispose()
{
ImGui.DestroyContext();
}
public void Load(Vector2 size)
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.AddFontDefault();
Resize(size);
LoadFontTexture();
SetupInput();
ImGui.NewFrame();
}
unsafe void LoadFontTexture()
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height);
// TODO: use engine API instead of Raylib.
Image image = new Image
{
data = pixels,
width = width,
height = height,
mipmaps = 1,
format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
};
_fontTexture = Raylib.LoadTextureFromImage(image);
io.Fonts.SetTexID(new IntPtr(_fontTexture.id));
io.Fonts.ClearTexData();
}
private void SetupInput()
{
ImGuiIOPtr io = ImGui.GetIO();
// Setup config flags
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
// Setup back-end capabilities flags
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.Tab;
io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.Left;
io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.Right;
io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.Up;
io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.Down;
io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.PageUp;
io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.PageDown;
io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.Home;
io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.End;
io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.Insert;
io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.Delete;
io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.Backspace;
io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.Spacebar;
io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.Enter;
io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.Escape;
io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.A;
io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.C;
io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.V;
io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.X;
io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.Y;
io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.Z;
}
public void Resize(Vector2 size)
{
ImGuiIOPtr io = ImGui.GetIO();
io.DisplaySize = size / _scaleFactor;
}
public void Update(double dt, InputHandler input)
{
ImGuiIOPtr io = ImGui.GetIO();
io.DisplayFramebufferScale = Vector2.One;
io.DeltaTime = (float)dt;
UpdateKeyboard(input);
UpdateMouse(input);
ImGui.NewFrame();
}
private void UpdateKeyboard(InputHandler input)
{
ImGuiIOPtr io = ImGui.GetIO();
// Modifiers are not reliable across systems
io.KeyCtrl = io.KeysDown[(int)KeyboardKey.LeftControl] || io.KeysDown[(int)KeyboardKey.RightControl];
io.KeyShift = io.KeysDown[(int)KeyboardKey.LeftShift] || io.KeysDown[(int)KeyboardKey.RightShift];
io.KeyAlt = io.KeysDown[(int)KeyboardKey.LeftAlt] || io.KeysDown[(int)KeyboardKey.RightAlt];
io.KeySuper = io.KeysDown[(int)KeyboardKey.LeftSuper] || io.KeysDown[(int)KeyboardKey.RightSuper];
// Key states
for (int i = (int)KeyboardKey.Spacebar; i < (int)KeyboardKey.KBMenu + 1; i++)
{
io.KeysDown[i] = input.IsKeyboardKeyDown((KeyboardKey)i);
}
// Key input
int keyPressed = input.GetCharPressed();
if (keyPressed != 0)
{
io.AddInputCharacter((uint)keyPressed);
}
}
private void UpdateMouse(InputHandler input)
{
ImGuiIOPtr io = ImGui.GetIO();
// Store button states
for (int i = 0; i < io.MouseDown.Count; i++)
{
io.MouseDown[i] = input.IsMouseButtonDown((MouseButton)i);
}
// Mouse scroll
io.MouseWheel += input.GetMouseWheelMovement();
// Mouse position
Vector2 mousePosition = io.MousePos;
// TODO:
// bool focused = Raylib.IsWindowFocused();
if (io.WantSetMousePos)
{
input.SetMousePosition(mousePosition);
}
else
{
io.MousePos = input.GetMousePosition();
}
// Mouse cursor state
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0 || input.IsCursorHidden())
{
ImGuiMouseCursor cursor = ImGui.GetMouseCursor();
if (cursor == ImGuiMouseCursor.None || io.MouseDrawCursor)
{
input.HideCursor();
}
else
{
input.ShowCursor();
}
}
}
private void Render()
{
ImGui.Render();
}
private void RenderCommandLists(ImDrawDataPtr data)
{
// Scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fbWidth = (int)(data.DisplaySize.X * data.FramebufferScale.X);
int fbHeight = (int)(data.DisplaySize.Y * data.FramebufferScale.Y);
// Avoid rendering if display is minimized or if the command list is empty
if (fbWidth <= 0 || fbHeight <= 0 || data.CmdListsCount == 0)
{
return;
}
Rlgl.rlDrawRenderBatchActive();
Rlgl.rlDisableBackfaceCulling();
Rlgl.rlEnableScissorTest();
data.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale);
for (int n = 0; n < data.CmdListsCount; n++)
{
int idxOffset = 0;
ImDrawListPtr cmdList = data.CmdListsRange[n];
// Vertex buffer and index buffer generated by DearImGui
ImPtrVector<ImDrawVertPtr> vtxBuffer = cmdList.VtxBuffer;
ImVector<ushort> idxBuffer = cmdList.IdxBuffer;
for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++)
{
ImDrawCmdPtr pcmd = cmdList.CmdBuffer[cmdi];
// Scissor rect
Vector2 pos = data.DisplayPos;
int rectX = (int)((pcmd.ClipRect.X - pos.X) * data.FramebufferScale.X);
int rectY = (int)((pcmd.ClipRect.Y - pos.Y) * data.FramebufferScale.Y);
int rectW = (int)((pcmd.ClipRect.Z - rectX) * data.FramebufferScale.Y);
int rectH = (int)((pcmd.ClipRect.W - rectY) * data.FramebufferScale.Y);
Rlgl.rlScissor(rectX, Raylib.GetScreenHeight() - (rectY + rectH), rectW, rectH);
if (pcmd.UserCallback != IntPtr.Zero)
{
// pcmd.UserCallback(cmdList, pcmd);
idxOffset += (int)pcmd.ElemCount;
}
else
{
DrawTriangles(pcmd.ElemCount, idxOffset, (int)pcmd.VtxOffset, idxBuffer, vtxBuffer, pcmd.TextureId);
idxOffset += (int)pcmd.ElemCount;
Rlgl.rlDrawRenderBatchActive();
}
}
}
Rlgl.rlSetTexture(0);
Rlgl.rlDisableScissorTest();
Rlgl.rlEnableBackfaceCulling();
}
private Color GetColor(uint hexValue)
{
Color color = new Color();
color.R = (byte)(hexValue & 0xFF) / 255f;
color.G = (byte)((hexValue >> 8) & 0xFF) / 255f;
color.B = (byte)((hexValue >> 16) & 0xFF) / 255f;
color.A = (byte)((hexValue >> 24) & 0xFF) / 255f;
return color;
}
void DrawTriangleVertex(ImDrawVertPtr idxVert)
{
Color c = GetColor(idxVert.col);
Rlgl.rlColor4ub((byte)Math.Round(c.R * 255f), (byte)Math.Round(c.G * 255f), (byte)Math.Round(c.B * 255f), (byte)Math.Round(c.A * 255f));
Rlgl.rlTexCoord2f(idxVert.uv.X, idxVert.uv.Y);
Rlgl.rlVertex2f(idxVert.pos.X, idxVert.pos.Y);
}
// Draw the imgui triangle data
private void DrawTriangles(uint count, int idxOffset, int vtxOffset, ImVector<ushort> idxBuffer, ImPtrVector<ImDrawVertPtr> idxVert, IntPtr textureId)
{
ushort index = 0;
ImDrawVertPtr vertex;
if (Rlgl.rlCheckRenderBatchLimit((int)count * 3))
{
Rlgl.rlDrawRenderBatchActive();
}
Rlgl.rlBegin(DrawMode.TRIANGLES);
Rlgl.rlSetTexture((uint)textureId);
for (int i = 0; i <= (count - 3); i += 3)
{
index = idxBuffer[idxOffset + i];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
index = idxBuffer[idxOffset + i + 1];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
index = idxBuffer[idxOffset + i + 2];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
}
Rlgl.rlEnd();
}
public void Draw(Renderer renderer)
{
ImGui.Render();
RenderCommandLists(ImGui.GetDrawData());
}
private IntPtr _context;
private Texture2D _fontTexture;
private Vector2 _scaleFactor = Vector2.One;
}
}

View File

@@ -0,0 +1,47 @@
using System.Drawing;
using System.Numerics;
namespace DaggerFramework
{
public static class MathUtils
{
public static float Lerp(float a, float b, double t) => a + (b - a) * (float)t;
public static Color LerpColor(Color colorA, Color colorB, double t)
{
var r = (byte)Lerp(colorA.R, colorB.R, t);
var g = (byte)Lerp(colorA.G, colorB.G, t);
var b = (byte)Lerp(colorA.B, colorB.B, t);
var a = (byte)Lerp(colorA.A, colorB.A, t);
return new Color(r, g, b, a);
}
public static Vector2 LerpVector2(Vector2 v1, Vector2 v2, double t)
{
var x = Lerp(v1.X, v2.X, t);
var y = Lerp(v1.X, v2.Y, t);
return new Vector2(x, y);
}
public static float EaseOutBack(float x)
{
var c1 = 1.70158f;
var c3 = c1 + 1f;
return 1f + c3 * (float)Math.Pow(x - 1f, 3f) + c1 * (float)Math.Pow(x - 1, 2);
}
public static float EaseOutElastic(float x)
{
var c4 = (2f * (float)Math.PI) / 3f;
return x == 0
? 0
: x == 1
? 1
: (float)Math.Pow(2, -10 * x) * (float)Math.Sin((x * 10 - 0.75f) * c4) + 1;
}
}
}