Après pas mal de modification du jeu, je reviens ici pour vous présenter les différentes nouveautés. Il est demandé dans l’exercice à faire d’implémenter un retour au départ quand le jour se fait toucher par un projectile. Je voulais plutôt pouvoir profiter de la propulsion fait lors de l’explosion d’une sphère électrique pour atteindre de nouveaux Platform alors j’ai ajouté une petite barre de vie et un Timer.
Donc voici comment implémenter la vie et mort dans Unity.
Coté graphique, on va créer un HUD Canvas pour placer nos différents éléments. J’ai ajouté un texte pour afficher le temps et un slider dont j’ai enlevé tous les objets cliquables. A droite du slider, un peu de texte statique qui affiche “Santé”. Tout le reste sera gérer par du code C# :
La gestion du temps.
Pour gérer l’affichage du temps et d’autres éléments globaux, j’ai créé un “Empty GameObject” appelée GameController et j’ai bien fait attention de rajouter un TAG GameController à l’élément (cela facilité le code après).
Et voici le code du TimeController :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public Text timeText; private string timePassed; private bool isRunning = false; private float passedTime = 0f; // Use this for initialization void Start () { timePassed = "00:00"; timeText.text = "Temps : " + timePassed.ToString(); } // Update is called once per frame void Update () { if (isRunning) { passedTime += Time.fixedUnscaledDeltaTime; timePassed = Mathf.Floor(passedTime / 60).ToString("00") + ":" + Mathf.Floor(passedTime % 60).ToString("00"); timeText.text = "Temps : " + timePassed; } } public void startTimer() { isRunning = true; } public void stopTimer() { isRunning = false; } |
La première ligne “public Text timeText;” a pour but d’attacher notre zone de texte de notre HUD. Un simple glisser / déposer de l’élément dur le script et nous pouvons modifier le texte sans erreurs.
Il y a quelques éléments pour calculer le temps et un Bool qui permettra de dire si le chronomètre a démarrer ou pas. L’idée ici est qu’on ne commencera le décompte que lors du passage des portais.
Au Start(), on initialise notre texte pour le mettre à 00:00.
Dans l’Update(), on vérifie si le chronomètre a démarrer grâce au “if (isRunning)”. Si oui, alors on ajoute le temps passé depuis le dernier frame et on calcule les minutes et secondes.
A la fin du fichier, nous avons deux fonctions publics qui permettent à d’autres scripts de démarrer et arrêter le décompte.
Déclenchement du Timer
Pour le départ et l’arrêt du Timer, j’ai placer un petit script sur un déclencheur qui couvre l’intérieur des portails :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public bool isStart = true; private TimeControler timeControler; // Use this for initialization void Start () { timeControler = GameObject.FindGameObjectWithTag("GameController").GetComponent<TimeControler>(); } // Update is called once per frame void Update () { } void OnTriggerEnter(Collider col) { if (col.tag == "Player") { if (isStart) { timeControler.startTimer(); } else { timeControler.stopTimer(); } } } |
On déclare un variable timeControler avec comme type TimeControler, le même que le script qu’on vient de créer.
Dans le Start(), on va chercher le composant avec le tag GameControler (il est important que ce game controller soit unique) pour ensuite s’en servir pour démarrer ou arrêter le chronomètre.
A savoir qu’il est également possible de faire comme pour le champ texte. On aurait très bien pu déclarer une variable publique et faire un glisser / déposer de l’élément GameControler. Je préfère tout gérer dans le script plutôt que de revenir sans arrêt dans unity pour attribuer mes éléments.
La vie et la mort
Et pour finir, nous allons gérer la vie du joueur grâce à un script placé directement sur notre FPS Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerHealth : MonoBehaviour { public int health = 100; private int startHealth; public bool isDead = false; public Slider healthSlider; private TeleportToStart teleportToStart; // Use this for initialization void Start () { healthSlider.maxValue = health; teleportToStart = GameObject.FindGameObjectWithTag("GameController").GetComponent<TeleportToStart>(); startHealth = health; } // Update is called once per frame void Update () { } public void looseHealth(int amountLost) { health -= amountLost; healthSlider.value = health; if (health <= 0) { isDead = true; //needed to block all movement; Might just call a disable from here. StartCoroutine(isDeadActions()); } } private IEnumerator isDeadActions() { gameObject.GetComponent<CharacterController>().enabled = false; //stop movement yield return new WaitForSecondsRealtime(0.4f); teleportToStart.teleportToStart(); yield return new WaitForSecondsRealtime(0.7f); gameObject.GetComponent<CharacterController>().enabled = true; } public void fullHealth() { health = startHealth; healthSlider.value = health; } } |
Dans les différents éléments, nous retrouvons le même principe que le déclencheur pour appeler le téléportation du joueur jusqu’au départ du niveau quand il est mort (TeleportToStart). On récupère également le slider qui va afficher le niveau de vie grâce au glisser / déposer comme pour le champ texte du Timer.
On va également placer la valeur de la vie du départ dans une variable au démarrage du script. Ceci pour pouvoir réinitialiser la quantité de vie lorsque le joueur revive. Il y a également un boolean isDead au cas où on aura besoin de faire réagir certains éléments quand le joueur est mort. Pour le moment ce fonctionnement n’est pas utilisée.
Il y a plusieurs fonctions publiques qui seront appelés par les différents éléments qui causeront des dégâts :
Le plus simple est fullHealth(). Il réinitialise la vie au quantité de départ. Ceci sera appeler par la fonction TeleportToStart :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private Vector3 startPosition = new Vector3(0f, 1.5f, 0f); private GameObject particles; private GameObject player; private PlayerHealth playerHealth; // Use this for initialization void Start () { player = GameObject.FindGameObjectWithTag("Player"); particles = player.transform.Find("RegenParticles").gameObject; playerHealth = player.GetComponent<PlayerHealth>(); } public void teleportToStart() { player.transform.position = startPosition; //we still have movement affected to the player. Will need to sort that out. Probably when the death animation comes. playerHealth.isDead = false; playerHealth.fullHealth(); if (particles != null) { particles.SetActive(true); } } |
Puis il y a notre fonction “perte de vie” (looseHealth) qui prend comme paramètre la quantité de vie perdu.
Chose intéressante, nous lançons un CoRoutine lorsque la vie passe en dessous de zéro. Un CoRoutine est un bout de programme qui s’exécute en parallèle de notre jeu. L’avantage c’est qu’un CoRoutine ne se finit pas à la fin de chaque frame. Il permet donc d’avoir un contrôle plus précise sur le temps. Surtout quand une action doit attendre une certaine quantité de temps. (Ici grâce a yield return new WaitForSecondsRealtime(Float);).
D’ailleurs, cette CoRoutine n’a que pour but de désactiver le mouvement du joueur avant de le téléporter au début du niveau. Je pense que j’ai encore quelques modifications à faire entre le téléportation et le isDead.
EDIT : Petit note de fin. Partout dans les scripts j’ai utilisé “fixedUnscaledDeltaTime” ou “WaitForSecondsRealtime” au lieu de “Time.deltaTime” et “WaitForSeconds” car ailleurs dans le jeu, j’ai implémenté un effet de ralentissement de temps. En utilisant RealTime je ne modifie pas le passage du temps quand le ralentis s’enclenche.