0

Here's a code sample I wrote with encapsulation and composition in mind:

class Bullet {
 private:
  Vector2 position;
  Vector2 speed;

 public:
  void move(float time_delta) {
    position += speed * time_delta;
  }
};

Basically, there's just a projectile moving in nowhere. However, a bullet can actually e. g. ricochet off a wall, having its speed changed significantly. Is there a good way of considering such interactions? I neither want my Bullet to know about "higher-rank" classes (which are supposed to use it themselves) nor write a single-use solution like this one:

template<typename F> void move(float time_delta, F collision_checker);

UPDATE: worth reading if you want this question narrowed. Here's a simplified example of the wished logic for moving Bullets (I don't exactly mean the Bullet::move() member function!) and their interactions with other entities:

Vector2 destination = bullet.position + bullet.speed * time_delta;
if (std::optional<Creature> target = get_first_creature(bullet.position, destination)) {
  // decrease Bullet::speed depending on the target (and calculate the damage)
} else if (std::optional<Wall> obstacle = get_first_wall(bullet.position, destination)) {
  // calculate the ricochet changing Bullet::position and Bullet::speed
}

All pieces of code represented by comments are supposed to use some properties of the Creature and Wall classes.

passing_through
  • 1,778
  • 12
  • 24
  • 1
    The set of interactions with your object are defined by the class methods. – stark May 28 '20 at 19:41
  • @stark it's the `Bullet` class interacting with a higher-rank one, not someone interacting with the `Bullet`. – passing_through May 28 '20 at 19:43
  • 1
    this question cannot be answered by looking only at the bullet. As you already realized, the bullet is currently moving in nowhere without any context. One solution could be to make `move` accept an additional parameter `void move(float time_delta, World& world)` but there are too many possibilities and you should look at the bigger picture instead of considering a single class – 463035818_is_not_an_ai May 28 '20 at 19:45
  • @idclev463035818 the thing is that `World` also knows about `Bullet`, your solution gives me a circular dependency, unfortunately. – passing_through May 28 '20 at 19:48
  • circular dependecies can be resolved, but as I already said, not by looking at only one part – 463035818_is_not_an_ai May 28 '20 at 19:49
  • @idclev463035818 which extra information does my question need to be improved enough? – passing_through May 28 '20 at 19:52
  • i am afraid the question is just too broad. Currently you only talk about the bullet but you need to consider the bigger picture. Actually the first comment summarizes it nicely: The interactions with the object are defined by the methods. I dont know with what your bullet is supposed to interact – 463035818_is_not_an_ai May 28 '20 at 19:55
  • You could add an example of Bullet interacting with higher class. – stark May 28 '20 at 20:28
  • @stark here it goes! – passing_through May 28 '20 at 21:14
  • @idclev463035818 I've added an example, hope it's narrowing enough. – passing_through May 28 '20 at 21:15
  • Introduce some sort of object manager. This manager has all the code that enables interaction between various objects. The manager knows if bullet is affected by gravity, or dense environment, or bullet stopping Neo character. The manager is the One. – Dialecticus May 28 '20 at 21:29
  • @Dialecticus it seems like turning `Bullet` into a C-like struct (or a class with getters and setters for each field). Is there a more object-based decision? – passing_through May 28 '20 at 21:35

1 Answers1

2

From a design point of view, it is probably best if your bullet doesn't know how to detect when it's ... passing_through an obstacle (scnr). So it might be better to turn your Bullet class in to a struct, i.e. have it behave like a thing that is acted upon instead of a thing that acts.

You can still add your convenience function but have it be non-mutating:

struct Bullet {
  Vector2 position;
  Vector2 speed;
  Vector2 move(float time_delta) const {
    return position + speed * time_delta;
  }
};

This way you can compute the collisions from the calling scope:

auto dest = bullet.move(dt);
while (std::optional<Collision> const col = detectCollision(bullet.position,dest)) {
  bullet.position = col->intersectPoint;
  bullet.speed = col->reflectedSpeed;
  dest = col->reflectDest;
}
bullet.position = dest;

Here detectCollision checks whether the line from the bullet's current position to the new position dest intersects with any obstacle and computes the parameters of the reflection. Effectively you zig-zag your way to the destination that will result from all successive ping-pongs of the bullet with potential obstacles.

bitmask
  • 32,434
  • 14
  • 99
  • 159