I. Introduction

Dans cette vidéo, nous allons apprendre les techniques couramment utilisées dans un jeu de tir : lancer de rayon, projectile et gestion de la vie.

II. Vidéo


Session en direct : comprendre les mécanismes d'un jeu de tir


III. Résumé

Au cours de la vidéo, vous allez découvrir deux techniques différentes pour la gestion des tirs : le lancer de rayon et le projectile physique. Le premier sera intégré sous la forme d'un laser alors que le second servira à simuler des grenades.

III-A. Démonstration

Le projet met en action un joueur en vue à la première personne, capable, grâce au clic gauche, de tirer. Les tirs impacteront l'ennemi et au bout de trois tirs, il disparaît. À chaque tir, un son est joué. Le laser produit un trait rouge et des particules en rencontrant un obstacle. Avec le clic droit, vous pouvez lancer une grenade.

Les ressources utilisées proviennent du projet d'exemple pour la réalité virtuelle.

III-B. Hiérarchie de la scène

Dans cette scène, il y a un « ShooterWeapon » contenant une source audio pour jouer le son du tir, les scripts RayCast Shoot et Projectile Shoot pour la gestion des armes et un « Line Renderer » pour afficher la ligne du laser.

III-C. Intégration du laser

III-C-1. Lancer un rayon

Généralement, dans un jeu de tir, le tir part du centre de l'écran dans la direction de la caméra. Pour obtenir le centre de l'écran, vous pouvez utiliser la fonction Camera.ViewportToWorldPoint() qui transformera un point dans l'espace de coordonnées de l'écran (ici (0.0, 0.0) et (1.0, 1.0)) en un point dans le monde (coordonnées 3D).

Le lancer de rayon se fait avec la fonction Physics.Raycast(). Celle-ci, en plus de l'origine (le centre de l'écran), a besoin de la direction dans laquelle le rayon part. Pour l'obtenir, il suffit d'utiliser la direction de la caméra (Transform.forward).

Les informations liées à la collision entre le rayon et un objet de la scène sont récupérées dans une variable de type RaycastHit. La structure contient l'emplacement de l'impact, mais aussi l'objet touché.

III-C-2. Coroutines

Les coroutines permettent notamment de réaliser des effets en fonction du temps. Une coroutine est une fonction dont le type de retour est I et qui sera appelée à travers de la fonction StartCoroutine(). Le mot- clé « yield » permet de mettre en pause la coroutine afin de laisser le reste du programme s'exécuter.

III-C-3. RayCastShootScript

 
Sélectionnez
using UnityEngine;
using System.Collections;

public class RayCastShootScript : MonoBehaviour {

    public float fireRate = .25f;
    public float range = 50;
    public ParticleSystem smokeParticles;
    public GameObject hitParticles;
    public GameObject shootFlare;
    public int damage = 1;
    public Transform gunEnd;

    private Camera fpsCam;
    private LineRenderer lineRenderer;
    private WaitForSeconds shotLength = new WaitForSeconds(.07f);
    private AudioSource source;
    private float nextFireTime;

    void Awake()
    {
        lineRenderer = GetComponent<LineRenderer> ();
        source = GetComponent<AudioSource> ();
        fpsCam = GetComponentInParent<Camera> ();
    }


    // Update is called once per frame
    void Update () 
    {
        RaycastHit hit;
        Vector3 rayOrigin = fpsCam.ViewportToWorldPoint (new Vector3 (.5f, .5f, 0));

        if (Input.GetButtonDown("Fire1") && Time.time > nextFireTime)
        {
            nextFireTime = Time.time + fireRate;

            if (Physics.Raycast(rayOrigin, fpsCam.transform.forward, out hit, range))
            {
                IDamageable dmgScript = hit.collider.gameObject.GetComponent<IDamageable>();
                if (dmgScript != null)
                {
                    dmgScript.Damage(damage, hit.point);
                }

                if(hit.rigidbody != null)
                {
                    hit.rigidbody.AddForce(-hit.normal * 100f);
                }

                lineRenderer.SetPosition(0, gunEnd.position);
                lineRenderer.SetPosition(1, hit.point);
                Instantiate(hitParticles, hit.point, Quaternion.identity);
            }
            StartCoroutine (ShotEffect ());
        }

    }


    private IEnumerator ShotEffect()
    {
        lineRenderer.enabled = true;
        source.Play ();
        smokeParticles.Play ();
        shootFlare.SetActive (true);
        yield return shotLength;
        lineRenderer.enabled = false;
        shootFlare.SetActive (false);
    }
}

III-D. Intégration des grenades

III-D-1. Lancer un projectile

Pour lancer un projectile, il suffit d'instancier un RigidBody et de lui ajouter une force.

Pour ces projectiles, il n'y a pas besoin d'utiliser la fonction FixedUpdate() car ici, le projectile reçoit une unique force initiale.

L'effet d'explosion est réalisé grâce à la détection de collision du moteur. Il suffit donc d'implémenter la fonction OnCollisionEnter().

III-D-2. ProjectileShootScript

 
Sélectionnez
using UnityEngine;
using System.Collections;

public class ProjectileShootScript : MonoBehaviour {

    public Rigidbody projectile;
    public Transform bulletSpawn;
    public float projectileForce = 500f;
    public float fireRate = .25f;

    private float nextFireTime;

    
    // Update is called once per frame
    void Update () 
    {
        if (Input.GetButtonDown ("Fire2") && Time.time > nextFireTime) 
        {
            Rigidbody cloneRb = Instantiate (projectile, bulletSpawn.position, Quaternion.identity) as Rigidbody;
            cloneRb.AddForce(bulletSpawn.transform.forward * projectileForce);
            nextFireTime = Time.time + fireRate;
        }
    }
}

III-D-3. Explosion des projectiles

L'explosion est gérée par un second script :

 
Sélectionnez
using UnityEngine;
using System.Collections;

public class Explode : MonoBehaviour {

    public GameObject explosionParticles;
    public float blastRadius = 1;
    public int damage = 1;

    private bool explode;

    void OnCollisionEnter()
    {

        explosionParticles.SetActive (true);
        explosionParticles.transform.SetParent (null);
        explode = true;


    }

    void FixedUpdate()
    {
        if (explode) {
            Collider[] hitColliders = Physics.OverlapSphere (transform.position, blastRadius);
            for (int i = 0; i < hitColliders.Length; i++) 
            {
                if (hitColliders [i].GetComponent<IDamageable> () != null) 
                {
                    hitColliders [i].GetComponent<IDamageable> ().Damage (damage, hitColliders[i].transform.position);
                }

                if (hitColliders [i].GetComponent<Rigidbody> () != null)
                {
                    hitColliders [i].GetComponent<Rigidbody> ().AddExplosionForce (6000, transform.position, blastRadius);
                }
            }
            this.gameObject.SetActive (false);
        } 
    }
    
}

Pour l'effet de choc de l'explosion, une sphère est utilisée afin de connaître les objets dans le rayon d'impact. Chaque objet se verra attribuer une nouvelle force pour le projeter.

III-D-3-a. Idamageable
 
Sélectionnez
using UnityEngine;

public interface IDamageable 
{
    void Damage(int damage, Vector3 hitPoint);
}

Ce script permet de créer une interface qui sera implémentée par tous les objets qui ont de la santé et qui pourront donc en perdre lorsqu'ils se font toucher.

III-E. Gestion de la santé

III-E-1. EnemyHealth

Finalement, pour gérer la santé, il suffit de faire un script implémentant IDamageable.

 
Sélectionnez
using UnityEngine;
using System.Collections;

public class EnemyHealth : MonoBehaviour, IDamageable {

    public int startingHealth = 3;
    public GameObject hitParticles;

    private int currentHealth;

    void Start()
    {
        currentHealth = startingHealth;
    }

    public void Damage(int damage, Vector3 hitPoint)
    {
        Instantiate(hitParticles, hitPoint, Quaternion.identity);
        currentHealth -= damage;
        if (currentHealth <= 0) 
        {
            Defeated();
        }
    }

    void Defeated()
    {
        gameObject.SetActive (false);
    }
    
}

IV. Commenter

Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.