diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index 921a8e58..2f510b2a 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -1,6 +1,7 @@ import 'package:flame/components.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; /// {@template bottom_group} /// Grouping of the board's symmetrical bottom [Component]s. @@ -8,7 +9,7 @@ import 'package:pinball_components/pinball_components.dart'; /// The [BottomGroup] consists of [Flipper]s, [Baseboard]s and [Kicker]s. /// {@endtemplate} // TODO(allisonryan0002): Consider renaming. -class BottomGroup extends Component { +class BottomGroup extends Component with Rendering { /// {@macro bottom_group} BottomGroup() : super( @@ -17,7 +18,9 @@ class BottomGroup extends Component { _BottomGroupSide(side: BoardSide.left), ], priority: RenderPriority.bottomGroup, - ); + ) { + zIndex = 2; + } } /// {@template bottom_group_side} diff --git a/lib/game/components/dino_desert.dart b/lib/game/components/dino_desert.dart index 9e912575..5cf15205 100644 --- a/lib/game/components/dino_desert.dart +++ b/lib/game/components/dino_desert.dart @@ -14,7 +14,7 @@ class DinoDesert extends Blueprint { DinoDesert() : super( components: [ - ChromeDino()..initialPosition = Vector2(12.3, -6.9), + // ChromeDino()..initialPosition = Vector2(12.3, -6.9), ], blueprints: [ DinoWalls(), diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 402164c2..663b4e3c 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -45,38 +45,36 @@ class PinballGame extends Forge2DGame await add(gameFlowController = GameFlowController(this)); await add(CameraController(this)); await add(Backboard.waiting(position: Vector2(0, -88))); - await add(BoardBackgroundSpriteComponent()); - await add(Drain()); - await add(BottomGroup()); - await addFromBlueprint(Boundaries()); - final launcher = Launcher(); - await add(launcher); - await add(FlutterForest()..priority = 1); - await add(Multipliers()); + await add( + PinballCanvasComponent( + camera: camera, + children: [ + BoardBackgroundSpriteComponent(), + Multipliers(), + Drain(), + BottomGroup(), + Launcher(), + FlutterForest(), + GoogleWord( + position: Vector2( + BoardDimensions.bounds.center.dx - 4.1, + BoardDimensions.bounds.center.dy + 1.8, + ), + ), + ], + ), + ); + + await addFromBlueprint(Boundaries()); await addFromBlueprint(SparkyScorch()); await addFromBlueprint(AndroidAcres()); await addFromBlueprint(DinoDesert()); await addFromBlueprint(Slingshots()); - await add( - GoogleWord( - position: Vector2( - BoardDimensions.bounds.center.dx - 4.1, - BoardDimensions.bounds.center.dy + 1.8, - ), - ), - ); await super.onLoad(); } - @override - void renderTree(Canvas canvas) { - final pinballCanvas = PinballCanvas(canvas, camera); - super.renderTree(pinballCanvas); - pinballCanvas.runPostActions(); - } - BoardSide? focusedBoardSide; @override @@ -152,8 +150,8 @@ class _GameBallsController extends ComponentController } @override - Future onLoad() async { - await super.onLoad(); + void onMount() { + super.onMount(); _spawnBall(); } @@ -164,7 +162,7 @@ class _GameBallsController extends ComponentController Vector2(41.1, 43).x, Vector2(41.1, 45).y - Ball.size.y, ); - component.add(ball); + component.firstChild()?.add(ball); } } diff --git a/packages/pinball_components/lib/src/components/board_background_sprite_component.dart b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart index 1a4e34c6..e2c683bb 100644 --- a/packages/pinball_components/lib/src/components/board_background_sprite_component.dart +++ b/packages/pinball_components/lib/src/components/board_background_sprite_component.dart @@ -2,8 +2,10 @@ import 'package:flame/components.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; -class BoardBackgroundSpriteComponent extends SpriteComponent with HasGameRef { +class BoardBackgroundSpriteComponent extends SpriteComponent + with HasGameRef, Rendering { BoardBackgroundSpriteComponent() : super( anchor: Anchor.center, diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index 29d74ade..71efb498 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -124,10 +124,11 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { } class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent - with HasGameRef { + with HasGameRef, Rendering { @override Future onLoad() async { await super.onLoad(); + zIndex = 1; final sprite = await gameRef.loadSprite( Assets.images.launchRamp.backgroundRailing.keyName, diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index ec1d38d7..8fe9070f 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -6,4 +6,5 @@ export 'src/contact_behavior.dart'; export 'src/keyboard_input_controller.dart'; export 'src/parent_is_a.dart'; export 'src/pinball_canvas.dart'; +export 'src/rendering/rendering.dart'; export 'src/sprite_animation.dart'; diff --git a/packages/pinball_flame/lib/src/pinball_canvas.dart b/packages/pinball_flame/lib/src/pinball_canvas.dart index 06fa8344..8b137891 100644 --- a/packages/pinball_flame/lib/src/pinball_canvas.dart +++ b/packages/pinball_flame/lib/src/pinball_canvas.dart @@ -1,207 +1 @@ -// ignore_for_file: public_member_api_docs -import 'dart:typed_data'; -import 'dart:ui'; - -import 'package:flame/components.dart'; -import 'package:flame/game.dart'; - -typedef CanvasAction = void Function(Canvas canvas); - -mixin AbsoluteRendering on PositionComponent { - @override - void renderTree(Canvas canvas) { - if (canvas is PinballCanvas) { - canvas.actions.add(this); - } - } -} - -class PinballCanvas implements Canvas { - PinballCanvas(this.canvas, this.camera); - - Canvas canvas; - Camera camera; - - List actions = []; - - void runPostActions() { - for (final action in actions) { - renderComponent(action); - } - actions.clear(); - } - - void renderComponent(PositionComponent component) { - canvas.save(); - // camera.viewport.apply(canvas); - camera.apply(canvas); - canvas.transform(component.transformMatrix.storage); - component.render(canvas); - canvas.restore(); - } - - @override - void clipPath(Path path, {bool doAntiAlias = true}) => - canvas.clipPath(path, doAntiAlias: doAntiAlias); - - @override - void clipRRect(RRect rrect, {bool doAntiAlias = true}) => - canvas.clipRRect(rrect, doAntiAlias: doAntiAlias); - - @override - void clipRect(Rect rect, - {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true}) => - canvas.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias); - - @override - void drawArc( - Rect rect, - double startAngle, - double sweepAngle, - bool useCenter, - Paint paint, - ) => - canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint); - - @override - void drawAtlas( - Image atlas, - List transforms, - List rects, - List? colors, - BlendMode? blendMode, - Rect? cullRect, - Paint paint, - ) => - canvas.drawAtlas( - atlas, - transforms, - rects, - colors, - blendMode, - cullRect, - paint, - ); - - @override - void drawCircle(Offset c, double radius, Paint paint) => canvas.drawCircle( - c, - radius, - paint, - ); - - @override - void drawColor(Color color, BlendMode blendMode) => - canvas.drawColor(color, blendMode); - - @override - void drawDRRect(RRect outer, RRect inner, Paint paint) => - canvas.drawDRRect(outer, inner, paint); - - @override - void drawImage(Image image, Offset offset, Paint paint) => - canvas.drawImage(image, offset, paint); - - @override - void drawImageNine(Image image, Rect center, Rect dst, Paint paint) => - canvas.drawImageNine(image, center, dst, paint); - - @override - void drawImageRect(Image image, Rect src, Rect dst, Paint paint) => - canvas.drawImageRect(image, src, dst, paint); - - @override - void drawLine(Offset p1, Offset p2, Paint paint) => - canvas.drawLine(p1, p2, paint); - - @override - void drawOval(Rect rect, Paint paint) => canvas.drawOval(rect, paint); - - @override - void drawPaint(Paint paint) => canvas.drawPaint(paint); - - @override - void drawParagraph(Paragraph paragraph, Offset offset) => - canvas.drawParagraph(paragraph, offset); - - @override - void drawPath(Path path, Paint paint) => canvas.drawPath(path, paint); - - @override - void drawPicture(Picture picture) => canvas.drawPicture(picture); - - @override - void drawPoints(PointMode pointMode, List points, Paint paint) => - canvas.drawPoints(pointMode, points, paint); - - @override - void drawRRect(RRect rrect, Paint paint) => canvas.drawRRect(rrect, paint); - - @override - void drawRawAtlas( - Image atlas, - Float32List rstTransforms, - Float32List rects, - Int32List? colors, - BlendMode? blendMode, - Rect? cullRect, - Paint paint, - ) => - canvas.drawRawAtlas( - atlas, - rstTransforms, - rects, - colors, - blendMode, - cullRect, - paint, - ); - - @override - void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) => - canvas.drawRawPoints(pointMode, points, paint); - - @override - void drawRect(Rect rect, Paint paint) => canvas.drawRect(rect, paint); - - @override - void drawShadow( - Path path, - Color color, - double elevation, - bool transparentOccluder, - ) => - canvas.drawShadow(path, color, elevation, transparentOccluder); - - @override - void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) => - canvas.drawVertices(vertices, blendMode, paint); - - @override - int getSaveCount() => canvas.getSaveCount(); - - @override - void restore() => canvas.restore(); - - @override - void rotate(double radians) => canvas.rotate(radians); - - @override - void save() => canvas.save(); - - @override - void saveLayer(Rect? bounds, Paint paint) => canvas.saveLayer(bounds, paint); - - @override - void scale(double sx, [double? sy]) => canvas.scale(sx); - - @override - void skew(double sx, double sy) => canvas.skew(sx, sy); - - @override - void transform(Float64List matrix4) => canvas.transform(matrix4); - - @override - void translate(double dx, double dy) => canvas.translate(dx, dy); -} diff --git a/packages/pinball_flame/lib/src/rendering/pinball_canvas.dart b/packages/pinball_flame/lib/src/rendering/pinball_canvas.dart new file mode 100644 index 00000000..aa7c7c40 --- /dev/null +++ b/packages/pinball_flame/lib/src/rendering/pinball_canvas.dart @@ -0,0 +1,238 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_flame/src/rendering/rendering.dart'; + +class PinballCanvas implements Canvas { + PinballCanvas({ + required this.camera, + }); + + late Canvas canvas; + + Camera camera; + + final List _zBuffer = []; + + void buffer(Rendering rendering) => _zBuffer.add(rendering); + + void render() { + _zBuffer + ..sort((a, b) => a.zIndex.compareTo(b.zIndex)) + ..whereType().forEach(_render); + // ..expand(_flatten).forEach(_render); + _zBuffer.clear(); + } + + Iterable _flatten(Rendering rendering) { + final components = []; + if (rendering is PositionComponent) { + components.add(rendering as PositionComponent); + } + components.addAll(rendering.descendants().whereType()); + return components; + } + + void _render(Component component) { + // TODO(alestiago): Uses zones to detect when super.renderTree is called, + // and call _render insted. + if (component is BodyComponent) { + final Matrix4 _transform = Matrix4.identity(); + double? _lastAngle; + + if (_transform.m14 != component.body.position.x || + _transform.m24 != component.body.position.y || + _lastAngle != component.angle) { + _transform.setIdentity(); + _transform.translate( + component.body.position.x, component.body.position.y); + _transform.rotateZ(component.angle); + _lastAngle = component.angle; + } + canvas.save(); + canvas.transform(_transform.storage); + component.children.forEach(_render); + canvas.restore(); + } else if (component is PositionComponent) { + canvas + ..save() + ..transform(component.transformMatrix.storage); + component.render(canvas); + component.children.forEach(_render); + canvas.restore(); + } else { + component.render(canvas); + component.children.forEach(_render); + } + } + + @override + void clipPath(Path path, {bool doAntiAlias = true}) => + canvas.clipPath(path, doAntiAlias: doAntiAlias); + + @override + void clipRRect(RRect rrect, {bool doAntiAlias = true}) => + canvas.clipRRect(rrect, doAntiAlias: doAntiAlias); + + @override + void clipRect(Rect rect, + {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true}) => + canvas.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias); + + @override + void drawArc( + Rect rect, + double startAngle, + double sweepAngle, + bool useCenter, + Paint paint, + ) => + canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint); + + @override + void drawAtlas( + Image atlas, + List transforms, + List rects, + List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint, + ) => + canvas.drawAtlas( + atlas, + transforms, + rects, + colors, + blendMode, + cullRect, + paint, + ); + + @override + void drawCircle(Offset c, double radius, Paint paint) => canvas.drawCircle( + c, + radius, + paint, + ); + + @override + void drawColor(Color color, BlendMode blendMode) => + canvas.drawColor(color, blendMode); + + @override + void drawDRRect(RRect outer, RRect inner, Paint paint) => + canvas.drawDRRect(outer, inner, paint); + + @override + void drawImage(Image image, Offset offset, Paint paint) => + canvas.drawImage(image, offset, paint); + + @override + void drawImageNine(Image image, Rect center, Rect dst, Paint paint) => + canvas.drawImageNine(image, center, dst, paint); + + @override + void drawImageRect(Image image, Rect src, Rect dst, Paint paint) => + canvas.drawImageRect(image, src, dst, paint); + + @override + void drawLine(Offset p1, Offset p2, Paint paint) => + canvas.drawLine(p1, p2, paint); + + @override + void drawOval(Rect rect, Paint paint) => canvas.drawOval(rect, paint); + + @override + void drawPaint(Paint paint) => canvas.drawPaint(paint); + + @override + void drawParagraph(Paragraph paragraph, Offset offset) => + canvas.drawParagraph(paragraph, offset); + + @override + void drawPath(Path path, Paint paint) => canvas.drawPath(path, paint); + + @override + void drawPicture(Picture picture) => canvas.drawPicture(picture); + + @override + void drawPoints(PointMode pointMode, List points, Paint paint) => + canvas.drawPoints(pointMode, points, paint); + + @override + void drawRRect(RRect rrect, Paint paint) => canvas.drawRRect(rrect, paint); + + @override + void drawRawAtlas( + Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint, + ) => + canvas.drawRawAtlas( + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint, + ); + + @override + void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) => + canvas.drawRawPoints(pointMode, points, paint); + + @override + void drawRect(Rect rect, Paint paint) => canvas.drawRect(rect, paint); + + @override + void drawShadow( + Path path, + Color color, + double elevation, + bool transparentOccluder, + ) => + canvas.drawShadow(path, color, elevation, transparentOccluder); + + @override + void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) => + canvas.drawVertices(vertices, blendMode, paint); + + @override + int getSaveCount() => canvas.getSaveCount(); + + @override + void restore() => canvas.restore(); + + @override + void rotate(double radians) => canvas.rotate(radians); + + @override + void save() => canvas.save(); + + @override + void saveLayer(Rect? bounds, Paint paint) => canvas.saveLayer(bounds, paint); + + @override + void scale(double sx, [double? sy]) => canvas.scale(sx); + + @override + void skew(double sx, double sy) => canvas.skew(sx, sy); + + @override + void transform(Float64List matrix4) => canvas.transform(matrix4); + + @override + void translate(double dx, double dy) => canvas.translate(dx, dy); +} diff --git a/packages/pinball_flame/lib/src/rendering/pinball_canvas_component.dart b/packages/pinball_flame/lib/src/rendering/pinball_canvas_component.dart new file mode 100644 index 00000000..5413dd28 --- /dev/null +++ b/packages/pinball_flame/lib/src/rendering/pinball_canvas_component.dart @@ -0,0 +1,24 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:ui' show Canvas; + +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:pinball_flame/src/rendering/rendering.dart'; + +class PinballCanvasComponent extends Component { + PinballCanvasComponent({ + required Camera camera, + Iterable? children, + }) : _pinballCanvas = PinballCanvas(camera: camera), + super(children: children); + + final PinballCanvas _pinballCanvas; + + @override + void renderTree(Canvas canvas) { + _pinballCanvas.canvas = canvas; + super.renderTree(_pinballCanvas); + _pinballCanvas.render(); + } +} diff --git a/packages/pinball_flame/lib/src/rendering/rendering.dart b/packages/pinball_flame/lib/src/rendering/rendering.dart new file mode 100644 index 00000000..ba16a653 --- /dev/null +++ b/packages/pinball_flame/lib/src/rendering/rendering.dart @@ -0,0 +1,3 @@ +export 'pinball_canvas.dart'; +export 'pinball_canvas_component.dart'; +export 'rendering_mixin.dart'; diff --git a/packages/pinball_flame/lib/src/rendering/rendering_mixin.dart b/packages/pinball_flame/lib/src/rendering/rendering_mixin.dart new file mode 100644 index 00000000..7eaef1e0 --- /dev/null +++ b/packages/pinball_flame/lib/src/rendering/rendering_mixin.dart @@ -0,0 +1,18 @@ +// ignore_for_file: public_member_api_docs +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:pinball_flame/src/rendering/rendering.dart'; + +mixin Rendering on Component { + int zIndex = 0; + + @override + void renderTree(Canvas canvas) { + if (canvas is PinballCanvas) { + canvas.buffer(this); + } else { + super.renderTree(canvas); + } + } +}