Commit 48a6cd83 authored by Viktor Lidholt's avatar Viktor Lidholt

Sprite physics, first iteration

parent 6fcdb64a
...@@ -3,6 +3,7 @@ dependencies: ...@@ -3,6 +3,7 @@ dependencies:
sky: any sky: any
sky_tools: any sky_tools: any
skysprites: any skysprites: any
box2d: any
dependency_overrides: dependency_overrides:
material_design_icons: material_design_icons:
path: ../../sky/packages/material_design_icons path: ../../sky/packages/material_design_icons
...@@ -10,3 +11,5 @@ dependency_overrides: ...@@ -10,3 +11,5 @@ dependency_overrides:
path: ../../sky/packages/sky path: ../../sky/packages/sky
skysprites: skysprites:
path: ../../skysprites path: ../../skysprites
box2d:
path: ../../../../box2d.dart
import 'dart:sky';
import 'package:sky/material.dart';
import 'package:sky/rendering.dart';
import 'package:sky/services.dart';
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;
TestBedApp _app;
main() async {
_images = new ImageMap(_bundle);
await _images.load([
'assets/sprites.png'
]);
String json = await _bundle.loadString('assets/sprites.json');
_spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json);
_app = new TestBedApp();
runApp(_app);
}
class TestBedApp extends App {
Widget build() {
ThemeData theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
return new Theme(
data: theme,
child: new Title(
title: 'Test Bed',
child: new SpriteWidget(
new TestBed(),
SpriteBoxTransformMode.letterbox
)
)
);
}
}
class TestBed extends NodeWithSize {
TestBed() : super(new Size(1024.0, 1024.0)) {
}
}
import 'dart:sky';
import 'package:sky/material.dart';
import 'package:sky/rendering.dart';
import 'package:sky/services.dart';
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;
TestBedApp _app;
main() async {
_images = new ImageMap(_bundle);
await _images.load([
'assets/sprites.png'
]);
String json = await _bundle.loadString('assets/sprites.json');
_spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json);
_app = new TestBedApp();
runApp(_app);
}
class TestBedApp extends App {
Widget build() {
ThemeData theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
return new Theme(
data: theme,
child: new Title(
title: 'Test Physics',
child: new SpriteWidget(
new TestBed(),
SpriteBoxTransformMode.letterbox
)
)
);
}
}
class TestBed extends NodeWithSize {
Sprite _ship;
Sprite _obstacle;
TestBed() : super(new Size(1024.0, 1024.0)) {
PhysicsNode physicsNode = new PhysicsNode(new Offset(0.0, 100.0));
_ship = new Sprite(_spriteSheet["ship.png"]);
_ship.position = new Point(512.0, 512.0);
_ship.size = new Size(64.0, 64.0);
_ship.physicsBody = new PhysicsBody(
new PhysicsShapeGroup([
new PhysicsShapeCircle(Point.origin, 32.0),
new PhysicsShapePolygon([new Point(0.0, 0.0), new Point(50.0, 0.0), new Point(50.0, 50.0), new Point(0.0, 50.0)])
]),
friction: 0.5,
tag: "ship"
);
physicsNode.addChild(_ship);
_obstacle = new Sprite(_spriteSheet["ship.png"]);
_obstacle.position = new Point(532.0, 800.0);
_obstacle.size = new Size(64.0, 64.0);
_obstacle.physicsBody = new PhysicsBody(
new PhysicsShapeCircle(Point.origin, 32.0),
type: PhysicsBodyType.static,
friction: 0.5,
tag: "obstacle"
);
physicsNode.addChild(_obstacle);
physicsNode.addContactCallback(myCallback, "obstacle", "ship", PhysicsContactType.begin);
addChild(physicsNode);
userInteractionEnabled = true;
}
void myCallback(PhysicsContactType type, PhysicsContact contact) {
print("CONTACT type: $type");
}
bool handleEvent(SpriteBoxEvent event) {
if (event.type == "pointerdown") {
Point pos = convertPointToNodeSpace(event.boxPosition);
_ship.position = pos;
}
return true;
}
}
...@@ -131,6 +131,19 @@ class Node { ...@@ -131,6 +131,19 @@ class Node {
void set rotation(double rotation) { void set rotation(double rotation) {
assert(rotation != null); assert(rotation != null);
if (_physicsBody != null && parent is PhysicsNode) {
PhysicsNode physicsNode = parent;
physicsNode._updateRotation(this.physicsBody, rotation);
return;
}
_rotation = rotation;
invalidateTransformMatrix();
}
void _setRotationFromPhysics(double rotation) {
assert(rotation != null);
_rotation = rotation; _rotation = rotation;
invalidateTransformMatrix(); invalidateTransformMatrix();
} }
...@@ -142,6 +155,19 @@ class Node { ...@@ -142,6 +155,19 @@ class Node {
void set position(Point position) { void set position(Point position) {
assert(position != null); assert(position != null);
if (_physicsBody != null && parent is PhysicsNode) {
PhysicsNode physicsNode = parent;
physicsNode._updatePosition(this.physicsBody, position);
return;
}
_position = position;
invalidateTransformMatrix();
}
void _setPositionFromPhysics(Point position) {
assert(position != null);
_position = position; _position = position;
invalidateTransformMatrix(); invalidateTransformMatrix();
} }
...@@ -609,4 +635,24 @@ class Node { ...@@ -609,4 +635,24 @@ class Node {
bool handleEvent(SpriteBoxEvent event) { bool handleEvent(SpriteBoxEvent event) {
return false; return false;
} }
// Physics
PhysicsBody _physicsBody;
PhysicsBody get physicsBody => _physicsBody;
set physicsBody(PhysicsBody physicsBody) {
if (parent != null) {
assert(parent is PhysicsNode);
if (physicsBody == null) {
physicsBody._detach();
} else {
physicsBody._attach(parent, this);
}
}
_physicsBody = physicsBody;
}
} }
part of skysprites;
enum PhysicsBodyType {
static,
dynamic
}
class PhysicsBody {
PhysicsBody(this.shape, {
this.tag: null,
this.type: PhysicsBodyType.dynamic,
this.density: 1.0,
this.friction: 0.0,
this.restitution: 0.0,
this.isSensor: false,
this.linearVelocity: Offset.zero,
this.angularVelocity: 0.0,
this.linearDampening: 0.0,
this.angularDampening: 0.0,
this.allowSleep: true,
this.awake: true,
this.fixedRotation: false,
this.bullet: false,
this.active: true,
this.gravityScale: 1.0
});
Object tag;
PhysicsShape shape;
PhysicsBodyType type;
double density;
double friction;
double restitution;
bool isSensor;
Offset linearVelocity;
double angularVelocity;
double linearDampening;
double angularDampening;
bool allowSleep;
bool awake;
bool fixedRotation;
bool bullet;
bool active;
double gravityScale;
PhysicsNode _physicsNode;
Node _node;
box2d.Body _body;
bool _attached = false;
void _attach(PhysicsNode physicsNode, Node node) {
assert(_attached == false);
// Create BodyDef
box2d.BodyDef bodyDef = new box2d.BodyDef();
bodyDef.linearVelocity = new Vector2(linearVelocity.dx, linearVelocity.dy);
bodyDef.angularVelocity = angularVelocity;
bodyDef.linearDamping = linearDampening;
bodyDef.angularDamping = angularDampening;
bodyDef.allowSleep = allowSleep;
bodyDef.awake = awake;
bodyDef.fixedRotation = fixedRotation;
bodyDef.bullet = bullet;
bodyDef.active = active;
bodyDef.gravityScale = gravityScale;
if (type == PhysicsBodyType.dynamic)
bodyDef.type = box2d.BodyType.DYNAMIC;
else
bodyDef.type = box2d.BodyType.STATIC;
double conv = physicsNode.b2WorldToNodeConversionFactor;
bodyDef.position = new Vector2(node.position.x / conv, node.position.y / conv);
bodyDef.angle = radians(node.rotation);
// Create Body
_body = physicsNode.b2World.createBody(bodyDef);
// Create FixtureDef
box2d.FixtureDef fixtureDef = new box2d.FixtureDef();
fixtureDef.friction = friction;
fixtureDef.restitution = restitution;
fixtureDef.density = density;
fixtureDef.isSensor = isSensor;
// Get shapes
List<box2d.Shape> b2Shapes = [];
List<PhysicsShape> physicsShapes = [];
_addB2Shapes(physicsNode, shape, b2Shapes, physicsShapes);
// Create fixtures
for (int i = 0; i < b2Shapes.length; i++) {
box2d.Shape b2Shape = b2Shapes[i];
PhysicsShape physicsShape = physicsShapes[i];
fixtureDef.shape = b2Shape;
box2d.Fixture fixture = _body.createFixtureFromFixtureDef(fixtureDef);
fixture.userData = physicsShape;
}
_body.userData = this;
_physicsNode = physicsNode;
_node = node;
_attached = true;
}
void _detach() {
if (_attached) {
_physicsNode.b2World.destroyBody(_body);
_attached = false;
}
}
void _addB2Shapes(PhysicsNode physicsNode, PhysicsShape shape, List<box2d.Shape> b2Shapes, List<PhysicsShape> physicsShapes) {
if (shape is PhysicsShapeGroup) {
for (PhysicsShape child in shape.shapes) {
_addB2Shapes(physicsNode, child, b2Shapes, physicsShapes);
}
} else {
b2Shapes.add(shape.getB2Shape(physicsNode));
physicsShapes.add(shape);
}
}
}
part of skysprites;
enum PhysicsContactType {
preSolve,
postSolve,
begin,
end
}
typedef void PhysicsContactCallback(PhysicsContactType type, PhysicsContact contact);
class PhysicsNode extends Node {
PhysicsNode(Offset gravity) {
b2World = new box2d.World.withGravity(
new Vector2(
gravity.dx / b2WorldToNodeConversionFactor,
gravity.dy / b2WorldToNodeConversionFactor));
_init();
}
PhysicsNode.fromB2World(this.b2World, this.b2WorldToNodeConversionFactor) {
_init();
}
void _init() {
_contactHandler = new _ContactHandler(this);
b2World.setContactListener(_contactHandler);
}
box2d.World b2World;
_ContactHandler _contactHandler;
double b2WorldToNodeConversionFactor = 500.0;
Offset get gravity {
Vector2 g = b2World.getGravity();
return new Offset(g.x, g.y);
}
set gravity(Offset gravity) {
// Convert from points/s^2 to m/s^2
b2World.setGravity(new Vector2(gravity.dx / b2WorldToNodeConversionFactor,
gravity.dy / b2WorldToNodeConversionFactor));
}
bool get allowSleep => b2World.isAllowSleep();
set allowSleep(bool allowSleep) {
b2World.setAllowSleep(allowSleep);
}
bool get subStepping => b2World.isSubStepping();
set subStepping(bool subStepping) {
b2World.setSubStepping(subStepping);
}
void _stepPhysics(double dt) {
// Calculate a step in the simulation
b2World.stepDt(dt, 10, 10);
// Iterate over the bodies
for (box2d.Body b2Body = b2World.bodyList; b2Body != null; b2Body = b2Body.getNext()) {
// Update visual position and rotation
PhysicsBody body = b2Body.userData;
body._node._setPositionFromPhysics(new Point(
b2Body.position.x * b2WorldToNodeConversionFactor,
b2Body.position.y * b2WorldToNodeConversionFactor
));
body._node._setRotationFromPhysics(degrees(b2Body.getAngle()));
}
}
void _updatePosition(PhysicsBody body, Point position) {
Vector2 newPos = new Vector2(
position.x / b2WorldToNodeConversionFactor,
position.y / b2WorldToNodeConversionFactor
);
double angle = body._body.getAngle();
body._body.setTransform(newPos, angle);
body._body.setAwake(true);
}
void _updateRotation(PhysicsBody body, double rotation) {
Vector2 pos = body._body.position;
double newAngle = radians(rotation);
body._body.setTransform(pos, newAngle);
body._body.setAwake(true);
}
void addChild(Node node) {
super.addChild(node);
if (node.physicsBody != null) {
node.physicsBody._attach(this, node);
}
}
void removeChild(Node node) {
super.removeChild(node);
if (node.physicsBody != null) {
node.physicsBody._detach();
}
}
void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, [PhysicsContactType type]) {
_contactHandler.addContactCallback(callback, tagA, tagB, type);
}
void paint(PaintingCanvas canvas) {
super.paint(canvas);
paintDebug(canvas);
}
void paintDebug(PaintingCanvas canvas) {
Paint shapePaint = new Paint();
shapePaint.setStyle(sky.PaintingStyle.stroke);
shapePaint.strokeWidth = 1.0;
for (box2d.Body body = b2World.bodyList; body != null; body = body.getNext()) {
canvas.save();
Point point = new Point(
body.position.x * b2WorldToNodeConversionFactor,
body.position.y * b2WorldToNodeConversionFactor);
canvas.translate(point.x, point.y);
canvas.rotate(body.getAngle());
if (body.getType() == box2d.BodyType.DYNAMIC) {
if (body.isAwake())
shapePaint.color = new Color(0xff00ff00);
else
shapePaint.color = new Color(0xff666666);
}
else if (body.getType() == box2d.BodyType.STATIC)
shapePaint.color = new Color(0xffff0000);
else if (body.getType() == box2d.BodyType.KINEMATIC)
shapePaint.color = new Color(0xffff9900);
for (box2d.Fixture fixture = body.getFixtureList(); fixture != null; fixture = fixture.getNext()) {
box2d.Shape shape = fixture.getShape();
if (shape.shapeType == box2d.ShapeType.CIRCLE) {
box2d.CircleShape circle = shape;
Point cp = new Point(
circle.p.x * b2WorldToNodeConversionFactor,
circle.p.y * b2WorldToNodeConversionFactor
);
double radius = circle.radius * b2WorldToNodeConversionFactor;
canvas.drawCircle(cp, radius, shapePaint);
} else if (shape.shapeType == box2d.ShapeType.POLYGON) {
box2d.PolygonShape poly = shape;
List<Point> points = [];
for (int i = 0; i < poly.getVertexCount(); i++) {
Vector2 vertex = poly.getVertex(i);
Point pt = new Point(
vertex.x * b2WorldToNodeConversionFactor,
vertex.y * b2WorldToNodeConversionFactor
);
points.add(pt);
}
if (points.length >= 2) {
for (int i = 0; i < points.length; i++) {
canvas.drawLine(points[i], points[(i + 1) % points.length], shapePaint);
}
}
}
}
canvas.restore();
}
}
}
class PhysicsContact {
PhysicsContact(
this.nodeA,
this.nodeB,
this.shapeA,
this.shapeB,
this.isTouching,
this.isEnabled
);
final Node nodeA;
final Node nodeB;
final PhysicsShape shapeA;
final PhysicsShape shapeB;
final isTouching;
bool isEnabled;
}
class _ContactCallbackInfo {
_ContactCallbackInfo(this.callback, this.tagA, this.tagB, this.type);
PhysicsContactCallback callback;
Object tagA;
Object tagB;
PhysicsContactType type;
}
class _ContactHandler extends box2d.ContactListener {
_ContactHandler(this.physicsNode);
PhysicsNode physicsNode;
List<_ContactCallbackInfo> callbackInfos = [];
void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, PhysicsContactType type) {
callbackInfos.add(new _ContactCallbackInfo(callback, tagA, tagB, type));
}
void handleCallback(PhysicsContactType type, box2d.Contact b2Contact, box2d.Manifold oldManifold, box2d.ContactImpulse impulse) {
// Get info about the contact
PhysicsBody bodyA = b2Contact.fixtureA.getBody().userData;
PhysicsBody bodyB = b2Contact.fixtureB.getBody().userData;
box2d.Fixture fixtureA = b2Contact.fixtureA;
box2d.Fixture fixtureB = b2Contact.fixtureB;
// Match callback with added callbacks
for (_ContactCallbackInfo info in callbackInfos) {
// Check that type is matching
if (info.type != null && info.type != type)
continue;
// Check if there is a match
bool matchA = (info.tagA == null) || info.tagA == bodyA.tag;
bool matchB = (info.tagB == null) || info.tagB == bodyB.tag;
bool match = (matchA && matchB);
if (!match) {
// Check if there is a match if we swap a & b
bool matchA = (info.tagA == null) || info.tagA == bodyB.tag;
bool matchB = (info.tagB == null) || info.tagB == bodyA.tag;
match = (matchA && matchB);
if (match) {
// Swap a & b
PhysicsBody tempBody = bodyA;
bodyA = bodyB;
bodyB = tempBody;
box2d.Fixture tempFixture = fixtureA;
fixtureA = fixtureB;
fixtureB = tempFixture;
}
}
if (match) {
// We have contact and a matched callback, setup contact info
PhysicsContact contact = new PhysicsContact(
bodyA._node,
bodyB._node,
fixtureA.userData,
fixtureB.userData,
b2Contact.isTouching(),
b2Contact.isEnabled()
);
// Make callback
info.callback(type, contact);
// Update Box2D contact
b2Contact.setEnabled(contact.isEnabled);
}
}
}
void beginContact(box2d.Contact contact) {
handleCallback(PhysicsContactType.begin, contact, null, null);
}
void endContact(box2d.Contact contact) {
handleCallback(PhysicsContactType.end, contact, null, null);
}
void preSolve(box2d.Contact contact, box2d.Manifold oldManifold) {
handleCallback(PhysicsContactType.preSolve, contact, oldManifold, null);
}
void postSolve(box2d.Contact contact, box2d.ContactImpulse impulse) {
handleCallback(PhysicsContactType.postSolve, contact, null, impulse);
}
}
part of skysprites;
abstract class PhysicsShape {
box2d.Shape _b2Shape;
Object userObject;
box2d.Shape getB2Shape(PhysicsNode node) {
if (_b2Shape == null) {
_b2Shape = _createB2Shape(node);
}
return _b2Shape;
}
box2d.Shape _createB2Shape(PhysicsNode node);
}
class PhysicsShapeCircle extends PhysicsShape {
PhysicsShapeCircle(this.point, this.radius);
final Point point;
final double radius;
box2d.Shape _createB2Shape(PhysicsNode node) {
box2d.CircleShape shape = new box2d.CircleShape();
shape.p.x = point.x / node.b2WorldToNodeConversionFactor;
shape.p.y = point.y / node.b2WorldToNodeConversionFactor;
shape.radius = radius / node.b2WorldToNodeConversionFactor;
return shape;
}
}
class PhysicsShapePolygon extends PhysicsShape {
PhysicsShapePolygon(this.points);
final List<Point> points;
box2d.Shape _createB2Shape(PhysicsNode node) {
List<Vector2> vectors = [];
for (Point point in points) {
Vector2 vec = new Vector2(
point.x / node.b2WorldToNodeConversionFactor,
point.y / node.b2WorldToNodeConversionFactor
);
vectors.add(vec);
}
box2d.PolygonShape shape = new box2d.PolygonShape();
shape.set(vectors, vectors.length);
return shape;
}
}
class PhysicsShapeGroup extends PhysicsShape {
PhysicsShapeGroup(this.shapes);
final List<PhysicsShape> shapes;
box2d.Shape _createB2Shape(PhysicsNode node) {
return null;
}
}
...@@ -10,6 +10,7 @@ import 'dart:math' as math; ...@@ -10,6 +10,7 @@ import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:box2d/box2d.dart' as box2d;
import 'package:mojo/core.dart'; import 'package:mojo/core.dart';
import 'package:sky_services/media/media.mojom.dart'; import 'package:sky_services/media/media.mojom.dart';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
...@@ -31,6 +32,9 @@ part 'node.dart'; ...@@ -31,6 +32,9 @@ part 'node.dart';
part 'node3d.dart'; part 'node3d.dart';
part 'node_with_size.dart'; part 'node_with_size.dart';
part 'particle_system.dart'; part 'particle_system.dart';
part 'physics_body.dart';
part 'physics_node.dart';
part 'physics_shape.dart';
part 'sound.dart'; part 'sound.dart';
part 'sound_manager.dart'; part 'sound_manager.dart';
part 'sprite.dart'; part 'sprite.dart';
......
...@@ -76,6 +76,8 @@ class SpriteBox extends RenderBox { ...@@ -76,6 +76,8 @@ class SpriteBox extends RenderBox {
List<Node> _constrainedNodes; List<Node> _constrainedNodes;
List<PhysicsNode> _physicsNodes;
Rect _visibleArea; Rect _visibleArea;
Rect get visibleArea { Rect get visibleArea {
...@@ -138,15 +140,17 @@ class SpriteBox extends RenderBox { ...@@ -138,15 +140,17 @@ class SpriteBox extends RenderBox {
// Adding and removing nodes // Adding and removing nodes
_registerNode(Node node) { void _registerNode(Node node) {
_actionControllers = null; _actionControllers = null;
_eventTargets = null; _eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null;
} }
_deregisterNode(Node node) { void _deregisterNode(Node node) {
_actionControllers = null; _actionControllers = null;
_eventTargets = null; _eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null;
} }
...@@ -359,6 +363,7 @@ class SpriteBox extends RenderBox { ...@@ -359,6 +363,7 @@ class SpriteBox extends RenderBox {
_callConstraintsPreUpdate(delta); _callConstraintsPreUpdate(delta);
_runActions(delta); _runActions(delta);
_callUpdate(_rootNode, delta); _callUpdate(_rootNode, delta);
_callStepPhysics(delta);
_callConstraintsConstrain(delta); _callConstraintsConstrain(delta);
// Schedule next update // Schedule next update
...@@ -370,20 +375,26 @@ class SpriteBox extends RenderBox { ...@@ -370,20 +375,26 @@ class SpriteBox extends RenderBox {
void _runActions(double dt) { void _runActions(double dt) {
if (_actionControllers == null) { if (_actionControllers == null) {
_actionControllers = []; _rebuildActionControllersAndPhysicsNodes();
_addActionControllers(_rootNode, _actionControllers);
} }
for (ActionController actions in _actionControllers) { for (ActionController actions in _actionControllers) {
actions.step(dt); actions.step(dt);
} }
} }
void _addActionControllers(Node node, List<ActionController> controllers) { void _rebuildActionControllersAndPhysicsNodes() {
if (node._actions != null) controllers.add(node._actions); _actionControllers = [];
_physicsNodes = [];
_addActionControllersAndPhysicsNodes(_rootNode);
}
void _addActionControllersAndPhysicsNodes(Node node) {
if (node._actions != null) _actionControllers.add(node._actions);
if (node is PhysicsNode) _physicsNodes.add(node);
for (int i = node.children.length - 1; i >= 0; i--) { for (int i = node.children.length - 1; i >= 0; i--) {
Node child = node.children[i]; Node child = node.children[i];
_addActionControllers(child, controllers); _addActionControllersAndPhysicsNodes(child);
} }
} }
...@@ -397,6 +408,15 @@ class SpriteBox extends RenderBox { ...@@ -397,6 +408,15 @@ class SpriteBox extends RenderBox {
} }
} }
void _callStepPhysics(double dt) {
if (_physicsNodes == null)
_rebuildActionControllersAndPhysicsNodes();
for (PhysicsNode physicsNode in _physicsNodes) {
physicsNode._stepPhysics(dt);
}
}
void _callConstraintsPreUpdate(double dt) { void _callConstraintsPreUpdate(double dt) {
if (_constrainedNodes == null) { if (_constrainedNodes == null) {
_constrainedNodes = []; _constrainedNodes = [];
......
...@@ -6,6 +6,9 @@ homepage: http://flutter.io ...@@ -6,6 +6,9 @@ homepage: http://flutter.io
dependencies: dependencies:
sky: ">=0.0.36 < 0.1.0" sky: ">=0.0.36 < 0.1.0"
sky_tools: ">=0.0.10 < 0.1.0" sky_tools: ">=0.0.10 < 0.1.0"
box2d: any
dependency_overrides: dependency_overrides:
sky: sky:
path: ../sky/packages/sky path: ../sky/packages/sky
box2d:
path: ../../../box2d.dart
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment