Skip to content

Latest commit

 

History

History
140 lines (108 loc) · 3.23 KB

File metadata and controls

140 lines (108 loc) · 3.23 KB

Render Module

Import:

import 'package:gamengine/render.dart';

What It Contains

  • Components: Sprite, TiledSprite, AnimatedSprite
  • Systems: RenderSystem, SpriteAnimationSystem, DebugSystem
  • Runtime: RenderQueue, RenderMetrics, CameraState, CameraFollowSystem, RenderPass
  • Flutter bridge: GameView + Painter

Basic Setup

final queue = RenderQueue();
final camera = CameraState();

engine.addSystem(SpriteAnimationSystem(world: world), 950);
engine.addSystem(
  RenderSystem(
    queue: queue,
    camera: camera,
  ),
);

Widget Usage

GameView(
  engine: engine,
  queue: queue,
  camera: camera,
);

Aspect Ratio Lock

When you want a fixed aspect ratio (like 16:9) with letterboxing, set a target viewport size and enable aspect ratio locking on the camera:

final camera = CameraState(
  targetViewportWidth: 1920,
  targetViewportHeight: 1080,
  lockViewportAspectRatio: true,
);

Letterbox bars use letterboxColor (default black).

Atlas Batching

Painter automatically batches eligible sprite draw calls through Flutter atlas rendering when:

  • same Image
  • same z
  • uniform positive scale (scaleX ~= scaleY)

Non-eligible sprites fall back to per-sprite drawing.

Tiled Sprites

TiledSprite repeats an image over a fixed areaSize by default.

Set extendInfinitely: true to have it cover the camera cull rect instead, which is useful for repeating backgrounds that should never run out.

Custom Render Passes

RenderSystem owns the main render pipeline, but you can inject custom drawing through RenderPass implementations. Passes write into the shared RenderQueue, so they use the same ordering and painter as the built-in world renderer.

class OffscreenIndicatorPass extends RenderPass {
  const OffscreenIndicatorPass();

  @override
  int get priority => 100;

  @override
  RenderPassStage get stage => RenderPassStage.afterWorld;

  @override
  void write(
    World world, {
    required CameraState camera,
    required RenderQueue queue,
  }) {
    final viewRect = camera.worldViewRect;
    final center = viewRect.center;
    final edgeRect = viewRect.deflate(24);

    for (final entity in world.query1<Transform>()) {
      final transform = entity.get<Transform>();
      final target = Offset(transform.position.x, transform.position.y);
      if (viewRect.contains(target)) {
        continue;
      }

      final dx = target.dx - center.dx;
      final dy = target.dy - center.dy;
      if (dx == 0 && dy == 0) {
        continue;
      }

      final scaleX = dx == 0
          ? double.infinity
          : (edgeRect.width * 0.5) / dx.abs();
      final scaleY = dy == 0
          ? double.infinity
          : (edgeRect.height * 0.5) / dy.abs();
      final t = scaleX < scaleY ? scaleX : scaleY;

      queue.add(
        DrawCircleCommand(
          center: Offset(center.dx + dx * t, center.dy + dy * t),
          radius: 8,
          z: 10_000,
        ),
      );
    }
  }
}

final renderSystem = RenderSystem(
  queue: queue,
  camera: camera,
  passes: const [OffscreenIndicatorPass()],
);

Use RenderPassStage.beforeWorld for background-style passes and RenderPassStage.afterWorld for overlays such as indicators or debug markers.