mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +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
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
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: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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user