Tutoriel Endless Runner pour Unity
Dans les jeux vidéo, quelle que soit la taille du monde, il a toujours une fin. Mais certains jeux tentent d'imiter le monde infini, de tels jeux entrent dans la catégorie appelée Endless Runner.
Endless Runner est un type de jeu dans lequel le joueur avance constamment tout en collectant des points et en évitant les obstacles. L'objectif principal est d'atteindre la fin du niveau sans tomber ou entrer en collision avec les obstacles, mais souvent, le niveau se répète à l'infini, augmentant progressivement la difficulté, jusqu'à ce que le joueur entre en collision avec l'obstacle.
Considérant que même les ordinateurs et appareils de jeu modernes ont une puissance de traitement limitée, il est impossible de créer un monde véritablement infini.
Alors, comment certains jeux créent-ils l'illusion d'un monde infini ? La réponse est en réutilisant les blocs de construction (c'est-à-dire en regroupant les objets), autrement dit, dès que le bloc passe derrière ou en dehors de la vue de la caméra, il est déplacé vers l'avant.
Pour créer un jeu de course sans fin en Unity, nous devrons créer une plateforme avec des obstacles et un contrôleur de joueur.
Étape 1: Créer la plateforme
Nous commençons par créer une plate-forme carrelée qui sera ensuite stockée dans le Prefab:
- Créez un nouveau GameObject et appelez-le "TilePrefab"
- Créer un nouveau cube (GameObject -> Objet 3D -> Cube)
- Déplacez le cube à l'intérieur de l'objet "TilePrefab", changez sa position à (0, 0, 0) et mettez-le à l'échelle à (8, 0,4, 20)
- En option, vous pouvez ajouter des rails sur les côtés en créant des cubes supplémentaires, comme ceci:
Pour les obstacles, j'aurai 3 variantes d'obstacles, mais vous pouvez en faire autant que nécessaire:
- Créez 3 GameObjects à l'intérieur de l'objet "TilePrefab" et nommez-les "Obstacle1", "Obstacle2" et "Obstacle3"
- Pour le premier obstacle, créez un nouveau cube et déplacez-le à l'intérieur de l'objet "Obstacle1"
- Ajustez le nouveau cube à environ la même largeur que la plate-forme et réduisez sa hauteur (le joueur devra sauter pour éviter cet obstacle)
- Créez un nouveau matériau, nommez-le "RedMaterial" et changez sa couleur en rouge, puis attribuez-le au cube (c'est juste pour que l'obstacle soit distingué de la plate-forme principale)
- Pour le "Obstacle2" créez quelques cubes et placez-les en forme triangulaire, en laissant un espace ouvert en bas (le joueur devra s'accroupir pour éviter cet obstacle)
- Et enfin, "Obstacle3" sera un doublon de "Obstacle1" et "Obstacle2", combinés ensemble
- Sélectionnez maintenant tous les objets à l'intérieur des obstacles et changez leur balise en "Finish", cela sera nécessaire plus tard pour détecter la collision entre le joueur et l'obstacle.
Pour générer une plate-forme infinie, nous aurons besoin de quelques scripts qui géreront le regroupement d'objets et l'activation des obstacles:
- Créez un nouveau script, appelez-le "SC_PlatformTile" et collez le code ci-dessous à l'intérieur:
SC_PlatformTile.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SC_PlatformTile : MonoBehaviour
{
public Transform startPoint;
public Transform endPoint;
public GameObject[] obstacles; //Objects that contains different obstacle types which will be randomly activated
public void ActivateRandomObstacle()
{
DeactivateAllObstacles();
System.Random random = new System.Random();
int randomNumber = random.Next(0, obstacles.Length);
obstacles[randomNumber].SetActive(true);
}
public void DeactivateAllObstacles()
{
for (int i = 0; i < obstacles.Length; i++)
{
obstacles[i].SetActive(false);
}
}
}
- Créez un nouveau script, appelez-le "SC_GroundGenerator" et collez le code ci-dessous à l'intérieur:
SC_GroundGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SC_GroundGenerator : MonoBehaviour
{
public Camera mainCamera;
public Transform startPoint; //Point from where ground tiles will start
public SC_PlatformTile tilePrefab;
public float movingSpeed = 12;
public int tilesToPreSpawn = 15; //How many tiles should be pre-spawned
public int tilesWithoutObstacles = 3; //How many tiles at the beginning should not have obstacles, good for warm-up
List<SC_PlatformTile> spawnedTiles = new List<SC_PlatformTile>();
int nextTileToActivate = -1;
[HideInInspector]
public bool gameOver = false;
static bool gameStarted = false;
float score = 0;
public static SC_GroundGenerator instance;
// Start is called before the first frame update
void Start()
{
instance = this;
Vector3 spawnPosition = startPoint.position;
int tilesWithNoObstaclesTmp = tilesWithoutObstacles;
for (int i = 0; i < tilesToPreSpawn; i++)
{
spawnPosition -= tilePrefab.startPoint.localPosition;
SC_PlatformTile spawnedTile = Instantiate(tilePrefab, spawnPosition, Quaternion.identity) as SC_PlatformTile;
if(tilesWithNoObstaclesTmp > 0)
{
spawnedTile.DeactivateAllObstacles();
tilesWithNoObstaclesTmp--;
}
else
{
spawnedTile.ActivateRandomObstacle();
}
spawnPosition = spawnedTile.endPoint.position;
spawnedTile.transform.SetParent(transform);
spawnedTiles.Add(spawnedTile);
}
}
// Update is called once per frame
void Update()
{
// Move the object upward in world space x unit/second.
//Increase speed the higher score we get
if (!gameOver && gameStarted)
{
transform.Translate(-spawnedTiles[0].transform.forward * Time.deltaTime * (movingSpeed + (score/500)), Space.World);
score += Time.deltaTime * movingSpeed;
}
if (mainCamera.WorldToViewportPoint(spawnedTiles[0].endPoint.position).z < 0)
{
//Move the tile to the front if it's behind the Camera
SC_PlatformTile tileTmp = spawnedTiles[0];
spawnedTiles.RemoveAt(0);
tileTmp.transform.position = spawnedTiles[spawnedTiles.Count - 1].endPoint.position - tileTmp.startPoint.localPosition;
tileTmp.ActivateRandomObstacle();
spawnedTiles.Add(tileTmp);
}
if (gameOver || !gameStarted)
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (gameOver)
{
//Restart current scene
Scene scene = SceneManager.GetActiveScene();
SceneManager.LoadScene(scene.name);
}
else
{
//Start the game
gameStarted = true;
}
}
}
}
void OnGUI()
{
if (gameOver)
{
GUI.color = Color.red;
GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 200), "Game Over\nYour score is: " + ((int)score) + "\nPress 'Space' to restart");
}
else
{
if (!gameStarted)
{
GUI.color = Color.red;
GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 200), "Press 'Space' to start");
}
}
GUI.color = Color.green;
GUI.Label(new Rect(5, 5, 200, 25), "Score: " + ((int)score));
}
}
- Attachez le script SC_PlatformTile à l'objet "TilePrefab"
- Affecter les objets "Obstacle1", "Obstacle2" et "Obstacle3" au tableau Obstacles
Pour le point de départ et le point d'arrivée, nous devons créer 2 GameObjects qui doivent être placés respectivement au début et à la fin de la plateforme:
- Affecter des variables de point de départ et de point de fin dans SC_PlatformTile
- Enregistrez l'objet "TilePrefab" dans Prefab et supprimez-le de la scène
- Créez un nouveau GameObject et appelez-le "_GroundGenerator"
- Attachez le script SC_GroundGenerator à l'objet "_GroundGenerator"
- Modifiez la position de la caméra principale sur (10, 1, -9) et modifiez sa rotation sur (0, -55, 0)
- Créez un nouveau GameObject, appelez-le "StartPoint" et changez sa position en (0, -2, -15)
- Sélectionnez l'objet "_GroundGenerator" et dans SC_GroundGenerator, attribuez les variables Caméra principale, Point de départ et Préfabrication de tuiles
Appuyez maintenant sur Play et observez comment la plateforme se déplace. Dès que la tuile de la plateforme sort du champ de vision de la caméra, elle revient à la fin avec un obstacle aléatoire activé, créant une illusion de niveau infini (passez à 0:11).
La caméra doit être placée de manière similaire à la vidéo, de sorte que les plates-formes aillent vers la caméra et derrière elle, sinon les plates-formes ne se répéteront pas.
Étape 2: Créer le lecteur
L'instance du joueur sera une simple sphère utilisant un contrôleur avec la possibilité de sauter et de s'accroupir.
- Créez une nouvelle sphère (GameObject -> Objet 3D -> Sphère) et supprimez son composant Sphere Collider
- Attribuez-lui le "RedMaterial" précédemment créé
- Créez un nouveau GameObject et appelez-le "Player"
- Déplacez la sphère à l'intérieur de l'objet "Player" et changez sa position en (0, 0, 0)
- Créez un nouveau script, appelez-le "SC_IRPlayer" et collez le code ci-dessous à l'intérieur:
SC_IRPlayer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class SC_IRPlayer : MonoBehaviour
{
public float gravity = 20.0f;
public float jumpHeight = 2.5f;
Rigidbody r;
bool grounded = false;
Vector3 defaultScale;
bool crouch = false;
// Start is called before the first frame update
void Start()
{
r = GetComponent<Rigidbody>();
r.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ;
r.freezeRotation = true;
r.useGravity = false;
defaultScale = transform.localScale;
}
void Update()
{
// Jump
if (Input.GetKeyDown(KeyCode.W) && grounded)
{
r.velocity = new Vector3(r.velocity.x, CalculateJumpVerticalSpeed(), r.velocity.z);
}
//Crouch
crouch = Input.GetKey(KeyCode.S);
if (crouch)
{
transform.localScale = Vector3.Lerp(transform.localScale, new Vector3(defaultScale.x, defaultScale.y * 0.4f, defaultScale.z), Time.deltaTime * 7);
}
else
{
transform.localScale = Vector3.Lerp(transform.localScale, defaultScale, Time.deltaTime * 7);
}
}
// Update is called once per frame
void FixedUpdate()
{
// We apply gravity manually for more tuning control
r.AddForce(new Vector3(0, -gravity * r.mass, 0));
grounded = false;
}
void OnCollisionStay()
{
grounded = true;
}
float CalculateJumpVerticalSpeed()
{
// From the jump height and gravity we deduce the upwards speed
// for the character to reach at the apex.
return Mathf.Sqrt(2 * jumpHeight * gravity);
}
void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.tag == "Finish")
{
//print("GameOver!");
SC_GroundGenerator.instance.gameOver = true;
}
}
}
- Attachez le script SC_IRPlayer à l'objet "Player" (vous remarquerez qu'il a ajouté un autre composant appelé Rigidbody)
- Ajoutez le composant BoxCollider à l'objet "Player"
- Placez l'objet "Player" légèrement au-dessus de l'objet "StartPoint", juste devant la caméra
Appuyez sur Play et utilisez la touche W pour sauter et la touche S pour vous accroupir. L'objectif est d'éviter les obstacles rouges:
Vérifiez ce Horizon Bending Shader.