Class: RGame::Core::Renderer
- Inherits:
-
Object
- Object
- RGame::Core::Renderer
- 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
-
#assets ⇒ Object
Returns the value of attribute assets.
-
#font ⇒ Object
The font this renderer draws with when a call does not name one.
Class Method Summary collapse
-
.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 andrenderer.sprite('hero.json', …)works with nothing set up.
Instance Method Summary collapse
- #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.
- #begin_record ⇒ Object
- #cancel_record ⇒ Object
-
#circle(cx, cy, radius, z: SHAPE_Z, color: nil, segments: CIRCLE_SEGMENTS) ⇒ Object
A filled circle, as a fan of triangles around its centre.
-
#clipped(x, y, width, height) ⇒ Object
Everything drawn in the block is confined to the given rectangle.
-
#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.
- #draw_circle(*args) ⇒ Object
- #draw_image(image, x, y, z, color) ⇒ Object
- #draw_image_rot(*args) ⇒ Object
- #draw_image_scaled(*args) ⇒ Object
- #draw_line(*args) ⇒ Object
- #draw_quad(*args) ⇒ Object
- #draw_rect(x, y, width, height, z, color) ⇒ Object
- #draw_text(*args) ⇒ Object
- #draw_triangle(*args) ⇒ Object
- #drawing? ⇒ Object
- #end_record ⇒ Object
-
#image(image, cx, cy, angle: 0, scale: 1, z: IMAGE_Z, color: nil) ⇒ Object
An image centred on (cx, cy), rotated
angledegrees clockwise about that centre and uniformly scaled. -
#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.
- #initialize(app) ⇒ Object constructor
-
#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.
-
#nine_slice(id, x, y, width, height, z: IMAGE_Z, tint: nil) ⇒ Object
A nine-slice filling (x, y, width, height), tinted by
tintif given. - #pop ⇒ Object
- #push_clip(x, y, width, height) ⇒ Object
- #push_rotate(degrees, pivot_x, pivot_y) ⇒ Object
- #push_scale(sx, sy) ⇒ Object
- #push_translate(dx, dy) ⇒ Object
-
#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.
-
#record ⇒ Object
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.
-
#rect(x, y, width, height, z: SHAPE_Z, color: nil) ⇒ Object
A filled axis-aligned rectangle.
-
#register_image(id, image) ⇒ Object
--- draw-by-id ---------------------------------------------------------.
- #register_nine_slice(id, nine_slice) ⇒ Object
- #register_sheet(id, sheet) ⇒ Object
- #register_tilemap(id, tilemap) ⇒ Object
-
#register_ui_atlas(atlas) ⇒ Object
Registers every element of a UiAtlas under its own name, since those names are what a widget asks for.
-
#rotated(angle, pivot_x, pivot_y) ⇒ Object
Everything drawn in the block is rotated
angledegrees about (pivot_x, pivot_y), so a node can spin all of its parts coherently around one point. - #scaled(sx, sy = sx) ⇒ Object
-
#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).
-
#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.
-
#text_height(font: nil) ⇒ Object
The line height: what to step y by for a second line.
-
#text_width(string, font: nil) ⇒ Object
What #text would occupy, for centring and layout.
-
#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).
-
#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
zthe scene picks so it lands over the actors. -
#translated(dx, dy) ⇒ Object
Everything drawn in the block is shifted by (dx, dy) screen pixels — the camera's view transform.
- #triangle(x1, y1, x2, y2, x3, y3, z: SHAPE_Z, color: nil) ⇒ Object
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
#assets ⇒ Object
Returns the value of attribute assets.
77 78 79 |
# File 'lib/rgame/core/renderer.rb', line 77 def assets @assets end |
#font ⇒ Object
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
#app ⇒ Object
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_record ⇒ Object
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_record ⇒ Object
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_record ⇒ Object
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 |
#pop ⇒ Object
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 |
#record ⇒ Object
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, , , elapsed: 0.0) lookup(:tilemap, id) .draw(self, camera_x, camera_y, , , 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 (id, camera_x, camera_y, , , z:, elapsed: 0.0) lookup(:tilemap, id).(self, camera_x, camera_y, , , 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 |