LCOV - code coverage report
Current view: top level - lib/src/game - viewport.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 44 44 100.0 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:math' as math;
       2             : import 'dart:ui';
       3             : 
       4             : import 'package:flutter/painting.dart';
       5             : 
       6             : import '../../extensions.dart';
       7             : import '../../game.dart';
       8             : import 'projector.dart';
       9             : 
      10             : /// A viewport is a class that potentially translates and resizes the screen.
      11             : /// The reason you might want to have a viewport is to make sure you handle any
      12             : /// screen size and resolution correctly depending on your needs.
      13             : ///
      14             : /// Not only screens can have endless configurations of width and height with
      15             : /// different ratios, you can also embed games as widgets within a Flutter app.
      16             : /// In fact, the size of the game can even change dynamically (if the layout
      17             : /// changes or in desktop, for example).
      18             : ///
      19             : /// For some simple games, that is not an issue. The game will just adapt
      20             : /// to fit the screen, so if the game world is 1:1 with screen it will just
      21             : /// be bigger or smaller. But if you want a consistent experience across
      22             : /// platforms and players, you should use a viewport.
      23             : ///
      24             : /// When using a viewport, [resize] should be called by the engine with
      25             : /// the raw canvas size (on startup and subsequent resizes) and that will
      26             : /// configure [getEffectiveSize()] and [getCanvasSize()].
      27             : /// The Viewport can also apply an offset to render and clip the canvas adding
      28             : /// borders (clipping) when necessary.
      29             : /// When rendering, call [render] and put all your rendering inside the lambda
      30             : /// so that the correct transformations are applied.
      31             : ///
      32             : /// You can think of a Viewport as mechanism to watch a wide-screen movie on a
      33             : /// square monitor. You can stretch the movie to fill the square, but the width
      34             : /// and height will be stretched by different amounts, causing distortion. You
      35             : /// can fill in the smallest dimension and crop the biggest (that causes
      36             : /// cropping). Or you can fill in the biggest and add black bars to cover the
      37             : /// unused space on the smallest (this is the [FixedResolutionViewport]).
      38             : ///
      39             : /// The other option is to not use a viewport ([DefaultViewport]) and have
      40             : /// your game dynamically render itself to fill in the existing space (basically
      41             : /// this means generating either a wide-screen or a square movie on the fly).
      42             : /// The disadvantage is that different players on different devices will see
      43             : /// different games. For example a hidden door because it's too far away to
      44             : /// render in Screen 1 might be visible on Screen 2. Specially if it's an
      45             : /// online/competitive game, it can give unfair advantages to users with certain
      46             : /// screen resolutions. If you want to "play director" and know exactly what
      47             : /// every player is seeing at every time, you should use a Viewport.
      48             : abstract class Viewport extends Projector {
      49             :   /// This configures the viewport with a new raw canvas size.
      50             :   /// It should immediately affect [effectiveSize] and [canvasSize].
      51             :   /// This must be called by the engine at startup and also whenever the
      52             :   /// size changes.
      53             :   void resize(Vector2 newCanvasSize);
      54             : 
      55             :   /// This transforms the canvas so that the coordinate system is viewport-
      56             :   /// -aware. All your rendering logic should be put inside the lambda.
      57             :   void render(Canvas c, void Function(Canvas c) renderGame);
      58             : 
      59             :   /// This returns the effective size, after viewport transformation.
      60             :   /// This is not the game widget size but for all intents and purposes,
      61             :   /// inside your game, this size should be used as the real one.
      62             :   Vector2 get effectiveSize;
      63             : 
      64             :   /// This returns the real widget size (well actually the logical Flutter
      65             :   /// size of your widget). This is the raw canvas size as it would be without
      66             :   /// any viewport.
      67             :   ///
      68             :   /// You probably don't need to care about this if you are using a viewport.
      69             :   Vector2 get canvasSize;
      70             : }
      71             : 
      72             : /// This is the default viewport if you want no transformation.
      73             : /// The raw canvasSize is just propagated to the effective size and no
      74             : /// translation is applied.
      75             : /// This basically no-ops the viewport.
      76             : class DefaultViewport extends Viewport {
      77             :   @override
      78             :   late Vector2 canvasSize;
      79             : 
      80           3 :   @override
      81             :   void render(Canvas c, void Function(Canvas c) renderGame) {
      82           3 :     renderGame(c);
      83             :   }
      84             : 
      85          24 :   @override
      86             :   void resize(Vector2 newCanvasSize) {
      87          24 :     canvasSize = newCanvasSize;
      88             :   }
      89             : 
      90          24 :   @override
      91          24 :   Vector2 get effectiveSize => canvasSize;
      92             : 
      93           1 :   @override
      94             :   Vector2 projectVector(Vector2 vector) => vector;
      95             : 
      96           5 :   @override
      97             :   Vector2 unprojectVector(Vector2 vector) => vector;
      98             : 
      99           1 :   @override
     100             :   Vector2 scaleVector(Vector2 vector) => vector;
     101             : 
     102           1 :   @override
     103             :   Vector2 unscaleVector(Vector2 vector) => vector;
     104             : }
     105             : 
     106             : /// This is the most common viewport if you want to have full control of what
     107             : /// the game looks like. Basically this viewport makes sure the ratio between
     108             : /// width and height is *always* the same in your game, no matter the platform.
     109             : ///
     110             : /// To accomplish this you choose a virtual size that will always match the
     111             : /// effective size.
     112             : ///
     113             : /// Under the hood, the Viewport will try to expand (or contract) the virtual
     114             : /// size so that it fits the most of the screen as it can. So for example,
     115             : /// if the viewport happens to be the same ratio of the screen, it will resize
     116             : /// to fit 100%. But if they are different ratios, it will resize the most it
     117             : /// can and then will add black (color is configurable) borders.
     118             : ///
     119             : /// Then, inside your game, you can always assume the game size is the fixed
     120             : /// dimension that you provided.
     121             : ///
     122             : /// Normally you can pick a virtual size that has the same ratio as the most
     123             : /// used device for your game (like a pretty standard mobile ratio if you
     124             : /// are doing a mobile game) and then in most cases this will apply no
     125             : /// transformation whatsoever, and if the a device with a different ratio is
     126             : /// used it will try to adapt the best as possible.
     127             : class FixedResolutionViewport extends Viewport {
     128             :   @override
     129             :   late Vector2 canvasSize;
     130             : 
     131             :   @override
     132             :   late Vector2 effectiveSize;
     133             : 
     134             :   final Vector2 _scaledSize = Vector2.zero();
     135           3 :   Vector2 get scaledSize => _scaledSize.clone();
     136             : 
     137             :   final Vector2 _resizeOffset = Vector2.zero();
     138           6 :   Vector2 get resizeOffset => _resizeOffset.clone();
     139             : 
     140             :   late double _scale;
     141           4 :   double get scale => _scale;
     142             : 
     143             :   /// The matrix used for scaling and translating the canvas
     144             :   final Matrix4 _transform = Matrix4.identity();
     145             : 
     146             :   /// The Rect that is used to clip the canvas
     147             :   late Rect _clipRect;
     148             : 
     149           2 :   FixedResolutionViewport(this.effectiveSize);
     150             : 
     151           2 :   @override
     152             :   void resize(Vector2 newCanvasSize) {
     153           2 :     canvasSize = newCanvasSize;
     154             : 
     155           4 :     _scale = math.min(
     156          10 :       canvasSize.x / effectiveSize.x,
     157          10 :       canvasSize.y / effectiveSize.y,
     158             :     );
     159             : 
     160           2 :     _scaledSize
     161           4 :       ..setFrom(effectiveSize)
     162           4 :       ..scale(_scale);
     163           2 :     _resizeOffset
     164           4 :       ..setFrom(canvasSize)
     165           4 :       ..sub(_scaledSize)
     166           2 :       ..scale(0.5);
     167             : 
     168           8 :     _clipRect = _resizeOffset & _scaledSize;
     169             : 
     170           4 :     _transform.setIdentity();
     171          12 :     _transform.translate(_resizeOffset.x, _resizeOffset.y);
     172           6 :     _transform.scale(_scale);
     173             :   }
     174             : 
     175           1 :   @override
     176             :   void render(Canvas c, void Function(Canvas) renderGame) {
     177           1 :     c.save();
     178           2 :     c.clipRect(_clipRect);
     179           3 :     c.transform(_transform.storage);
     180           1 :     renderGame(c);
     181           1 :     c.restore();
     182             :   }
     183             : 
     184           1 :   @override
     185             :   Vector2 projectVector(Vector2 viewportCoordinates) {
     186           4 :     return (viewportCoordinates * _scale)..add(_resizeOffset);
     187             :   }
     188             : 
     189           1 :   @override
     190             :   Vector2 unprojectVector(Vector2 screenCoordinates) {
     191           5 :     return (screenCoordinates - _resizeOffset)..scale(1 / _scale);
     192             :   }
     193             : 
     194           1 :   @override
     195             :   Vector2 scaleVector(Vector2 viewportCoordinates) {
     196           2 :     return viewportCoordinates * scale;
     197             :   }
     198             : 
     199           1 :   @override
     200             :   Vector2 unscaleVector(Vector2 screenCoordinates) {
     201           2 :     return screenCoordinates / scale;
     202             :   }
     203             : }

Generated by: LCOV version 1.15