[
Les première et
deuxième parties du tutoriel]
- Nous plaçons sur le terrain de la tour.
- Nous visons les ennemis Ă l'aide de la physique.
- Nous les suivons autant que possible.
- Nous les photographions avec un faisceau laser.
Ceci est la troisième partie d'une série de tutoriels sur la création d'un genre de tower defense simple. Il décrit la création de tours, visant et tirant sur les ennemis.
Le didacticiel a été créé dans Unity 2018.3.0f2.
Réchauffons les ennemis.Création de tour
Les murs ne font que ralentir les ennemis, augmentant ainsi la longueur du chemin à parcourir. Mais le but du jeu est de détruire les ennemis avant qu'ils n'atteignent le point final. Ce problème est résolu en plaçant des tours sur le terrain qui leur tireront dessus.
Contenu des tuiles
Les tours sont un autre type de contenu de tuiles, alors ajoutons une entrée pour elles dans
GameTileContent
.
public enum GameTileContentType { Empty, Destination, Wall, SpawnPoint, Tower€ }
Dans ce didacticiel, nous prendrons en charge un seul type de tour, qui peut ĂŞtre implĂ©mentĂ© en fournissant Ă
GameTileContentFactory
un lien vers le préfabriqué de tour, dont une instance peut également être créée via
Get
.
[SerializeField] GameTileContent towerPrefab = default; public GameTileContent Get (GameTileContentType type) { switch (type) { … case GameTileContentType.Tower€: return Get(towerPrefab); } … }
Mais les tours doivent tirer, leur état devra donc être mis à jour et ils auront besoin de leur propre code. Créez une classe
Tower
à cet effet qui étend la classe
GameTileContent
.
using UnityEngine; public class Tower : GameTileContent {}
Vous pouvez faire en sorte que le préfabriqué de tour ait son propre composant en remplaçant le type de champ d'usine par
Tower
. Étant donné que la classe est toujours considérée comme un
GameTileContent
, rien d'autre ne doit être changé.
Tower towerPrefab = default;
Préfabriqué
Créez un préfabriqué pour la tour. Vous pouvez commencer par dupliquer le préfabriqué du mur et remplacer son composant
GameTileContent
par le composant
Tower
, puis changer son type en
Tower . Pour que la tour s'adapte aux murs, enregistrez le cube mural comme base de la tour. Placez ensuite un autre cube dessus. Je lui ai donné une échelle de 0,5. Mettez un autre cube dessus, indiquant une tourelle, cette partie visera et tirera sur les ennemis.
Trois cubes formant une tour.La tourelle tournera, et comme elle a un collisionneur, elle sera suivie par un moteur physique. Mais nous n'avons pas besoin d'être aussi précis, car nous n'utilisons des collisionneurs de tour que pour sélectionner les cellules. Cela peut être fait approximativement. Retirez le collisionneur de cubes de tourelle et changez le collisionneur de cubes de tour afin qu'il couvre les deux cubes.
Collider cube tower.La tour tirera un faisceau laser. Il peut être visualisé de plusieurs façons, mais nous utilisons simplement un cube translucide, que nous allons étirer pour former un faisceau. Chaque tour doit avoir sa propre poutre, alors ajoutez-la au préfabriqué de la tour. Placez-le à l'intérieur de la tourelle pour qu'il soit caché par défaut et donnez-lui une échelle plus petite, par exemple 0,2. Faisons-en un enfant de la racine préfabriquée, pas du cube de tourelle.
Cube caché d'un faisceau laser.Créez un matériau adapté au faisceau laser. J'ai juste utilisé le matériau noir translucide standard et désactivé tous les reflets, et lui ai également donné une couleur rouge émise.
Le matériau du faisceau laser.Vérifiez que le faisceau laser n'a pas de collisionneur, et désactivez également sa dominante et son ombre.
Le faisceau laser n'interagit pas avec les ombres.Après avoir terminé la création de la tour préfabriquée, nous l'ajouterons à l'usine.
Usine avec une tour.Placement de la tour
Nous ajouterons et supprimerons des tours en utilisant une autre méthode de commutation. Vous pouvez simplement dupliquer
GameBoard.ToggleWall
en modifiant le nom de la méthode et le type de contenu.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower€) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Tower€); if (!FindPaths()) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } } }
Dans
Game.HandleTouch
, maintenir la touche Maj enfoncée fera basculer les tours plutôt que les murs.
void HandleTouch () { GameTile tile = board.GetTile(TouchRay); if (tile != null) { if (Input.GetKey(KeyCode.LeftShift)) { board.ToggleTower(tile); } else { board.ToggleWall(tile); } } }
Tours sur le terrain.Blocage de chemin
Jusqu'Ă prĂ©sent, seuls les murs peuvent bloquer la recherche d'un chemin, donc les ennemis se dĂ©placent Ă travers les tours. Ajoutons Ă
GameTileContent
propriété auxiliaire qui indique si le contenu bloque le chemin. Le chemin est bloqué s'il s'agit d'un mur ou d'une tour.
public bool BlocksPath => Type == GameTileContentType.Wall || Type == GameTileContentType.Tower€;
Utilisez cette propriété dans
GameTile.GrowPathTo
au lieu de vérifier le type de contenu.
GameTile GrowPathTo (GameTile neighbor, Direction direction) { … return
Maintenant, le chemin est bloqué par des murs et des tours.Remplacer les murs
Très probablement, le joueur remplacera souvent les murs par des tours. Il sera gênant pour lui de retirer le mur en premier, et en outre, les ennemis peuvent pénétrer dans cet espace temporairement apparu. Vous pouvez implémenter un remplacement direct en forçant
GameBoard.ToggleTower
à vérifier si le mur est actuellement sur la tuile. Si c'est le cas, remplacez-le immédiatement par une tour. Dans ce cas, nous n'avons pas à chercher d'autres moyens, car la tuile les bloque toujours.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { … } else if (tile.Content.Type == GameTileContentType.Wall) { tile.Content = contentFactory.Get(GameTileContentType.Tower); } }
Nous visons les ennemis
Une tour ne peut remplir sa tâche que lorsqu'elle trouve un ennemi. Après avoir trouvé l'ennemi, elle doit décider quelle partie de celui-ci viser.
Point de visée
Pour détecter des cibles, nous utiliserons le moteur physique. Comme dans le cas du collisionneur de tours, nous n'avons pas besoin que le collisionneur ennemi coïncide nécessairement avec sa forme. Vous pouvez choisir le collisionneur le plus simple, c'est-à -dire une sphère. Après avoir détecté l'ennemi, nous utiliserons la position de l'objet de jeu avec le collisionneur attaché comme point de visée.
Nous ne pouvons pas attacher le collisionneur à l’objet racine de l’ennemi, car il ne coïncide pas toujours avec la position du modèle et fera viser la tour au sol. Autrement dit, vous devez placer le collisionneur quelque part sur le modèle. Le moteur physique nous fournira un lien vers cet objet, que nous pouvons utiliser pour viser, mais nous avons toujours besoin d'accéder au composant
Enemy
de l'objet racine. Pour simplifier la tâche, créons le composant
TargetPoint
. Donnons-lui une propriété pour affectation privée et réception publique du composant
Enemy
, et une autre propriété pour obtenir sa position dans le monde.
using UnityEngine; public class TargetPoint : MonoBehaviour { public Enemy Enemy€ { get; private set; } public Vector3 Position => transform.position; }
Donnons-lui une méthode
Awake
qui établit un lien vers son composant
Enemy
. Accédez directement à l'objet racine à l'aide de
transform.root
. Si le composant
Enemy
n'existe pas, alors nous avons fait une erreur lors de la création de l'ennemi, alors ajoutons une déclaration pour cela.
void Awake () { Enemy€ = transform.root.GetComponent<Enemy>(); Debug.Assert(Enemy€ != null, "Target point without Enemy root!", this); }
De plus, le collisionneur doit être attaché au même objet de jeu auquel
TargetPoint
attaché.
Debug.Assert(Enemy€ != null, "Target point without Enemy root!", this); Debug.Assert( GetComponent<SphereCollider>() != null, "Target point without sphere collider!", this );
Ajoutez un composant et un collisionneur au cube préfabriqué de l'ennemi. Cela fera viser les tours au centre du cube. Nous utilisons un collisionneur sphérique avec un rayon de 0,25. Le cube a une échelle de 0,5, donc le vrai rayon du collisionneur sera de 0,125. Grâce à cela, l'ennemi devra traverser visuellement le cercle de portée de la tour, et seulement après un certain temps, le véritable objectif devient. La taille du collisionneur est également affectée par l'échelle aléatoire de l'ennemi, donc sa taille dans le jeu variera également légèrement.
Un ennemi avec un point de visée et un collisionneur sur un cube.Couche ennemie
Les tours ne se soucient que des ennemis, et elles ne visent rien d'autre, nous allons donc mettre tous les ennemis dans une couche distincte. Nous utiliserons la couche 9. Changez son nom pour
Enemy dans la fenĂŞtre
Layers & Tags , qui peut ĂŞtre ouverte via l'option
Edit Layers dans le menu déroulant
Layers dans le coin supérieur droit de l'éditeur.
La couche 9 sera utilisée pour les ennemis.Cette couche n'est nécessaire que pour la reconnaissance des ennemis, et non pour les interactions physiques. Signalons-le en les désactivant dans la
matrice de collision des couches , qui se trouve dans le panneau
Physique des paramètres du projet.
Matrice des collisions de couches.Assurez-vous que l'objet de jeu du point de visée se trouve sur la couche souhaitée. Le reste du préfabriqué ennemi peut se trouver sur d'autres calques, mais il sera plus facile de tout coordonner et de placer le préfabriqué entier dans le calque
ennemi . Si vous modifiez le calque de l'objet racine, vous serez invité à modifier le calque de tous ses objets enfants.
Ennemi sur la bonne couche.Ajoutons la déclaration que
TargetPoint
vraiment sur la bonne couche.
void Awake () { … Debug.Assert(gameObject.layer == 9, "Target point on wrong layer!", this); }
De plus, les actions du joueur doivent ĂŞtre ignorĂ©es par les collisionneurs ennemis. Cela peut ĂŞtre rĂ©alisĂ© en ajoutant un argument de masque de calque Ă
Physics.Raycast
dans
GameBoard.GetTile
. Cette méthode a une forme qui prend la distance au faisceau et au masque de calque comme arguments supplémentaires. Nous lui donnerons la distance maximale et le masque de calque par défaut, c'est-à -dire 1.
public GameTile GetTile (Ray ray) { if (Physics.Raycast(ray, out RaycastHit hit, float.MaxValue, 1)) { … } return null; }
Le masque de calque ne doit-il pas être 0?L'index de calque par défaut est zéro, mais nous passons le masque de calque. Le masque change les bits individuels d'un entier en 1 si la couche doit être activée. Dans ce cas, vous devez définir uniquement le premier bit, c'est-à -dire le moins significatif, ce qui signifie 2 0 , ce qui équivaut à 1.
Mise Ă jour du contenu des vignettes
Les tours ne peuvent effectuer leur tâche que lorsque leur statut est mis à jour. La même chose s'applique au contenu de toutes les tuiles, bien que le reste du contenu ne fasse rien jusqu'à présent. Par conséquent, ajoutez une méthode virtuelle
GameTileContent
Ă
GameUpdate
, qui ne fait rien par défaut.
public virtual void GameUpdate () {}
Faisons en sorte que
Tower
redéfinisse, même si pour l'instant il affiche simplement dans la console qu'il recherche une cible.
public override void GameUpdate () { Debug.Log("Searching for target..."); }
GameBoard
traite des tuiles et de leur contenu, il gardera donc également la trace du contenu à mettre à jour. Pour ce faire, ajoutez-y la liste et la méthode publique
GameUpdate
, qui met Ă jour tout dans la liste.
List<GameTileContent> updatingContent = new List<GameTileContent>(); … public void GameUpdate () { for (int i = 0; i < updatingContent.Count; i++) { updatingContent[i].GameUpdate(); } }
Dans notre tutoriel, il vous suffit de mettre Ă jour les tours. Modifiez
ToggleTower
pour qu'il ajoute et supprime du contenu si nécessaire. Si d'autres contenus sont également nécessaires, nous aurons besoin d'une approche plus générale, mais pour l'instant, cela suffit.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower) { updatingContent.Remove(tile.Content); tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Tower);
Pour que cela fonctionne, il nous suffit maintenant de simplement mettre Ă jour le champ dans
Game.Update
. Nous mettrons à jour le champ après les ennemis. Grâce à cela, les tours pourront viser exactement où se trouvent les ennemis. Si nous faisions autrement, les tours viseraient où se trouvaient les ennemis dans la dernière image.
void Update () { … enemies.GameUpdate(); board.GameUpdate(); }
Portée de visée
Les tours ont un rayon de visée limité. Personnalisons-le en ajoutant un champ à la classe
Tower
. La distance est mesurée à partir du centre de la tuile tour, donc à une portée de 0,5, elle ne couvrira que sa propre tuile. Par conséquent, une plage minimale et standard raisonnable serait de 1,5, couvrant la plupart des carreaux voisins.
[SerializeField, Range(1.5f, 10.5f)] float targetingRange = 1.5f;
Portée de visée 2.5.Visualisons la plage avec gizmo. Nous n'avons pas besoin de le voir constamment, nous allons donc créer la méthode
OnDrawGizmosSelected
appelée uniquement pour les objets sélectionnés. Nous dessinons le cadre jaune de la sphère avec un rayon égal à la distance et centré par rapport à la tour. Placez-le légèrement au-dessus du sol afin qu'il soit toujours clairement visible.
void OnDrawGizmosSelected () { Gizmos.color = Color.yellow; Vector3 position = transform.localPosition; position.y += 0.01f; Gizmos.DrawWireSphere(position, targetingRange); }
Portée de visée du Gizmo.Nous pouvons maintenant voir lequel des ennemis est une cible abordable pour chacune des tours. Mais choisir des tours dans la fenêtre de la scène n'est pas pratique, car nous devons sélectionner l'un des cubes enfants, puis basculer vers l'objet racine de la tour. D'autres types de contenu de tuiles souffrent également du même problème. Nous pouvons forcer la sélection de la racine du contenu de la
GameTileContent
dans la fenêtre de scène en ajoutant l'attribut
SelectionBase
au
GameTileContent
.
[SelectionBase] public class GameTileContent : MonoBehaviour { … }
Capture de cible
Ajoutez un champ
TargetPoint
Ă la classe
Tower
afin qu'il puisse suivre sa cible capturée. Ensuite, nous
GameUpdate
pour appeler la nouvelle méthode
AquireTarget
, qui renvoie des informations
AquireTarget
si elle a trouvé la cible. Lors de la détection, il affichera un message dans la console.
TargetPoint target; public override void GameUpdate () { if (AcquireTarget()) { Debug.Log("Acquired target!"); } }
Dans
AcquireTarget
nous obtenons toutes les cibles disponibles en appelant
Physics.OverlapSphere
avec une position et une plage de tour comme arguments. Le résultat sera un tableau de
Collider
contenant tous les collisionneurs en contact avec la sphère. Si la longueur du tableau est positive, alors il y a au moins un point de visée, et nous sélectionnons simplement le premier. Prenez son composant
TargetPoint
, qui doit toujours exister, affectez-le au champ cible et signalez le succès. Sinon, nous effaçons la cible et signalons l'échec.
bool AcquireTarget () { Collider[] targets = Physics.OverlapSphere( transform.localPosition, targetingRange ); if (targets.Length > 0) { target = targets[0].GetComponent<TargetPoint>(); Debug.Assert(target != null, "Targeted non-enemy!", targets[0]); return true; } target = null; return false; }
Nous sommes assurés d'obtenir les bons points de visée, si nous ne prenons en compte les collisionneurs que sur la couche d'ennemis. Ceci est le calque 9, nous allons donc passer le masque de calque correspondant.
const int enemyLayerMask = 1 << 9; … bool AcquireTarget () { Collider[] targets = Physics.OverlapSphere( transform.localPosition, targetingRange, enemyLayerMask ); … }
Comment fonctionne ce masque de bits?Puisque la couche ennemie a un indice de 9, le dixième bit du masque binaire doit avoir la valeur 1. Cela correspond à un entier 2 9 , soit 512. Mais un tel enregistrement de masque binaire n'est pas intuitif. Nous pouvons également écrire un littéral binaire, par exemple 0b10_0000_0000
, mais nous devons ensuite compter les zéros. Dans ce cas, l'entrée la plus pratique serait d'utiliser l'opérateur de décalage gauche <<
, qui décale les bits vers la gauche. ce qui correspond à un nombre au pouvoir de deux.
Vous pouvez visualiser la cible capturée en traçant une ligne de gizmo entre les positions de la tour et de la cible.
void OnDrawGizmosSelected () { … if (target != null) { Gizmos.DrawLine(position, target.Position); } }
Visualisation des objectifs.Pourquoi ne pas utiliser des méthodes comme OnTriggerEnter?L'avantage de la vérification manuelle des objectifs transversaux est que nous ne pouvons le faire qu'en cas de besoin. Il n'y a aucune raison de vérifier les cibles si la tour en a déjà une. De plus, en obtenant tous les objectifs potentiels à la fois, nous n'avons pas à traiter une liste d'objectifs potentiels pour chaque tour, qui est en constante évolution.
Verrouillage de cible
La cible choisie pour la capture dépend de l'ordre dans lequel ils sont représentés par le moteur physique, c'est-à -dire en fait arbitraire. Par conséquent, il apparaîtra que la cible capturée change sans raison. Une fois que la tour a reçu la cible, il est plus logique pour elle de la suivre et de ne pas passer à une autre. Ajoutez une méthode
TrackTarget
qui implémente un tel suivi et renvoie des informations pour savoir si elle a réussi. Tout d'abord, nous vous informerons simplement si la cible est capturée.
bool TrackTarget () { if (target == null) { return false; } return true; }
Nous appellerons cette méthode dans
GameUpdate
et ce n'est qu'en renvoyant false que nous appellerons
AcquireTarget
. Si la méthode est retournée vraie, alors nous avons un objectif. Cela peut être fait en plaçant les deux appels de méthode dans une vérification
if
avec l'opérateur OR, car si le premier opérande renvoie
true
, le second ne sera pas vérifié et l'appel sera manqué. L'opérateur AND agit de manière similaire.
public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) { Debug.Log("Locked on target!"); } }
Suivi des objectifs.En conséquence, les tours sont fixées sur la cible jusqu'à ce qu'elle atteigne le point final et soit détruite. Si vous utilisez des ennemis à plusieurs reprises, vous devez plutôt vérifier l'exactitude du lien, comme cela se fait avec les liens vers les figures traitées dans une série de didacticiels de
gestion des
objets .
Pour suivre les cibles uniquement lorsqu'elles sont à portée,
TrackTarget
doit suivre la distance entre la tour et la cible. S'il dépasse la valeur de la plage, la cible doit être réinitialisée et renvoyer false. Vous pouvez utiliser la méthode
Vector3.Distance
pour cette vérification.
bool TrackTarget () { if (target == null) { return false; } Vector3 a = transform.localPosition; Vector3 b = target.Position; if (Vector3.Distance(a, b) > targetingRange) { target = null; return false; } return true; }
Cependant, ce code ne prend pas en compte le rayon du collisionneur. Par conséquent, en conséquence, la tour peut perdre la cible, puis la capturer à nouveau, seulement pour arrêter de la suivre dans l'image suivante, etc. Nous pouvons éviter cela en ajoutant un rayon de collisionneur à la plage.
if (Vector3.Distance(a, b) > targetingRange + 0.125f) { … }
Cela nous donne les résultats corrects, mais seulement si l'échelle de l'ennemi n'est pas modifiée. Puisque nous donnons à chaque ennemi une échelle aléatoire, nous devons en tenir compte lors du changement de portée. Pour ce faire, nous devons nous souvenir de l'échelle donnée par
Enemy
et l'ouvrir à l'aide de la propriété getter.
public float Scale { get; private set; } … public void Initialize (float scale, float speed, float pathOffset) { Scale = scale; … }
Nous pouvons maintenant vérifier la plage correcte dans
Tower.TrackTarget
.
if (Vector3.Distance(a, b) > targetingRange + 0.125f * target.Enemy€.Scale) { … }
Nous synchronisons la physique
Tout semble bien fonctionner, mais les tours qui peuvent viser au centre du champ sont capables de capturer des cibles qui devraient être hors de portée. Ils ne pourront pas suivre ces objectifs, ils ne sont donc fixés sur eux que pour une seule image.
Visée incorrecte.Cela se produit car l'état du moteur physique est imparfaitement synchronisé avec l'état du jeu. Des instances de tous les ennemis sont créées à l'origine du monde, qui coïncide avec le centre du champ. Ensuite, nous les déplaçons au point de création, mais le moteur physique ne le sait pas tout de suite.
Vous pouvez activer la synchronisation instantanée qui se produit lorsque vous modifiez les transformations d'objets en définissant
Physics.autoSyncTransforms
sur
true
. Mais par défaut, il est désactivé, car il est beaucoup plus efficace de tout synchroniser ensemble et si nécessaire. Dans notre cas, la synchronisation n'est requise que lors de la mise à jour de l'état des tours. Nous pouvons l'exécuter en appelant
Physics.SyncTransforms
entre les mises Ă jour ennemies et sur le terrain dans
Game.Update
.
void Update () { … enemies.GameUpdate(); Physics.SyncTransforms(); board.GameUpdate(); }
Ignorer la hauteur
En fait, notre gameplay se déroule en 2D. Par conséquent, changeons la
Tower
sorte que lors de la visée et du suivi, elle ne prenne en compte que les coordonnées X et Z. Le moteur physique fonctionne dans l'espace 3D, mais en substance, nous pouvons effectuer
AcquireTarget
en 2D: étirez la sphère vers le haut afin qu'elle couvre tous les collisionneurs, quel que soit de leur position verticale. Cela peut être fait en utilisant une capsule au lieu d'une sphère, dont le deuxième point sera à plusieurs unités au-dessus du sol (par exemple, trois).
bool AcquireTarget () { Vector3 a = transform.localPosition; Vector3 b = a; by += 3f; Collider[] targets = Physics.OverlapCapsule( a, b, targetingRange, enemyLayerMask ); … }
2D-?, XZ, 2D- XY. , , 2D- . 3D-.
TrackTarget
. , 2D-
Vector2.Distance
, , . .
bool TrackTarget () { if (target == null) { return false; } Vector3 a = transform.localPosition; Vector3 b = target.Position; float x = ax - bx; float z = az - bz; float r = targetingRange + 0.125f * target.Enemy€.Scale; if (x * x + z * z > r * r) { target = null; return false; } return true; }
Physics.OverlapCapsule
, . ,
OverlapCapsuleNonAlloc
. . . , 1.
OverlapCapsuleNonAlloc
, , .
static Collider[] targetsBuffer = new Collider[1]; … bool AcquireTarget () { Vector3 a = transform.localPosition; Vector3 b = a; by += 2f; int hits = Physics.OverlapCapsuleNonAlloc( a, b, targetingRange, targetsBuffer, enemyLayerMask ); if (hits > 0) { target = targetsBuffer[0].GetComponent<TargetPoint>(); Debug.Assert(target != null, "Targeted non-enemy!", targetsBuffer[0]); return true; } target = null; return false; }
, , . , .
Pour diriger la tourelle vers la cible, la classe Tower
doit avoir un lien vers le composant Transform
tourelle. Ajoutez un champ de configuration pour cela et connectez-le au préfabriqué de la tour. [SerializeField] Transform turret = default;
La tourelle attachée.S'il GameUpdate
y a une vraie cible, alors nous devons la tirer. Mettez le code de prise de vue dans une méthode distincte. Faites-lui tourner la tourelle vers la cible, en appelant sa méthode Transform.LookAt
avec le point de visée comme argument. public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) {
Visant juste.Nous tirons un laser
Pour positionner le faisceau laser, la classe a Tower
également besoin d'un lien vers celui-ci. [SerializeField] Transform turret = default, laserBeam = default;
Nous avons connecté un faisceau laser.Pour transformer un cube en un véritable faisceau laser, vous devez suivre trois étapes. Tout d'abord, son orientation doit correspondre à l'orientation de la tourelle. Cela peut être fait en copiant sa rotation. void Shoot () { Vector3 point = target.Position; turret.LookAt(point); laserBeam.localRotation = turret.localRotation; }
Deuxièmement, nous mettons à l'échelle le faisceau laser de sorte que sa longueur soit égale à la distance entre le point d'origine local de la tourelle et le point de visée. Nous le mettons à l'échelle le long de l'axe Z, c'est-à -dire l'axe local dirigé vers la cible. Pour conserver l'échelle XY d'origine, nous notons l'échelle d'origine lorsque nous réveillons la tourelle Awake. Vector3 laserBeamScale; void Awake () { laserBeamScale = laserBeam.localScale; } … void Shoot () { Vector3 point = target.Position; turret.LookAt(point); laserBeam.localRotation = turret.localRotation; float d = Vector3.Distance(turret.position, point); laserBeamScale.z = d; laserBeam.localScale = laserBeamScale; }
Troisièmement, nous plaçons le faisceau laser au milieu entre la tourelle et le point de visée. laserBeam.localScale = laserBeamScale; laserBeam.localPosition = turret.localPosition + 0.5f * d * laserBeam.forward;
Prise de vue au laser.N'est-il pas possible de faire d'un rayon laser un enfant d'une tourelle?, , forward. , . .
Cela fonctionne pendant que la tourelle est fixée sur la cible. Mais quand il n'y a pas de cible, le laser reste actif. Nous pouvons désactiver l'affichage laser en GameUpdate
définissant son échelle à 0. public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) { Shoot(); } else { laserBeam.localScale = Vector3.zero; } }
Les tours inactives ne tirent pas.Santé ennemie
. , . ,
Enemy
. , 100. , , .
float Health { get; set; } … public void Initialize (float scale, float speed, float pathOffset) { … Health = 100f * scale; }
,
ApplyDamage
, . , , .
public void ApplyDamage (float damage) { Debug.Assert(damage >= 0f, "Negative damage applied."); Health -= damage; }
, .
GameUpdate
.
public bool GameUpdate () { if (Health <= 0f) { OriginFactory.Reclaim(this); return false; } … }
, , , , .
, .
Tower
. , (damage per second).
Shoot
Enemy
.
[SerializeField, Range(1f, 100f)] float damagePerSecond = 10f; … void Shoot () { … target.Enemy.ApplyDamage(damagePerSecond * Time.deltaTime); }
— 20 ., , . , , , . , .
, , . , , 100. , , .
static Collider[] targetsBuffer = new Collider[100];
Maintenant, au lieu de choisir la première cible potentielle, nous allons sélectionner un élément aléatoire dans le tableau. bool AcquireTarget () { … if (hits > 0) { target = targetsBuffer[Random.Range(0, hits)].GetComponent<TargetPoint>(); … } target = null; return false; }
Visée aléatoire.D'autres critères de choix des objectifs peuvent-ils être utilisés?, , . , , . . .
Ainsi, dans notre jeu de tower defense, des tours sont enfin apparues. Dans la prochaine partie, le jeu prendra encore plus sa forme finale.