Fulmina

Ce projet de conception de jeu à été réalisé en équipe de 4 contenant 2 artistes et 2 programmeurs.
Le but du jeu est de réussir à transformer l’île sur laquelle vous tombez à partir du ciel. Plus vous gelez l’île, plus la lave recouvrant celle-ci descend pour vous permettre d’avancer. Certains monstres vous barrent la route, vous devez leur lancer des boules de neige provenant de la neige que vous laisser derrière vous pour les combattre.
Lors de ce travail d’équipe, mes tâches étaient de :
- Concevoir la mécanique de génération d’une carte conçue à partir de blocs de façon totalement aléatoire en utilisant du « perlin noise ».
- Déterminer les états des blocs en fonction de l’avancement de la partie (Style monde de feu de façon normale, changer en glace lorsque le personnage passe près de celui-ci, etc.).
- Concevoir l’animation de changement entre l’état de glace et l’état de neige.
- Concevoir une carte dans l’interface de l’utilisateur représentant l’avancement de la partie.
- Configurer certains ennemis pour qu’ils poursuivent le personnage.
- Gérer le niveau de la lave lorsque le joueur gèle assez de blocs pour passer à la prochaine étape.
Exemples de code conçus lors de ce projet
Fonction pour afficher l’état de la carte en « mini-map ».
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Image))]
public class MiniMap : MonoBehaviour
{
// Camera des icones de la minimap, vision modifiee en fonction de la grandeur de l'ile
[SerializeField] private Camera cameraIconesMiniMap;
// Gameobject de la lave, pour determiner sa position
private GameObject laveManager;
public GameObject LaveManager { get => laveManager; set => laveManager = value; }
// composant Image de l'element ui affichant la minimap (socket texture utilise pour obtenir la texture sur laquelle afficher)
private Image imageUI;
// tableau des gamobject des cases (pour obtenir infos de plus qu'etat ex: position)
private GameObject[,] casesMap;
// etat manager du cube, pour determiner son etat precedent
private BiomesEtatsManager[,] cubesEtatsManager;
public void StartGenererMap(GameObject[,] casesMapGenerateurIle){
// obtient la ref des case a partir du generateur de l'ile
casesMap = casesMapGenerateurIle;
// obtient la composant image de l'element UI affichant la minimap
imageUI = GetComponent<Image>();
// obtient un tableau des etat manager de toutes les cases de la map
GetBiomesEtatsManagers();
// start la coroutine d'affichage de la minimap
StartCoroutine(CoroutineDessinerMap());
}
/// <summary>
/// Donne la bonne grandeur a la camera des icones en fonction de la grandeur de l'ile
/// </summary>
/// <param name="grandeurIle">Grandeur de l'ile</param>
public void InitCameraMiniMap(int grandeurIle){
cameraIconesMiniMap.orthographicSize = grandeurIle/2;
}
/// <summary>
/// obtient un tableau des composantes de BiomesEtatsManager de chaque cube pour eviter GetComponent en runtime
/// </summary>
private void GetBiomesEtatsManagers(){
int largeur = casesMap.GetLength(0); // obtient la largeur de l'ile en fonction de la quantite d'elements en x
int profondeur = casesMap.GetLength(1); // obtient la profondeur de l'ile en fonction de la quantite d'elements en z
// tableau de la grandeur de l'ile pour enregistrer les valeurs
cubesEtatsManager = new BiomesEtatsManager[largeur,profondeur];
for (int x = 0; x < largeur; x++)
{
// passe toutes les rangees
for (int z = 0; z < profondeur; z++)
{
// si le gameobject est null, la case est vide, different de null, obtient la composante
if(casesMap[z,x] != null)
{
cubesEtatsManager[z,x] = casesMap[z,x].GetComponent<BiomesEtatsManager>();
}
else
{
cubesEtatsManager[z,x] = null;
}
}
}
}
// Boucle d'affichage de la map apres un delais fixe
private IEnumerator CoroutineDessinerMap(){
while(true){
DessinerMap();
yield return new WaitForSeconds(0.2f);
}
}
/// <summary>
/// Dessine la minimap dans une texture 2d et l'applique sur la materiel de l'element ui
/// </summary>
/// <param name="map">Map de l'ile</param>
private void DessinerMap()
{
int largeur = cubesEtatsManager.GetLength(0); // obtient la largeur de l'ile en fonction de la quantite d'elements en x dans la map en parametre
int profondeur = cubesEtatsManager.GetLength(1); // obtient la profondeur de l'ile en fonction de la quantite d'elements en z dans la map en parametre
// Cree un texture 2D ainsi qu array de couleur de la taille de la map
Texture2D pngTexture = new Texture2D(largeur,profondeur);
// Donne un aspect pixelise a la map
pngTexture.filterMode = FilterMode.Point;
// Tableau 1d des couleurs de la minimap
Color[] couleursTexture = new Color[largeur*profondeur];
// passe toute les collones
for (int x = 0; x < largeur; x++)
{
// passe toutes les rangees
for (int z = 0; z < profondeur; z++)
{
// en cas ou la case est null, couleur trensparente
// sinon enregistre la couleur du pixel temporairement
Color couleur = new Color(0,0,0,0);
// si la reference au cube n'est pas nulle, que sa position est plus eleve que la hauteur de la lave et que
// la lave n'est pas a la position 0 en y (position de base lors de l'instantiation, empeche de voir la map avant qu'elle soit revelee par le passage du joueur)
if(cubesEtatsManager[z,x] != null && casesMap[z,x].transform.position.y > laveManager.transform.position.y && laveManager.transform.position.y != 0){
// obtient l'etat present de la case
BiomesEtatsManager etatCase = cubesEtatsManager[z,x];
// Couleur en fonction de l'etat present de la case
if(etatCase.etatActuel == etatCase.initial){
// couleur brune
couleur = new Color(0.6f,0.3f,0);
}
else if(etatCase.etatActuel == etatCase.glaceNeige || etatCase.etatActuel == etatCase.glaceEternelle){
// couleur bleu pale
couleur = new Color(0.5f,0.8f,1);
}
else if(etatCase.etatActuel == etatCase.neige){
// couleur blanche
couleur = Color.white;
}
else if(etatCase.etatActuel == etatCase.magma){
// couleur jaune
couleur = Color.yellow;
}
else if(etatCase.etatActuel == etatCase.neigeEternel){
// couleur jaune
couleur = Color.white;
}
}
// parcoure les valeurs d'un tableau 2d (passe les valeurs unes a unes) et le enregistre dans un tableau 1d
couleursTexture[x*largeur+z] = couleur;
}
}
// Dessine ma texture et l'applique
pngTexture.SetPixels(couleursTexture);
// Applique les couleurs sur la texture 2d
pngTexture.Apply();
// Obtient la texture d'affichage et modifie l'image de sa texture pour celle de la minimap, ce qui l'affiche dans le UI
// Reference a une texture obligatoire dans la composant image
imageUI.material.mainTexture = pngTexture;
}
}
Quelques scripts pour créer la machine d’état des blocs.
using UnityEngine;
public abstract class BiomesEtatsBase
{
public abstract void InitEtat(BiomesEtatsManager biome);
public abstract void TriggerEnterEtat(BiomesEtatsManager biome, Collider other);
public abstract void OnTriggerExitEtat(BiomesEtatsManager biome, Collider other);
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BiomesEtatsManager : MonoBehaviour
{
public BiomesEtatsBase etatActuel;
public BiomesEtatsBase etatPrecedent;
// états possibles (filles)
public BiomesEtatInitial initial = new BiomesEtatInitial();
public BiomesEtatGlaceEternelle glaceEternelle = new BiomesEtatGlaceEternelle();
public BiomesEtatGlaceNeige glaceNeige = new BiomesEtatGlaceNeige();
public BiomesEtatNeige neige = new BiomesEtatNeige();
public BiomesEtatMagma magma = new BiomesEtatMagma();
public BiomesEtatNeigeEternel neigeEternel = new BiomesEtatNeigeEternel();
public Dictionary<string,Material> materiels = new Dictionary<string, Material>();
public Dictionary<string,GameObject> meshes = new Dictionary<string, GameObject>();
public GameObject[] goObjetsFeu;
public GameObject[] goObjetsGlace;
public GameObject goSurCase = null;
public bool estEnFeu = true;
public int indexGoObjet;
public GameObject particulesSlam{get; set;}
public GameObject particulesRecolteNeige {get; set;}
public GameObject slimeFeu{get; set;}
public GameObject perso{get; set;}
void Start(){
// État initial obtenu durant la generation du monde
// ChangerEtat(initial);
}
public void ChangerEtat(BiomesEtatsBase etat) {
etatPrecedent = etatActuel;
etatActuel = etat;
etatActuel.InitEtat(this);
}
/// <summary>
/// OnTriggerEnter is called when the Collider other enters the trigger.
/// </summary>
/// <param name="other">The other Collider involved in this collision.</param>
private void OnTriggerEnter(Collider other)
{
if(other.tag == "NeigeEternelle"){
ChangerEtat(neigeEternel);
}
else if(other.tag == "Magma" && etatActuel != neigeEternel){
ChangerEtat(magma);
}
etatActuel.TriggerEnterEtat(this,other);
}
/// <summary>
/// OnTriggerEnter is called when the Collider other exits the trigger.
/// </summary>
/// <param name="other">The other Collider involved in this collision.</param>
private void OnTriggerExit(Collider other)
{
etatActuel.OnTriggerExitEtat(this,other);
}
public GameObject InstantiateObjet(GameObject objet, Vector3 position){
GameObject go = Instantiate(objet, transform.position + position, Quaternion.identity);
StartCoroutine(ObjetPousse(go));
return(go);
}
public void DestroyGameobject(GameObject go){
Destroy(go);
}
public void ToggleObjetGlaceFeu(){
if(goSurCase != null){
Destroy(goSurCase);
if(estEnFeu){
goSurCase = Instantiate(goObjetsGlace[indexGoObjet], transform.position + new Vector3(0,0.7f,0), Quaternion.identity);
}
else
{
goSurCase = Instantiate(goObjetsFeu[indexGoObjet], transform.position + new Vector3(0,0.7f,0), Quaternion.identity);
}
}
}
private IEnumerator ObjetPousse(GameObject objet){
objet.transform.localScale = Vector3.one * 0.01f;
while(objet != null && objet.transform.localScale.x < 1){
objet.transform.localScale += Vector3.one * 0.6f * Time.deltaTime;
yield return new WaitForEndOfFrame();
}
yield return null;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BiomesEtatNeige : BiomesEtatsBase
{
public override void InitEtat(BiomesEtatsManager biome){
biome.GetComponent<Renderer>().material = biome.materiels["neige"];
}
public override void TriggerEnterEtat(BiomesEtatsManager biome, Collider other){
if(other.tag == "Player" || other.tag == "Allier")
{
GameManager.instance.UpdateNeigeJoueur(0.04f);
ParticleSystemForceField forceField = biome.perso.GetComponent<ParticleSystemForceField>();
GameObject particulesNeige = biome.InstantiateObjet(biome.particulesRecolteNeige, Vector3.zero);
ParticleSystem.ExternalForcesModule externalForcesModule;
externalForcesModule = particulesNeige.GetComponent<ParticleSystem>().externalForces;
externalForcesModule.AddInfluence(forceField);
ParticleSystem.TriggerModule triggerModule;
triggerModule = particulesNeige.GetComponent<ParticleSystem>().trigger;
triggerModule.AddCollider(biome.perso.GetComponent<CharacterController>());
if(Random.Range(0,11) == 10){
biome.ChangerEtat(biome.glaceEternelle);
}else{
biome.ChangerEtat(biome.glaceNeige);
}
}
else if(other.tag == "Ennemi")
{
// reduit le nombe de cases gelees dans le gestionnaire de la partie et affiche le pourcentage de completion
GameManager.instance.UpdateCasesGelees(-1);
biome.ChangerEtat(biome.initial);
}
if(other.tag == "Player"){
}
}
public override void OnTriggerExitEtat(BiomesEtatsManager biomesEtatsManager, Collider other){
}
}
Logiciels utilisés
- Unity
- Blender
- Photoshop
Crédits et sources
- Modèles 3D, musique, composants UI et textures crées par Alex Tremblay et Xavier Coursol
- La programmation à été réalisée avec l’aide de Vincent Chalifoux
- Effets sonores provenant de opengameart.org