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
Attack
parDirectAttack
- Ajoutez la classe
SwordAttack
qui dérive deEntity
et 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
attack
dansAvatar
qui est invoqué dansupdate
- ajoutez la méthode
cancelAttack
afin 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();
}
}