Skip to content
51 changes: 32 additions & 19 deletions examples/readme.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,56 @@
# How to use Quack! Engine
# How to use Quack! Engine examples 🦆

This document provides a overview of how to use the Quack! game engine by running or checking out various examples.
> You need to have .NET 10.0 SDK installed to run theses examples as [dotnet run app.cs].
This document provides an overview of how to run and explore the Quack! game engine examples.
> You need to have **.NET 10.0 SDK** installed to run theses examples via [dotnet run app.cs].

To run a example, run the following command in the terminal:
## Prerequisites

- [.NET 10.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)
- SDL and its dependencies should be installed (see [Setup section](../readme.md#setup)).

> Quack! examples are single-file C# scripts, so most IDEs cannot run them directly.
>
> VS Code provides intellisense but requires running via the terminal.

## Running Examples

Run the example directly in your terminal:

#### Window
```bash
# On Windows
dotnet ./examples/windows/window.cs
```

# On Linux
chmod +x ./examples/windows/window.cs
#### Linux
```bash
chmod +x ./examples/windows/window.cs # only needed once
./examples/windows/window.cs
```

You can also open the example in your preferred IDE (e.g., Visual Studio, Rider, etc.).
> Any IDE doesn't support single-file execution, so you will need to run as CLI.
Alternatively, you can open the example in VS Code for editing and exploring the code, but
execution must be done via the terminal as shown above.

> Visual Code has intellisense support for single-file but can't run it directly.
> For more details on single-file, see [dotnet run app.cs]

## Examples

Here the examples are categorized by module. Each example is a C# file that demonstrates a specific feature or capability of the Quack! engine.
Each example demonstrates a specific feature or capability of the Quack! engine.

## Windows

- [Progress Bar](./windows/progress.cs) - Demonstrates how to use the window's taskbar progress bar.
- [Window](./windows/window.cs) - Demonstrates how to create a simple window.
- [Window](./windows/window.cs) - Show how to create a simple window.
- [Progress Bar](./windows/progress.cs) - Show how to use the window's taskbar progress bar.

## Graphics

- [Debug](./graphics/debug.cs) - Demonstrates how to draw text for debugging purposes.
- [Drawable](./graphics/drawable.cs) - Demonstrates how to use the IDrawable interface.
- [Sine Wave](./graphics/sinewave.cs) - Demonstrates how to render a sine wave.
- [Triangle](./graphics/triangle.cs) - Demonstrates how to draw a triangle using vertices.
- [Debug](./graphics/debug.cs) - Draw text for debugging purposes.
- [Drawable](./graphics/drawable.cs) - Demonstrates the `IDrawable` interface.
- [Sine Wave](./graphics/sinewave.cs) - Clear the screen with a sine wave pattern.
- [Triangle](./graphics/triangle.cs) - Draw a triangle using vertices and indices.

## Inputs

- [Keyboard Input](./inputs/keyboard.cs) - Demonstrates how to handle keyboard input.
- [Mouse Input](./inputs/mouse.cs) - Demonstrates how to handle mouse input.
- [Keyboard Input](./inputs/keyboard.cs) - Handle keyboard input events.
- [Mouse Input](./inputs/mouse.cs) - Handle mouse input events.

[dotnet run app.cs]: https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/
3 changes: 1 addition & 2 deletions quack.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
<File Path="examples/windows/window.cs" />
</Folder>
<Folder Name="/sdl3/">
<File Path="sdl3/build.cs" />
<File Path="sdl3/dockerfile" />
<File Path="sdl3/setup.cs" />
</Folder>
<Folder Name="/sandbox/">
<File Path="sandbox/Directory.Packages.props" />
Expand Down
99 changes: 57 additions & 42 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
# Quack! 🦆 ![NuGet Version](https://img.shields.io/nuget/vpre/KappaDuck.Quack?style=flat&label=stable)

Quack! is a fast, lightweight, and user-friendly 2D/3D game engine built on [SDL] and its extensions ([SDL_image], [SDL_mixer], [SDL_ttf]).
It is designed for modern .NET 9+ games and applications. It provides a clean, flexible and intuitive API that simplifies game development by hiding low-level complexities.
It offers a range of features such as rendering ([SDL_renderer] and [SDL_gpu]), input handling, audio playback, resource management, window management, and more.
Quack! is a fast, lightweight, and code-first 2D/3D game engine built on [SDL] and its extensions ([SDL_image], [SDL_mixer], [SDL_ttf]).
It is designed for modern .NET 9+ games and desktop apps, providing a clean and flexible API that abstracts low-level platform details.

Quack! offers a wide range of features, including:

- Rendering 2D/3D graphics using SDL's rendering API ([SDL_renderer] and [SDL_gpu])
- Window management and event handling
- Input handling for keyboard, mouse, and game controllers
- Audio playback and management
- And much more!

## Quack & SDL compatibility

Below is a list of Quack! versions and their compatible SDL versions:
Quack! is shipped with native binaries of SDL and its extensions.
Below is a compatibility table showing which versions of SDL are used in each Quack! release.

| Quack! version | SDL version | SDL_image version | SDL_mixer version | SDL_ttf version | SDL_mixer version |
| :------------: | :---------: | :---------------: | :---------------: | :-------------: | :---------------: |
| `source` | `3.4.0` | `3.2.6` | `N/A` | `3.2.2` | `N/A` |
| `0.3.0` | `3.2.30` | `3.2.6` | `N/A` | `3.2.2` | `N/A` |
| `0.2.0` | `3.2.28` | `3.2.4` | `N/A` | `3.2.2` | `N/A` |
| `0.1.0` | `3.2.18` | `N/A` | `N/A` | `N/A` | `N/A` |
| Quack! version | SDL version | SDL_image version | SDL_ttf version | SDL_mixer version |
| :------------: | :---------: | :---------------: | :-------------: | :---------------: |
| `source` | `3.4.0` | `3.2.6` | `3.2.2` | `N/A` |
| `0.3.0` | `3.2.30` | `3.2.6` | `3.2.2` | `N/A` |
| `0.2.0` | `3.2.28` | `3.2.4` | `3.2.2` | `N/A` |
| `0.1.0` | `3.2.18` | `N/A` | `N/A` | `N/A` |

> The current Quack! development can update the SDL dependencies many times before the release.
> :warning: During active development, SDL dependencies may be updated frequently. :warning:

## Cross-platform support

Quack! currently supports Windows and Linux.

The API is designed to be cross-platform thanks to SDL's abstraction layer. So porting to other platforms should be straightforward.
The API is designed to be cross-platform thanks to SDL's abstraction layer, making porting to other platforms straightforward.

> Other platforms may be considered such as Android and WebAssembly in the future, but there are no current plans for them.
> Other platforms such as Android or WebAssembly may be supported in the future, but there are no immediate plans.

## Installation

Quack! is available as a NuGet package. You can install it using the following command:
Quack! is available on [NuGet]. Install it via the .NET CLI:

```bash
dotnet add package KappaDuck.Quack -v 0.3.0
```

or by adding the following line to your `.csproj` file:
or add it directly to your `.csproj` file:

```xml
<PackageReference Include="KappaDuck.Quack" Version="0.3.0" />
```

or by using the NuGet Package Manager in Visual Studio or JetBrains Rider.
You can also install via the NuGet Package Manager in your Visual Studio or JetBrains Rider.

## Usage

A simple example of how to use Quack! to create a window:
A minimal example creating a resizable window and handling quit events:

```csharp
using KappaDuck.Quack.Events;
Expand All @@ -66,53 +74,55 @@ while (window.IsOpen)
}
```

You can find more examples in the [Examples] directory.
More examples can be found in the [Examples] directory.

## Development
## Development & Playground

To build Quack! from source, you will need the following tools installed:
You can build Quack! from source or run quick experiments using a playground file.

### Prerequisites

- [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)
- [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)

> The SDK includes everything you need to build and run .NET applications on your machine.
> The SDK includes everything needed to build and run .NET applications.

### Setup

After installing the prerequisites, you can set up the project by following these steps:

Clone the repository
1. Clone the repository
```bash
git clone https://github.com/KappaDuck/quack.git
```
Navigate to the project directory
```bash
cd quack
```

Install SDL and its extensions using the provided script
> Make sure you have .NET 10.0 SDK installed, to do [dotnet run app.cs].
2. Install SDL and its extensions

#### Windows
```bash
# On Windows:
dotnet ./SDL3/setup.cs
```

# On Linux:
chmod +x ./SDL3/setup.cs # Make the script executable. No need to do this every time.
#### Linux
```bas
chmod +x ./SDL3/setup.cs
./SDL3/setup.cs
```

Open the solution file in your preferred IDE (e.g., Visual Studio, Rider, etc.):
> Any IDE doesn't support single-file execution, so you will need to run as CLI.
> The setup script installs SDL and all required extensions. On linux, you only need to make it executable once.

### Build & Run

Open the solution in your preferred IDE (e.g., Visual Studio, Rider, VS Code).
> Most IDEs do not support running single-file scripts directly, so you'll need to run the playground file from the command line.
>
> Visual Code have intellisense support for single-file but can't run it directly.
> VS Code provides intellisense but cannot run the playground file directly.

### Playground file (quack.playground.cs)

To test any features, you can run the examples provided in the [Examples] directory or create a file named `quack.playground.cs` at the root and run it directly.
> The `quack.playground.cs` file is ignored by git, so you can use it to test your code without affecting the repository.
The playground allows you to experiment with windows, input, rendering, and more without modifying the main source code.

You can use the following code snippet as a starting point for your `quack.playground.cs` file:
Create a file named `quack.playground.cs` at the root of the repository with the following content:
> This file is ignored by git, so it's safe to use for your experiments.

```csharp
#!/usr/bin/env dotnet
Expand Down Expand Up @@ -144,18 +154,22 @@ while (window.IsOpen)

```

### Run the playground

#### Windows
```bash
# On Windows:
dotnet ./quack.playground.cs
```

# On Linux:
chmod +x ./quack.playground.cs # Make the script executable. No need to do this every time.
#### Linux
```bash
chmod +x ./quack.playground.cs # only needed once
./quack.playground.cs
```

## Credits

Quack! leverages and draws inspiration from the following projects:
Quack! draws inspiration from and leverages the following projects:

- [SDL]
- [SDL_image]
Expand All @@ -174,3 +188,4 @@ Quack! leverages and draws inspiration from the following projects:
[SDL_gpu]: https://wiki.libsdl.org/CategoryGPU
[Examples]: examples
[dotnet run app.cs]: https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/
[NuGet]: https://www.nuget.org/packages/KappaDuck.Quack/
10 changes: 10 additions & 0 deletions rules/code.style.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,13 @@ csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
# Always use file scoped namespace
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0160
csharp_style_namespace_declarations = file_scoped

# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes
# Enable the warning to protect from malicious DLL in the default DLL search directories
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5392
dotnet_diagnostic.CA5392.severity = warning

# CA5393: Do not use unsafe DllImportSearchPath value
# Enable the warning to protect from malicious DLL in the default DLL search directories
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5393
dotnet_diagnostic.CA5393.severity = warning
13 changes: 13 additions & 0 deletions src/KappaDuck.Quack/Core/QuackEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The source code is licensed under MIT License.

using KappaDuck.Quack.Exceptions;
using KappaDuck.Quack.Interop.Win32;
using System.Runtime.Versioning;

namespace KappaDuck.Quack.Core;

Expand All @@ -13,6 +15,7 @@ public static class QuackEngine
private static readonly Lock _lock = new();
private static int _refCount;
private static Subsystem _subsystems;
private static bool _windowsMessageHooked;

/// <summary>
/// Gets the application metadata.
Expand Down Expand Up @@ -111,6 +114,16 @@ internal static void DangerousAcquire(Subsystem subsystem)
}
}

[SupportedOSPlatform(nameof(OSPlatform.Windows))]
internal static void HookToWindowsMessage(Win32.MessageCallback callback)
{
if (_windowsMessageHooked)
return;

Native.SDL_SetWindowsMessageHook(callback);
_windowsMessageHooked = true;
}

internal static void Release()
{
lock (_lock)
Expand Down
4 changes: 3 additions & 1 deletion src/KappaDuck.Quack/Events/Event.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) KappaDuck. All rights reserved.
// The source code is licensed under MIT License.

using KappaDuck.Quack.Windows;

namespace KappaDuck.Quack.Events;

/// <summary>
Expand Down Expand Up @@ -126,7 +128,7 @@ public struct Event
/// </summary>
/// <remarks>
/// <para>
/// It is already managed by <see cref="Windows.Window.Poll(out Event)"/> but you can
/// It is already managed by <see cref="WindowBase.Poll(out Event)"/>
/// manage manually.
/// </para>
/// The event is available when <see cref="EventType"/> is
Expand Down
15 changes: 9 additions & 6 deletions src/KappaDuck.Quack/Geometry/MathExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ namespace KappaDuck.Quack.Geometry;

internal static class MathExtensions
{
/// <summary>
/// The machine epsilon for <see cref="float"/>. It is based on C++'s FLT_EPSILON.
/// </summary>
private const float MachineEpsilon = 1.192092896e-07f;

extension(Math)
{
internal static void ThrowIfDividedByZero<T>(T value) where T : INumber<T>
Expand All @@ -23,6 +18,14 @@ internal static void ThrowIfDividedByZero<T>(T value) where T : INumber<T>

extension(MathF)
{
internal static bool IsNearlyZero(float value) => MathF.Abs(value) < MachineEpsilon;
/// <summary>
/// Determines whether the value is zero within a small tolerance.
/// </summary>
/// <remarks>
/// The tolerance is based on the C++'s FLT_EPSILON constant.
/// </remarks>
/// <param name="value">The value to check.</param>
/// <returns><see langword="true"/> if the value is nearly zero; otherwise, <see langword="false"/>.</returns>
internal static bool IsNearlyZero(float value) => MathF.Abs(value) < 1.192092896e-07f;
}
}
4 changes: 1 addition & 3 deletions src/KappaDuck.Quack/Graphics/Pixels/Palette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ internal Palette(SDL_Palette* handle)
/// </summary>
internal SDL_Palette* Handle { get; private set; }

/// <summary>
/// Releases the unmanaged resources used by the palette.
/// </summary>
/// <inheritdoc/>
public void Dispose()
{
if (Handle is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ public static unsafe uint MapRGBA(PixelFormatDetails details, byte r, byte g, by
/// <param name="palette">The palette to use for indexed color formats, or <see langword="null" /> for non-indexed formats.</param>
/// <returns>The pixel value.</returns>
public static uint MapColor(PixelFormatDetails details, Color color, Palette? palette = null) => MapRGBA(details, color.R, color.G, color.B, color.A, palette);
}

[SuppressMessage("Minor Code Smell", "S3398:\"private\" methods called only by inner classes should be moved to those classes", Justification = "Sonar seems to be confused by the 'extension' syntax.")]
private static unsafe SDL_Palette* GetPaletteHandle(Palette? palette) => palette is not null ? palette.Handle : null;
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Visual Studio has a bug where it thinks the method is not used.")]
private static unsafe SDL_Palette* GetPaletteHandle(Palette? palette) => palette is not null ? palette.Handle : null;
}
}
4 changes: 1 addition & 3 deletions src/KappaDuck.Quack/Graphics/Pixels/Surface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ public Palette CreatePalette()
return new Palette(Handle);
}

/// <summary>
/// Disposes the surface and releases its unmanaged resources.
/// </summary>
/// <inheritdoc/>
public void Dispose()
{
if (Handle is null)
Expand Down
Loading