ShapeComponent and Hitbox to take transform of parents full ancestor tree into consideration (#1076)

* `ShapeComponent` changes size, position and angle of underlying Shape

* Added description to ShapeComponent

* Fix test

* Update packages/flame/lib/src/components/shape_component.dart

Co-authored-by: Erick <erickzanardoo@gmail.com>

* Add absoluteScale and absoluteAngle to PositionComponent

* Refactor ShapeComponent

* Should be scaled by total scale, not scaled size

* Premature optimization for creation for objects in Polygon

* Use path for default Polygon constructor

* Do not sync component and hitbox shape

* Fix analyze issue

* Add example for flipping with collision detection

* Don't use absoluteScale

* Fix examples

* Fix examples

* Doesn't need super.render

* Fix Circle dartdoc

* Update changelog

* Update names of vertices caches in Polygon

* Update text docs

* Revert "Update text docs"

This reverts commit 73a68a465d76eb0eb50bb3753e57b2f4e3b5a7f4.

* Fix examples

* ShapeComponents docs

* Move example games to the top

* Fix dartdoc comment about polygon vertex relation

* Fix order of polygon vertices in dartdoc

* Fix anchor for PolygonComponent.fromPoints

* Add test with ancestors

* Update doc/components.md

Co-authored-by: Pasha Stetsenko <stpasha@google.com>

* Update doc/components.md

Co-authored-by: Erick <erickzanardoo@gmail.com>

* Rename example classes

* Fix linting issues in examples

* Don't use px

* Use isTrue and isFalse

* Update doc/components.md

Co-authored-by: Erick <erickzanardoo@gmail.com>

* Fixed comments on PR

Co-authored-by: Erick <erickzanardoo@gmail.com>
Co-authored-by: Pasha Stetsenko <stpasha@google.com>
This commit is contained in:
Lukas Klingsbo
2021-11-13 16:00:24 +01:00
committed by GitHub
parent d53ac50859
commit cd7a0bbb65
28 changed files with 1224 additions and 390 deletions

View File

@ -9,21 +9,95 @@ import 'package:flame/input.dart';
import 'package:flame/palette.dart';
import 'package:flutter/material.dart' hide Image, Draggable;
const multipleShapesInfo = '''
An example with many hitboxes that move around on the screen and during
collisions they change color depending on what it is that they have collided
with.
The snowman, the component built with three circles on top of each other, works
a little bit differently than the other components to show that you can have
multiple hitboxes within one component.
On this example, you can "throw" the components by dragging them quickly in any
direction.
''';
enum Shapes { circle, rectangle, polygon }
class MultipleShapesExample extends FlameGame
with HasCollidables, HasDraggableComponents, FPSCounter {
static const description = '''
An example with many hitboxes that move around on the screen and during
collisions they change color depending on what it is that they have collided
with.
The snowman, the component built with three circles on top of each other,
works a little bit differently than the other components to show that you
can have multiple hitboxes within one component.
On this example, you can "throw" the components by dragging them quickly in
any direction.
''';
final TextPaint fpsTextPaint = TextPaint(
config: TextPaintConfig(
color: BasicPalette.white.color,
),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final screenCollidable = ScreenCollidable();
final snowman = CollidableSnowman(
Vector2.all(150),
Vector2(100, 200),
Vector2(-100, 100),
screenCollidable,
);
MyCollidable lastToAdd = snowman;
add(screenCollidable);
add(snowman);
var totalAdded = 1;
while (totalAdded < 20) {
lastToAdd = nextRandomCollidable(lastToAdd, screenCollidable);
final lastBottomRight =
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
if (lastBottomRight.x < size.x && lastBottomRight.y < size.y) {
add(lastToAdd);
totalAdded++;
} else {
break;
}
}
}
final _rng = Random();
final _distance = Vector2(100, 0);
MyCollidable nextRandomCollidable(
MyCollidable lastCollidable,
ScreenCollidable screenCollidable,
) {
final collidableSize = Vector2.all(50) + Vector2.random(_rng) * 100;
final isXOverflow = lastCollidable.position.x +
lastCollidable.size.x / 2 +
_distance.x +
collidableSize.x >
size.x;
var position = _distance + Vector2(0, lastCollidable.position.y + 200);
if (!isXOverflow) {
position = (lastCollidable.position + _distance)
..x += collidableSize.x / 2;
}
final velocity = (Vector2.random(_rng) - Vector2.random(_rng)) * 400;
return randomCollidable(
position,
collidableSize,
velocity,
screenCollidable,
rng: _rng,
);
}
@override
void render(Canvas canvas) {
super.render(canvas);
fpsTextPaint.render(
canvas,
'${fps(120).toStringAsFixed(2)}fps',
Vector2(0, size.y - 24),
);
}
}
abstract class MyCollidable extends PositionComponent
with Draggable, HasHitboxes, Collidable {
double rotationSpeed = 0.0;
@ -41,11 +115,7 @@ abstract class MyCollidable extends PositionComponent
Vector2 size,
this.velocity,
this.screenCollidable,
) {
this.position = position;
this.size = size;
anchor = Anchor.center;
}
) : super(position: position, size: size, anchor: Anchor.center);
@override
Future<void> onLoad() async {
@ -137,7 +207,7 @@ class CollidablePolygon extends MyCollidable {
Vector2 velocity,
ScreenCollidable screenCollidable,
) : super(position, size, velocity, screenCollidable) {
final shape = HitboxPolygon([
final hitbox = HitboxPolygon([
Vector2(-1.0, 0.0),
Vector2(-0.8, 0.6),
Vector2(0.0, 1.0),
@ -147,7 +217,7 @@ class CollidablePolygon extends MyCollidable {
Vector2(0, -1.0),
Vector2(-0.8, -0.8),
]);
addHitbox(shape);
addHitbox(hitbox);
}
}
@ -169,8 +239,7 @@ class CollidableCircle extends MyCollidable {
Vector2 velocity,
ScreenCollidable screenCollidable,
) : super(position, size, velocity, screenCollidable) {
final shape = HitboxCircle();
addHitbox(shape);
addHitbox(HitboxCircle());
}
}
@ -179,7 +248,7 @@ class SnowmanPart extends HitboxCircle {
final hitPaint = Paint();
SnowmanPart(double definition, Vector2 relativeOffset, Color hitColor)
: super(definition: definition) {
: super(normalizedRadius: definition) {
this.relativeOffset.setFrom(relativeOffset);
hitPaint..color = startColor;
onCollision = (Set<Vector2> intersectionPoints, HitboxShape other) {
@ -245,77 +314,3 @@ MyCollidable randomCollidable(
..rotationSpeed = rotationSpeed;
}
}
class MultipleShapes extends FlameGame
with HasCollidables, HasDraggableComponents, FPSCounter {
final TextPaint fpsTextPaint = TextPaint(
config: TextPaintConfig(
color: BasicPalette.white.color,
),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final screenCollidable = ScreenCollidable();
final snowman = CollidableSnowman(
Vector2.all(150),
Vector2(100, 200),
Vector2(-100, 100),
screenCollidable,
);
MyCollidable lastToAdd = snowman;
add(screenCollidable);
add(snowman);
var totalAdded = 1;
while (totalAdded < 20) {
lastToAdd = nextRandomCollidable(lastToAdd, screenCollidable);
final lastBottomRight =
lastToAdd.toAbsoluteRect().bottomRight.toVector2();
if (lastBottomRight.x < size.x && lastBottomRight.y < size.y) {
add(lastToAdd);
totalAdded++;
} else {
break;
}
}
}
final _rng = Random();
final _distance = Vector2(100, 0);
MyCollidable nextRandomCollidable(
MyCollidable lastCollidable,
ScreenCollidable screenCollidable,
) {
final collidableSize = Vector2.all(50) + Vector2.random(_rng) * 100;
final isXOverflow = lastCollidable.position.x +
lastCollidable.size.x / 2 +
_distance.x +
collidableSize.x >
size.x;
var position = _distance + Vector2(0, lastCollidable.position.y + 200);
if (!isXOverflow) {
position = (lastCollidable.position + _distance)
..x += collidableSize.x / 2;
}
final velocity = (Vector2.random(_rng) - Vector2.random(_rng)) * 400;
return randomCollidable(
position,
collidableSize,
velocity,
screenCollidable,
rng: _rng,
);
}
@override
void render(Canvas canvas) {
super.render(canvas);
fpsTextPaint.render(
canvas,
'${fps(120).toStringAsFixed(2)}fps',
Vector2(0, size.y - 24),
);
}
}