Class: RGame::Core::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/rgame/core/renderer.rb,
ext/rgame_core/ruby/renderer_ext.c

Overview

What a game draws with.

class MyGame < RGame::Core::App
def initialize
  super(width: 800, height: 600, caption: 'demo')
  @renderer = RGame::Core::Renderer.new(self)
  @hero = RGame::Core::Image.new(self, 'hero.png')
end

def draw
  @renderer.rect(10, 10, 100, 40, color: RGame::Util::Color::WHITE)
  @renderer.image(@hero, 400, 300, angle: 45)
end
end

Drawing is only legal inside draw, and calling one of these outside it raises. That is on purpose: the frame is not open, so the vertices would be silently discarded, and an invisible failure is the worst kind.

Nothing is drawn immediately. Calls accumulate and are z-sorted when the frame closes, so z: decides what ends up on top — not call order. Equal z keeps call order, which is what stops same-layer sprites flickering between frames.

The C half of this class (ext/rgame_core/ruby/renderer_ext.c) has the draw_* and push_* primitives; everything here is the comfortable surface over them.

Colours

Every drawing method takes color:, accepting whatever Color.coerce does: nil (white — an untinted draw), [r, g, b], [r, g, b, a], or a RGame::Util::Color. Passing a Color is the allocation-free path and is what per-frame code should do; an array allocates one colour per call.

Constant Summary collapse

Color =
RGame::Util::Color
SHAPE_Z =

Shapes default above sprites, so a debug box or a health bar drawn without a z: lands on top of the scene rather than under it. The values match the layer this replaces.

50
IMAGE_Z =
0
CIRCLE_SEGMENTS =

Enough segments that a circle reads as round at the sizes a 2D game draws one, and few enough that a screenful of them is still one batch.

64
TEXT_Z =

Text defaults above sprites but below shapes, and the size matches what the layer this replaces used, so ported UI lays out unchanged.

10
FONT_SIZE =
18
DEBUG_BOX_COLOR =

Translucent red, for #debug_box.

Color.new(255, 40, 40, 120)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'ext/rgame_core/ruby/renderer_ext.c', line 98

static VALUE renderer_initialize(VALUE self, VALUE app) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);

    ref->app = rgame_app_unwrap(app); /* raises TypeError on anything else */
    ref->app_object = app;
    ref->recorded_images = Qnil;
    return self;
}

Instance Attribute Details

#assetsObject

Returns the value of attribute assets.



77
78
79
# File 'lib/rgame/core/renderer.rb', line 77

def assets
  @assets
end

#fontObject

The font this renderer draws with when a call does not name one.

Built on first use rather than in the constructor: creating a font needs a GL context, and a renderer is often built before there is one. Set your own with #font= to change what every unqualified #text call uses.



261
262
263
# File 'lib/rgame/core/renderer.rb', line 261

def font
  @font ||= Font.new(app, FONT_SIZE)
end

Class Method Details

.new(app, assets: nil) ⇒ Object

assets: is where a draw id that is not registered gets resolved from, and defaults to the app's own manager — so the common case wires itself and renderer.sprite('hero.json', …) works with nothing set up.

A Ruby self.new because the C initialize has fixed arity and no business knowing what an asset manager is; the same shape Font's path: uses.



71
72
73
74
75
# File 'lib/rgame/core/renderer.rb', line 71

def self.new(app, assets: nil)
  renderer = super(app)
  renderer.assets = assets.nil? ? app.assets : assets
  renderer
end

Instance Method Details

#appObject



115
116
117
118
119
# File 'ext/rgame_core/ruby/renderer_ext.c', line 115

static VALUE renderer_app(VALUE self) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
    return ref->app_object;
}

#background(image, x = 0, y = 0, z: IMAGE_Z, color: nil) ⇒ Object

An image with its top-left at (x, y), at its natural size — a full-screen backdrop by default. image_at with both scales at 1, kept because "put this at the origin" is worth a name of its own.



192
193
194
# File 'lib/rgame/core/renderer.rb', line 192

def background(image, x = 0, y = 0, z: IMAGE_Z, color: nil)
  draw_image(resolve_image(image), x, y, z, packed(color))
end

#begin_recordObject



309
310
311
312
313
314
315
316
317
318
# File 'ext/rgame_core/ruby/renderer_ext.c', line 309

static VALUE renderer_begin_record(VALUE self) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);

    if (!rgame_app_begin_record(drawing_app(self))) {
        rb_raise(rb_eRuntimeError, "already recording (recordings do not nest)");
    }
    ref->recorded_images = rb_ary_new();
    return self;
}

#cancel_recordObject



335
336
337
338
339
340
341
342
# File 'ext/rgame_core/ruby/renderer_ext.c', line 335

static VALUE renderer_cancel_record(VALUE self) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);

    ref->recorded_images = Qnil;
    rgame_app_cancel_record(ref->app);
    return self;
}

#circle(cx, cy, radius, z: SHAPE_Z, color: nil, segments: CIRCLE_SEGMENTS) ⇒ Object

A filled circle, as a fan of triangles around its centre.



161
162
163
# File 'lib/rgame/core/renderer.rb', line 161

def circle(cx, cy, radius, z: SHAPE_Z, color: nil, segments: CIRCLE_SEGMENTS)
  draw_circle(cx, cy, radius, segments, z, packed(color))
end

#clipped(x, y, width, height) ⇒ Object

Everything drawn in the block is confined to the given rectangle.

A clip only ever narrows: nesting one inside another intersects them, so a child cannot draw outside the region its parent allowed. Give each player a clipped block and you have split-screen.



247
248
249
250
251
252
253
254
# File 'lib/rgame/core/renderer.rb', line 247

def clipped(x, y, width, height)
  push_clip(x, y, width, height)
  begin
    yield
  ensure
    pop
  end
end

#debug_box(x, y, width, height, z: SHAPE_Z) ⇒ Object

A translucent overlay for visualising a collision box, so a scene can ask for one without knowing what colour "debug" is.



312
313
314
# File 'lib/rgame/core/renderer.rb', line 312

def debug_box(x, y, width, height, z: SHAPE_Z)
  rect(x, y, width, height, z: z, color: DEBUG_BOX_COLOR)
end

#draw_circle(*args) ⇒ Object



175
176
177
178
179
180
181
182
# File 'ext/rgame_core/ruby/renderer_ext.c', line 175

static VALUE renderer_draw_circle(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 6, 6);

    rgame_app_draw_circle(drawing_app(self), (float)NUM2DBL(argv[0]), (float)NUM2DBL(argv[1]),
                          (float)NUM2DBL(argv[2]), NUM2INT(argv[3]), packed_color(argv[5]),
                          NUM2DBL(argv[4]));
    return self;
}

#draw_image(image, x, y, z, color) ⇒ Object



206
207
208
209
210
211
212
213
214
# File 'ext/rgame_core/ruby/renderer_ext.c', line 206

static VALUE renderer_draw_image(VALUE self, VALUE image, VALUE x, VALUE y, VALUE z,
                                 VALUE color) {
    check_drawn(rgame_app_draw_image(drawing_app(self), rgame_image_unwrap(image),
                                     (float)NUM2DBL(x), (float)NUM2DBL(y), packed_color(color),
                                     NUM2DBL(z)),
                image);
    note_recorded_image(self, image);
    return self;
}

#draw_image_rot(*args) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
# File 'ext/rgame_core/ruby/renderer_ext.c', line 230

static VALUE renderer_draw_image_rot(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 7, 7);

    check_drawn(rgame_app_draw_image_rot(drawing_app(self), rgame_image_unwrap(argv[0]),
                                         (float)NUM2DBL(argv[1]), (float)NUM2DBL(argv[2]),
                                         (float)NUM2DBL(argv[3]), (float)NUM2DBL(argv[4]),
                                         packed_color(argv[6]), NUM2DBL(argv[5])),
                argv[0]);
    note_recorded_image(self, argv[0]);
    return self;
}

#draw_image_scaled(*args) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
# File 'ext/rgame_core/ruby/renderer_ext.c', line 218

static VALUE renderer_draw_image_scaled(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 7, 7);

    check_drawn(rgame_app_draw_image_scaled(drawing_app(self), rgame_image_unwrap(argv[0]),
                                            (float)NUM2DBL(argv[1]), (float)NUM2DBL(argv[2]),
                                            (float)NUM2DBL(argv[3]), (float)NUM2DBL(argv[4]),
                                            packed_color(argv[6]), NUM2DBL(argv[5])),
                argv[0]);
    note_recorded_image(self, argv[0]);
    return self;
}

#draw_line(*args) ⇒ Object



166
167
168
169
170
171
172
173
# File 'ext/rgame_core/ruby/renderer_ext.c', line 166

static VALUE renderer_draw_line(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 7, 7);

    rgame_app_draw_line(drawing_app(self), (float)NUM2DBL(argv[0]), (float)NUM2DBL(argv[1]),
                        (float)NUM2DBL(argv[2]), (float)NUM2DBL(argv[3]),
                        (float)NUM2DBL(argv[4]), packed_color(argv[6]), NUM2DBL(argv[5]));
    return self;
}

#draw_quad(*args) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
# File 'ext/rgame_core/ruby/renderer_ext.c', line 142

static VALUE renderer_draw_quad(int argc, VALUE *argv, VALUE self) {
    /* Ten positional arguments is past the point where naming each parameter
     * helps, so this one takes an argv and unpacks it in a loop. */
    rb_check_arity(argc, 10, 10);

    float xy8[8];
    for (int i = 0; i < 8; i++) {
        xy8[i] = (float)NUM2DBL(argv[i]);
    }
    rgame_app_draw_quad(drawing_app(self), xy8, packed_color(argv[9]), NUM2DBL(argv[8]));
    return self;
}

#draw_rect(x, y, width, height, z, color) ⇒ Object



134
135
136
137
138
139
140
# File 'ext/rgame_core/ruby/renderer_ext.c', line 134

static VALUE renderer_draw_rect(VALUE self, VALUE x, VALUE y, VALUE width, VALUE height,
                                VALUE z, VALUE color) {
    rgame_app_draw_rect(drawing_app(self), (float)NUM2DBL(x), (float)NUM2DBL(y),
                        (float)NUM2DBL(width), (float)NUM2DBL(height), packed_color(color),
                        NUM2DBL(z));
    return self;
}

#draw_text(*args) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'ext/rgame_core/ruby/renderer_ext.c', line 248

static VALUE renderer_draw_text(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 6, 6);

    VALUE font = argv[0];
    VALUE string = argv[1];
    /* RSTRING_PTR on a non-String reads whatever the object's second word
     * happens to be and hands it to C as a char* — a segfault, not an
     * exception. Everything else here goes through NUM2DBL or an unwrap, which
     * type-check on the way; this is the one argument that has to say so.
     * StringValue converts what can convert and raises TypeError otherwise. */
    StringValue(string);
    const char *text = RSTRING_PTR(string);
    long length = RSTRING_LEN(string);

    int drawn = rgame_app_draw_text(drawing_app(self), rgame_font_unwrap(font), text,
                                    (size_t)length, (float)NUM2DBL(argv[2]),
                                    (float)NUM2DBL(argv[3]), packed_color(argv[5]),
                                    NUM2DBL(argv[4]));
    /* RSTRING_PTR hands out a pointer the collector does not know about. */
    RB_GC_GUARD(string);

    check_drawn(drawn, font);
    return self;
}

#draw_triangle(*args) ⇒ Object



155
156
157
158
159
160
161
162
163
164
# File 'ext/rgame_core/ruby/renderer_ext.c', line 155

static VALUE renderer_draw_triangle(int argc, VALUE *argv, VALUE self) {
    rb_check_arity(argc, 8, 8);

    float xy6[6];
    for (int i = 0; i < 6; i++) {
        xy6[i] = (float)NUM2DBL(argv[i]);
    }
    rgame_app_draw_triangle(drawing_app(self), xy6, packed_color(argv[7]), NUM2DBL(argv[6]));
    return self;
}

#drawing?Object



122
123
124
125
126
# File 'ext/rgame_core/ruby/renderer_ext.c', line 122

static VALUE renderer_drawing_p(VALUE self) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
    return ref->app && rgame_app_is_drawing(ref->app) ? Qtrue : Qfalse;
}

#end_recordObject



320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'ext/rgame_core/ruby/renderer_ext.c', line 320

static VALUE renderer_end_record(VALUE self) {
    rgame_renderer_ref *ref;
    TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);

    VALUE images = ref->recorded_images;
    ref->recorded_images = Qnil;

    rgame_recording *recording = rgame_app_end_record(ref->app);
    if (!recording) {
        rb_raise(rb_eRuntimeError, "no recording is open");
    }

    return rgame_recording_wrap(recording, ref->app_object, ref->app, images);
}

#image(image, cx, cy, angle: 0, scale: 1, z: IMAGE_Z, color: nil) ⇒ Object

An image centred on (cx, cy), rotated angle degrees clockwise about that centre and uniformly scaled. Unrotated and unscaled is a fast path that skips the transform stack entirely.

Takes an Image or an id for one — see #resolve_image.



170
171
172
# File 'lib/rgame/core/renderer.rb', line 170

def image(image, cx, cy, angle: 0, scale: 1, z: IMAGE_Z, color: nil)
  draw_image_rot(resolve_image(image), cx, cy, angle, scale, z, packed(color))
end

#image_at(image, x, y, scale_x: 1, scale_y: 1, z: IMAGE_Z, color: nil) ⇒ Object

An image with its top-left at (x, y), scaled independently per axis — a tile, a nine-slice corner, a sprite-sheet frame.

A negative scale mirrors the image inside the same rectangle; it does not move it. So a frame drawn at (x, y) covers the same pixels whichever way it faces, and scale_x: -1 means "facing the other way" rather than "one width to the left":

renderer.image_at(frame, x, y, scale_x: facing_left ? -1 : 1)

A zero scale draws nothing.



185
186
187
# File 'lib/rgame/core/renderer.rb', line 185

def image_at(image, x, y, scale_x: 1, scale_y: 1, z: IMAGE_Z, color: nil)
  draw_image_scaled(resolve_image(image), x, y, scale_x, scale_y, z, packed(color))
end

#line(x1, y1, x2, y2, thickness: 1.0, z: SHAPE_Z, color: nil) ⇒ Object

A line of real thickness — drawn as a quad, because GL's own line width is a suggestion drivers may ignore above one pixel.



156
157
158
# File 'lib/rgame/core/renderer.rb', line 156

def line(x1, y1, x2, y2, thickness: 1.0, z: SHAPE_Z, color: nil)
  draw_line(x1, y1, x2, y2, thickness, z, packed(color))
end

#nine_slice(id, x, y, width, height, z: IMAGE_Z, tint: nil) ⇒ Object

A nine-slice filling (x, y, width, height), tinted by tint if given. Registration only: a nine-slice id names an element of an atlas, not a file, so there is nothing for the asset manager to resolve it to.



116
117
118
# File 'lib/rgame/core/renderer.rb', line 116

def nine_slice(id, x, y, width, height, z: IMAGE_Z, tint: nil)
  lookup(:nine_slice, id).draw(self, x, y, width, height, z: z, color: tint)
end

#popObject



344
345
346
347
# File 'ext/rgame_core/ruby/renderer_ext.c', line 344

static VALUE renderer_pop(VALUE self) {
    rgame_app_pop(drawing_app(self));
    return self;
}

#push_clip(x, y, width, height) ⇒ Object



289
290
291
292
293
294
295
296
297
298
# File 'ext/rgame_core/ruby/renderer_ext.c', line 289

static VALUE renderer_push_clip(VALUE self, VALUE x, VALUE y, VALUE width, VALUE height) {
    if (!rgame_app_push_clip(drawing_app(self), NUM2INT(x), NUM2INT(y), NUM2INT(width),
                             NUM2INT(height))) {
        /* Clipping cannot be baked into a recording; see core.h. Saying so
         * beats recording geometry that quietly ignores the clip. */
        rb_raise(rb_eRuntimeError,
                 "a clip cannot be recorded — wrap the replay in #clipped instead");
    }
    return self;
}

#push_rotate(degrees, pivot_x, pivot_y) ⇒ Object



278
279
280
281
282
# File 'ext/rgame_core/ruby/renderer_ext.c', line 278

static VALUE renderer_push_rotate(VALUE self, VALUE degrees, VALUE pivot_x, VALUE pivot_y) {
    rgame_app_push_rotate(drawing_app(self), (float)NUM2DBL(degrees), (float)NUM2DBL(pivot_x),
                          (float)NUM2DBL(pivot_y));
    return self;
}

#push_scale(sx, sy) ⇒ Object



284
285
286
287
# File 'ext/rgame_core/ruby/renderer_ext.c', line 284

static VALUE renderer_push_scale(VALUE self, VALUE sx, VALUE sy) {
    rgame_app_push_scale(drawing_app(self), (float)NUM2DBL(sx), (float)NUM2DBL(sy));
    return self;
}

#push_translate(dx, dy) ⇒ Object



273
274
275
276
# File 'ext/rgame_core/ruby/renderer_ext.c', line 273

static VALUE renderer_push_translate(VALUE self, VALUE dx, VALUE dy) {
    rgame_app_push_translate(drawing_app(self), (float)NUM2DBL(dx), (float)NUM2DBL(dy));
    return self;
}

#quad(x1, y1, x2, y2, x3, y3, x4, y4, z: SHAPE_Z, color: nil) ⇒ Object

Four arbitrary points, in loop order: listing them in Z order gives an hourglass rather than a shape.



146
147
148
# File 'lib/rgame/core/renderer.rb', line 146

def quad(x1, y1, x2, y2, x3, y3, x4, y4, z: SHAPE_Z, color: nil)
  draw_quad(x1, y1, x2, y2, x3, y3, x4, y4, z, packed(color))
end

#recordObject

Bakes everything the block draws into a RGame::Core::Recording, which can then be replayed for the cost of one call per texture however many draws went into it. Nothing is drawn now — the block's output goes into the recording instead of into this frame.

ground = renderer.record { tiles.each { |t| renderer.image(t.img, t.x, t.y) } }
ground.draw(-camera.x, -camera.y)

Recording happens inside draw like everything else, and does not nest. A clip pushed inside the block raises: clipping cannot be baked, so clip the replay instead. See RGame::Core::Recording.



295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/rgame/core/renderer.rb', line 295

def record
  begin_record
  completed = false
  begin
    yield
    completed = true
  ensure
    # A block that raised leaves a half-built recording open, and the
    # next frame would keep drawing into it. Unwinding here means the
    # exception is the only thing the caller has to deal with.
    cancel_record unless completed
  end
  end_record
end

#rect(x, y, width, height, z: SHAPE_Z, color: nil) ⇒ Object

A filled axis-aligned rectangle.



140
141
142
# File 'lib/rgame/core/renderer.rb', line 140

def rect(x, y, width, height, z: SHAPE_Z, color: nil)
  draw_rect(x, y, width, height, z, packed(color))
end

#register_image(id, image) ⇒ Object

--- draw-by-id ---------------------------------------------------------

Game logic names assets, it does not hold them: the engine layer may hold RGame::Util values but no RGame::Core handle at all, so a Symbol or a path is the only thing a node can carry. Resolving it is this side of the boundary's job.

An id is normally a root-relative path, resolved through the asset manager and then remembered, so a per-frame draw neither re-resolves nor allocates a lookup key:

renderer.sprite('example 09/player.json', row, col, x, y)

register_* pre-binds an id to a chosen object, for the two things a path cannot name: an id that is not a file (nine-slice ids are atlas element names) and an object the game assembled itself.



96
# File 'lib/rgame/core/renderer.rb', line 96

def register_image(id, image) = registry(:image)[id] = image

#register_nine_slice(id, nine_slice) ⇒ Object



99
# File 'lib/rgame/core/renderer.rb', line 99

def register_nine_slice(id, nine_slice) = registry(:nine_slice)[id] = nine_slice

#register_sheet(id, sheet) ⇒ Object



97
# File 'lib/rgame/core/renderer.rb', line 97

def register_sheet(id, sheet) = registry(:sheet)[id] = sheet

#register_tilemap(id, tilemap) ⇒ Object



98
# File 'lib/rgame/core/renderer.rb', line 98

def register_tilemap(id, tilemap) = registry(:tilemap)[id] = tilemap

#register_ui_atlas(atlas) ⇒ Object

Registers every element of a UiAtlas under its own name, since those names are what a widget asks for.



103
104
105
106
# File 'lib/rgame/core/renderer.rb', line 103

def register_ui_atlas(atlas)
  atlas.nine_slices.each { |id, nine_slice| register_nine_slice(id, nine_slice) }
  self
end

#rotated(angle, pivot_x, pivot_y) ⇒ Object

Everything drawn in the block is rotated angle degrees about (pivot_x, pivot_y), so a node can spin all of its parts coherently around one point.

A zero angle skips the push entirely — unrotated drawing pays nothing, which matters because most drawing is unrotated.



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/rgame/core/renderer.rb', line 202

def rotated(angle, pivot_x, pivot_y)
  return yield if angle.zero?

  push_rotate(angle, pivot_x, pivot_y)
  begin
    yield
  ensure
    # An ensure, not a plain pop: a scene that raises mid-draw would
    # otherwise leave the stack deeper than it found it, and every
    # later frame would draw askew for a reason nothing points at.
    pop
  end
end

#scaled(sx, sy = sx) ⇒ Object



231
232
233
234
235
236
237
238
239
240
# File 'lib/rgame/core/renderer.rb', line 231

def scaled(sx, sy = sx)
  return yield if sx == 1 && sy == 1

  push_scale(sx, sy)
  begin
    yield
  ensure
    pop
  end
end

#sprite(id, row, col, x, y, flip_x: false, z: IMAGE_Z) ⇒ Object

One frame of a registered or resolvable sprite sheet, top-left at (x, y).



109
110
111
# File 'lib/rgame/core/renderer.rb', line 109

def sprite(id, row, col, x, y, flip_x: false, z: IMAGE_Z)
  lookup(:sheet, id).draw(self, row, col, x, y, flip_x: flip_x, z: z)
end

#text(string, x, y, z: TEXT_Z, color: nil, font: nil) ⇒ Object

One line of text, with its top-left corner at (x, y) — the same corner every other drawing method takes, rather than the baseline typography would use.

Newlines are not special. A caller wanting two lines draws two, stepping by #text_height.



273
274
275
# File 'lib/rgame/core/renderer.rb', line 273

def text(string, x, y, z: TEXT_Z, color: nil, font: nil)
  draw_text(font || self.font, string, x, y, z, packed(color))
end

#text_height(font: nil) ⇒ Object

The line height: what to step y by for a second line.



282
# File 'lib/rgame/core/renderer.rb', line 282

def text_height(font: nil) = (font || self.font).height

#text_width(string, font: nil) ⇒ Object

What #text would occupy, for centring and layout. Unlike the drawing methods this works outside draw, because measuring touches no GL.



279
# File 'lib/rgame/core/renderer.rb', line 279

def text_width(string, font: nil) = (font || self.font).text_width(string)

#tilemap(id, camera_x, camera_y, viewport_width, viewport_height, elapsed: 0.0) ⇒ Object

A tile map's below-the-actor band (ground and same-level detail).

elapsed is the seconds its animated tiles have been running for, and is an argument rather than a clock read on purpose — see CLAUDE.md, "draw renders state; time enters through update". A scene accumulates it in update, which is what makes pausing work.



126
127
128
129
# File 'lib/rgame/core/renderer.rb', line 126

def tilemap(id, camera_x, camera_y, viewport_width, viewport_height, elapsed: 0.0)
  lookup(:tilemap, id)
    .draw(self, camera_x, camera_y, viewport_width, viewport_height, elapsed: elapsed)
end

#tilemap_overlay(id, camera_x, camera_y, viewport_width, viewport_height, z:, elapsed: 0.0) ⇒ Object

Its above-the-actor band (canopies, roofs), at a z the scene picks so it lands over the actors.



133
134
135
136
137
# File 'lib/rgame/core/renderer.rb', line 133

def tilemap_overlay(id, camera_x, camera_y, viewport_width, viewport_height,
                    z:, elapsed: 0.0)
  lookup(:tilemap, id).draw_overlay(self, camera_x, camera_y, viewport_width,
                                    viewport_height, z: z, elapsed: elapsed)
end

#translated(dx, dy) ⇒ Object

Everything drawn in the block is shifted by (dx, dy) screen pixels — the camera's view transform. Because it is a draw-time transform rather than something baked into positions, the same world can be drawn again under a different offset and clip, which is what split-screen is.



220
221
222
223
224
225
226
227
228
229
# File 'lib/rgame/core/renderer.rb', line 220

def translated(dx, dy)
  return yield if dx.zero? && dy.zero?

  push_translate(dx, dy)
  begin
    yield
  ensure
    pop
  end
end

#triangle(x1, y1, x2, y2, x3, y3, z: SHAPE_Z, color: nil) ⇒ Object



150
151
152
# File 'lib/rgame/core/renderer.rb', line 150

def triangle(x1, y1, x2, y2, x3, y3, z: SHAPE_Z, color: nil)
  draw_triangle(x1, y1, x2, y2, x3, y3, z, packed(color))
end