17cupsofcoffee/tetra

Tetra is a simple 2D game framework written in Rust

Common building blocks built-in, such as:

Tetra

Tetra is a simple 2D game framework written in Rust. It uses SDL2 for event handling and OpenGL 3.2+ for rendering.

  • Website
  • Tutorial
  • API Docs
  • FAQ

Status

Tetra is not currently under active development (as of January 2022).

I will still be fixing minor bugs/updating dependencies where needed, but I don't plan on implementing any new features going forward.

PRs are welcome, but please open an issue/discussion if you're thinking of making a large change, so I can tell you if it's likely to be accepted or not!

For more information, see this blog post.

Features

  • XNA/MonoGame-inspired API
  • Efficient 2D rendering, with draw call batching by default
  • Easy input handling, via polling or events, with support for gamepads
  • Deterministic game loop by default, à la Fix Your Timestep
  • Common building blocks built-in, such as:
    • Font rendering
    • Cameras
    • Screen scaling

Installation

To add Tetra to your project, add the following line to your Cargo.toml file:

You will also need to install the SDL2 native libraries - full details are provided in the documentation.

Examples

To get a simple window displayed on screen, the following code can be used:

You can see this example in action by running cargo run --example hello_world.

The full list of examples is available here.

Support/Feedback

Tetra is fairly early in development, so you might run into bugs/flaky docs/general weirdness. Please feel free to open an issue/PR if you find something! You can also contact me via Twitter or the Rust Game Development Discord.

Issues

Collection of the latest Issues

Nekomaru-PKU

Nekomaru-PKU

Type: Feature Request
Comment Icon0

Summary

After a few days' work, I've successfully integrated ImGui (imgui-rust) into Tetra without breaking API changes. All changes are under a cargo feature called experimental_imgui, except that glow, a dependency of Tetra, must be downgraded from 0.11.0 to 0.10.0, since glow 0.11.0 introduced API changes that break the latest imgui-rs glow renderer.

I prefer to keep my changes on a separate branch (maybe experimental-imgui) if they are accepted into the main repo.After such a branch is created for me, I will raise a pull request.

Motivation/Examples

Motivations

At the present, game developers using Tetra have to struggle wih their own in-game GUI solutions, since Tetra doesn't provide one, and this is especially troublesome when an in-game editor or debug dashboard is strongly needed before further development.

Immediate Mode GUI library Dear ImGui has been widely used as a simple and strong GUI solution for in-game developer tools in the game industry. As is described in the ImGui repo (https://github.com/ocornut/imgui):

Dear ImGui is a bloat-free graphical user interface library for C++. It outputs optimized vertex buffers that you can render anytime in your 3D-pipeline enabled application. It is fast, portable, renderer agnostic and self-contained (no external dependencies).

Dear ImGui is designed to enable fast iterations and to empower programmers to create content creation tools and visualization / debug tools (as opposed to UI for the average end-user). It favors simplicity and productivity toward this goal, and lacks certain features normally found in more high-level libraries.

Dear ImGui is particularly suited to integration in games engine (for tooling), real-time 3D applications, fullscreen applications, embedded applications, or any applications on consoles platforms where operating system features are non-standard.

Examples

This example is based on Tetra keyboard example, and a slider is added to control the size of the player sprite like this: image

The ImGui part is like this (diff with original keyboard example)

Alternatives Considered

Implementing our own GUI solution means a lot of hard work, and on the other hand, ImGui has been considered as a de facto standard in game industry.

davideGiovannini

davideGiovannini

Type: Bug
Comment Icon2

Summary

I apologize for the lack of details, I'm still trying to find a way to reliably reproduce this.


Setup

I'm using a ScreenScaler, a Texture atlas to draw tiles and entities and Mesh to draw particles and debug graphic.

This works fine, except that randomly the screen glitches and 1 frame is drawn at a huge scale.

https://user-images.githubusercontent.com/14345181/144098815-39bc43fc-71e2-49b5-8f8c-8e6add2dc1bf.mp4

Glitched frame

glitched_frame

So far after some testing this seems to happen only if I draw Mesh objects. I have not found any pattern yet, sometime it happens only 1 or 2 times after a lot of time, while more rarely it happens for longer bursts.

If I had to guess it looks like a possible buffer overflow.

Steps to Reproduce

I will update this once I manage to reproduce it in a small example.

Additional Info

No response

RichMQ

RichMQ

Type: Bug
Comment Icon1

Summary

I have a laptop with dual Windows/Ubuntu boot and I have noticed a performance difference between the two systems when running a tetra app. It turns out that the same app compiled on Windows selects an integrated GPU while on Ubuntu it selects a discrete one.

Steps to Reproduce

Take any of the examples, use ContextBuilder::debug_info and run on Windows 10.

Additional Info

On Windows 10 I get:

On Ubuntu 21.10:

The same thing happens on 0.6.7 and the main branch.

17cupsofcoffee

17cupsofcoffee

Type: Feature Request
Comment Icon0

Summary

aka: why did my GUI library just yeet my transform matrix

One thing I've noticed as I've been writing some games/libraries with Tetra, is that it's quite hard to build rendering abstractions that don't leak into the global rendering state.

It would be good to add something to the API that would let you push/pop graphics state changes. There's a similar API in a lot of engines I've used (most notably Love2D), and it ends up being pretty convenient.

In the past I've said I wouldn't implement this because it's possible to do in user-space, but that doesn't take into account the fact that code you don't control (e.g. third party libraries) can't benefit from that.

Motivation/Examples

  • In one of my game projects, I have a piece of code that wants to draw to a canvas, and then switch back to whatever canvas was previously bound. At the moment, this means you have to pass the canvas in, which makes things a bit more unwieldy.
  • I'm working on an imgui renderer for Tetra, and this requires me to set the scissor rectangle and blend modes to ensure correct rendering. Again, if I want to be able to leave the state how I found it, I'd have to make the API consumer pass it in, which feels a bit gross.

Alternatives Considered

  • We could just leave things as is - it's not causing any earth-shattering issues, and people haven't complained so far.
  • We could try to make the API less stateful - though I'm not sure how to do this without adding a ton of parameters to a load of methods...
17cupsofcoffee

17cupsofcoffee

Type: Feature Request
Comment Icon0

Summary: ScreenScaler currently can't access the multisampling/stencil buffer functionality of the underlying canvas.

I could add extra constructors for this, but I'm kinda wondering if having ScreenScaler be a wrapper is the wrong approach - it might be nicer to just have a function for drawing any canvas at window scale.

Why is this needed? To avoid having to create extra canvases when multisampling/stencilling.

fossegutten

fossegutten

Type: Feature Request
Comment Icon2

Summary: Drawing a mesh multiple times per frame is super slow. Even slower when creating the mesh every frame. (40 fps with 325 rectangles). Creating a complex mesh with many rectangles, using GeometryBuilder every frame, is also slow. (120fps) Creating a complex mesh once and drawing it every frame seems fast. (500 fps). Would it be possible to reach the same speeds from drawing a simple mesh multiple times by batching or some other approach?

Edit: sorry, didn´t read enough docs. I havent tried using one geometrybuilder to create meshes every frame yet

Why is this needed? To be able to write simpler code and still have good performance

fossegutten

fossegutten

Type: Feature Request
Comment Icon1

Summary: Add booleans for horizontal and vertical flip to DrawParams, that does not offset the textures. This would be nice because scaling in negative direction also offsets the texture in the negative direction. As a workaround to avoid this, we need to add half texture size to position and to origin.

Steps to reproduce:

Texture now moved up and to the left.

Expected behavior: Expected the texture to draw at some_position, just mirrored.

Additional info:

17cupsofcoffee

17cupsofcoffee

Type: Investigation
Comment Icon4

Summary: In 0.5.6, we switched from doing std::thread::yield_now at the end of the game loop to doing a 1ms std::thread::sleep. This was to avoid issues some people were having with high CPU usage when the game is minimized or when vsync is turned off.

This appears to be due to the fact that std::thread::yield_now doesn't guarentee the thread will yield - it'll only yield if there's work to do elsewhere. std::thread::sleep, on the other hand, guarentees that we'll yield for some amount of time, putting an effective cap on the max FPS and preventing the game from maxing the CPU core. This is the approach used by Love2D's default game loop, and I've not noticed any ill effects from it there.

That said, I would like to investigate whether there's a smarter approach to this, as sleeping for 1ms in all cases seems like a bit of a clunky solution (e.g. if the game is running slowly, you might want that extra headroom rather than a millisecond being wasted). I'm not entirely sure if it's necessary though.

Why is this needed? Just for my own peace of mind, and to make sure that we're not leaving any performance on the table.

17cupsofcoffee

17cupsofcoffee

Type: Investigation
Comment Icon2

Summary: Tetra using a giant Context struct for all of its internal state is a bit of a pain - it makes it impossible for the internals of the renderer to use the public graphics API (because you can't pass ctx and something stored in ctx into the same method), and it's tricky to break it up into something that people can use in a custom game loop.

For me, the main constraints on making a change like this would be:

  • No implicit state - I do not want to simply hide Context away in a static and pretend it's not there.
  • No added API complexity - functions should not take any more arguments than they already do, just more specific ones
  • No added boilerplate - users shouldn't have to write their own game loop from scratch/initialize individual chunks of the engine unless they specifically want/need to do so

I'm not sure if there's an API that meets those criteria! But it's worth thinking about.

johanlindfors

johanlindfors

Type: Investigation
Comment Icon3

I am using Visual Studio Code to develop with tetra on MacOS, and it's a very nice environment after having added the "LLDB" and "Rust (rls)" extensions. By adding launch configurations (Visual Studio Code automatically adds debug configurations by examining cargo.toml), I can debug and set breakpoints.

An interesting aspect though is that when running any sample I get the following output in the debug terminal, this is prior to having done any explicit graphics::draw call:

2019-11-24 10:54:51.396156+0100 tetras[9199:547938] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x1011042c0> F8BB1C28-BAE8-11D6-9C31-00039315CD46 2019-11-24 10:54:51.417284+0100 tetras[9199:547938] HALC_ShellDriverPlugIn::Open: Can't get a pointer to the Open routine 2019-11-24 10:54:51.417528+0100 tetras[9199:547938] HALC_ShellDriverPlugIn::Open: Can't get a pointer to the Open routine 2019-11-24 10:54:51.604498+0100 tetras[9199:547992] flock failed to lock maps file: errno = 35 2019-11-24 10:54:51.604770+0100 tetras[9199:547992] flock failed to lock maps file: errno = 35 2019-11-24 10:55:27.478664+0100 tetras[9199:547938] [tetras] CGContextSetFillColorWithColor: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable. 2019-11-24 10:55:27.478692+0100 tetras[9199:547938] [tetras] CGContextGetCompositeOperation: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable. 2019-11-24 10:55:27.478700+0100 tetras[9199:547938] [tetras] CGContextSetCompositeOperation: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable. 2019-11-24 10:55:27.478706+0100 tetras[9199:547938] [tetras] CGContextFillRects: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable. 2019-11-24 10:55:27.478713+0100 tetras[9199:547938] [tetras] CGContextSetCompositeOperation: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

The code will run and work as expected but maybe you have some ideas on what might be causing this? Feel free to close this if it's "expected" behaviour.

17cupsofcoffee

17cupsofcoffee

Type: Feature Request
Comment Icon2

It'd be nice if it were possible for users to opt out of the standard game loop and write their own, if they so desire.

Most of the groundwork for this is already there - the missing bits are:

  • Access to the timing APIs (not strictly neccecary, as it's easy to implement yourself)
  • A way to force the engine to poll for SDL events
  • Access to the API that updates input state
  • A way to show/hide the window

The implementation of the actual looping needs to provided by Tetra, so that we can abstract over blocking/non-blocking APIs. I think this will probably boil down to the user providing a closure that represents one iteration of the game loop, and then scheduling that closure will be handled by the engine.

17cupsofcoffee

17cupsofcoffee

Type: Investigation
Comment Icon3

...probably not!

This is a tracking issue for ensuring that the OpenGL layer in Tetra makes correct use of unsafe, and that there's no way to cause undefined behavior via Tetra's API.

Long term, we might want to move to using a more established library for the graphics backend (#51), but in the short term we should probably at least avoid shooting ourselves in the foot.

Issues found so far:

  • #1 - OpenGL handles can be used-after-free if the context gets dropped (fixed)
17cupsofcoffee

17cupsofcoffee

Type: Feature Request
Comment Icon3

I kinda don't get along very well with Rodio's API (I basically tried my best to bypass it by just writing a custom Source). If I do a 0.3, I'd be very tempted to just rip the whole thing out and implement the whole thing with SDL's built in audio interface instead (plus maybe the signal crate for sample rate conversions, etc). That'd remove one of the main blockers to getting Tetra running on iOS and Android too, I think.

That said, I nearly burnt myself out on this project entirely doing the original version of the sound API, so this can wait...

Versions

Find the latest versions by id

0.7.0 - Mar 23, 2022

0.7.0-alpha1 - Jan 07, 2022

0.6.7 - Nov 05, 2021

0.6.6 - Oct 10, 2021

0.6.5 - May 26, 2021

0.6.4 - May 14, 2021

0.6.3 - Apr 09, 2021

0.6.2 - Mar 15, 2021

0.6.1 - Mar 15, 2021

0.6.0 - Feb 05, 2021

0.6.0-alpha0 - Feb 03, 2021

0.5.8 - Jan 26, 2021

0.5.7 - Jan 15, 2021

0.5.6 - Dec 20, 2020

0.5.5 - Dec 03, 2020

0.5.4 - Dec 03, 2020

0.5.3 - Nov 21, 2020

0.5.2 - Nov 21, 2020

0.5.1 - Oct 14, 2020

0.5.0 - Sep 02, 2020

0.4.2 - Aug 14, 2020

0.4.1 - Aug 02, 2020

0.4.0 - Jun 24, 2020

0.3.6 - May 15, 2020

0.3.5 - Apr 25, 2020

0.3.4 - Apr 12, 2020

0.3.3 - Apr 04, 2020

0.3.2 - Jan 15, 2020

0.3.1 - Dec 15, 2019

0.3.0 - Dec 14, 2019

Information - Updated Aug 01, 2022

Stars: 726
Forks: 48
Issues: 17

Arsenal 2D retro Rust Game engine

Katharos Technology is focused on producing our first game prototype, bevy game engine and our own Katharos...

Arsenal 2D retro Rust Game engine

Grumpy Visitors 2D Rust game for code reference and learning

Grumpy Visitors is a top-down 2D action arcade game

Grumpy Visitors 2D Rust game for code reference and learning

Rust Game Remake

Remake in rust of a game I made during the here

Rust Game Remake

A 3d rust game using OpenGl and Emscripten to build for the wasm32-unknown-emscripten

It can also run standalone, developed and tested on Linux but will

A 3d rust game using OpenGl and Emscripten to build for the wasm32-unknown-emscripten

Rust Game of Life

This is a simulator of Conway's Game of Life in Rust, using SDL2 to render the game graphically

Rust Game of Life

INOX - Rust Game engine integrated in Blender

It's a Game Engine written in Rust with some bindings for external libs and with Blender as editor

INOX - Rust Game engine integrated in Blender

Rust GameBoy Advanced Emulator

Building this project on linux requires cmake and gtk3

Rust GameBoy Advanced Emulator

A Rust Game Engine

In an effort to learn the Rust language (because I am a nut), I am building a game engine

A Rust Game Engine

A Rust GameBoy emulator!

TetrisZelda: Link's Awakening

A Rust GameBoy emulator!

Another Rust game engine

It's started as a rust version of the tutorial from

Another Rust game engine
Facebook Instagram Twitter GitHub Dribbble
Privacy