ruby-sfml

Modern, idiomatic Ruby bindings for SFML 3.x via CSFML and Ruby FFI.

gem version CI docs

Status: API surface complete for SFML 3.0 — system, window, graphics (incl. stencil buffer + VBOs), audio (incl. 3D positional + custom DSP + procedural streams), network (incl. HTTP / FTP / socket selector), input (keyboard, mouse, joystick, touch, sensors), plus higher-level helpers (App, Scene, Assets, SpriteSheet, Animation, TextureAtlas, ParticleSystem, fixed timestep, input-action DSL, vector math). 640 RSpec examples, 35 runnable example folders.

Why

The original rbSFML is unmaintained and only works against SFML 2 and Ruby 2.2. ruby-sfml targets the current SFML 3.x line, modern Ruby (3.2+), and a Ruby-first API — blocks instead of polling loops, symbols instead of enums, operators on vectors, automatic resource cleanup via GC.

Installation

ruby-sfml needs Ruby ≥ 3.2 and CSFML 3.0 (or compatible 3.x) on the system. Install in two steps:

1. Install dependencies (CSFML)

OS Command Notes
Ubuntu 25.04+ / Debian sudo apt install libcsfml-dev Ships CSFML 3
Ubuntu 22.04 / 24.04 repo too old (CSFML 2.5) Build from 3.0.0 release
macOS (brew) brew install csfml Currently 3.x
Arch Linux sudo pacman -S csfml Currently 3.x
Windows www.sfml-dev.org/download/csfml/ Pick the 3.0 tarball

2. Install the gem

In a Bundler-managed project, add to your Gemfile:

gem "ruby-sfml", "~> 3.0"

then bundle install.

Or drop it in directly:

gem install ruby-sfml

Hosted on rubygems.org. HTML docs (RDoc) live at ruby-sfml-doc.netlify.app— built from the source tree of the latest release and served as static HTML. RubyGems’ auto-generated docs at rubydoc.info/gems/ruby-sfml work too as a fallback.

How the CSFML check happens

ruby-sfml verifies the linked CSFML in two places so a missing or out-of-date library never falls through to a cryptic segfault:

A 13-line app

require "sfml"

class Hello < SFML::App
  title        "Hello"
  background   SFML::Color.cornflower_blue
  antialiasing 4

  def setup
    @ball = SFML::CircleShape.new(radius: 30, fill_color: SFML::Color.white,
                                  position: [200, 200])
  end

  def update(dt) = @ball.move(60 * dt.as_seconds * SFML::Vector2[1, 0])
  def draw       = window.draw(@ball)
end

Hello.new.run

SFML::App handles window creation, the main loop, event pumping, dt, and the Esc/close-button quit. Override setup / update / draw / on_event. Class-level macros (title, framerate, antialiasing, background, …) set per-class defaults; per-instance kwargs to .new still override on a case-by-case basis. Drop into the manual loop style any time you want full control.

A 5-line manual loop

require "sfml"

window = SFML::RenderWindow.new(800, 600, "Hello", framerate: 60)

while window.open?
  window.each_event do |event|
    case event
    in {type: :closed}                     then window.close
    in {type: :key_pressed, code: :escape} then window.close
    else # always include `else` — case/in raises on unmatched events.
    end
  end

  window.clear(SFML::Color.cornflower_blue)
  window.display
end

Available modules

Area Classes
System Vector2, Vector3, Rect, Time, Clock
Window RenderWindow, Window (bare, GL-only), VideoMode, Event, Keyboard, Mouse, Joystick, Touch, Sensor, Cursor, Clipboard
Graphics Color, Image, Texture, RenderTexture, Sprite, CircleShape, RectangleShape, ConvexShape, Vertex, VertexArray, VertexBuffer, Font, Text, View, BlendMode, StencilMode, RenderStates, Shader, Transform
Audio SoundBuffer, Sound, Music, Listener, SoundCone, SoundStream, SoundRecorder, SoundBufferRecorder (3D positional + cones + Doppler + custom DSP via effect_processor=)
Helpers Assets (search-path + cache), App (lifecycle main loop with class-level config + on_key DSL + pause / on_resize), Scene (stateful screens + switch_to between them), ContextSettings (MSAA / GL version)

Network: IpAddress, TcpSocket, TcpListener, UdpSocket, SocketSelector for stream / datagram networking, plus the niche Http and Ftp clients (use Ruby’s Net::HTTP / Net::FTP if you have the choice — these exist for parity with CSFML).

What’s intentionally not wrapped

Three corners of CSFML 3 deliberately stay out:

If anything else is missing or blocking you, open an issue.

Other Ruby bindings worth knowing about

Examples

Each example is a self-contained folder under examples/, numbered roughly in learning order. Assets each example needs sit next to its script. Run from the gem root:

bundle exec ruby examples/<NN_name>/<name>.rb

The numbering reflects a rough learning order — earlier examples introduce concepts that later ones build on. Pick the section that matches what you want to learn first.

Foundations — window, events, input

# Example What it shows
01 hello_window Empty window, manual event loop
02 events_demo Pattern matching on input events
03 mouse_demo Polling vs. events; paint with the mouse
04 bouncing_ball dt-based physics on a manual main loop
05 app_class Same idea on top of SFML::App — see how much boilerplate goes away
06 vector_math Vector2 helpers (distance / angle_to / rotated / lerp / clamp_length) in real motion
07 input_actions action :name, keys:, scancodes:, mouse_buttons: DSL + axis(...) digital steering

Drawing — geometry and assets

# Example What it shows
08 draw_primitives Raw draw_primitives — line burst rebuilt every frame
09 custom_shape Abstract SFML::Shape subclass — parametric star / heart / gear
10 image_viewer Load a PNG, mutate the Image, re-upload to Texture on a key
11 pixel_paint Paint into a CPU Image, blit to GPU Texture each dirty frame
12 sprite_animation Procedural SpriteSheet → Animation walk cycle
13 texture_atlas Aseprite-style JSON atlas → 3 named animations with auto-fps
14 window_icon Procedural 32Ă—32 icon set as the window/taskbar icon
15 cursors_clipboard All 21 system Cursor shapes + Clipboard copy/paste

Camera, GPU, effects

# Example What it shows
16 screenshot RenderWindow#screenshot(path) / capture_image for in-memory frames
17 scrolling_world View as a 2D camera: drag-pan, wheel-zoom around cursor, FPS HUD
18 render_texture Off-screen RenderTexture for trail / motion-blur effects
19 tilemap Textured VertexArray tilemap + additive BlendMode torch
20 particle_system SFML::ParticleSystem fountain — VertexArray-backed pool
21 particles Same fountain hand-rolled on VertexArray + ConvexShape ground
22 shader_wave Pure GLSL fragment Shader — procedural ripple + plasma
23 stencil_mask Two-pass StencilMode masking — cursor spotlight clip
24 vertex_buffer 120 K-vertex VertexBuffer drawn in one call, animated via View only
25 bare_window SFML::Window (no 2D batcher) — events for raw-OpenGL apps

Game-loop polish

# Example What it shows
26 scenes Title → play → game-over flow with SFML::Scene
27 fixed_timestep fixed_timestep 30 + interpolation_alpha for jitter-free physics
28 pong Two-player Pong with in-window score (Text) and bounce Sound
29 joystick_demo Live gamepad inspector (axes, buttons, connect/disconnect)

Audio — simple to procedural

# Example What it shows
30 spatial_audio 3D positional Sound + Listener — three drones around the cursor
31 voice_memo Record from microphone via SoundBufferRecorder, save + play back
32 sound_stream Real-time sine synth via SFML::SoundStream subclass
33 procedural_synth SoundBuffer.from_samples mini-piano (Z–M keys, chromatic C4..B4)

Networking

# Example What it shows
34 udp_loopback UDP send/receive on localhost via Network::UdpSocket
35 tcp_chat TcpListener + TcpSocket + typed Network::Packet over loopback

Idioms baked in

Versioning

The gem version is MAJOR.MINOR.PATCH.GEM_PATCH — the first three segments mirror the CSFML release the gem was built against; the fourth is our own patch level for fixes / additions on top of the same upstream:

gem version targets CSFML meaning
3.0.0.0 3.0.0 First cut against CSFML 3.0.0
3.0.0.1 3.0.0 Bug fix on top of CSFML 3.0.0
3.0.1.0 3.0.1 CSFML 3.0.1 ships, we re-cut
3.1.0.0 3.1.0 New CSFML minor — added bindings for new APIs

SFML::CSFML_VERSION exposes the upstream string at runtime.

Bundler-pinning patterns:

gem "ruby-sfml", "~> 3.0"      # any 3.x.x.x — typical
gem "ruby-sfml", "~> 3.0.0"    # only 3.0.0.x — hold across a CSFML minor
gem "ruby-sfml", "~> 3.0.0.0"  # only our patches on CSFML 3.0.0 — paranoid pin

Process exit

ruby-sfml installs a single at_exit hook that:

  1. Stops every live SFML::Sound / SFML::Music so the audio thread quiets before anything is freed.

  2. Calls Kernel#exit! with the appropriate status, bypassing Ruby’s natural finalizer pass.

This is intentional. CSFML’s GL context, font glyph atlases, and OpenAL state are reclaimed by the OS on process exit; running each FFI::AutoPointer finalizer in a non-deterministic order tends to crash inside libGL/libopenal. The OS doesn’t care, and now neither do we.

The trade-off: any user at_exit hook registered before require "sfml" will be skipped. Hooks registered after the require run first (Ruby’s at_exit is LIFO) and are unaffected. Put your require at the top of the file (the normal place for it) and there’s nothing to think about.

Architecture

Two layers. Users only touch the top one.

SFML::C    # thin FFI wrapper around CSFML, 1:1 with the C API
SFML       # idiomatic Ruby on top

Each render target (RenderWindow + RenderTexture) includes a Graphics::RenderTarget mixin that dispatches clear, display, draw, view=, map_pixel_to_coords etc. through the includer’s CSFML_PREFIX. Adding a new target (say a future RenderImage) is ~30 lines.

When SFML 3.1 / CSFML 3.1 ships, only the bottom layer typically needs to move.

Development

bundle install
bundle exec rspec        # 410 examples (all subsystems on Linux)
bundle exec rake rdoc    # generate HTML docs in doc/ (Aliki theme via RDoc 7)

The spec suite hits real CSFML for everything that isn’t pure Ruby — Clock reads the real monotonic clock, Text#local_bounds measures real glyphs, audio loads a WAV — so a green run also confirms the FFI bindings line up. spec/fixtures/ holds the only assets the suite touches (a font and a tiny WAV) so tests are independent of examples/.

CI runs the full suite on Ubuntu and macOS Ă— Ruby 3.2 / 3.3 / 3.4. Linux builds CSFML 3 from source (cached), then runs specs under xvfb-run so the headless runner has an X server for RenderWindow.

Audio specs on macOS

CoreAudio + the CSFML OpenAL backend occasionally hang an audio test group on macOS. To keep a darwin run reliable, every spec under spec/sfml/audio/ is auto-tagged :audio and excluded by default on darwin. Force them in when you do want to run them:

bundle exec rspec --tag audio                  # only audio specs
bundle exec rspec spec/sfml/audio/sound_spec.rb --tag audio   # one file

On Linux the :audio filter doesn’t fire — the whole suite runs by default.

License

MIT. See LICENSE.txt.

The gem also bundles DejaVu Sans under its permissive license — used as the default font when you don’t supply your own.