mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
Adding new text related components and upgrading old components to use the new features like anchor
This commit is contained in:
@ -36,16 +36,12 @@ class AnimationComponent extends PositionComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool loaded() {
|
bool loaded() => animation.loaded();
|
||||||
return this.animation.loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
if (loaded()) {
|
prepareCanvas(canvas);
|
||||||
prepareCanvas(canvas);
|
animation.getSprite().render(canvas, width, height);
|
||||||
animation.getSprite().render(canvas, width, height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
import '../sprite.dart';
|
import '../sprite.dart';
|
||||||
import '../position.dart';
|
import '../position.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import '../anchor.dart';
|
||||||
|
|
||||||
/// This represents a Component for your game.
|
/// 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.
|
/// 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) {}
|
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.
|
/// 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.
|
/// 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.
|
/// 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;
|
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).
|
/// HUD objects ignore the [BaseGame.camera] when rendered (so their position coordinates are considered relative to the device screen).
|
||||||
bool isHud() => false;
|
bool isHud() => false;
|
||||||
@ -52,12 +54,14 @@ abstract class Component {
|
|||||||
int priority() => 0;
|
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 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 {
|
abstract class PositionComponent extends Component {
|
||||||
double x = 0.0, y = 0.0, angle = 0.0;
|
double x = 0.0, y = 0.0, angle = 0.0;
|
||||||
double width = 0.0, height = 0.0;
|
double width = 0.0, height = 0.0;
|
||||||
|
Anchor anchor = Anchor.topLeft;
|
||||||
|
|
||||||
Position toPosition() => new Position(x, y);
|
Position toPosition() => new Position(x, y);
|
||||||
void setByPosition(Position position) {
|
void setByPosition(Position position) {
|
||||||
@ -88,27 +92,24 @@ abstract class PositionComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void prepareCanvas(Canvas canvas) {
|
void prepareCanvas(Canvas canvas) {
|
||||||
canvas.translate(x, y);
|
double ax = x - anchor.relativePosition.dx * width;
|
||||||
|
double ay = y - anchor.relativePosition.dy * height;
|
||||||
// rotate around center
|
canvas.translate(ax, ay);
|
||||||
canvas.translate(width / 2, height / 2);
|
|
||||||
canvas.rotate(angle);
|
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 {
|
class SpriteComponent extends PositionComponent {
|
||||||
Sprite sprite;
|
Sprite sprite;
|
||||||
|
|
||||||
final Paint paint = new Paint()..color = new Color(0xffffffff);
|
|
||||||
|
|
||||||
SpriteComponent();
|
SpriteComponent();
|
||||||
|
|
||||||
SpriteComponent.square(double size, String imagePath)
|
SpriteComponent.square(double size, String imagePath) : this.rectangle(size, size, imagePath);
|
||||||
: this.rectangle(size, size, imagePath);
|
|
||||||
|
|
||||||
SpriteComponent.rectangle(double width, double height, String imagePath)
|
SpriteComponent.rectangle(double width, double height, String imagePath) : this.fromSprite(width, height, new Sprite(imagePath));
|
||||||
: this.fromSprite(width, height, new Sprite(imagePath));
|
|
||||||
|
|
||||||
SpriteComponent.fromSprite(double width, double height, this.sprite) {
|
SpriteComponent.fromSprite(double width, double height, this.sprite) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
@ -117,15 +118,13 @@ class SpriteComponent extends PositionComponent {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
render(Canvas canvas) {
|
render(Canvas canvas) {
|
||||||
if (sprite != null && sprite.loaded() && x != null && y != null) {
|
prepareCanvas(canvas);
|
||||||
prepareCanvas(canvas);
|
sprite.render(canvas, width, height);
|
||||||
sprite.render(canvas, width, height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool loaded() {
|
bool loaded() {
|
||||||
return this.sprite.loaded();
|
return sprite != null && sprite.loaded() && x != null && y != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flame/components/component.dart';
|
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
|
/// A component that lets your component be composed by others
|
||||||
/// It resembles [BaseGame]. It has an [components] property and an [add] method
|
/// It resembles [BaseGame]. It has an [components] property and an [add] method
|
||||||
mixin ComposedComponent on Component {
|
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
|
@override
|
||||||
render(Canvas canvas) {
|
render(Canvas canvas) {
|
||||||
@ -45,4 +45,4 @@ mixin ComposedComponent on Component {
|
|||||||
|
|
||||||
List<Resizable> children() =>
|
List<Resizable> children() =>
|
||||||
this.components.where((r) => r is Resizable).cast<Resizable>().toList();
|
this.components.where((r) => r is Resizable).cast<Resizable>().toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ class DebugComponent extends PositionComponent {
|
|||||||
/// Don't do anything (change as desired)
|
/// Don't do anything (change as desired)
|
||||||
void update(double t) {}
|
void update(double t) {}
|
||||||
|
|
||||||
/// Renders the recatangle
|
/// Renders the rectangle
|
||||||
void render(Canvas c) {
|
void render(Canvas c) {
|
||||||
prepareCanvas(c);
|
prepareCanvas(c);
|
||||||
c.drawRect(new Rect.fromLTWH(0.0, 0.0, width, height), paint);
|
c.drawRect(new Rect.fromLTWH(0.0, 0.0, width, height), paint);
|
||||||
|
|||||||
160
lib/components/text_box_component.dart
Normal file
160
lib/components/text_box_component.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
lib/components/text_component.dart
Normal file
46
lib/components/text_component.dart
Normal 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) {}
|
||||||
|
}
|
||||||
@ -1,17 +1,15 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flame/position.dart';
|
import 'dart:async';
|
||||||
import 'package:flame/flame.dart';
|
import 'flame.dart';
|
||||||
import 'package:flutter/material.dart' show Colors;
|
import 'position.dart';
|
||||||
|
import 'palette.dart';
|
||||||
|
|
||||||
class Sprite {
|
class Sprite {
|
||||||
Paint paint = whitePaint;
|
Paint paint = BasicPalette.white.paint;
|
||||||
Image image;
|
Image image;
|
||||||
Rect src;
|
Rect src;
|
||||||
|
|
||||||
static final Paint whitePaint = new Paint()..color = Colors.white;
|
|
||||||
|
|
||||||
Sprite(
|
Sprite(
|
||||||
String fileName, {
|
String fileName, {
|
||||||
double x = 0.0,
|
double x = 0.0,
|
||||||
@ -69,6 +67,7 @@ class Sprite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double get _imageWidth => this.image.width.toDouble();
|
double get _imageWidth => this.image.width.toDouble();
|
||||||
|
|
||||||
double get _imageHeight => this.image.height.toDouble();
|
double get _imageHeight => this.image.height.toDouble();
|
||||||
|
|
||||||
Position get originalSize {
|
Position get originalSize {
|
||||||
@ -127,7 +126,6 @@ class Sprite {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size ??= this.size;
|
size ??= this.size;
|
||||||
renderRect(canvas,
|
renderRect(canvas, new Rect.fromLTWH(p.x - size.x / 2, p.y - size.y / 2, size.x, size.y));
|
||||||
new Rect.fromLTWH(p.x - size.x / 2, p.y - size.y / 2, size.x, size.y));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' as material;
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'position.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.
|
/// 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.
|
/// Use this in order to get it to work in case your app also contains other widgets.
|
||||||
|
|||||||
Reference in New Issue
Block a user