Adding new text related components and upgrading old components to use the new features like anchor

This commit is contained in:
Luan Nico
2019-01-01 22:48:08 -02:00
parent 307e375378
commit 8dacfc90a6
8 changed files with 239 additions and 78 deletions

View File

@ -36,16 +36,12 @@ class AnimationComponent extends PositionComponent {
}
@override
bool loaded() {
return this.animation.loaded();
}
bool loaded() => animation.loaded();
@override
void render(Canvas canvas) {
if (loaded()) {
prepareCanvas(canvas);
animation.getSprite().render(canvas, width, height);
}
prepareCanvas(canvas);
animation.getSprite().render(canvas, width, height);
}
@override

View File

@ -1,9 +1,11 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/painting.dart';
import '../sprite.dart';
import '../position.dart';
import 'package:flutter/painting.dart';
import '../anchor.dart';
/// This represents a Component for your game.
///
@ -27,7 +29,7 @@ abstract class Component {
/// You can use the [Resizable] mixin if you want an implementation of this hook that keeps track of the current size.
void resize(Size size) {}
/// Wether this component has been loaded yet. If not loaded, [BaseGame] will not try to render it.
/// Whether this component has been loaded yet. If not loaded, [BaseGame] will not try to render it.
///
/// Sprite based components can use this to let [BaseGame] know not to try to render when the [Sprite] has not been loaded yet.
/// Note that for a more consistent experience, you can pre-load all your assets beforehand with Flame.images.loadAll.
@ -38,7 +40,7 @@ abstract class Component {
/// It will be called once per component per loop, and if it returns true, [BaseGame] will mark your component for deletion and remove it before the next loop.
bool destroy() => false;
/// Wether this component is HUD object or not.
/// Whether this component is HUD object or not.
///
/// HUD objects ignore the [BaseGame.camera] when rendered (so their position coordinates are considered relative to the device screen).
bool isHud() => false;
@ -52,12 +54,14 @@ abstract class Component {
int priority() => 0;
}
/// A [Component] implementation that represents a component that has a specific, possibly mutatable position on the screen.
/// A [Component] implementation that represents a component that has a specific, possibly dynamic position on the screen.
///
/// It represents a rectangle of dimension ([width], [height]), on the position ([x], [y]), rotate around its center with angle [angle].
/// It also uses the [anchor] property to properly position itself.
abstract class PositionComponent extends Component {
double x = 0.0, y = 0.0, angle = 0.0;
double width = 0.0, height = 0.0;
Anchor anchor = Anchor.topLeft;
Position toPosition() => new Position(x, y);
void setByPosition(Position position) {
@ -88,27 +92,24 @@ abstract class PositionComponent extends Component {
}
void prepareCanvas(Canvas canvas) {
canvas.translate(x, y);
// rotate around center
canvas.translate(width / 2, height / 2);
double ax = x - anchor.relativePosition.dx * width;
double ay = y - anchor.relativePosition.dy * height;
canvas.translate(ax, ay);
canvas.rotate(angle);
canvas.translate(-width / 2, -height / 2);
}
}
/// A [PositionComponent] that renders a single [Sprite] at the designated position, scaled to have the designated size and rotated to the designated angle.
///
/// This is the most commonly used child of [Component].
class SpriteComponent extends PositionComponent {
Sprite sprite;
final Paint paint = new Paint()..color = new Color(0xffffffff);
SpriteComponent();
SpriteComponent.square(double size, String imagePath)
: this.rectangle(size, size, imagePath);
SpriteComponent.square(double size, String imagePath) : this.rectangle(size, size, imagePath);
SpriteComponent.rectangle(double width, double height, String imagePath)
: this.fromSprite(width, height, new Sprite(imagePath));
SpriteComponent.rectangle(double width, double height, String imagePath) : this.fromSprite(width, height, new Sprite(imagePath));
SpriteComponent.fromSprite(double width, double height, this.sprite) {
this.width = width;
@ -117,15 +118,13 @@ class SpriteComponent extends PositionComponent {
@override
render(Canvas canvas) {
if (sprite != null && sprite.loaded() && x != null && y != null) {
prepareCanvas(canvas);
sprite.render(canvas, width, height);
}
prepareCanvas(canvas);
sprite.render(canvas, width, height);
}
@override
bool loaded() {
return this.sprite.loaded();
return sprite != null && sprite.loaded() && x != null && y != null;
}
@override

View File

@ -1,4 +1,3 @@
import 'dart:ui';
import 'package:flame/components/component.dart';
@ -10,7 +9,8 @@ import 'package:ordered_set/ordered_set.dart';
/// A component that lets your component be composed by others
/// It resembles [BaseGame]. It has an [components] property and an [add] method
mixin ComposedComponent on Component {
OrderedSet<Component> components = new OrderedSet(Comparing.on((c) => c.priority()));
OrderedSet<Component> components =
new OrderedSet(Comparing.on((c) => c.priority()));
@override
render(Canvas canvas) {
@ -45,4 +45,4 @@ mixin ComposedComponent on Component {
List<Resizable> children() =>
this.components.where((r) => r is Resizable).cast<Resizable>().toList();
}
}

View File

@ -19,7 +19,7 @@ class DebugComponent extends PositionComponent {
/// Don't do anything (change as desired)
void update(double t) {}
/// Renders the recatangle
/// Renders the rectangle
void render(Canvas c) {
prepareCanvas(c);
c.drawRect(new Rect.fromLTWH(0.0, 0.0, width, height), paint);

View File

@ -0,0 +1,160 @@
import 'dart:ui';
import 'dart:math' as math;
import 'package:flutter/src/painting/text_painter.dart';
import 'component.dart';
import 'resizable.dart';
import '../text_config.dart';
import '../palette.dart';
import '../position.dart';
class TextBoxConfig {
final double maxWidth;
final double margin;
final double timePerChar;
final double dismissDelay;
const TextBoxConfig({
this.maxWidth: 200.0,
this.margin: 8.0,
this.timePerChar: 0.0,
this.dismissDelay: 0.0,
});
}
class TextBoxComponent extends PositionComponent with Resizable {
Position p = Position.empty();
String _text;
TextConfig _config;
TextBoxConfig _boxConfig;
List<String> _lines;
double _maxLineWidth = 0.0;
double _lineHeight;
double _lifeTime = 0.0;
Image _cache;
String get text => _text;
TextConfig get config => _config;
TextBoxConfig get boxConfig => _boxConfig;
TextBoxComponent(String text, {TextConfig config = const TextConfig(), TextBoxConfig boxConfig = const TextBoxConfig()}) {
_boxConfig = boxConfig;
_config = config;
_text = text;
_lines = [''];
text.split(' ').forEach((word) {
String possibleLine = _lines.last + ' ' + word;
TextPainter p = config.toTextPainter(possibleLine);
if (_lineHeight == null) {
_lineHeight = p.height;
}
if (p.width <= _boxConfig.maxWidth - 2 * _boxConfig.margin) {
_lines.last = possibleLine;
_updateMaxWidth(p.width);
} else {
_lines.add(word);
_updateMaxWidth(config.toTextPainter(word).width);
}
});
_cache = _redrawCache();
}
void _updateMaxWidth(double w) {
if (w > _maxLineWidth) {
_maxLineWidth = w;
}
}
double get totalCharTime => _text.length * _boxConfig.timePerChar;
bool get finished => _lifeTime > totalCharTime + _boxConfig.dismissDelay;
int get currentChar => _boxConfig.timePerChar == 0.0 ? _text.length - 1 : math.min(_lifeTime ~/ _boxConfig.timePerChar, _text.length - 1);
int get currentLine {
int totalCharCount = 0;
int _currentChar = currentChar;
for (int i = 0; i < _lines.length; i++) {
totalCharCount += _lines[i].length;
if (totalCharCount > _currentChar) {
return i;
}
}
return _lines.length - 1;
}
double _withMargins(double size) => size + 2 * _boxConfig.margin;
@override
double get width => currentWidth;
@override
double get height => currentHeight;
double get totalWidth => _withMargins(_maxLineWidth);
double get totalHeight => _withMargins(_lineHeight * _lines.length);
double getLineWidth(String line, int charCount) {
return _withMargins(_config.toTextPainter(line.substring(0, math.min(charCount, line.length))).width);
}
double get currentWidth {
int i = 0;
int totalCharCount = 0;
int _currentChar = currentChar;
int _currentLine = currentLine;
return _lines.sublist(0, _currentLine + 1).map((line) {
int charCount = (i < _currentLine) ? line.length : (_currentChar - totalCharCount);
totalCharCount += line.length;
i++;
return getLineWidth(line, charCount);
}).reduce(math.max);
}
double get currentHeight => _withMargins((currentLine + 1) * _lineHeight);
void render(Canvas c) {
prepareCanvas(c);
c.drawImage(_cache, Offset.zero, BasicPalette.white.paint);
}
Image _redrawCache() {
PictureRecorder recorder = new PictureRecorder();
Canvas c = new Canvas(recorder, new Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()));
_fullRender(c);
return recorder.endRecording().toImage(width.toInt(), height.toInt());
}
void drawBackground(Canvas c) {}
void _fullRender(Canvas c) {
drawBackground(c);
int _currentLine = currentLine;
int charCount = 0;
double dy = _boxConfig.margin;
for (int line = 0; line < _currentLine; line++) {
charCount += _lines[line].length;
_config.toTextPainter(_lines[line]).paint(c, new Offset(_boxConfig.margin, dy));
dy += _lineHeight;
}
int max = math.min(currentChar - charCount, _lines[_currentLine].length);
_config.toTextPainter(_lines[_currentLine].substring(0, max)).paint(c, new Offset(_boxConfig.margin, dy));
}
void update(double dt) {
int prevCurrentChar = currentChar;
_lifeTime += dt;
if (prevCurrentChar != currentChar) {
_cache = _redrawCache();
}
}
}

View File

@ -0,0 +1,46 @@
import 'dart:ui';
import 'package:flutter/src/painting/text_painter.dart';
import 'component.dart';
import '../position.dart';
import '../text_config.dart';
class TextComponent extends PositionComponent {
String _text;
TextConfig _config;
get text => _text;
set text(String text) {
_text = text;
_updateBox();
}
get config => _config;
set config(TextConfig config) {
_config = config;
_updateBox();
}
TextComponent(this._text, { TextConfig config = const TextConfig() }) {
this._config = config;
_updateBox();
}
void _updateBox() {
TextPainter tp = config.toTextPainter(text);
this.width = tp.width;
this.height = tp.height;
}
@override
void render(Canvas c) {
prepareCanvas(c);
config.render(c, text, Position.empty());
}
@override
void update(double t) {}
}

View File

@ -1,17 +1,15 @@
import 'dart:ui';
import 'dart:async';
import 'package:flame/position.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart' show Colors;
import 'dart:async';
import 'flame.dart';
import 'position.dart';
import 'palette.dart';
class Sprite {
Paint paint = whitePaint;
Paint paint = BasicPalette.white.paint;
Image image;
Rect src;
static final Paint whitePaint = new Paint()..color = Colors.white;
Sprite(
String fileName, {
double x = 0.0,
@ -69,6 +67,7 @@ class Sprite {
}
double get _imageWidth => this.image.width.toDouble();
double get _imageHeight => this.image.height.toDouble();
Position get originalSize {
@ -127,7 +126,6 @@ class Sprite {
return;
}
size ??= this.size;
renderRect(canvas,
new Rect.fromLTWH(p.x - size.x / 2, p.y - size.y / 2, size.x, size.y));
renderRect(canvas, new Rect.fromLTWH(p.x - size.x / 2, p.y - size.y / 2, size.x, size.y));
}
}

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart';
import 'position.dart';
@ -49,43 +48,6 @@ class Util {
});
}
/// Returns a [material.TextPainter] that allows for text rendering and size measuring.
///
/// Rendering text on the Canvas is not as trivial as it should.
/// This methods exposes all possible parameters you might want to pass to render text, with sensible defaults.
/// Only the [text] is mandatory.
/// It returns a [material.TextPainter]. that have the properties: paint, width and height.
/// Example usage:
///
/// final tp = Flame.util.text('Score: $score', fontSize: 48.0, fontFamily: 'Awesome Font');
/// tp.paint(c, Offset(size.width - p.width - 10, size.height - p.height - 10));
///
material.TextPainter text(
String text, {
double fontSize: 24.0,
Color color: material.Colors.black,
String fontFamily: 'Arial',
TextAlign textAlign: TextAlign.left,
TextDirection textDirection: TextDirection.ltr,
}) {
material.TextStyle style = new material.TextStyle(
color: color,
fontSize: fontSize,
fontFamily: fontFamily,
);
material.TextSpan span = new material.TextSpan(
style: style,
text: text,
);
material.TextPainter tp = new material.TextPainter(
text: span,
textAlign: textAlign,
textDirection: textDirection,
);
tp.layout();
return tp;
}
/// This properly binds a gesture recognizer to your game.
///
/// Use this in order to get it to work in case your app also contains other widgets.