7.1 Attaque de l'avatar
Téléchargement fichiers référence desktop.zip core.src.zip core.assets.zip
Nous avons déjà créé une classe Attack afin de représenter une attaque infligée par un ennemi. Cependant contrairement à la classe Attack, l'attaque effectue par l'avatar ne partage pas le même Collider que l'avatar lui-même. Une manière naïve d'implémenter cette capacité d'attaque serait de gérer les attaques instantanées (DirectAttack) et les attaques physiques (SwordAttack) différemment. Cependant, une interface nous permet de résoudre le problème de manière plus élégante.

Étapes à suivre
- Ajoutez un interface
IAttack- Renommez la classe
AttackparDirectAttack- Ajoutez la classe
SwordAttackqui dérive deEntityet implantez les composantes deIAttack
/* IAttack.java */
package com.tutorialquest;
// import ..
public interface IAttack {
float getDamage();
float getKnockback();
Vector2 getDirection();
}/* DirectAttack.java */
package com.tutorialquest;
// import ..
// MODIF:
// public class DirectAttack
public class DirectAttack implements IAttack
{
// ...
}/* SwordAttack.java */
package com.tutorialquest.entities;
// import ..
public class SwordAttack extends PhysicalObject implements IAttack
{
protected Vector2 direction;
private float damage = 10;
private float knockback = 40f;
public float getDamage() {return damage;}
public float getKnockback() {return knockback; }
public Vector2 getDirection() { return direction;}
public SwordAttack(
Vector2 position,
Vector2 direction,
float damage,
float knockback,
int mask)
{
super(position);
this.collider = new Collider(
new Vector2(
WIDTH,
HEIGHT),
Collider.FLAG_NONE);
this.collider.origin = new Vector2(
WIDTH/2,
HEIGHT/2);
this.knockback = knockback;
this.damage = damage;
this.direction = direction;
this.mask = mask;
}
}Il est aussi important de s'assurer que chaque attaque ne soit pas effective indéfiniment. Nous devons donc imposer une limite de temps à la classe SwordAttack après laquelle l'attaque est retirée du monde.
Étapes à suivre
- dans la méthode
update, vérifiez pour les collisions avec un ennemi- ajoutez une limite de temps après lequel l'attaque est retirée du monde.
/* SwordAttack.java */
package com.tutorialquest.entities;
// import ..
public class SwordAttack extends PhysicalObject implements IAttack
{
// AJOUT:
// Variables de contrôle pour imposer une limite de temps
private float timeLimit = 0.2f;
private float elapsedTime = 0f;
// AJOUT:
// `hits` contient les ennemis déjà attaques
private List<PhysicalObject> hits = new LinkedList<>();
// ..
// AJOUT:
@Override
public void update(float deltaTime) {
super.update(deltaTime);
collider.update(position);
List<PhysicalObject> collisionResults = new LinkedList<>();
collider.getObjectCollisions(
this,
0,
0,
mask,
collisionResults);
// Endommagez les objets avec lesquels l'attaque rentre en contact
for(PhysicalObject result : collisionResults)
{
if(hits.contains(result))
return;
result.onAttacked(this);
hits.add(result);
}
// Retirez l'attaque si la limite de temps expire
elapsedTime += deltaTime;
if(elapsedTime > timeLimit) {
Game.level.remove(this);
finished = true;
}
}
}Finalement, ajoutons le code qui gère la création de l'attaque depuis l'avatar. Il s'agit d'instancier l'attaque en avant du personnage. Puisque l'attaque n'est sous forme de carre, nous cherchons à redimensionner la boîte de collision dépendamment de l'orientation du personnage.
Étapes à suivre
- ajoutez la méthode
attackdansAvatarqui est invoqué dansupdate- ajoutez la méthode
cancelAttackafin de permettre qu'une attaque en maintenant.
package com.tutorialquest.entities;
//import ..
public class Avatar extends Character {
// ...
private static final float ATTACK_RANGE = 8f;
protected static final float DAMAGE = 4f;
protected static final float KNOCKBACK = 200;
// AJOUT:
private SwordAttack attack;
// ...
public void attack() {
// On annule l'attaque
// lorsque le personnage change de direction
if (
attack != null &&
!attack.direction.epsilonEquals(Utils.toVector(fixedDirection)))
{
cancelAttack();
}
// On détermine les dimensions et la position de l'attaque
// selon la direction du personnage
// Pour la position:
Vector2 attackOffset =
fixedDirection == Utils.Direction.LEFT ||
fixedDirection == Utils.Direction.RIGHT ?
new Vector2(ATTACK_RANGE, 0)
.scl(fixedDirection == Utils.Direction.RIGHT ? 2 : -2) :
new Vector2(0, ATTACK_RANGE)
.scl(fixedDirection == Utils.Direction.UP ? 2 : -2);
// Pour la dimension:
Vector2 attackSize =
fixedDirection == Utils.Direction.LEFT ||
fixedDirection == Utils.Direction.RIGHT ?
new Vector2(SwordAttack.HEIGHT, SwordAttack.WIDTH) :
new Vector2(SwordAttack.WIDTH, SwordAttack.HEIGHT);
// Ajoute une attaque dans le monde lorsque
// le joueur appui sur la touche requise
if (input.isAttackJustPressed())
{
cancelAttack();
Game.level.add(
attack = new SwordAttack(
new Vector2(position).add(attackOffset),
Utils.toVector(fixedDirection),
damage,
knockback,
Collider.FLAG_ENEMY
));
}
if (attack != null) {
if (attack.finished) {
cancelAttack();
return;
}
attack.position
.set(position)
.add(attackOffset);
attack.resize(attackSize);
}
}
public void cancelAttack() {
if (attack != null) {
Game.level.remove(attack);
attack = null;
}
}
@Override
public void update(float deltaTime) {
super.update(deltaTime);
control(deltaTime);
turn();
updateVelocity(deltaTime);
push(Collider.FLAG_PUSHABLE);
move();
// AJOUT:
attack();
}
}