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 sorted when the frame closes, so what ends up on top is decided by z rather than by call order. Equal z keeps call order, which is what stops same-layer sprites flickering between frames.
z: is an offset inside the current layer, not a global number
A z: is added to whatever #layered most recently pushed, and must be
within RGame::Util::Z::Z_MIN..Z_MAX — one node's worth of room. It
orders that node's own drawing (its panel under its label, a shadow under
its sprite) and can reach nothing else. Which node comes before which is
the scene graph's business, resolved by the traversal and handed here as a
layer; see RGame::Util::Z.
A caller outside any #layered block draws at the base layer 0, which is
what a spec or a bare script gets.
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
- Z =
RGame::Util::Z
- SHAPE_Z =
Shapes default above sprites, so a debug box or a health bar drawn without a
z:lands on top of that node's sprite rather than under it. Well inside one slot, so the defaults order a node's own drawing and nothing further. 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, within the same slot.
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
- #layer ⇒ Object
-
#layered(band = Z::DEFAULT) ⇒ Object
Everything drawn in the block draws in its own layer: a fresh slot in
band, which everyz:inside is then an offset from. -
#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.
- #next_layer_slot(band) ⇒ Object
-
#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_layer(base) ⇒ 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, layer, cull_x, cull_y, cull_width, cull_height, elapsed: 0.0) ⇒ Object
One layer of a tile map — the layer a Tiled
.tmxlists atlayer, counting from the bottom. -
#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.
92 93 94 |
# File 'lib/rgame/core/renderer.rb', line 92 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.
309 310 311 |
# File 'lib/rgame/core/renderer.rb', line 309 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.
86 87 88 89 90 |
# File 'lib/rgame/core/renderer.rb', line 86 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.
212 213 214 |
# File 'lib/rgame/core/renderer.rb', line 212 def background(image, x = 0, y = 0, z: IMAGE_Z, color: nil) draw_image(resolve_image(image), x, y, Z.offset(z), packed(color)) end |
#begin_record ⇒ Object
329 330 331 332 333 334 335 336 337 338 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 329
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
355 356 357 358 359 360 361 362 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 355
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.
181 182 183 |
# File 'lib/rgame/core/renderer.rb', line 181 def circle(cx, cy, radius, z: SHAPE_Z, color: nil, segments: CIRCLE_SEGMENTS) draw_circle(cx, cy, radius, segments, Z.offset(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.
295 296 297 298 299 300 301 302 |
# File 'lib/rgame/core/renderer.rb', line 295 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.
360 361 362 |
# File 'lib/rgame/core/renderer.rb', line 360 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
340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 340
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.
190 191 192 |
# File 'lib/rgame/core/renderer.rb', line 190 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.offset(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.
205 206 207 |
# File 'lib/rgame/core/renderer.rb', line 205 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.offset(z), packed(color)) end |
#layer ⇒ Object
311 312 313 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 311 static VALUE renderer_layer(VALUE self) { return DBL2NUM(rgame_app_layer(drawing_app(self))); } |
#layered(band = Z::DEFAULT) ⇒ Object
Everything drawn in the block draws in its own layer: a fresh slot in
band, which every z: inside is then an offset from.
renderer.layered(:hud) { renderer.text(score, 12, 10) }
Two things fall out of it, and they are the whole of draw order.
Slots are handed out in the order they are asked for, so nesting
this the way a scene graph is nested makes draw order tree order — a
node drawn later is in front, and its whole subtree with it. And a
band is a hard partition: every slot in :hud is above every slot in
:world, whatever either drew, because they are 2**40 apart and a z:
cannot reach out of one slot. See RGame::Util::Z.
A caller that never uses this draws at layer 0 and gets exactly the z it passes, which is what a spec or a one-off script wants.
277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/rgame/core/renderer.rb', line 277 def layered(band = Z::DEFAULT) index = Z.index(band) push_layer(Z.slot_base(index, next_layer_slot(index))) begin yield ensure # An ensure for the same reason `rotated` has one: a scene that raises # mid-draw would otherwise leave every later node drawing in its # layer, and the frame would come out interleaved with no clue why. pop end 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.
176 177 178 |
# File 'lib/rgame/core/renderer.rb', line 176 def line(x1, y1, x2, y2, thickness: 1.0, z: SHAPE_Z, color: nil) draw_line(x1, y1, x2, y2, thickness, Z.offset(z), packed(color)) end |
#next_layer_slot(band) ⇒ Object
316 317 318 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 316
static VALUE renderer_next_layer_slot(VALUE self, VALUE band) {
return UINT2NUM(rgame_app_next_layer_slot(drawing_app(self), NUM2INT(band)));
}
|
#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.
131 132 133 |
# File 'lib/rgame/core/renderer.rb', line 131 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
364 365 366 367 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 364 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_layer(base) ⇒ Object
306 307 308 309 |
# File 'ext/rgame_core/ruby/renderer_ext.c', line 306
static VALUE renderer_push_layer(VALUE self, VALUE base) {
rgame_app_push_layer(drawing_app(self), NUM2DBL(base));
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.
166 167 168 |
# File 'lib/rgame/core/renderer.rb', line 166 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.offset(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.
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/rgame/core/renderer.rb', line 343 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.
160 161 162 |
# File 'lib/rgame/core/renderer.rb', line 160 def rect(x, y, width, height, z: SHAPE_Z, color: nil) draw_rect(x, y, width, height, Z.offset(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.
111 |
# File 'lib/rgame/core/renderer.rb', line 111 def register_image(id, image) = registry(:image)[id] = image |
#register_nine_slice(id, nine_slice) ⇒ Object
114 |
# File 'lib/rgame/core/renderer.rb', line 114 def register_nine_slice(id, nine_slice) = registry(:nine_slice)[id] = nine_slice |
#register_sheet(id, sheet) ⇒ Object
112 |
# File 'lib/rgame/core/renderer.rb', line 112 def register_sheet(id, sheet) = registry(:sheet)[id] = sheet |
#register_tilemap(id, tilemap) ⇒ Object
113 |
# File 'lib/rgame/core/renderer.rb', line 113 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.
118 119 120 121 |
# File 'lib/rgame/core/renderer.rb', line 118 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.
222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/rgame/core/renderer.rb', line 222 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
251 252 253 254 255 256 257 258 259 260 |
# File 'lib/rgame/core/renderer.rb', line 251 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).
124 125 126 |
# File 'lib/rgame/core/renderer.rb', line 124 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.
321 322 323 |
# File 'lib/rgame/core/renderer.rb', line 321 def text(string, x, y, z: TEXT_Z, color: nil, font: nil) draw_text(font || self.font, string, x, y, Z.offset(z), packed(color)) end |
#text_height(font: nil) ⇒ Object
The line height: what to step y by for a second line.
330 |
# File 'lib/rgame/core/renderer.rb', line 330 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.
327 |
# File 'lib/rgame/core/renderer.rb', line 327 def text_width(string, font: nil) = (font || self.font).text_width(string) |
#tilemap(id, layer, cull_x, cull_y, cull_width, cull_height, elapsed: 0.0) ⇒ Object
One layer of a tile map — the layer a Tiled .tmx lists at layer,
counting from the bottom.
One layer rather than the whole map, because a scene draws its actors
between two of them: trunks under, canopies over. Which is which is the
scene tree's business — RGame::Engine::TileMapLayer mounts a node per
layer — so this takes no z:.
Drawn in world coordinates: a tile at column 3 lands at
3 * tile_width, and getting it onto the screen is the caller's
transform, like every other drawing method here. The rectangle is a
cull rect — which part of the world is worth drawing — so a camera
supplies it but does not move the result. That is what lets one map be
drawn through several cameras in a frame.
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.
154 155 156 157 |
# File 'lib/rgame/core/renderer.rb', line 154 def tilemap(id, layer, cull_x, cull_y, cull_width, cull_height, elapsed: 0.0) lookup(:tilemap, id) .draw_layer(self, layer, cull_x, cull_y, cull_width, cull_height, 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.
240 241 242 243 244 245 246 247 248 249 |
# File 'lib/rgame/core/renderer.rb', line 240 def translated(dx, dy) return yield if dx.zero? && dy.zero? push_translate(dx, dy) begin yield ensure pop end end |