mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #1216 from vlidholt/master
Adds TexturedLine and animated EffectLine to sprites
This commit is contained in:
commit
d83b3ba0c8
BIN
examples/game/assets/checker.png
Normal file
BIN
examples/game/assets/checker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/game/assets/line_effects.png
Normal file
BIN
examples/game/assets/line_effects.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 382 KiB |
166
examples/game/test_sprite_mesh.dart
Normal file
166
examples/game/test_sprite_mesh.dart
Normal file
@ -0,0 +1,166 @@
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/services.dart';
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/theme/colors.dart' as colors;
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:skysprites/skysprites.dart';
|
||||
|
||||
AssetBundle _initBundle() {
|
||||
if (rootBundle != null)
|
||||
return rootBundle;
|
||||
return new NetworkAssetBundle(Uri.base);
|
||||
}
|
||||
|
||||
final AssetBundle _bundle = _initBundle();
|
||||
|
||||
ImageMap _images;
|
||||
SpriteSheet _spriteSheet;
|
||||
TestApp _app;
|
||||
|
||||
main() async {
|
||||
_images = new ImageMap(_bundle);
|
||||
|
||||
await _images.load([
|
||||
'assets/checker.png',
|
||||
'assets/line_effects.png'
|
||||
]);
|
||||
|
||||
assert(_images["assets/checker.png"] != null);
|
||||
|
||||
_app = new TestApp();
|
||||
runApp(_app);
|
||||
}
|
||||
|
||||
class TestApp extends App {
|
||||
|
||||
TestApp() {
|
||||
_testBed = new TestBed(_labelTexts[_selectedLine]);
|
||||
}
|
||||
|
||||
TestBed _testBed;
|
||||
int _selectedLine = 0;
|
||||
|
||||
List<String> _labelTexts = [
|
||||
"Colored",
|
||||
"Smoke",
|
||||
"Electric"
|
||||
];
|
||||
|
||||
Widget build() {
|
||||
ThemeData theme = new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: colors.Purple
|
||||
);
|
||||
|
||||
return new Theme(
|
||||
data: theme,
|
||||
child: new Title(
|
||||
title: 'Test drawAtlas',
|
||||
child: _buildColumn()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildColumn() {
|
||||
return new Column([
|
||||
new Flexible(child: _buildSpriteWidget()),
|
||||
_buildTabBar()
|
||||
]);
|
||||
}
|
||||
|
||||
TabBar _buildTabBar() {
|
||||
return new TabBar(
|
||||
labels: _buildTabLabels(),
|
||||
selectedIndex: _selectedLine,
|
||||
onChanged: (int selectedLine) {
|
||||
setState(() {
|
||||
_selectedLine = selectedLine;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
List<TabLabel> _buildTabLabels() {
|
||||
List<TabLabel> labels = [];
|
||||
for(String text in _labelTexts) {
|
||||
labels.add(new TabLabel(text: text));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
SpriteWidget _buildSpriteWidget() {
|
||||
_testBed.setupLine(_labelTexts[_selectedLine]);
|
||||
|
||||
return new SpriteWidget(
|
||||
_testBed,
|
||||
SpriteBoxTransformMode.letterbox
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestBed extends NodeWithSize {
|
||||
EffectLine _line;
|
||||
|
||||
TestBed(String lineType) : super(new Size(1024.0, 1024.0)) {
|
||||
userInteractionEnabled = true;
|
||||
setupLine(lineType);
|
||||
}
|
||||
|
||||
void setupLine(String lineType) {
|
||||
if (_line != null) {
|
||||
_line.removeFromParent();
|
||||
}
|
||||
|
||||
if (lineType == "Colored") {
|
||||
// Create a line with no texture and a color sequence
|
||||
_line = new EffectLine(
|
||||
texture: null,
|
||||
colorSequence: new ColorSequence.fromStartAndEndColor(new Color(0xffff0000), new Color(0xff0000ff)),
|
||||
widthMode: EffectLineWidthMode.barrel,
|
||||
minWidth: 20.0,
|
||||
maxWidth: 50.0,
|
||||
animationMode: EffectLineAnimationMode.scroll,
|
||||
fadeAfterDelay: 1.0,
|
||||
fadeDuration: 1.0
|
||||
);
|
||||
} else if (lineType == "Smoke") {
|
||||
Texture baseTexture = new Texture(_images['assets/line_effects.png']);
|
||||
Texture smokyLineTexture = baseTexture.textureFromRect(new Rect.fromLTRB(0.0, 0.0, 1024.0, 128.0));
|
||||
|
||||
_line = new EffectLine(
|
||||
texture: smokyLineTexture,
|
||||
textureLoopLength: 300.0,
|
||||
colorSequence: new ColorSequence.fromStartAndEndColor(new Color(0xffffffff), new Color(0x00ffffff)),
|
||||
widthMode: EffectLineWidthMode.barrel,
|
||||
minWidth: 20.0,
|
||||
maxWidth: 80.0,
|
||||
animationMode: EffectLineAnimationMode.scroll
|
||||
);
|
||||
} else if (lineType == "Electric") {
|
||||
Texture baseTexture = new Texture(_images['assets/line_effects.png']);
|
||||
Texture electricLineTexture = baseTexture.textureFromRect(new Rect.fromLTRB(0.0, 384.0, 1024.0, 512.0));
|
||||
|
||||
_line = new EffectLine(
|
||||
texture: electricLineTexture,
|
||||
textureLoopLength: 300.0,
|
||||
widthMode: EffectLineWidthMode.barrel,
|
||||
minWidth: 20.0,
|
||||
maxWidth: 100.0,
|
||||
animationMode: EffectLineAnimationMode.random
|
||||
);
|
||||
}
|
||||
|
||||
addChild(_line);
|
||||
}
|
||||
|
||||
bool handleEvent(SpriteBoxEvent event) {
|
||||
if (event.type == "pointerdown") _line.points = [];
|
||||
|
||||
if (event.type == "pointerdown" || event.type == "pointermove") {
|
||||
Point pos = convertPointToNodeSpace(event.boxPosition);
|
||||
_line.addPoint(pos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,8 @@ class ColorSequence {
|
||||
Color colorAtPosition(double pos) {
|
||||
assert(pos >= 0.0 && pos <= 1.0);
|
||||
|
||||
if (pos == 0.0) return colors[0];
|
||||
|
||||
double lastStop = colorStops[0];
|
||||
Color lastColor = colors[0];
|
||||
|
||||
|
||||
159
packages/flutter_sprites/lib/effect_line.dart
Normal file
159
packages/flutter_sprites/lib/effect_line.dart
Normal file
@ -0,0 +1,159 @@
|
||||
part of skysprites;
|
||||
|
||||
enum EffectLineWidthMode {
|
||||
linear,
|
||||
barrel,
|
||||
}
|
||||
|
||||
enum EffectLineAnimationMode {
|
||||
none,
|
||||
scroll,
|
||||
random,
|
||||
}
|
||||
|
||||
class EffectLine extends Node {
|
||||
|
||||
EffectLine({
|
||||
this.texture: null,
|
||||
List<Point> points,
|
||||
this.widthMode : EffectLineWidthMode.linear,
|
||||
this.minWidth: 10.0,
|
||||
this.maxWidth: 10.0,
|
||||
this.animationMode: EffectLineAnimationMode.none,
|
||||
this.scrollSpeed: 0.1,
|
||||
this.fadeDuration: null,
|
||||
this.fadeAfterDelay: null,
|
||||
this.textureLoopLength: null,
|
||||
this.simplify: true,
|
||||
ColorSequence colorSequence
|
||||
}) {
|
||||
if (points == null) this.points = [];
|
||||
else this.points = points;
|
||||
|
||||
_colorSequence = colorSequence;
|
||||
if (_colorSequence == null)
|
||||
_colorSequence = new ColorSequence.fromStartAndEndColor(
|
||||
new Color(0xffffffff),
|
||||
new Color(0xffffffff));
|
||||
|
||||
_painter = new TexturedLinePainter(points, _colors, _widths, texture);
|
||||
_painter.textureLoopLength = textureLoopLength;
|
||||
}
|
||||
|
||||
final Texture texture;
|
||||
|
||||
final EffectLineWidthMode widthMode;
|
||||
final double minWidth;
|
||||
final double maxWidth;
|
||||
|
||||
final EffectLineAnimationMode animationMode;
|
||||
final double scrollSpeed;
|
||||
ColorSequence _colorSequence;
|
||||
ColorSequence get colorSequence => _colorSequence;
|
||||
|
||||
List<Point> _points;
|
||||
|
||||
List<Point> get points => _points;
|
||||
|
||||
set points(List<Point> points) {
|
||||
_points = points;
|
||||
_pointAges = [];
|
||||
for (int i = 0; i < _points.length; i++) {
|
||||
_pointAges.add(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
List<double> _pointAges;
|
||||
List<Color> _colors;
|
||||
List<double> _widths;
|
||||
|
||||
final double fadeDuration;
|
||||
final double fadeAfterDelay;
|
||||
|
||||
final double textureLoopLength;
|
||||
|
||||
final bool simplify;
|
||||
|
||||
TexturedLinePainter _painter;
|
||||
double _offset = 0.0;
|
||||
|
||||
void update(double dt) {
|
||||
// Update scrolling position
|
||||
if (animationMode == EffectLineAnimationMode.scroll) {
|
||||
_offset += dt * scrollSpeed;
|
||||
_offset %= 1.0;
|
||||
} else if (animationMode == EffectLineAnimationMode.random) {
|
||||
_offset = randomDouble();
|
||||
}
|
||||
|
||||
// Update age of line points, and remove if neccessary
|
||||
if (fadeDuration != null && fadeAfterDelay != null) {
|
||||
for (int i = _points.length - 1; i >= 0; i--) {
|
||||
_pointAges[i] += dt;
|
||||
if (_pointAges[i] > (fadeDuration + fadeAfterDelay)) {
|
||||
_pointAges.removeAt(i);
|
||||
_points.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void paint(PaintingCanvas canvas) {
|
||||
if (points.length < 2) return;
|
||||
|
||||
//_painter.textureLoopLength = textureLoopLength;
|
||||
|
||||
_painter.points = points;
|
||||
|
||||
// Calculate colors
|
||||
List<double> stops = _painter.calculatedTextureStops;
|
||||
|
||||
List<Color> colors = [];
|
||||
for (int i = 0; i < stops.length; i++) {
|
||||
double stop = stops[i];
|
||||
Color color = _colorSequence.colorAtPosition(stop);
|
||||
|
||||
if (fadeDuration != null && fadeAfterDelay != null) {
|
||||
double age = _pointAges[i];
|
||||
if (age > fadeAfterDelay) {
|
||||
double fade = 1.0 - (age - fadeAfterDelay) / fadeDuration;
|
||||
int alpha = (color.alpha * fade).toInt().clamp(0, 255);
|
||||
color = new Color.fromARGB(alpha, color.red, color.green, color.blue);
|
||||
}
|
||||
}
|
||||
colors.add(color);
|
||||
}
|
||||
_painter.colors = colors;
|
||||
|
||||
// Calculate widths
|
||||
List<double> widths = [];
|
||||
for (double stop in stops) {
|
||||
if (widthMode == EffectLineWidthMode.linear) {
|
||||
double width = minWidth + (maxWidth - minWidth) * stop;
|
||||
widths.add(width);
|
||||
} else if (widthMode == EffectLineWidthMode.barrel) {
|
||||
double width = minWidth + math.sin(stop * math.PI) * (maxWidth - minWidth);
|
||||
widths.add(width);
|
||||
}
|
||||
}
|
||||
_painter.widths = widths;
|
||||
|
||||
_painter.textureStopOffset = _offset;
|
||||
|
||||
_painter.paint(canvas);
|
||||
}
|
||||
|
||||
void addPoint(Point point) {
|
||||
// Skip duplicate points
|
||||
if (points.length > 0 && point.x == points[points.length - 1].x && point.y == points[points.length - 1].y)
|
||||
return;
|
||||
|
||||
if (simplify) {
|
||||
|
||||
}
|
||||
|
||||
// Add point and point's age
|
||||
_points.add(point);
|
||||
_pointAges.add(0.0);
|
||||
}
|
||||
}
|
||||
@ -21,9 +21,10 @@ import 'package:sky_services/media/media.mojom.dart';
|
||||
import 'package:vector_math/vector_math.dart';
|
||||
|
||||
part 'action.dart';
|
||||
part 'constraint.dart';
|
||||
part 'action_spline.dart';
|
||||
part 'color_secuence.dart';
|
||||
part 'constraint.dart';
|
||||
part 'effect_line.dart';
|
||||
part 'image_map.dart';
|
||||
part 'label.dart';
|
||||
part 'layer.dart';
|
||||
@ -38,5 +39,6 @@ part 'spritesheet.dart';
|
||||
part 'sprite_box.dart';
|
||||
part 'sprite_widget.dart';
|
||||
part 'texture.dart';
|
||||
part 'textured_line.dart';
|
||||
part 'util.dart';
|
||||
part 'virtual_joystick.dart';
|
||||
|
||||
269
packages/flutter_sprites/lib/textured_line.dart
Normal file
269
packages/flutter_sprites/lib/textured_line.dart
Normal file
@ -0,0 +1,269 @@
|
||||
part of skysprites;
|
||||
|
||||
class TexturedLine extends Node {
|
||||
TexturedLine(List<Point> points, List<Color> colors, List<double> widths, [Texture texture, List<double> textureStops]) {
|
||||
painter = new TexturedLinePainter(points, colors, widths, texture, textureStops);
|
||||
}
|
||||
|
||||
TexturedLinePainter painter;
|
||||
|
||||
void paint(PaintingCanvas canvas) {
|
||||
painter.paint(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
class TexturedLinePainter {
|
||||
TexturedLinePainter(this._points, this.colors, this.widths, [Texture texture, this.textureStops]) {
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
List<Point> _points;
|
||||
|
||||
List<Point> get points => _points;
|
||||
|
||||
set points(List<Point> points) {
|
||||
_points = points;
|
||||
_calculatedTextureStops = null;
|
||||
}
|
||||
|
||||
List<Color> colors;
|
||||
List<double> widths;
|
||||
Texture _texture;
|
||||
|
||||
Texture get texture => _texture;
|
||||
|
||||
set texture(Texture texture) {
|
||||
_texture = texture;
|
||||
if (texture == null) {
|
||||
_cachedPaint = new Paint();
|
||||
} else {
|
||||
Matrix4 matrix = new Matrix4.identity();
|
||||
ImageShader shader = new ImageShader(texture.image,
|
||||
TileMode.repeated, TileMode.repeated, matrix.storage);
|
||||
|
||||
_cachedPaint = new Paint();
|
||||
_cachedPaint.setShader(shader);
|
||||
}
|
||||
}
|
||||
|
||||
List<double> textureStops;
|
||||
|
||||
List<double> _calculatedTextureStops;
|
||||
|
||||
List<double> get calculatedTextureStops {
|
||||
if (_calculatedTextureStops == null)
|
||||
_calculateTextureStops();
|
||||
return _calculatedTextureStops;
|
||||
}
|
||||
|
||||
double _length;
|
||||
|
||||
double get length {
|
||||
if (_calculatedTextureStops == null)
|
||||
_calculateTextureStops();
|
||||
return _length;
|
||||
}
|
||||
|
||||
double textureStopOffset = 0.0;
|
||||
|
||||
double _textureLoopLength;
|
||||
|
||||
get textureLoopLength => textureLoopLength;
|
||||
|
||||
set textureLoopLength(double textureLoopLength) {
|
||||
_textureLoopLength = textureLoopLength;
|
||||
_calculatedTextureStops = null;
|
||||
}
|
||||
|
||||
Paint _cachedPaint = new Paint();
|
||||
|
||||
void paint(PaintingCanvas canvas) {
|
||||
// Check input values
|
||||
assert(_points != null);
|
||||
if (_points.length < 2) return;
|
||||
|
||||
assert(_points.length == colors.length);
|
||||
assert(_points.length == widths.length);
|
||||
|
||||
// Calculate normals
|
||||
List<Vector2> vectors = [];
|
||||
for (Point pt in _points) {
|
||||
vectors.add(new Vector2(pt.x, pt.y));
|
||||
}
|
||||
List<Vector2> miters = _computeMiterList(vectors, false);
|
||||
|
||||
List<Point> vertices = [];
|
||||
List<int> indicies = [];
|
||||
List<Color> verticeColors = [];
|
||||
List<Point> textureCoordinates;
|
||||
double textureTop;
|
||||
double textureBottom;
|
||||
List<double> stops;
|
||||
|
||||
// Add first point
|
||||
Point lastPoint = _points[0];
|
||||
Vector2 lastMiter = miters[0];
|
||||
|
||||
// Add vertices and colors
|
||||
_addVerticesForPoint(vertices, lastPoint, lastMiter, widths[0]);
|
||||
verticeColors.add(colors[0]);
|
||||
verticeColors.add(colors[0]);
|
||||
|
||||
if (texture != null) {
|
||||
assert(texture.rotated == false);
|
||||
|
||||
// Setup for calculating texture coordinates
|
||||
textureTop = texture.frame.top;
|
||||
textureBottom = texture.frame.bottom;
|
||||
textureCoordinates = [];
|
||||
|
||||
// Use correct stops
|
||||
if (textureStops != null) {
|
||||
assert(_points.length == textureStops.length);
|
||||
stops = textureStops;
|
||||
} else {
|
||||
if (_calculatedTextureStops == null) _calculateTextureStops();
|
||||
stops = _calculatedTextureStops;
|
||||
}
|
||||
|
||||
// Texture coordinate points
|
||||
double xPos = _xPosForStop(stops[0]);
|
||||
textureCoordinates.add(new Point(xPos, textureTop));
|
||||
textureCoordinates.add(new Point(xPos, textureBottom));
|
||||
}
|
||||
|
||||
// Add the rest of the points
|
||||
for (int i = 1; i < _points.length; i++) {
|
||||
// Add vertices
|
||||
Point currentPoint = _points[i];
|
||||
Vector2 currentMiter = miters[i];
|
||||
_addVerticesForPoint(vertices, currentPoint, currentMiter, widths[i]);
|
||||
|
||||
// Add references to the triangles
|
||||
int lastIndex0 = (i - 1) * 2;
|
||||
int lastIndex1 = (i - 1) * 2 + 1;
|
||||
int currentIndex0 = i * 2;
|
||||
int currentIndex1 = i * 2 + 1;
|
||||
indicies.addAll([lastIndex0, lastIndex1, currentIndex0]);
|
||||
indicies.addAll([lastIndex1, currentIndex1, currentIndex0]);
|
||||
|
||||
// Add colors
|
||||
verticeColors.add(colors[i]);
|
||||
verticeColors.add(colors[i]);
|
||||
|
||||
if (texture != null) {
|
||||
// Texture coordinate points
|
||||
double xPos = _xPosForStop(stops[i]);
|
||||
textureCoordinates.add(new Point(xPos, textureTop));
|
||||
textureCoordinates.add(new Point(xPos, textureBottom));
|
||||
}
|
||||
|
||||
// Update last values
|
||||
lastPoint = currentPoint;
|
||||
lastMiter = currentMiter;
|
||||
}
|
||||
|
||||
canvas.drawVertices(VertexMode.triangles, vertices, textureCoordinates, verticeColors, TransferMode.modulate, indicies, _cachedPaint);
|
||||
}
|
||||
|
||||
double _xPosForStop(double stop) {
|
||||
if (_textureLoopLength == null) {
|
||||
return texture.frame.left + texture.frame.width * (stop - textureStopOffset);
|
||||
} else {
|
||||
return texture.frame.left + texture.frame.width * (stop - textureStopOffset * (_textureLoopLength / length)) * (length / _textureLoopLength);
|
||||
}
|
||||
}
|
||||
|
||||
void _addVerticesForPoint(List<Point> vertices, Point point, Vector2 miter, double width) {
|
||||
double halfWidth = width / 2.0;
|
||||
|
||||
Offset offset0 = new Offset(miter[0] * halfWidth, miter[1] * halfWidth);
|
||||
Offset offset1 = new Offset(-miter[0] * halfWidth, -miter[1] * halfWidth);
|
||||
|
||||
vertices.add(point + offset0);
|
||||
vertices.add(point + offset1);
|
||||
}
|
||||
|
||||
void _calculateTextureStops() {
|
||||
List<double> stops = [];
|
||||
double length = 0.0;
|
||||
|
||||
// Add first stop
|
||||
stops.add(0.0);
|
||||
|
||||
// Calculate distance to each point from the first point along the line
|
||||
for (int i = 1; i < _points.length; i++) {
|
||||
Point lastPoint = _points[i - 1];
|
||||
Point currentPoint = _points[i];
|
||||
|
||||
double dist = GameMath.pointQuickDist(lastPoint, currentPoint);
|
||||
length += dist;
|
||||
stops.add(length);
|
||||
}
|
||||
|
||||
// Normalize the values in the range [0.0, 1.0]
|
||||
for (int i = 1; i < points.length; i++) {
|
||||
stops[i] = stops[i] / length;
|
||||
new Point(512.0, 512.0);
|
||||
}
|
||||
|
||||
_calculatedTextureStops = stops;
|
||||
_length = length;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 _computeMiter(Vector2 lineA, Vector2 lineB) {
|
||||
Vector2 miter = new Vector2(- (lineA[1] + lineB[1]), lineA[0] + lineB[0]);
|
||||
miter.normalize();
|
||||
|
||||
double miterLength = 1.0 / dot2(miter, new Vector2(-lineA[1], lineA[0]));
|
||||
miter = miter.scale(miterLength);
|
||||
|
||||
return miter;
|
||||
}
|
||||
|
||||
Vector2 _vectorNormal(Vector2 v) {
|
||||
return new Vector2(-v[1], v[0]);
|
||||
}
|
||||
|
||||
Vector2 _vectorDirection(Vector2 a, Vector2 b) {
|
||||
Vector2 result = a - b;
|
||||
return result.normalize();
|
||||
}
|
||||
|
||||
List<Vector2> _computeMiterList(List<Vector2> points, bool closed) {
|
||||
List<Vector2> out = [];
|
||||
Vector2 curNormal = null;
|
||||
|
||||
if (closed) {
|
||||
points = new List<Vector2>.from(points);
|
||||
points.add(points[0]);
|
||||
}
|
||||
|
||||
int total = points.length;
|
||||
for (int i = 1; i < total; i++) {
|
||||
Vector2 last = points[i - 1];
|
||||
Vector2 cur = points[i];
|
||||
Vector2 next = (i < total - 1) ? points[i + 1] : null;
|
||||
|
||||
Vector2 lineA = _vectorDirection(cur, last);
|
||||
if (curNormal == null) {
|
||||
curNormal = _vectorNormal(lineA);
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
out.add(curNormal);
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
curNormal = _vectorNormal(lineA);
|
||||
out.add(curNormal);
|
||||
} else {
|
||||
Vector2 lineB = _vectorDirection(next, cur);
|
||||
Vector2 miter = _computeMiter(lineA, lineB);
|
||||
out.add(miter);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user