Fonctionnement de Box2D pour simuler un "tir"

Le problème exposé dans ce sujet a été résolu.

Hello tout le monde,

Je suis un véritable newbie avec Box2D. J’essaie de l’utiliser pour coder un petit jeu sur mon temps perso mais c’est la galère totale pour l’instant ! A titre informatif, j’utilise cette librarie qui est un portage de Box2D en TypeScript (elle est très largement sous côtée si vous voulez mon avis).

Voici un screen du "jeu". J’essaie d’envoyer la petite boule vers la droite à l’action d’une touche.

J’ai essayé plusieurs variantes mais rien n’est utile. Je trouve très peu de documentation sur box2d… Notamment sur ce point précis. Au point que je pense louper quelque chose d’important. Voici les classes qui peuvent être intéressantes dans ce que j’essaie de faire. Ne jugez pas, c’est des esssais :) .

// Ceci est la classe qui gère (grosso modo) la physique du cercle
export class Circle {
    private _body:box2d.b2Body;
    private _fixture:box2d.b2Fixture;

    constructor(world:World, r:number, position: Position = {x: 0, y: 0}, userData: string) {
        let ballBodyDef = new box2d.b2BodyDef();
        ballBodyDef.bullet = true;
        ballBodyDef.linearDamping = 3.5;
        ballBodyDef.angularDamping = 1.6;
        ballBodyDef.type = box2d.b2BodyType.b2_dynamicBody;

        this._body = world.box2d.CreateBody(ballBodyDef);

        let playerFixDef = new box2d.b2FixtureDef();
        playerFixDef.friction = .1;
        playerFixDef.restitution = 0;
        playerFixDef.density = 0.000001; // Un doute sur ce point pour commencer
        playerFixDef.shape = new box2d.b2CircleShape(r + .5); // La taille est celle en pixels du cercle
        playerFixDef.userData = userData;

        this._fixture = this._body.CreateFixture(playerFixDef);
        this._body.SetPosition(new box2d.b2Vec2(position.x, position.y));
    }

    setPosition(position: Position) {
        this._body.SetPosition(new box2d.b2Vec2(position.x, position.y));
    }

    public getPosition(): Position {
        let p = this._body.GetPosition();
        return {
            x: p.x,
            y: p.y
        };
    }

    setVelocity(velocity: Velocity) {
        this._body.SetLinearVelocity(new box2d.b2Vec2(velocity.x, velocity.y));
    }

    getVelocity(): Velocity {
        let v = this._body.GetLinearVelocity();

        return {
            x: v.x,
            y: v.y
        };
    }

  get body(): box2d.b2Body {
    return this._body;
  }
}
// Ceci est la classe qui est appelée lors d'une collision entre mes différents éléments.
export class ContactListener extends box2d.b2ContactListener{
  private _game: Gb;

  constructor(game: Gb) {
    super();
    this._game = game;
  }

  public PostSolve(contact: b2Contact, impulse: b2ContactImpulse): void {
    if (this.checkContactEvent(contact, 'player') && this.checkContactEvent(contact, 'ball')) {
      this.ballAndPlayerContact(contact);
    }
  }

  private ballAndPlayerContact(contact: b2Contact): void {
    if (this._game._controller.isKicking) {
      const playerVelocity = this._game._ball.body.body.GetLinearVelocity();
      playerVelocity.SelfMul(1000000);
      this._game._ball.body.body.ApplyLinearImpulse(playerVelocity, this._game._ball.body.body.GetWorldCenter()); // Ne fonctionne pas du tout.
    }
  }

  private checkContactEvent(contact: b2Contact, userData: string) {
    return contact.m_fixtureA.m_userData === userData || contact.m_fixtureB.m_userData === userData;
  }
}

La fonction ApplyLinearImpulse est probablement mal appelée mais je ne sais pas comment récupérer un autre point que le "world center" (et tout ce que j’ai pu voir en rapport faisaient mention de cela). Dans la logique des choses je pensais spécifier le point d’impact mais je ne sais pas non plus comment le récupérer.

Si vous avez des connaissances avec Box2D n’hésitez pas à m’expliquer quelques trucs, je suis vraiment une buse sur ce sujet !

Merci d’avance :)

+0 -0

Salut ! :)

A priori, il y a quelques explications sur Box2D, peut être pas associée à chaque implémentation dans chaque langage, mais ça devrait s’adapter à ton cas.

Tout d’abord, il me semble que tu ne peux pas appliquer d’impulsion, ni changer quoi que ce soit pendant un callback comme PostSolve. Je cite le manuel de Box2D (p 53)

It is tempting to implement game logic that alters the physics world inside a contact callback. For example, you may have a collision that applies damage and try to destroy the associated actor and its rigid body. However, Box2D does not allow you to alter the physics world inside a callback because you might destroy objects that Box2D is currently processing, leading to orphaned pointers.

Pour récupérer le point de collision, d’après ce tutoriel, il faut utiliser le manifold, qui contient la position des points de contact en coordonnées locales. En fait, le manuel est plus précis et explique comment obtenir la position en coordonnées world qui t’intéresse (p 22):

b2WorldManifold worldManifold;
worldManifold.Initialize(&manifold, transformA, shapeA.m_radius,
 transformB, shapeB.m_radius);
for (int32 i = 0; i < manifold.pointCount; ++i)
{
 b2Vec2 point = worldManifold.points[i];
 …
}

Bon ici c’est en C++, mais ça devrait bien s’adapter, a priori le manifold est associé à un contact.

A priori la densité à 0.00001, c’est toi qui vois, mais ça ne changera rien aux calculs tant que tu n’auras pas un objet plus dense que l’autre. Donc 0.000001 ou 1 c’est pareil.

D’ailleurs sur ce thème, ça peut être intéressant de fonctionner avec des unités réelles, i.e. du système international (m, kg, etc.), ça te permet :

  • de mieux percevoir l’ordre de grandeur des objets
  • l’homogénéité de tes calculs de physique, qui peut toujours être utile.

Voilà, voilà :)

Hello, au top ta réponse merci. Je m’empresse de tester tout cela.

Cependant je m’interroge sur la façon d’utiliser les unités réelles. J’ai lu un passage de la doc à ce sujet en effet (et j’avoue que c’est pour ça que j’ai précisé que j’utilisais les pixels). Comment est-ce que je dois gérer la conversion pixel/mètres ?

L’idée c’est que ça doit être géré du côté affichage et input et que Box2D n’en entend pas parler. Donc effectuer des conversions des positions pour passer des coordonnées du monde à celles de l’écran, donc la multiplication par une échelle pour passer les mètres en pixels. Mais aussi dans l’autre sens lors d’un clic de souris par exemple.

EDIT: (J’avais pas fini j’ai publié sans faire exprès.) Avec certains frameworks et moteurs de jeu, la notion de caméra permet de passer des coordonnées du monde à celles de l’écran. On peut alors gérer non seulement le changement d’unité (ie zoom), la translation et la rotation de la caméra, ce qui permet plus de souplesse :)

+1 -0

Justement mon EDIT répond un peu. ^^Je détaille un peu plus le coup des matrices. Le principe c’est qu’on multiplie notre vecteur de coordonnées dans un certain système de coordonnées par une matrice (dite de passage, si on y tient) pour changer de système de coordonnées. Par exemple, plus généralement en 3D, on a ça : Schéma des matrices de changement de coordonnées en 3D (source: opengl-tutorial)

C’est pas un enfer à gérer, ça revient à une ligne de multiplication où bien à l’appel à une fonction. Quoi qu’il arrive, lorsqu’on travaille avec des coordonnées, on ne peut se soustraire aux changements de bases et donc vérifier qu’on parle bien de coordonnées world ou screen dans ton cas.

EDIT: TL;DR: En 2D, à partir du moment où tu veux implémenter une notion de caméra, y’a pas plus de travail pour changer d’échelle, c’est pour ça que les dévs de Box2D le conseillent.

+1 -0

Faire un ratio n’était au final pas compliqué du tout ! Et ça résoud tous les problèmes :) . Encore merci !

class World {
    public static readonly RATIO = 30;
}

class Circle {
    public getPosition():XY {
        let b2Position = this.body.GetPosition();
        return { x: b2Position.x * World.RATIO, y: b2Position.y * World.RATIO };
    }

    public setVelocity(velocity:b2Vec2) {
         this.body.SetLinearVelocity(new b2Vec2(velocity.x / World.RATIO, velocity.y / World.RATIO);
    }
}

Thanks a lot !

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte