Aller au contenu principal

Amélioration de l'UI et du Juice

· 2 minutes de lecture
Valentin Beauget
Valentin Beauget
Audric Fullhardt
Audric Fullhardt
Gabriel Maillard
Gabriel Maillard
Marius Nogueron
Marius Nogueron

Marius

Encore un nouveau jeu dans la collection !

Il s'appelle Loop. Le principe : zoomer et dézoomer sur un objet jusqu'à trouver la zone de netteté parfaite. C'est à cet instant précis que la validation du jeu est déclenchée.

Je me suis également chargé de la refonte de la page d'accueil. J'ai opté pour des caractères japonais — l'esthétique colle parfaitement avec l'univers visuel du jeu.

alt text

J'ai aussi créé le système de vies. Des morceaux de pastèque pour symboliser les vies restantes — le rendu est parfaitement cohérent avec l'ambiance du jeu.

alt text

Gabriel

Pour le jeu Timer, j'ai conçu nos avatars personnalisés.

J'ai aussi introduit des écrans de transition entre chaque mini-jeu : un mot-clé apparaît pour décrire le jeu suivant, donnant au joueur une indication précieuse avant d'être plongé dans l'action.

Deux nouveaux assets en fonction de la réussite / défaite d'un mini jeu

alt text

alt text

J'ai également conçu une scène dédiée à l'écran de défaite.

alt text

Audric

DropTheFish manquait de dynamisme. J'ai donc ajouté une plage en arrière-plan et resserré la caméra sur l'action.

alt text

J'ai aussi intégré un système d'indication des touches entre chaque jeu, afin que le joueur sache exactement quoi utiliser avant chaque jeu.

Valentin

J'ai peaufiné FlashTheCar

Pour PopTheBottle, j'ai revu l'interface pour un résultat plus propre et plus lisible. Les illustrations sont signées Gabriel ce goat.

Nouveaux jeux et correction de bugs

· 2 minutes de lecture
Valentin Beauget
Valentin Beauget
Audric Fullhardt
Audric Fullhardt
Gabriel Maillard
Gabriel Maillard
Marius Nogueron
Marius Nogueron

Marius

Création de DiceGame.

Le principe est simple : un chiffre s'affiche à l'écran, et à l'aide du joystick, le joueur doit faire pivoter un dé en 3D pour aligner la bonne face avec le chiffre indiqué.

alt text

Côté correctif, MentalMath n'indiquait pas clairement quels boutons associer à quelles réponses. J'ai donc intégré un asset conçu par Gabriel pour guider le joueur. J'ai également réglé un bug étrange : la police d'écriture transformait les 6 en 9, ce qui faussait complètement les calculs.

Gabriel

Création de TimerGame.

alt text

Le principe : une durée en secondes est affichée à l'écran. Le chrono démarre, les secondes s'écoulent, et le joueur doit buzzer au bon moment. Simple en apparence, redoutable en pratique — avec l'un de nos meilleurs professeurs comme animateur.

J'ai aussi peaufiné le jeu de tri. Après réflexion, trier des formes géométriques s'avère plus intuitif et plus lisible que trier des fruits. Ce n'est pas un nouveau jeu, juste un rebranding assumé :

alt text

Audric

Création d'ExplodeTheBalloon.

Le concept est aussi direct qu'il en a l'air : un ballon apparaît, on spam une touche pour le gonfler, et on le fait exploser.

alt text

J'ai également travaillé sur la progression de la difficulté : un système ajuste automatiquement certains paramètres — dont la vitesse et le timer — à chaque mini-jeu.

J'ai aussi implémenté le système de scoring affiché en fin de partie.

Valentin

Création de FlashTheCar.

Des voitures défilent à vitesse variable : la plupart roulent normalement, mais certaines sont en infraction. Au joueur de repérer et de verbaliser avant qu'elles passent la ligne rouge !

alt text

Nouveaux prototypes et Création d'un Scène Loader

· 3 minutes de lecture
Valentin Beauget
Valentin Beauget
Audric Fullhardt
Audric Fullhardt
Gabriel Maillard
Gabriel Maillard
Marius Nogueron
Marius Nogueron

Valentin

Optimisation de la CI/CD pour un déploiement en production plus rapide du site web.

optimisation CI/CD

Cette dernière était hébergée sur mon site mon VPS, j'ai fait en sorte que le déploiement et le host soient sur mon serveur. Le VPS était trop lent et n'avait pas assez de place pour supporter la charge

Implémentation de la logique pour le retour menu :

  • Appui du bouton échap (correspondant au bouton blanc sur la borne)
  • Système d'anti afk, nous ramenant au menu au bout de 200 secondes.

optimisation CI/CD

Bug des listeners audio multiples réglé. Ces derniers se créaient en masse dans une scène lors du lancement de manière dynamique

J'ai donc créé des fonctions qui s'assurent qu'il n'y en ait juste un.

Audric

Avec Valentin : Création du Loader de minijeu (code ci-dessous) :

ScenesLoader.cd
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

public class ScenesLoader : MonoBehaviour
{
// [SerializeField] private GameObject sceneContainer;
public GameObject sceneContainer; // Ton GameObject vide
public Vector3 targetScale = new Vector3(0.5f, 0.5f, 1f);

private bool SceneLoaded = false;

public void LoadMiniGame(string sceneName)
{
StartCoroutine(LoadMiniGameCoroutine(sceneName));
}

IEnumerator LoadMiniGameCoroutine(string sceneName)
{
// Charge la scène additive
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
while (!asyncLoad.isDone)
yield return null;

// Récupère la scène
Scene miniScene = SceneManager.GetSceneByName(sceneName);

// Parent tous les objets racines au container
foreach (GameObject go in miniScene.GetRootGameObjects())
{
if (go.name == "Main Camera")
{
Destroy(go);
continue;
}
; // Ignore la caméra principale
go.transform.SetParent(sceneContainer.transform, false);
}
sceneContainer.transform.localScale = new Vector3(0.5f, 0.5f, 1f);
}

public void UnloadMiniGame(string sceneName)
{
SceneManager.UnloadSceneAsync(sceneName);
foreach (Transform child in sceneContainer.transform)
{
Destroy(child.gameObject);
}
}
}

Intégration dans le GameManager + Récupération et affichage random des jeux (code ci-dessous) :

GameManager.cd
private string GetRandomGame()
{
string[] scenesList = Directory.GetFiles("Assets/Scenes/MiniGames", "\*.unity");
for (int i = 0; i < scenesList.Length; i++)
{
scenesList[i] = Path.GetFileNameWithoutExtension(scenesList[i]);
}

System.Random rand = new System.Random();
int index = rand.Next(scenesList.Length);

if (currentGame == scenesList[index])
{
index = (index + 1) % scenesList.Length;
}

return scenesList[index];

}

if (Input.GetButtonDown("P1_B6"))
{
string nextGame = GetRandomGame(); //ou alors le jeu que vous voulez tester comme ça : nextGame = "SlotMachine";
scenesLoader.LoadMiniGame(nextGame);
currentGame = nextGame;
}
if (Input.GetButtonDown("P1_B3"))
{
if (currentGame != "")
{
scenesLoader.UnloadMiniGame(currentGame);
}
}

Gabriel

Faire en sorte que mes mini-jeux aient le bon système de timer.

Réglage d'un bug qui lançait le timer et certaines logiques de MentalMath à l'allumage du jeu et non pas au load de la scène de MentalMath.

Je commence la réalisation d'assets graphiques pour un mini jeu pour Valentin (celui de la bouteille de champagne)

Marius

Mise à jour du site Blog et ésthétique de ce dernier, ajout d'un logo + Création en cours d'un nouveau jeu Taiko

Ce dernier, en ajoutant une musique dans un fichier grâce à un algo, créera deux types de boules de couleurs qui passeront de gauche à droite et qui devront être cliqué à un instant t.

setup unity

En fonction de la précision de la pression, retour visuel.

retour visuel sur la précision d&#39;un click

Il y a aussi un facteur de difficulté qui sera en accord avec un facteur de difficulté défini au début de la partie.

Initialisation du projet et début des prototypes

· 5 minutes de lecture
Valentin Beauget
Valentin Beauget
Audric Fullhardt
Audric Fullhardt
Gabriel Maillard
Gabriel Maillard
Marius Nogueron
Marius Nogueron

Valentin

J'ai mis le site web en production sur mon VPS.

Nous voulions directement pouvoir déployer le site avec un push sur le repository Github.

Nous utilisons Docusaurus, l'équivalent d'un Gitbook.

La problématique avec ce dernier est qu'il a quelques exigences concernant son déploiement.

Ce dernier doit être npm install et build npm run build.

cicd

Pour tout le processus de déploiement, j'utilise n8n

ci.yml
name: CI/CD call n8n auto deploy

on:
push:
branches: - main
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Trigger n8n webhook
run: curl -fsSL "https://n8n.beauget.fr/webhook/d81232cf-0467-4dd0-9d23-2d52acc5cabf"

J'ai également réalisé un premier prototype : 'SlotMachine' où l'utilisateur observe une roulette avec 3 slots faisant défiler des fruits et doit appuyer 3 fois d'affilée à un moment précis lorsqu'un 7 apparaît pour réussir le mini-jeu.

7

Gabriel

Je me suis occupé de créer une bonne base que ce soit dans la hiérarchisation des éléments du jeu ou dans les méthodes communes réutilisables par tous les autres dans la création des mini-jeux (GameManager).

GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;

public class GameManager : MonoBehaviour
{
[Header("Config Initiale")]
[SerializeField] public int startingLives = 2;
[SerializeField] public int difficulty = 1;
[SerializeField] private int lives;
public int Lives { get; private set; }

[Header("Timer Partagé")]
[SerializeField] private bool timerRunning;
public bool TimerRunning { get; private set; }
[SerializeField] private float remainingTime;
public float RemainingTime { get; private set; }
[SerializeField] private float duration;
public float Duration { get; private set; }
private Coroutine _timerCo;
public event Action OnTimerEnded;
public event Action<float> OnTimerTick;

[Header("Leaderboard")]
[SerializeField] private int roundsPlayed;
public int RoundsPlayed { get; private set; }

[Header("UI")]
[SerializeField] public TimerUI timerUI;
[SerializeField] private LivesUI livesUI;

//ACTION À EFFECTUER À LA FIN D'UN MINI-JEU
public event Action OnMinigameWon;
public event Action OnMinigameFailed;

void Awake()
{
Lives = startingLives;
livesUI?.SetLives(Lives);
RoundsPlayed = 0;
}

public void AddRound()
{
RoundsPlayed++;
//ON POURRA RAJOUTER D'AUTRES ACTIONS AU CHANGEMENT DE ROUND ICI
}

//GESTION DES VIES
public void LoseLife()
{
Lives--;
livesUI?.SetLives(Lives);
}

public void ResetLives()
{
Lives = startingLives;
livesUI?.SetLives(Lives);
}

//GESTION DU TIMER
public void StartTimer(float seconds)
{
StopTimer(); //pour être sur qu'on en a pas deux qui tournent
Duration = Mathf.Max(0f, seconds);
RemainingTime = Duration;
TimerRunning = true;
timerUI?.Show(Duration);
_timerCo = StartCoroutine(CoTimer());
}

public void StopTimer()
{
if (_timerCo != null) StopCoroutine(_timerCo);
_timerCo = null;
TimerRunning = false;
timerUI?.Hide();
}

IEnumerator CoTimer()
{
while (RemainingTime > 0f)
{
RemainingTime -= Time.deltaTime;
if (RemainingTime < 0f) RemainingTime = 0f;
timerUI?.UpdateTime(RemainingTime, Duration);
OnTimerTick?.Invoke(RemainingTime);
yield return null;
}
TimerRunning = false;
OnTimerEnded?.Invoke();
}

//ACTIONS QUI SE LANCENT QUAND ON GAGNE OU PERD UN MINI-JEU
public void NotifyWin()
{
StopTimer();
OnMinigameWon?.Invoke();
}

public void NotifyFail()
{
StopTimer();
OnMinigameFailed?.Invoke();
}

//TOUT CE QUI EST EN DESSOUS : NE PAS UTILISER - A REFACTORER SUR MON MINI-JEU
public TMP_Text livesText;

void Update()
{
livesText.text = "Vies: " + lives;
}
}

J'ai également réalisé un prototype 'TriPommePoire'. Un fruit aléatoire entre ces deux apparaît à l'écran et la personne doit choisir rapidement entre le panier de gauche contenant les pommes ou celui de droite avec les poires.

tripommepoire

Audric

J'ai créé l'API en Node.js pour implémenter une communication directe entre la borne et le site. En l'occurrence, le site va afficher les leaderboard.

const express = require("express");
const cors = require("cors");
const data = require("./data.json");
const fs = require("fs");
require("dotenv").config();

const app = express();
const PORT = 3001;

app.use(cors());
app.use(express.json());

app.get("/", (req, res) => {
res.send("API !");
});

let leaderboard = data;

function checkApiKey(req, res, next) {
const apiKey = req.headers["x-api-key"];
if (apiKey && apiKey === process.env.API_KEY) {
next();
} else {
res.status(403).json({ error: "Clé API manquante ou invalide" });
}
}

app.get("/leaderboard", (req, res) => {
leaderboard.sort((a, b) => b.score - a.score);
res.json(leaderboard);
});

app.post("/leaderboard", checkApiKey, (req, res) => {
let { name, score } = req.body;
if (!name || typeof score !== "number") {
return res.status(400).json({ error: "Nom et score requis" });
}
if (name.length > 3) {
name = name.substring(0, 3);
}
name = name.toUpperCase();
const existingEntry = leaderboard.find((entry) => entry.name === name);

if (existingEntry) {
if (score > existingEntry.score) {
existingEntry.score = score;
} else {
return res.json({
success: false,
message: "Score non mis à jour (inférieur ou égal au score existant).",
leaderboard,
});
}
} else {
leaderboard.push({ name, score });
}
leaderboard.sort((a, b) => b.score - a.score);
fs.writeFileSync("./data.json", JSON.stringify(leaderboard, null, 2));
res.json({ success: true, leaderboard });
});

app.listen(PORT);

api leaderboard

J'ai fait un prototype en 3D d'un jeu type 'Dropper' où l'on doit ajouter les différentes balles dans les cylindres avec un bon timing.

dropper

Marius

J'ai créé le site web. On utilise docusaurus, un outil similaire à GitBook qui supporte nativement la gestion d'un blog.

J'ai commencé à créer le prototype d'un jeu 'MentalMath' qui est un jeu de calcul mental similaire à "l'Entraînement Cérébral du Dr Kawashima". Un calcul assez simple, 3 propositions, si le choix est bon, apparition d'un visage joyeux et inversement.

Entraînement Cérébral du Dr Kawashima

J'ai réutilisé les méthodes créées par Gabriel pour mon `CalculManager'

CalculManager.cd
using UnityEngine;

public class CalculManager : MonoBehaviour
{
[SerializeField] private GameManager gameManager;
[SerializeField] private CalculLogic calculLogic;
[SerializeField] private CalculUIManager calculUIManager;

void Start()
{
gameManager.StartTimer(10f);
GenerateNewCalculation();
}

public void GenerateNewCalculation()
{
var calcul = calculLogic.GenerateCalculation();
calculUIManager.DisplayCalculation(calcul);
}

public bool OnAnswerSelected(int index)
{
bool correct = calculLogic.CheckAnswer(index);

if (correct)
{
gameManager.NotifyWin();
}
else
{
gameManager.LoseLife();
gameManager.NotifyFail();
}

return correct;
}
}

Commencement du projet

· Une minute de lecture
Marius Nogueron
Marius Nogueron

Première réflexion sur le jeu : Réflexions

Après réflexion, nous proposons un Wario Ware like, une multitude de mini-jeux très rapide que l'on enchaine avec un certain nombre de vie (souvent quelques secondes par jeux avec une difficultée croissante). Il en va donc de soit que ces mini-jeux doivent être compréhensible au premier coup d'oeil.