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▲
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▲
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 :
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▲
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.
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.