Construire un Plattformer - Comment déterminer si un joueur est autorisé à sauter?

16

Je construis un simple jeu Plattformer Jump n 'Run Style. Je n'utilise pas de tuiles - à la place, j'ai des formes géométriques pour mes entités de niveau (et le joueur en est un aussi). J'ai terminé mon code de détection de collision et tout fonctionne bien jusqu'à présent.

Ensuite, je voulais implémenter le saut. Il suffit de vérifier si le joueur frappe la touche appropriée et d'ajouter une certaine vitesse vers le haut. Fonctionne bien. Mais cela fonctionne même si le joueur est en l'air, ce qui n'est pas ce que je veux. ;-)

Donc, je dois vérifier si le joueur se tient sur quelque chose. Ma première idée était de vérifier s'il y avait une collision dans la dernière image et de marquer le joueur comme "capable de sauter", mais cela se déclencherait même si le joueur heurte un mur en l'air. Comme mes compétences en mathématiques ne sont pas si bonnes, je demande de l'aide - même des astuces pourraient faire comment mettre en œuvre cela.

Merci!

Malax
la source

Réponses:

14

Deux options viennent à l'esprit:

  • La première chose à faire est d'étiqueter la géométrie avec un ID, puis de vérifier si la collision est avec une géométrie étiquetée comme sol. Cela offre le plus de contrôle des surfaces pouvant être sautées, mais à un coût en temps de création de niveau.
  • La deuxième consiste à vérifier la normale de la collision, si elle pointe vers le haut, autorisez le saut. Ou dans une certaine marge, cela dépend si vous avez des sols inclinés. C'est flexible et ne nécessite aucun étiquetage, mais si vous avez des sols et des murs inclinés, vous pourriez sauter un peu là où vous ne le voulez pas.
wkerslake
la source
Le chèque normal me semble une excellente idée. Merci d'avoir posté ça.
Christopher Horenstein du
+1 pour une vérification normale. Cela garantit qu'un sol peut facilement passer à un mur sans causer de problèmes.
Soviut
1
+1 Vérifiez définitivement la collision normale. Avoir différents types de géométrie du monde est bien et permet une conception de niveau plus flexible, mais cela ne résout pas votre problème principal. Un «mur» peut également être de type «sol», selon que vous le rencontrez ou que vous vous tenez dessus. C'est là que la collision normale aidera.
bummzack
Je trouve la solution normale très agréable et résoudrait mon problème. Même l'autre bonne réponse est agréable et telle - mais cela répond mieux à ma question. Merci votre wkerslake!
Malax
8

Vous devez sûrement implémenter une sorte de type de surface. Pensez-y, comment allez-vous gérer si vous pouvez monter une échelle si vous ne pouvez pas savoir si votre personnage vient de heurter un mur ou une échelle? Vous pouvez simplement utiliser la POO pour gérer une hiérarchie de types en utilisant l'héritage, mais je vous suggère d'utiliser des "catégories" implémentées en utilisant un type énuméré:

Voici l'idée: une énumération "Collisions" a un indicateur pour chaque catégorie. Par exemple:

namespace Collisions
{
    enum Type
    {
        None   = 0,
        Floor  = 1 << 0,
        Ladder = 1 << 1,
        Enemy  = 1 << 2,
        ... // And whatever else you need.

        // Then, you can construct named groups of flags.
        Player = Floor | Ladder | Enemy
    };
}

Avec cette méthode, vous pourrez tester si le joueur vient d'entrer en collision avec tout ce que vous devez gérer, afin que votre moteur puisse appeler une méthode "entrée en collision" de l'entité:

void Player::Collided( Collisions::Type group )
{
   if ( group & Collisions::Ladder )
   {
      // Manage Ladder Collision
   }
   if ( group & Collisions::Floor )
   {
      // Manage Floor Collision
   }
   if ( group & Collisions::Enemy )
   {
      // Manage Enemy Collision
   }
}

La méthode utilise des drapeaux au niveau du bit et l'opérateur "Ou" au niveau du bit pour garantir que chaque groupe a une valeur différente, basée sur la valeur binaire de la catégorie. Cette méthode fonctionne bien et est facilement évolutive afin que vous puissiez créer des groupes de collision douanière. Chaque entité (joueur, ennemi, etc.) de votre jeu possède des bits appelés "filtre", qui sont utilisés pour déterminer avec quoi elle peut entrer en collision. Votre code de collision doit vérifier si les bits correspondent et réagir en conséquence, avec du code qui pourrait ressembler à:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}
Frédérick Imbeault
la source
Y a-t-il une raison d'utiliser des constants au lieu d'une énumération? Dans le cas dégénéré d'un seul indicateur ou groupe nommé, le type énuméré est plus lisible et vous obtenez des capacités de vérification de type supplémentaires dans la plupart des compilateurs.
Eh bien oui, l'énumération ne peut pas (je pense) utiliser des opérateurs binaires pour créer de nouveaux groupes personnalisés. La raison pour laquelle j'ai fait ces const est que j'utilise une classe Config avec des membres statiques publics pour des raisons de lisibilité, en gardant la sécurité des paramètres de configuration.
Frédérick Imbeault du
Ça peut. L'énumération "rvalues" peut être des opérations au niveau du bit sur d'autres valeurs constantes (je pense que cela peut être n'importe quelle expression constante en fait, mais je suis trop paresseux pour vérifier la norme). Les valeurs non statiques sont obtenues exactement de la même manière que votre exemple, mais sont plus précisément saisies. Je n'ai aucune idée de ce que vous entendez par «sécurité», mais exactement la même syntaxe sans la surcharge associée à une classe peut être obtenue avec un espace de noms.
J'ai mis à jour vos exemples de code pour utiliser un type énuméré.
Peut-être que cela facilite la lecture. J'approuve les modifications que vous avez apportées, la méthode que j'ai utilisée était correcte mais je n'avais pas ajusté le code pour l'explication enouph, merci pour les corrections. Pour l'espace de noms, vous avez tout à fait raison, mais ce n'est pas vrai pour tous les langages (langages de script par exemple). La «sécurité» est que mes options de configuration sont statiques, donc elles ne peuvent pas être modifiées, mais en utilisant une énumération, cela empêche également cela.
Frédérick Imbeault du
1

Si vous considérez le pied de votre personnage comme toujours sous le personnage d'une certaine distance, et si vous ne vous éloignez pas de la surface, votre personnage est au sol.

En pseudo-code approximatif:

bool isOnGround(Character& chr)
{
   // down vector is opposite from your characters current up vector.
   // if you want to support wall jumps, animate the vecToFoot as appropriate.
   vec vecToFoot = -chr.up * chr.footDistanceFromCentre;

// if feet are moving away from any surface, can't be on the ground if (dot(chr.velocity, down) < 0.0f) return false;

// generate line from character centre to the foot position vec linePos0 = chr.position; vec linePos1 = chr.position + vecToFoot;

// test line against world. If it returns a hit surface, we're on the ground if (testLineAgainstWorld(line0, line1, &surface) return true;

return false; }

jpaver
la source
Cela ne fonctionnera pas si vous souhaitez ajouter des fonctionnalités telles que les doubles sauts ou les sauts au mur, je pense?
Frédérick Imbeault du
Je ne crois pas que l'OP ait mentionné les doubles sauts ou les sauts muraux, n'est-ce pas?
jpaver
J'ai révisé le code pour montrer comment abstraire le vecteur à votre pied du centre du personnage. Si vous animez cela et que les pieds se retrouvent sur le côté et heurtent un mur, vous pourrez soutenir le saut de mur.
jpaver
Il s'agit essentiellement d'un moyen moins flexible de vérifier la normale de collision. (Moins flexible parce que vous pouvez distinguer trivialement un saut de mur d'un saut au sol d'un saut au plafond simplement en vérifiant la direction de la normale.)
2
N'appelez jamais une variable char, pas même en pseudo-code.
replié à droite