Build 2D games with DragonRuby GTK using Ruby. Covers game loop, rendering, input, state management, testing, and the State pattern.
Expert guidance for building 2D games with DragonRuby Game Toolkit (DRGTK) using Ruby. This skill helps you follow DRGTK conventions for game loop architecture, rendering, input handling, state management, unit testing, and the State pattern.
**Scope & Structure**
**State Management**
**Rendering**
**Input**
**Hot Reload**
From repo root:
```bash
./dragonruby.exe
```
Or double-click `dragonruby.exe`. To explore samples:
```bash
./dragonruby.exe samples/01_rendering_basics
```
Place game assets under:
Reference by filename in outputs (lazy-loaded on first use).
Reference `samples/12_c_extensions/*` for expected directory layout and build scripts when adding native code.
Use `dragonruby-publish.exe` for packaging. Prefer reusing this tool over custom scripts.
**Running tests:**
```bash
./dragonruby mygame --test app/tests.rb --no-tick
```
**Test file location:**
Place tests in `mygame/app/tests.rb` or any `.rb` file passed to `--test`.
**Test method signature:**
```ruby
def test_example args, assert
# setup
game = MyGame.new
game.args = args
game.tick
# assertions
assert.true! condition, "failure message"
assert.false! condition, "failure message"
assert.equal! expected, actual, "optional message"
assert.not_equal! expected, actual
assert.ok! # mark test passed without specific assertion
end
```
**Assertion methods:**
**Starting tests programmatically:**
```ruby
$gtk.reset 100
$gtk.tests.start
```
**Benchmarking:**
```ruby
GTK.benchmark iterations: 1000,
label_one: -> { code_block_1 },
label_two: -> { code_block_2 }
GTK.benchmark seconds: 5,
label_one: -> { code_block_1 },
label_two: -> { code_block_2 }
```
**Test isolation:**
Reset global state (`$game = nil`) at start of each test to avoid cross-test contamination.
Uses the State pattern from https://gameprogrammingpatterns.com/state.html for player behavior.
**Base state class:**
`PlayerState` in `mygame/app/lib/player_state.rb` defines the interface:
**Concrete states:**
Each encapsulates its own behavior and data.
**State transitions:**
States return a new state object (or `nil` to stay). Owner calls `change_state(new_state)` which invokes `exit` on old state and `enter` on new state.
**State-specific data:**
Keep data relevant only to a state inside that state class:
**Owner delegation:**
The `Player` entity delegates `handle_input` and `update_state` to its current state.
**Example state transition flow:**
```ruby
def handle_input(mouse)
return nil if mouse.key_held.left # stay in this state
IdlePlayerState.new(player) # transition to idle
end
def change_state(new_state)
@state.exit
@state = new_state
@state.enter
end
```
**Benefits:**
**Basic game loop:**
```ruby
def tick args
args.state.player ||= { x: 640, y: 360, w: 32, h: 32 }
# Input
if args.inputs.keyboard.left
args.state.player.x -= 5
elsif args.inputs.keyboard.right
args.state.player.x += 5
end
# Render
args.outputs.sprites << {
x: args.state.player.x,
y: args.state.player.y,
w: args.state.player.w,
h: args.state.player.h,
path: 'sprites/player.png'
}
end
```
**State pattern implementation:**
```ruby
class Player
attr_accessor :state
def initialize
@state = IdlePlayerState.new(self)
@state.enter
end
def handle_input(mouse)
new_state = @state.handle_input(mouse)
change_state(new_state) if new_state
end
def update
@state.update
end
def change_state(new_state)
@state.exit
@state = new_state
@state.enter
end
end
class IdlePlayerState
def initialize(player)
@player = player
end
def enter
puts "Entering idle state"
end
def exit
puts "Exiting idle state"
end
def handle_input(mouse)
return ChargingPlayerState.new(@player) if mouse.key_down.left
nil
end
def update
# Idle behavior
end
end
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/dragonruby-game-toolkit-github-copilot/raw