#pixel-art #game-engine #assets #utility #gamedev #2d #gui

pixel-game-lib

AGPL licensed and opinionated game engine for pixel-art games

25 releases (8 breaking)

0.9.0-alpha.6 Apr 8, 2024
0.9.0-alpha.4 Mar 29, 2024
0.8.1 Apr 25, 2024
0.4.3 Dec 23, 2023
0.4.1 Nov 26, 2023

#739 in Game dev


Used in blit

AGPL-3.0

170KB
2.5K SLoC

pixel-game-lib

Build Status Crates.io Documentation License: AGPL-3.0 Dependency Status Downloads

Documentation

AGPL licensed and opinionated game engine for 2D pixel-art games.

Features

  • Pixel-perfect pixel art rendering with built-in rotsprite rotation shader.
  • Window creation with independent update and render game loop.
  • Hot-reloadable assets, seeing your assets update live in the game when you save them is a great boost in productivity for quickly iterating on ideas.
  • Single-binary, all assets should be embedded in the binary when deploying.
  • Simple bitmap font drawing.
  • Dialogue scripting system.
  • Audio playback.
  • In game CPU & memory profiler GUI.

Goals

  • Ergonomic API with a focus on quickly creating small games, especially for game jams.
  • Reasonable performance, drawing thousands of animated sprites at the same time shouldn't be a problem.
  • Proper web support, it should be very easy to bundle as WASM for the web.

Non-Goals

  • An ECS (Entity component system), although an ECS architecture is great for cache locality and performance, I feel that it's overkill for most small games. Nothing is stopping you to add your own on top of this engine if that's what you want though!
  • 3D, this engine is only for 2D pixel art.
  • Vector graphics, similar to the above, this engine is focused specifically on pixel art with lower resolutions.
  • Reinventing the wheel for everything, when there's a proper crate with good support I prefer to use that instead of creating additional maintainer burden.

Usage

Using this crate is quite simple, there is a single trait PixelGame with two required functions, PixelGame::update and PixelGame::render, that need to be implemented for a game state object.

use pixel_game_lib::{PixelGame, Context, GameConfig};

struct MyGame;

impl PixelGame for MyGame {
  fn update(&mut self, ctx: Context) {
    // ..
  }

  fn render(&mut self, ctx: Context) {
    // ..
  }
}

// In main
let game = MyGame;

game.run(GameConfig::default())?;

Feature Flags

All major feature flags are enabled by default, I would recommend installing pixel_game_lib with default-features = false and adding the required features as needed.

cargo add pixel_game_lib --no-default-features
hot-reloading-assets (default)

Hot-reload assets from disk when they are saved. Has no effect on the web target.

embedded-assets (default on web)

Bake all assets in the assets/ folder in the binary. When creating a release binary this feature flag should be enabled.

dialogue (default)

A thin wrapper around Yarn Spinner. Allows creating hot-reloadable dialogue systems.

audio (default)

A thin wrapper around Kira. Play sounds and music files which can be hot-reloadable using assets.

To keep the binary and compile-times small only .ogg audio files are supported.

Requirements

On Linux you need to install asound2-dev:

sudo apt install libasound2-dev
in-game-profiler (default)

A profiler window overlay, implemented with puffin_egui.

Other profiling methods in your game can also be implemented, the profiling crate is enabled even when this feature flag is disabled.

Example

This example will show a window with a counter that's incremented when pressing the left mouse button^left-mouse. The counter is rendered as text^text loaded from a font in the top-left corner. When the 'Escape' key is pressed^escape-key the game will exit and the window will close.

use pixel_game_lib::{PixelGame, Context, GameConfig, MouseButton, KeyCode, glamour::Vector2};

/// Object holding all game state.
struct MyGame {
  /// A simple counter we increment by clicking on the screen.
  counter: u32,
}

impl PixelGame for MyGame {
  fn update(&mut self, ctx: Context) {
    // ^1
    // Increment the counter when we press the left mouse button
    if ctx.mouse_pressed(MouseButton::Left) {
      self.counter += 1;
    }

    // ^3
    // Exit the game if 'Escape' is pressed
    if ctx.key_pressed(KeyCode::Escape) {
      ctx.exit();
    }
  }

  fn render(&mut self, ctx: Context) {
    // ^2
    // Display the counter with a font called 'font' automatically loaded from the `assets/` directory
    // It will be shown in the top-left corner
    ctx.text("font", &format!("Counter: {}", self.counter)).draw();
  }
}

// In main

// Initialize the game state
let game = MyGame { counter: 0 };

// Run the game until exit is requested
game.run(GameConfig::default().with_title("My Game"))?;

Rotation Algorithms

In the library it's possible to choose between multiple upscale implementations for the single-pass RotSprite algorithm, see the Rust documentation for more information:

Nearest Neighbor

This doesn't apply any extra rotation effects.

Nearest Neighbor

Scale3x

Scale3x

Diag2x

Diag2x

Scale2x

Scale2x

Credits

  • gtoknu for the branchless scale2x shader.
  • @damieng for the font behind the default-font feature.
  • KenneyNL for the audio sample in the example.

Dependencies

~12–53MB
~1M SLoC