Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isColliding is giving inconsistent results when adding/removing Hitboxes per update #3417

Open
gaaclarke opened this issue Dec 19, 2024 · 1 comment
Labels

Comments

@gaaclarke
Copy link

What happened?

I'm not getting consistent results from the collision detection. Sometimes things are colliding, sometimes they are not.

What do you expect?

When using the arrow keys to move the circle through the red area full of colliders I would expect that it would refuse to enter it because this.isColliding == true. Instead it just seems to stutter, sometimes failing to move, sometimes not.

How can we reproduce this?

In this example, on one update a collider is added that represents where the component will move. If it doesn't collide it will actually execute the move on the next update. Then the hitbox is removed and a new one is added which represents the next move.

I attempted removing the hitbox if there is a collision instead of using isColliding and I got the same results.

What steps should take to fix this?

No response

Do have an example of where the bug occurs?

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

Vector2 CalcPerpendicular(Vector2 vec, {bool clockwise = true}) {
  if (clockwise) {
    return Vector2(vec.y, -vec.x);
  } else {
    return Vector2(-vec.y, vec.x);
  }
}

class Ninja extends RectangleComponent
    with KeyboardHandler, CollisionCallbacks {
  final Set<LogicalKeyboardKey> keysPressed = {};
  final Set<LogicalKeyboardKey> keysDown = {};
  final double speed = 64;
  PolygonComponent? _hitbox;
  Vector2? _delta;

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawCircle(Offset(this.size.x / 2, this.size.y / 2), this.size.x / 2,
        Paint()..color = Color.fromARGB(255, 178, 186, 247));
  }

  @override
  void update(double dt) {
    if (_hitbox != null) {
      if (!isColliding) {
        print('move');
        this.position += _delta!;
      }
      remove(_hitbox!);
      _hitbox = null;
    }

    double dx = 0;
    double dy = 0;
    if (keysPressed.contains(LogicalKeyboardKey.arrowRight)) {
      dx = dt * speed;
    }
    if (keysPressed.contains(LogicalKeyboardKey.arrowLeft)) {
      dx = -dt * speed;
    }
    if (keysPressed.contains(LogicalKeyboardKey.arrowUp)) {
      dy = -dt * speed;
    }
    if (keysPressed.contains(LogicalKeyboardKey.arrowDown)) {
      dy = dt * speed;
    }

    _delta = Vector2(dx, dy);
    if (_delta!.length2 > 0) {
      _hitbox = PolygonHitbox([
        CalcPerpendicular(_delta!, clockwise: true).normalized() * (width / 2) +
            (this.size * 0.5),
        CalcPerpendicular(_delta!, clockwise: false).normalized() *
                (width / 2) +
            (this.size * 0.5),
        CalcPerpendicular(_delta!, clockwise: false).normalized() *
                (width / 2) +
            (this.size * 0.5) +
            _delta! +
            _delta!.normalized() * 16,
        CalcPerpendicular(_delta!, clockwise: true).normalized() * (width / 2) +
            (this.size * 0.5) +
            _delta! +
            _delta!.normalized() * 16,
      ]);
      print('attempt ${_delta} ${_hitbox!.vertices}');
      _hitbox!.debugColor = Color(0xffff0000);
      add(_hitbox!);
    }
  }

  bool onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
    this.keysDown.clear();
    for (final key in keysPressed) {
      if (!this.keysPressed.contains(key)) {
        this.keysDown.add(key);
      }
    }
    this.keysPressed.clear();
    this.keysPressed.addAll(keysPressed);
    return false;
  }

  @override
  void onCollision(Set<Vector2> points, PositionComponent other) {
    print('collision $other');
    super.onCollision(points, other);
  }
}

class NinjaWorld extends World {
  @override
  Future<void> onLoad() async {
    final test =
        RectangleComponent(position: Vector2(100, 100), size: Vector2(16, 16))
          ..add(RectangleHitbox())
          ..debugMode = true;
    add(test);

    for (int j = 12; j <= 14; ++j) {
      for (int i = 0; i < 30; ++i) {
        int xpos = i * 16;
        int ypos = j * 16;
        final ShapeComponent shape = RectangleComponent(
            position: Vector2(xpos.toDouble(), ypos.toDouble()),
            paint: Paint()..color = Color.fromARGB(255, 205, 35, 35),
            size: Vector2(16, 16))
          ..add(RectangleHitbox());
        add(shape);
      }
    }

    final ninja = Ninja();
    ninja.position = Vector2(0, 0);
    ninja.size = Vector2(32, 32);
    ninja.debugMode = true;
    add(ninja);
  }
}

class NinjaGame extends FlameGame
    with HasKeyboardHandlerComponents, HasQuadTreeCollisionDetection {
  NinjaGame()
      : super(
            camera:
                CameraComponent.withFixedResolution(width: 512, height: 288),
            world: NinjaWorld());

  @override
  Future<void> onLoad() async {
    initializeCollisionDetection(
      mapDimensions: const Rect.fromLTWH(0, 0, 5120, 288),
    );
    camera.moveTo(Vector2(256, 144));
    camera.viewfinder.zoom = 1.0;
  }
}

void main() {
  final game = NinjaGame();
  runApp(GameWidget(game: game));
}

Relevant log output

flutter: attempt [0.533312,0.0] [[0.0,0.0], [0.0,32.0], [16.533311999999995,32.0], [16.533311999999995,0.0]]
flutter: collision RectangleComponent(
  position: Vector2(256.0, 208.0),
  size: Vector2(16.0, 16.0),
  angle: 0.0,
  scale: [1.0,1.0],
)
flutter: move

Notice that there is a collision happening, but the "move" command is still printed out afterwards.



### Execute in a terminal and put output into the code block below

[!] Flutter (Channel [user-branch], 3.27.0-1.0.pre.644, on macOS 14.7.1 23H222 darwin-arm64, locale en)
! Flutter version 3.27.0-1.0.pre.644 on channel [user-branch] at /Users/aaclarke/dev/flutter
Currently on an unknown channel. Run flutter channel to switch to an official channel.
If that doesn't fix the issue, reinstall Flutter by following instructions at https://flutter.dev/setup.
! Upstream repository unknown source is not a standard remote.
Set environment variable "FLUTTER_GIT_URL" to unknown source to dismiss this error.
• Framework revision f3f72ede04 (3 weeks ago), 2024-11-25 16:14:30 -0800
• Engine revision fe45a66086
• Dart version 3.7.0 (build 3.7.0-183.0.dev)
• DevTools version 2.41.0-dev.2
• If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and
upgrades.


### Affected platforms

macOS

### Other information

Flame version: 1.22.0

### Are you interested in working on a PR for this?

- [ ] I want to work on this
@gaaclarke gaaclarke added the bug label Dec 19, 2024
@spydon
Copy link
Member

spydon commented Dec 19, 2024

What happens here is that when you remove a component from the component tree it is not fully removed until after the tick is done, and that is after the collision detection for the tick has run.
We'd have to somehow directly remove it from the broadphase (or set it to CollisionType.inactive) when remove(hitbox) is called. We don't have any lifecycle methods for when a removal has started (onRemove is when it is done), so it'll require some thought for how we can hook that up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants