mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	Bring back collision detection example (#736)
This commit is contained in:
		| @ -25,6 +25,11 @@ the latter is very useful for accurate gesture detection. The collision detectio | |||||||
| what should happen when two hitboxes collide, so it is up to the user to implement what will happen | what should happen when two hitboxes collide, so it is up to the user to implement what will happen | ||||||
| when for example two position components have intersecting hitboxes. | when for example two position components have intersecting hitboxes. | ||||||
|  |  | ||||||
|  | Do note that the built-in collision detection system does not take collisions between two hitboxes | ||||||
|  | that overshoot each other into account, this could happen when they either move too fast or `update` | ||||||
|  | being called with a large delta time (for example if your app is not in the foreground). This | ||||||
|  | behaviour is called tunneling, if you want to read more about it. | ||||||
|  |  | ||||||
| ## Mixins | ## Mixins | ||||||
| ### Hitbox | ### Hitbox | ||||||
| The `Hitbox` mixin is mainly used for two things; to make detection of collisions with other | The `Hitbox` mixin is mainly used for two things; to make detection of collisions with other | ||||||
|  | |||||||
| @ -59,8 +59,12 @@ Example of usage, where visibility of two components are handled by a wrapper: | |||||||
| ```dart | ```dart | ||||||
| class GameOverPanel extends PositionComponent with HasGameRef<MyGame> { | class GameOverPanel extends PositionComponent with HasGameRef<MyGame> { | ||||||
|   bool visible = false; |   bool visible = false; | ||||||
|  |   final Image spriteImage; | ||||||
|  |  | ||||||
|   GameOverPanel(Image spriteImage) : super() { |   GameOverPanel(this.spriteImage); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> onLoad() async { | ||||||
|     final gameOverText = GameOverText(spriteImage); // GameOverText is a Component |     final gameOverText = GameOverText(spriteImage); // GameOverText is a Component | ||||||
|     final gameOverButton = GameOverButton(spriteImage); // GameOverRestart is a SpriteComponent |     final gameOverButton = GameOverButton(spriteImage); // GameOverRestart is a SpriteComponent | ||||||
|  |  | ||||||
| @ -196,15 +200,15 @@ FlareController that can play multiple animations and control nodes. | |||||||
| import 'package:flame_flare/flame_flare.dart'; | import 'package:flame_flare/flame_flare.dart'; | ||||||
|  |  | ||||||
| class YourFlareController extends FlareControls { | class YourFlareController extends FlareControls { | ||||||
|      |  | ||||||
|     ActorNode rightHandNode; |   late ActorNode rightHandNode; | ||||||
|      |  | ||||||
|     void initialize(FlutterActorArtboard artboard) { |   void initialize(FlutterActorArtboard artboard) { | ||||||
|         super.initialize(artboard); |     super.initialize(artboard); | ||||||
|          |  | ||||||
|         // get flare node |     // get flare node | ||||||
|         rightHand = artboard.getNode('right_hand'); |     rightHand = artboard.getNode('right_hand'); | ||||||
|     } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| final fileName = 'assets/george_washington.flr'; | final fileName = 'assets/george_washington.flr'; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import 'package:flame/flame.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| import 'stories/animations/animations.dart'; | import 'stories/animations/animations.dart'; | ||||||
|  | import 'stories/collision_detection/collision_detection.dart'; | ||||||
| import 'stories/components/components.dart'; | import 'stories/components/components.dart'; | ||||||
| import 'stories/controls/controls.dart'; | import 'stories/controls/controls.dart'; | ||||||
| import 'stories/effects/effects.dart'; | import 'stories/effects/effects.dart'; | ||||||
| @ -15,12 +16,13 @@ import 'stories/widgets/widgets.dart'; | |||||||
|  |  | ||||||
| void main() async { | void main() async { | ||||||
|   final dashbook = Dashbook( |   final dashbook = Dashbook( | ||||||
|     title: 'Flame Example', |     title: 'Flame Examples', | ||||||
|     theme: ThemeData.dark(), |     theme: ThemeData.dark(), | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   addAnimationStories(dashbook); |   addAnimationStories(dashbook); | ||||||
|   addComponentsStories(dashbook); |   addComponentsStories(dashbook); | ||||||
|  |   addCollisionDetectionStories(dashbook); | ||||||
|   addEffectsStories(dashbook); |   addEffectsStories(dashbook); | ||||||
|   addTileMapStories(dashbook); |   addTileMapStories(dashbook); | ||||||
|   addControlsStories(dashbook); |   addControlsStories(dashbook); | ||||||
|  | |||||||
							
								
								
									
										71
									
								
								examples/lib/stories/collision_detection/circles.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								examples/lib/stories/collision_detection/circles.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | |||||||
|  | import 'dart:ui'; | ||||||
|  |  | ||||||
|  | import 'package:flame/components.dart'; | ||||||
|  | import 'package:flame/extensions.dart'; | ||||||
|  | import 'package:flame/game.dart'; | ||||||
|  | import 'package:flame/geometry.dart'; | ||||||
|  | import 'package:flame/gestures.dart'; | ||||||
|  | import 'package:flutter/material.dart' hide Image, Draggable; | ||||||
|  |  | ||||||
|  | class MyCollidable extends PositionComponent | ||||||
|  |     with HasGameRef<Circles>, Hitbox, Collidable { | ||||||
|  |   late Vector2 velocity; | ||||||
|  |   final _collisionColor = Colors.amber; | ||||||
|  |   final _defaultColor = Colors.cyan; | ||||||
|  |   bool _isWallHit = false; | ||||||
|  |   bool _isCollision = false; | ||||||
|  |  | ||||||
|  |   MyCollidable(Vector2 position) | ||||||
|  |       : super( | ||||||
|  |           position: position, | ||||||
|  |           size: Vector2.all(100), | ||||||
|  |           anchor: Anchor.center, | ||||||
|  |         ) { | ||||||
|  |     addShape(HitboxCircle()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> onLoad() async { | ||||||
|  |     final center = gameRef.size / 2; | ||||||
|  |     velocity = (center - position)..scaleTo(150); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void update(double dt) { | ||||||
|  |     super.update(dt); | ||||||
|  |     if (_isWallHit) { | ||||||
|  |       remove(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     debugColor = _isCollision ? _collisionColor : _defaultColor; | ||||||
|  |     position.add(velocity * dt); | ||||||
|  |     _isCollision = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void render(Canvas canvas) { | ||||||
|  |     super.render(canvas); | ||||||
|  |     renderShapes(canvas); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onCollision(Set<Vector2> intersectionPoints, Collidable other) { | ||||||
|  |     if (other is ScreenCollidable) { | ||||||
|  |       _isWallHit = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     _isCollision = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class Circles extends BaseGame with HasCollidables, TapDetector { | ||||||
|  |   @override | ||||||
|  |   Future<void> onLoad() async { | ||||||
|  |     add(ScreenCollidable()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onTapDown(TapDownDetails details) { | ||||||
|  |     add(MyCollidable(details.localPosition.toVector2())); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | import 'package:dashbook/dashbook.dart'; | ||||||
|  | import 'package:flame/game.dart'; | ||||||
|  |  | ||||||
|  | import '../../commons/commons.dart'; | ||||||
|  | import 'circles.dart'; | ||||||
|  | import 'multiple_shapes.dart'; | ||||||
|  |  | ||||||
|  | void addCollisionDetectionStories(Dashbook dashbook) { | ||||||
|  |   dashbook.storiesOf('Collision Detection') | ||||||
|  |     ..add( | ||||||
|  |       'Circles', | ||||||
|  |       (_) => GameWidget(game: Circles()), | ||||||
|  |       codeLink: baseLink('collision_detection/circles.dart'), | ||||||
|  |     ) | ||||||
|  |     ..add( | ||||||
|  |       'Multiple shapes', | ||||||
|  |       (_) => GameWidget(game: MultipleShapes()), | ||||||
|  |       codeLink: baseLink('collision_detection/multiple_shapes.dart'), | ||||||
|  |     ); | ||||||
|  | } | ||||||
							
								
								
									
										237
									
								
								examples/lib/stories/collision_detection/multiple_shapes.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								examples/lib/stories/collision_detection/multiple_shapes.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,237 @@ | |||||||
|  | import 'dart:math'; | ||||||
|  | import 'dart:ui'; | ||||||
|  |  | ||||||
|  | import 'package:flame/components.dart'; | ||||||
|  | import 'package:flame/extensions.dart'; | ||||||
|  | import 'package:flame/game.dart'; | ||||||
|  | import 'package:flame/geometry.dart'; | ||||||
|  | import 'package:flame/palette.dart'; | ||||||
|  | import 'package:flutter/material.dart' hide Image, Draggable; | ||||||
|  |  | ||||||
|  | enum Shapes { circle, rectangle, polygon } | ||||||
|  |  | ||||||
|  | abstract class MyCollidable extends PositionComponent | ||||||
|  |     with Draggable, Hitbox, Collidable { | ||||||
|  |   double rotationSpeed = 0.0; | ||||||
|  |   final Vector2 velocity; | ||||||
|  |   final delta = Vector2.zero(); | ||||||
|  |   double angleDelta = 0; | ||||||
|  |   bool _isDragged = false; | ||||||
|  |   final _activePaint = Paint()..color = Colors.amber; | ||||||
|  |   double _wallHitTime = double.infinity; | ||||||
|  |  | ||||||
|  |   MyCollidable(Vector2 position, Vector2 size, this.velocity) { | ||||||
|  |     this.position = position; | ||||||
|  |     this.size = size; | ||||||
|  |     anchor = Anchor.center; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void update(double dt) { | ||||||
|  |     super.update(dt); | ||||||
|  |     if (_isDragged) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     _wallHitTime += dt; | ||||||
|  |     delta.setFrom(velocity * dt); | ||||||
|  |     position.add(delta); | ||||||
|  |     angleDelta = dt * rotationSpeed; | ||||||
|  |     angle = (angle + angleDelta) % (2 * pi); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void render(Canvas canvas) { | ||||||
|  |     super.render(canvas); | ||||||
|  |     renderShapes(canvas); | ||||||
|  |     final localCenter = (size / 2).toOffset(); | ||||||
|  |     if (_isDragged) { | ||||||
|  |       canvas.drawCircle(localCenter, 5, _activePaint); | ||||||
|  |     } | ||||||
|  |     if (_wallHitTime < 1.0) { | ||||||
|  |       // Show a rectangle in the center for a second if we hit the wall | ||||||
|  |       canvas.drawRect( | ||||||
|  |         Rect.fromCenter(center: localCenter, width: 10, height: 10), | ||||||
|  |         debugPaint, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void onCollision(Set<Vector2> intersectionPoints, Collidable other) { | ||||||
|  |     final averageIntersection = intersectionPoints.reduce((sum, v) => sum + v) / | ||||||
|  |         intersectionPoints.length.toDouble(); | ||||||
|  |     final collisionDirection = (averageIntersection - absoluteCenter) | ||||||
|  |       ..normalize() | ||||||
|  |       ..round(); | ||||||
|  |     if (velocity.angleToSigned(collisionDirection).abs() > 3) { | ||||||
|  |       // This entity got hit by something else | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     final angleToCollision = velocity.angleToSigned(collisionDirection); | ||||||
|  |     if (angleToCollision.abs() < pi / 8) { | ||||||
|  |       velocity.rotate(pi); | ||||||
|  |     } else { | ||||||
|  |       velocity.rotate(-pi / 2 * angleToCollision.sign); | ||||||
|  |     } | ||||||
|  |     position.sub(delta * 2); | ||||||
|  |     angle = (angle - angleDelta) % (2 * pi); | ||||||
|  |     if (other is ScreenCollidable) { | ||||||
|  |       _wallHitTime = 0; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool onDragUpdate(int pointerId, DragUpdateDetails details) { | ||||||
|  |     _isDragged = true; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool onDragEnd(int pointerId, DragEndDetails details) { | ||||||
|  |     velocity.setFrom(details.velocity.pixelsPerSecond.toVector2() / 10); | ||||||
|  |     _isDragged = false; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CollidablePolygon extends MyCollidable { | ||||||
|  |   CollidablePolygon(Vector2 position, Vector2 size, Vector2 velocity) | ||||||
|  |       : super(position, size, velocity) { | ||||||
|  |     final shape = HitboxPolygon([ | ||||||
|  |       Vector2(-1.0, 0.0), | ||||||
|  |       Vector2(-0.8, 0.6), | ||||||
|  |       Vector2(0.0, 1.0), | ||||||
|  |       Vector2(0.6, 0.9), | ||||||
|  |       Vector2(1.0, 0.0), | ||||||
|  |       Vector2(0.6, -0.8), | ||||||
|  |       Vector2(0, -1.0), | ||||||
|  |       Vector2(-0.8, -0.8), | ||||||
|  |     ]); | ||||||
|  |     addShape(shape); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CollidableRectangle extends MyCollidable { | ||||||
|  |   CollidableRectangle(Vector2 position, Vector2 size, Vector2 velocity) | ||||||
|  |       : super(position, size, velocity) { | ||||||
|  |     addShape(HitboxRectangle()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CollidableCircle extends MyCollidable { | ||||||
|  |   CollidableCircle(Vector2 position, Vector2 size, Vector2 velocity) | ||||||
|  |       : super(position, size, velocity) { | ||||||
|  |     final shape = HitboxCircle(); | ||||||
|  |     addShape(shape); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SnowmanPart extends HitboxCircle { | ||||||
|  |   static const startColor = Colors.white; | ||||||
|  |   final hitPaint = Paint() | ||||||
|  |     ..color = startColor | ||||||
|  |     ..strokeWidth = 1 | ||||||
|  |     ..style = PaintingStyle.stroke; | ||||||
|  |  | ||||||
|  |   SnowmanPart(double definition, Vector2 relativePosition, Color hitColor) | ||||||
|  |       : super(definition: definition) { | ||||||
|  |     this.relativePosition.setFrom(relativePosition); | ||||||
|  |     onCollision = (Set<Vector2> intersectionPoints, HitboxShape other) { | ||||||
|  |       if (other.component is ScreenCollidable) { | ||||||
|  |         hitPaint..color = startColor; | ||||||
|  |       } else { | ||||||
|  |         hitPaint..color = hitColor; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void render(Canvas canvas, Paint paint) { | ||||||
|  |     super.render(canvas, hitPaint); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CollidableSnowman extends MyCollidable { | ||||||
|  |   CollidableSnowman(Vector2 position, Vector2 size, Vector2 velocity) | ||||||
|  |       : super(position, size, velocity) { | ||||||
|  |     rotationSpeed = 0.2; | ||||||
|  |     final top = SnowmanPart(0.4, Vector2(0, -0.8), Colors.red); | ||||||
|  |     final middle = SnowmanPart(0.6, Vector2(0, -0.3), Colors.yellow); | ||||||
|  |     final bottom = SnowmanPart(1.0, Vector2(0, 0.5), Colors.green); | ||||||
|  |     addShape(top); | ||||||
|  |     addShape(middle); | ||||||
|  |     addShape(bottom); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class MultipleShapes extends BaseGame | ||||||
|  |     with HasCollidables, HasDraggableComponents { | ||||||
|  |   final TextConfig fpsTextConfig = TextConfig( | ||||||
|  |     color: BasicPalette.white.color, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> onLoad() async { | ||||||
|  |     final screen = ScreenCollidable(); | ||||||
|  |     final snowman = CollidableSnowman( | ||||||
|  |       Vector2.all(150), | ||||||
|  |       Vector2(100, 200), | ||||||
|  |       Vector2(-100, 100), | ||||||
|  |     ); | ||||||
|  |     MyCollidable lastToAdd = snowman; | ||||||
|  |     add(screen); | ||||||
|  |     add(snowman); | ||||||
|  |     var totalAdded = 1; | ||||||
|  |     while (totalAdded < 20) { | ||||||
|  |       lastToAdd = createRandomCollidable(lastToAdd); | ||||||
|  |       final lastBottomRight = | ||||||
|  |           lastToAdd.toAbsoluteRect().bottomRight.toVector2(); | ||||||
|  |       if (screen.containsPoint(lastBottomRight)) { | ||||||
|  |         add(lastToAdd); | ||||||
|  |         totalAdded++; | ||||||
|  |       } else { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   final _rng = Random(); | ||||||
|  |   final _distance = Vector2(100, 0); | ||||||
|  |  | ||||||
|  |   MyCollidable createRandomCollidable(MyCollidable lastCollidable) { | ||||||
|  |     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) * 200; | ||||||
|  |     final rotationSpeed = 0.5 - _rng.nextDouble(); | ||||||
|  |     final shapeType = Shapes.values[_rng.nextInt(Shapes.values.length)]; | ||||||
|  |     switch (shapeType) { | ||||||
|  |       case Shapes.circle: | ||||||
|  |         return CollidableCircle(position, collidableSize, velocity); | ||||||
|  |       case Shapes.rectangle: | ||||||
|  |         return CollidableRectangle(position, collidableSize, velocity) | ||||||
|  |           ..rotationSpeed = rotationSpeed; | ||||||
|  |       case Shapes.polygon: | ||||||
|  |         return CollidablePolygon(position, collidableSize, velocity) | ||||||
|  |           ..rotationSpeed = rotationSpeed; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void render(Canvas canvas) { | ||||||
|  |     super.render(canvas); | ||||||
|  |     fpsTextConfig.render( | ||||||
|  |       canvas, | ||||||
|  |       '${fps(120).toStringAsFixed(2)}fps', | ||||||
|  |       Vector2(0, size.y - 24), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -21,6 +21,8 @@ | |||||||
|  - Add a `renderPoint` method to `Canvas` |  - Add a `renderPoint` method to `Canvas` | ||||||
|  - Add zoom to the camera |  - Add zoom to the camera | ||||||
|  - Add `moveToTarget` as an extension method to `Vector2` |  - Add `moveToTarget` as an extension method to `Vector2` | ||||||
|  |  - Bring back collision detection examples | ||||||
|  |  - Fix collision detection in Collidable with multiple offset shapes | ||||||
|  |  | ||||||
| ## 1.0.0-rc8 | ## 1.0.0-rc8 | ||||||
|  - Migrate to null safety |  - Migrate to null safety | ||||||
|  | |||||||
| @ -38,8 +38,8 @@ mixin Hitbox on PositionComponent { | |||||||
|     if (!_cachedBoundingRect.isCacheValid([position, size])) { |     if (!_cachedBoundingRect.isCacheValid([position, size])) { | ||||||
|       final maxRadius = size.length; |       final maxRadius = size.length; | ||||||
|       _cachedBoundingRect.updateCache( |       _cachedBoundingRect.updateCache( | ||||||
|         Rect.fromCenter( |         RectExtension.fromVector2Center( | ||||||
|           center: absoluteCenter.toOffset(), |           center: absoluteCenter, | ||||||
|           width: maxRadius, |           width: maxRadius, | ||||||
|           height: maxRadius, |           height: maxRadius, | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -37,11 +37,7 @@ extension RectExtension on Rect { | |||||||
|       bottomLeft.toVector2(), |       bottomLeft.toVector2(), | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| } |  | ||||||
|  |  | ||||||
| // Until [extension] will allow static methods we need to keep these functions |  | ||||||
| // in a utility class |  | ||||||
| class RectFactory { |  | ||||||
|   /// Creates bounds in from of a [Rect] from a list of [Vector2] |   /// Creates bounds in from of a [Rect] from a list of [Vector2] | ||||||
|   static Rect fromBounds(List<Vector2> pts) { |   static Rect fromBounds(List<Vector2> pts) { | ||||||
|     final minX = pts.map((e) => e.x).reduce(min); |     final minX = pts.map((e) => e.x).reduce(min); | ||||||
| @ -50,4 +46,19 @@ class RectFactory { | |||||||
|     final maxY = pts.map((e) => e.y).reduce(max); |     final maxY = pts.map((e) => e.y).reduce(max); | ||||||
|     return Rect.fromPoints(Offset(minX, minY), Offset(maxX, maxY)); |     return Rect.fromPoints(Offset(minX, minY), Offset(maxX, maxY)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /// Constructs a rectangle from its center point (specified as a Vector2), | ||||||
|  |   /// width and height. | ||||||
|  |   static Rect fromVector2Center({ | ||||||
|  |     required Vector2 center, | ||||||
|  |     required double width, | ||||||
|  |     required double height, | ||||||
|  |   }) { | ||||||
|  |     return Rect.fromLTRB( | ||||||
|  |       center.x - width / 2, | ||||||
|  |       center.y - height / 2, | ||||||
|  |       center.x + width / 2, | ||||||
|  |       center.y + height / 2, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -56,10 +56,12 @@ mixin HitboxShape on Shape { | |||||||
|   @override |   @override | ||||||
|   double get angle => component.angle; |   double get angle => component.angle; | ||||||
|  |  | ||||||
|   /// The shapes center, before rotation |   /// The shape's absolute center | ||||||
|   @override |   @override | ||||||
|   Vector2 get shapeCenter { |   Vector2 get shapeCenter { | ||||||
|     return (component.absoluteCenter + position) |     return component.absoluteCenter + | ||||||
|  |         position + | ||||||
|  |         ((size / 2)..multiply(relativePosition)) | ||||||
|       ..rotate(angle, center: anchorPosition); |       ..rotate(angle, center: anchorPosition); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Lukas Klingsbo
					Lukas Klingsbo