SpriteWidget
SpriteWidget是一个用于使用Flutter构建复杂、高性能动画和2D游戏的工具包。您的精灵渲染树位于一个与Flutter和其他Material组件无缝集成的Widget内。您可以使用SpriteWidget创建从动画图标到完整游戏的任何内容。
本指南假定您对Flutter和Dart有基本了解。通过在StackOverflow上标记spritewidget提出问题来获得支持。
您可以在example目录中找到示例,或查看完整的Space Blast游戏。
将SpriteWidget添加到您的项目
SpriteWidget作为标准包提供。只需将其添加为pubspec.yaml的依赖项,即可开始使用。
dependencies:
flutter:
sdk: flutter
spritewidget:
创建SpriteWidget
要使用SpriteWidget,您首先需要设置一个用于绘制其内容的根节点。添加到根节点的任何精灵节点都将由SpriteWidget渲染。通常,您的根节点是您应用程序状态的一部分。下面是一个如何使用SpriteWidget设置自定义有状态Widget的示例:
import 'package:flutter/material.dart';
import 'package:spritewidget/spritewidget.dart';
class MyWidget extends StatefulWidget {
@override
MyWidgetState createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
NodeWithSize rootNode;
@override
void initState() {
super.initState();
rootNode = new NodeWithSize(const Size(1024.0, 1024.0));
}
@override
Widget build(BuildContext context) {
return new SpriteWidget(rootNode);
}
}
传递给SpriteWidget的根节点是一个NodeWithSize,根节点的大小定义了SpriteWidget使用的坐标系。默认情况下,SpriteWidget使用letterboxing来显示其内容。这意味着您为根节点提供的大小将决定SpriteWidget的内容将如何缩放以适应。如果它不能完美地适应Widget的区域,则其顶部和底部或左侧和右侧将被裁剪。您可以根据需要选择性地为SpriteWidget传递一个参数以获得其他缩放选项。
当您将SpriteWidget添加到应用程序的build方法后,它将自动开始运行动画并处理用户输入。无需任何其他额外设置。
将对象添加到节点图
您的SpriteWidget管理着一个节点图,根节点是创建SpriteWidget时传递给它的NodeWithSize。要渲染精灵、粒子系统或任何其他对象,只需将它们添加到节点图中。
节点图中的每个节点都有一个变换。变换会由其子节点继承,这使得通过将对象分组为节点的子节点,然后操作父节点来构建更复杂的结构成为可能。例如,以下代码创建了一个带有两个附加轮子的汽车精灵。汽车被添加到根节点。
Sprite car = new Sprite.fromImage(carImage);
Sprite frontWheel = new Sprite.fromImage(wheelImage);
Sprite rearWheel = new Sprite.fromImage(wheelImage);
frontWheel.position = const Offset(100, 50);
rearWheel.position = const Offset(-100, 50);
car.addChild(frontWheel);
car.addChild(rearWheel);
rootNode.addChild(car);
您可以通过设置position、rotation、scale和skew属性来操作变换。
精灵、纹理和精灵表
要加载图像资源,最简单的方法是使用ImageMap类。ImageMap可以一次加载一个或多个图像。
Image类不会通过flutter/material自动导入,因此您可能需要在文件顶部添加一个import语句。
import 'dart:ui' as ui show Image;
现在您可以使用ImageMap加载图像了。请注意,加载方法是异步的,因此此示例代码需要放在一个异步方法中。有关加载图像的完整示例,请参阅天气演示。
ImageMap images = new ImageMap(rootBundle);
// Load a single image
ui.Image image = await images.loadImage('assets/my_image.png');
// Load multiple images
await images.load(<String>[
'assets/image_0.png',
'assets/image_1.png',
'assets/image_2.png',
]);
// Access a loaded image from the ImageMap
image = images['assets/image_0.png'];
最常见的节点类型是Sprite节点。Sprite只是将图像绘制到屏幕上。Sprite可以从Image对象或SpriteTexture对象绘制。纹理是Image的一部分。使用SpriteSheet,您可以将多个纹理元素打包在单个图像中。这可以节省设备GPU内存空间,还可以加快绘图速度。目前SpriteWidget支持JSON格式的精灵表,由TexturePacker等工具生成。手动编辑精灵表文件是不常见的。您可以创建一个具有JSON定义和图像的SpriteSheet
SpriteSheet sprites = new SpriteSheet(myImage, jsonCode);
SpriteTexture texture = sprites['texture.png'];
帧周期
每次将新帧渲染到屏幕时,SpriteWidget将执行一系列操作。有时,在创建更高级的交互式动画或游戏时,这些操作的执行顺序可能很重要。
事情发生的顺序如下:
- 处理输入事件
- 运行动画操作
- 调用节点上的update函数
- 应用约束
- 将帧渲染到屏幕
下面可以阅读有关每个不同阶段的更多信息。
处理用户输入
您可以继承任何节点类型来处理触摸。要接收触摸,您需要将userInteractionEnabled属性设置为true并覆盖handleEvent方法。如果您继承的节点没有大小,您还需要覆盖isPointInside方法。
class EventHandlingNode extends NodeWithSize {
EventHandlingNode(Size size) : super(size) {
userInteractionEnabled = true;
}
@override handleEvent(SpriteBoxEvent event) {
if (event.type == PointerDownEvent)
...
else if (event.type == PointerMoveEvent)
...
return true;
}
}
如果您希望您的节点接收多个触摸,请将handleMultiplePointers属性设置为true。每次触摸按下或拖动触摸都会为handleEvent方法生成一个单独的调用,您可以通过pointer属性区分每次触摸。
使用操作进行动画
SpriteWidget提供了易于使用的函数,通过操作来为节点设置动画。您可以组合简单的操作块来创建更复杂的动画。
要执行操作动画,您首先构建操作本身,然后将其传递给节点的action manager的run方法(有关示例,请参阅下面的Tweens部分)。
Tweens
Tweens是创建动画的最简单的构建块。它将在指定的持续时间内插值一个值或属性。您为ActionTween类提供一个setter函数、其开始和结束值以及tween的持续时间。
创建tween后,通过节点的action manager运行它来执行它。
Node myNode = new Node();
ActionTween myTween = new ActionTween<Offset> (
(a) => myNode.position = a,
Offset.zero,
const Offset(100.0, 0.0),
1.0
);
myNode.actions.run(myTween);
您可以设置不同类型的值,如浮点数、点、矩形甚至颜色。您还可以选择性地为ActionTween类提供一个缓动函数。
序列
当您需要按顺序播放两个或多个操作时,请使用ActionSequence类。
ActionSequence sequence = new ActionSequence([
firstAction,
middleAction,
lastAction
]);
组
使用ActionGroup并行播放操作。
ActionGroup group = new ActionGroup([
action0,
action1
]);
重复
您可以循环播放任何操作,无论是固定的次数,还是直到永远。
ActionRepeat repeat = new ActionRepeat(loopedAction, 5);
ActionRepeatForever longLoop = new ActionRepeatForever(loopedAction);
组合
可以通过以任何方式组合它们来创建更复杂的操作。
ActionSequence complexAction = new ActionSequence([
new ActionRepeat(myLoop, 2),
new ActionGroup([
action0,
action1
])
]);
处理更新事件
每一帧,更新事件都会被发送到当前节点树中的每个节点。覆盖update方法以手动执行动画或执行游戏逻辑。
MyNode extends Node {
@override
update(double dt) {
// Move the node at a constant speed
position += new Offset(dt * 1.0, 0.0);
}
}
定义约束
约束用于约束节点属性。它们可用于相对于其他节点定位节点,或调整旋转或缩放。您可以将多个约束应用于单个节点。
例如,您可以使用约束使一个节点以特定距离平滑跟随另一个节点。平滑效果将使跟随节点运动更加顺畅。
followingNode.constraints = [
new ConstraintPositionToNode(
targetNode,
offset: const Offset(0.0, 100.0),
dampening: 0.5
)
];
约束在帧周期结束时应用。如果您需要在其他时间应用它们,您可以直接调用Node对象的applyConstraints方法。
执行自定义绘图
SpriteWidget提供了一组默认的绘图图元,但在某些情况下,您可能需要执行自定义绘图。要做到这一点,您需要继承Node或NodeWithSize类并覆盖paint方法。
class RedCircle extends Node {
RedCircle(this.radius);
double radius;
@override
void paint(Canvas canvas) {
canvas.drawCircle(
Offset.zero,
radius,
new Paint()..color = const Color(0xffff0000)
);
}
}
如果您正在覆盖NodeWithSize,您可能需要调用applyTransformForPivot,然后再开始绘图,以考虑节点的支点。调用后,坐标系已设置好,您可以从原点开始绘制到节点的大小。
@override
void paint(Canvas canvas) {
applyTransformForPivot(canvas);
canvas.drawRect(
new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
myPaint
);
}
使用粒子系统添加效果
粒子系统非常适合创建雨、烟或火等效果。设置粒子系统很容易,但有很多属性可以调整。了解它们工作原理的最佳方法是自己尝试。
这是一个如何创建、配置粒子系统并将其添加到场景的示例:
ParticleSystem particles = new ParticleSystem(
particleTexture,
posVar: const Point(100, 100.0),
startSize: 1.0,
startSizeVar: 0.5,
endSize: 2.0,
endSizeVar: 1.0,
life: 1.5 * distance,
lifeVar: 1.0 * distance
);
rootNode.addChild(particles);