Gestion du tir (2/2)
Notre superbe vaisseau dégomme des pauvres poulpes innocents. Ce n’est pas juste, il faut équilibrer cela !
Réutilisons ce que nous avons fait dans la première partie pour que ces braves bêtes puissent tirer.
Le projectile ennemi
Nous utiliserons cette image pour faire un sprite :
(Clic droit pour sauver l’image sur votre disque)
Vous pouvez créer un nouveau sprite, avec un rigidbody, un collider, bref tout l’équipement habituel.
Ou si vous aussi aussi fainéant que moi, dupliquez le prefab “PlayerShot” en “EnemyShot1”.
"La paresse est moteur de progrès." X. Le Guillou.
Astuce : Deux techniques pour la duplication.
- Instancier le prefab, renommer l’objet créé, sauver cet objet comme un nouveau préfab (drag’n’drop dans l’onglet Projects).
- Utilisez les raccourcis claviers dans l’onglet Projects sur le prefab :
cmd+D
(OS X) ouctrl+D
(Windows)
Pensez à utiliser notre nouvelle image. La bonne échelle est (0.35, 0.35, 1)
.
Ce qui vous donne au final :
En démarrant le jeu, vous verrez le tir se déplacer et potentiellement détruire un ennemi. C’est grâce (ou à cause) des valeurs paramétrées dans le “ShotScript” que nous avons copiés, prévu pour détruire les Poulpis.
Ne changez pas ces valeurs, car on s’en fiche. Rappelez-vous du “WeaponScript” de la partie précédente : il écrase ces valeurs par les bonnes quand il crée un projectile.
Nous avons notre prefab “EnemyShot1”. Supprimez toute instance éventuelle de la scène.
Tir ennemi
Exactement comme pour le joueur, nous allons ajouter à nos ennemis une arme et appeler Attack()
dans un script.
Modifications des ennemis
Souvenez-vous, nous modifions soit le prefab des ennemis, soit une instance en pensant à sauver les modifications en cliquant sur le bouton “Apply”.
- Ajoutez un “WeaponScript” aux ennemis
- Drag & drop du prefab “EnemyShot1” dans le champ “Shot Prefab” du script.
- Créez un nouveau script “EnemyScript” :
using UnityEngine;
/// <summary>
/// Comportement générique pour les méchants
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript weapon;
void Awake()
{
// Récupération de l'arme au démarrage
weapon = GetComponent<WeaponScript>();
}
void Update()
{
// Tir auto
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
Ajoutez ce script à notre ennemi. Vous devriez obtenir ceci :
Lancez le jeu pour voir !
Bon, ça marche, mais ce n’est pas vraiment ce que l’on attendait. L’arme tire vers la droite… parce que c’est ce qu’on lui dit de faire.
Si vous tournez l’ennemi, vous pourrez alors le faire tirer sur la gauche mais… ça ne donne pas ce que l’on veut :
Mais alors que faire ?
Tirer dans toute les directions
Le script “WeaponScript” a été fait dans un but : tirer dans la direction de l’objet auquel il est attaché. Cela pourrait par la suite servir à faire des bras de robots géants qui crachent des missiles sans rien toucher au code.
Ce n’est donc pas lui que l’on va modifier mais l’a manière dont on s’en sert. Ici l’astuce va être de l’ajouter à un objet vide enfant de l’objet ennemi.
Il nous faut :
- Créer un objet vide. Appelons le “WeaponObject”.
- Supprimer le composant “WeaponScript” attaché à l’objet / au prefab de l’ennemi.
- Ajouter un “WeaponScript” à notre “WeaponObject” et remettre le prefab du projectile en paramètre.
- Appliquer une rotation de
(0, 0, 180)
au “WeaponObject”.
Au final ce n’est pas très compliqué, il vous faut juste obtenir ceci :
Il nous faut aussi modifier légèrement notre “EnemyScript”.
En l’état actuel des choses, ce script utilise GetComponent<WeaponScript>()
qui nous retournera null
car il n’y a plus de WeaponScript
directement lié à l’objet.
Mais heureusement, il suffit de remplacer par GetComponentInChildren<WeaponScript>()
pour rechercher cette fois dans toute la hiérarchie de l’objet.
Note : Comme pour GetComponent<>()
, GetComponentInChildren<>()
se décline au pluriel (remarquez le s
après “Component”).
Et juste pour le plaisir, nous avons ajouté la gestion de plusieurs armes pour chaque ennemi (il nous suffit de garder une liste d’arme plutôt qu’une seule).
Voici “EnemyScript” au complet :
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Comportement générique pour les méchants
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript[] weapons;
void Awake()
{
// Récupération de toutes les armes de l'ennemi
weapons = GetComponentsInChildren<WeaponScript>();
}
void Update()
{
foreach (WeaponScript weapon in weapons)
{
// On fait tirer toutes les armes automatiquement
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
}
Enfin, changez si besoin les valeurs des paramètres du “MoveScript” du prefab du projectile “EnemyShot1” : il faut quand même qu’il soit plus rapide que l’ennemi en lui-même :
Maintenant ça ne rigole plus, les Poulpis sont méchants !
Bonus : tir multiple
Ajouter une deuxième arme pour tirer deux projectiles à la fois à notre ennemi n’est qu’une question de quelques clics, il n’y a aucune ligne de script à modifier :
- Ajoutez un nouveau “WeaponObject” (en dupliquant l’existant).
- Modifiez la rotation de chaque arme.
Et c’est tout, votre ennemi tire dans deux directions. Un exemple :
C’est un bon exemple de la logique Unity : nous réutilisons des scripts simples et indépendants. Le code est réduit, et moins de code = moins d’erreurs.
Dégâts du joueur
Nos poulpis ont beau être armés jusqu’au dents (les poulpes ont-ils des dents ?), ils ne peuvent en fait toujours rien faire au joueur.
Pour y remédier, ajoutez un “HealthScript” à l’objet du joueur. Pensez à décochez le champ “IsEnemy”.
Lancez le jeu et observez la différence :
Bonus
Ce qui suit n’est qu’une piste d’améliorations et de réflexions autour du développement d’un shmup. Si le genre ne vous intéresse pas, vous pouvez passez à l’étape suivante ;).
Pool de projectiles
En jouant un peu avec votre projet actuel vous verrez que l’onglet Hierarchy se remplit rapidement de projectiles qui mettent du temps à être détruits : 20 secondes d’après le script s’ils ne touchent pas leur cible.
Si vous voulez faire un danmaku avec BEAUCOUP de “boulettes”, il va falloir mieux gérer le nombre d’objets à l’écran et en mémoire.
Une solution pour gérer beaucoup de projectiles et de limiter leur nombre (c’est le système de pool).
Concrètement, cela peut être un tableau à taille limité référençant les objets créés. Une fois plein, on supprime les plus anciens pour en recréer des nouveaux.
Nous n’allons pas le faire ici mais nous eu l’occasion de l’utiliser pour un script de peinture.
Vous pouvez aussi juste réduire la durée de vie d’un projectile pour qu’il disparaisse rapidement, le risque étant qu’il n’ait pas le temps de toucher sa cible.
Attention : Dans tous les cas, rappelez-vous que la méthode Instantiate
est très coûteuse en performance. Le pool peut aussi permettre de créer des objets prêts à être utilisés.
Tirs différés
Si vous ajoutez plusieurs ennemis dans la scène, vous verrez qu’ils tirent de manière synchronisée.
Une solution simple serait d’ajouter un délai de tir à notre arme (initialiser le cooldown à autre chose que 0). Si les armes ne sont pas initialisées avec la même valeur (aléatoire ou paramètre précis) alors elles ne tireront pas en même temps.
Mais ici aussi c’est un choix à faire qui dépend entièrement du gameplay que vous visez.
Prochaine étape
Nous avons vu comment ajouter des armes à nos ennemis, en réutilisant en partie ce que l’on avait fait précédemment.
Le jeu que nous avons maintenant commence à prendre forme :
Essayez d’ajouter des ennemis, avec différentes propriétés. Bien sûr pour le moment le décor est statique et rappel “Space Invaders”.
Justement le prochain chapitre est consacré au défilement du décor et de l’arrière-plan !