This commit is contained in:
2024-06-12 21:03:42 +02:00
parent 4685d9942b
commit aef3b3ab97
1548 changed files with 5615 additions and 72 deletions

View File

@@ -0,0 +1,264 @@
using System;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;
using static GameHandler;
using static LevelSpawner;
using static PlayerScript;
using static PlayersHandler;
namespace Assets.Scripts
{
public class DataBearer : SingletonMB<DataBearer>
{
#region PROPERTIES
public bool DataSaved { get; private set; }
public Attempt CurrentAttempt { get; private set; }
public SessionData Session
{
get
{
if (_session == null)
{
_session = GetSettings();
}
return _session;
}
private set
{
_session = value;
}
} private SessionData _session = null;
public GameState GameState { get; set; } = new();
public Players Players { get { return Session.Players; } }
public Rules Rules { get { return Session.Rules; } }
#endregion
#region VARIABLES
private GameHandler _gameHandler;
private ScoreHandler _scoreHandler;
private PlayersHandler _playerHandler;
private LevelSpawner _levelSpawner;
#endregion
#region EVENTS
public void RegisterAttempt_OnAttemptEnded(object sender, AttemptEventArgs eventArgs)
{
Session.Attempts.Add(CurrentAttempt);
CurrentAttempt = null;
} //
public void RegisterPlayer_OnPlayerInstanciated(object sender, PlayerEventArgs args)
{
var player = args.player;
var newPlayer = new RegisteredPlayer()
{
Name = player.Name,
Id = player.Id,
Human = player.IsHuman
};
if(!Session.RegisteredPlayers.Exists(rp => rp.Name == newPlayer.Name))
{
Session.RegisteredPlayers.Add(newPlayer);
}
player.OnBonusTouched += RecordBonus_OnBonusTouched;
player.OnObstacleTouched += RecordObstacle_OnObstacleTouched;
} //
public void CreateAttemptRecord_OnAttemptStarted(object sender, AttemptEventArgs args)
{
CurrentAttempt = args.attempt;
foreach(var player in Session.RegisteredPlayers)
{
PlayerAttempt playerAttempt = new PlayerAttempt()
{
PlayerName = player.Name
};
CurrentAttempt.PlayersAttempts.Add(player.Name, playerAttempt);
}
} //
public void RecordAttemptObstacle_OnPrefabSpawned(object sender, PrefabSpawnedEventArgs args)
{
LevelPrefab obstacle = new() {
PrefabName = args.pattern.name, //todo : prefab name has "(clone)" at the end
PrefabNumber = CurrentAttempt.Level.Count + 1,
TickTimeWhenTouched = _gameHandler.TickAttemptDuration
};
CurrentAttempt.Level.Add(obstacle);
} //
public void RecordObstacle_OnObstacleTouched(object sender, PrefabTouchedEventArgs args)
{
var prefab = args.objectTouched;
TouchedGameObject touchedGameObject = new()
{
FullObjectName = prefab.name, //todo : prefab name is laser... It should be full pattern name
ObjectTypeName = SortingLayer.IDToName(prefab.layer), // todo : <unknown layer>
GameSpeed = _gameHandler.MapSpeed,
PrefabName = prefab.name,
TickTimeWhenTouched = _gameHandler.TickAttemptDuration
};
CurrentAttempt.PlayersAttempts[args.playerName].ObstaclesTouched.Add(touchedGameObject);
RecordPlayerScore(args.playerId, args.playerName);
} //
public void RecordBonus_OnBonusTouched(object sender, PrefabTouchedEventArgs args)
{
var prefab = args.objectTouched;
TouchedGameObject touchedGameObject = new()
{
FullObjectName = prefab.name,
ObjectTypeName = SortingLayer.IDToName(prefab.layer),
GameSpeed = _gameHandler.MapSpeed,
PrefabName = prefab.name,
TickTimeWhenTouched = _gameHandler.TickAttemptDuration
};
CurrentAttempt.PlayersAttempts[args.playerName].BonusTouched.Add(touchedGameObject);
RecordPlayerScore(args.playerId, args.playerName);
} //
#endregion
#region ENDPOINTS
public void ResetSession()
{
_session = null;
}
public void SaveData()
{
string directoryPath = Path.Combine(Application.persistentDataPath, "SessionsData");
if (!Directory.Exists(directoryPath))// Crée le répertoire s'il n'existe pas déjà
{
Directory.CreateDirectory(directoryPath);
}
var existingFiles = Directory.GetFiles(directoryPath, "*.json");
int sessionNumber = existingFiles.Length + 1;
Session.SessionNumber = sessionNumber;
string jsonData = JsonConvert.SerializeObject(Session);
string fileName = $"Session_{sessionNumber}.json";
string filePath = Path.Combine(directoryPath, fileName);
File.WriteAllText(filePath, jsonData);
DataSaved = true;
Debug.Log($"Session saved to {filePath}");
}
public void RecordSessionTime(float time)
{
Session.SessionTime = time;
}
public void RecordSessionDate(DateTime date)
{
Session.Date = date;
}
/// <summary>
/// Record all players scores
/// </summary>
public void RecordPlayersScore()
{
Session.RegisteredPlayers.ForEach(p =>
{
RecordPlayerScore(p.Id, p.Name);
});
}
/// <summary>
/// Find and record a player score
/// </summary>
public void RecordPlayerScore(int playerId, string playerName)
{
int score = (int) _scoreHandler.GetPlayerScore(playerId);
var tickTime = _gameHandler.TickAttemptDuration;
GenericVector<int, int> ScoreByTime = new() { X = tickTime, Y = score };
CurrentAttempt.PlayersAttempts[playerName].PlayerScoresOverTicksTime.Add(ScoreByTime);
}
/// <summary>
/// Record a player final score
/// </summary>
public void RecordPlayerFinalScore(int score, string playerName)
{
CurrentAttempt.PlayersAttempts[playerName].FinalScore = score;
}
/// <summary>
/// Record the distance parcoured by the player
/// </summary>
public void RecordPlayerDistance(string playerName)
{
CurrentAttempt.PlayersAttempts[playerName].Distance = (int)_gameHandler.TotalDistance;
}
public void RecordPlayerAttemptDuration(string playerName)
{
CurrentAttempt.PlayersAttempts[playerName].AttemptDurationSeconds = _gameHandler.TimeAttemptDuration;
CurrentAttempt.PlayersAttempts[playerName].AttemptDurationTicks = _gameHandler.TickAttemptDuration;
}
#endregion
#region METHODS
private SessionData GetSettings()
{
SessionData session = new();
session.Players.Human = bool.Parse(PlayerPrefs.GetString(Settings.HumanPlayerSelected));
session.Players.OmnicientBot = bool.Parse(PlayerPrefs.GetString(Settings.OmnicientBotSelected));
session.Players.ObserverBot = bool.Parse(PlayerPrefs.GetString(Settings.ObserverBotSelected));
session.Players.IteratorBot = bool.Parse(PlayerPrefs.GetString(Settings.IteratorBotSelected));
session.Rules.ChangeSeedEachTry = bool.Parse(PlayerPrefs.GetString(Settings.ChangeSeedSetting));
session.Rules.UsesDefaultSeed = bool.Parse(PlayerPrefs.GetString(Settings.UseDefaultSeedSetting));
session.Rules.Lasers = bool.Parse(PlayerPrefs.GetString(Settings.LaserSettingToggle));
session.Rules.Missiles = bool.Parse(PlayerPrefs.GetString(Settings.MissileSettingToggle));
session.Rules.BonusCoins = bool.Parse(PlayerPrefs.GetString(Settings.CoinSettingToggle));
session.Rules.MalusCoins = bool.Parse(PlayerPrefs.GetString(Settings.BadCoinSettingToggle));
session.Rules.ScoreOnDistance = bool.Parse(PlayerPrefs.GetString(Settings.SurviveScoreSettingToggle));
session.Rules.RushMode = bool.Parse(PlayerPrefs.GetString(Settings.RushModeSettingToggle));
session.Rules.MaxDistance = PlayerPrefs.GetFloat(Settings.MaxDistanceSettingValue);
session.Rules.MaxAttempts = PlayerPrefs.GetInt(Settings.MaxTrySettingValue);
session.Rules.MaxSessionTime = PlayerPrefs.GetInt(Settings.MaxTimeSettingValue);
session.Rules.MaxSpeed = PlayerPrefs.GetFloat(Settings.MaxSpeedSettingValue);
session.Rules.StartSpeed = PlayerPrefs.GetFloat(Settings.StartSpeedSettingValue);
session.Rules.DifficultyMultiplier = PlayerPrefs.GetFloat(Settings.DifficultyMultiplierSettingValue);
return session;
}
#endregion
#region LIFECYCLE
protected override void Awake()
{
DataSaved = false;
Session = GetSettings();
base.Awake();
_gameHandler = GameHandler.Instance;
_scoreHandler = ScoreHandler.Instance;
_playerHandler = PlayersHandler.Instance;
_levelSpawner = LevelSpawner.Instance;
_gameHandler.OnAttemptEnded += RegisterAttempt_OnAttemptEnded;
_gameHandler.OnAttemptStarted += CreateAttemptRecord_OnAttemptStarted;
_playerHandler.OnPlayerInstanciated += RegisterPlayer_OnPlayerInstanciated;
_levelSpawner.OnPrefabSpawned += RecordAttemptObstacle_OnPrefabSpawned;
}
#endregion
}
}

View File

@@ -0,0 +1,164 @@
using Assets.Scripts;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using static GameHandler;
public class GameController : SingletonMB<GameController>
{
#region PROPERTIES
public bool IsExiting { get; private set; }
public SessionData Session { get; private set; } = new SessionData();
#endregion
#region VARIABLES
private GameHandler _gameHandler;
private ScoreHandler _scoreHandler;
private LevelSpawner _LevelSpawner;
private PlayersHandler _playerHandler;
private DataBearer _dataBearer;
private int maxSessionTime;
private float currentSessionTime;
private int maxAttempts;
private int currentAttempts;
#endregion
#region EVENTS
public event EventHandler<SessionEventArgs> OnSessionEnded;
public class SessionEventArgs : EventArgs
{
public SessionData session;
public SessionEventArgs(SessionData session)
{
this.session = session;
}
}
private void ProcessNextAttempt_OnAttemptEnded(object sender, AttemptEventArgs args)
{
if(!IsExiting)
{
PrepareNextRound();
}
}
#endregion
#region METHODS
/// <summary>
/// Initialize a new round if there are more available attempts
/// </summary>
public void PrepareNextRound()
{
currentAttempts+=1;
if(maxAttempts == 0 || _dataBearer.Session.Attempts.Count < maxAttempts)
{
_gameHandler.InitAttempt();
}
else
{
ExitToMenu("Nombre maximum d'essais atteint");
}
}
public void ExitToMenu(string message)
{
IsExiting = true;
Debug.Log("Session END");
//StartCoroutine(ExitToMenuCoroutine(message));
_gameHandler.ResumeGame();
//yield return null;
if (!_gameHandler.AttemptEnding) _gameHandler.HaltAttempt(message);
Clean();
SceneManager.LoadScene("MainMenu");
_dataBearer.ResetSession();
}
//private IEnumerator ExitToMenuCoroutine(string message)
//{
// _gameHandler.ResumeGame();
// //yield return null;
// if(!_gameHandler.AttemptEnding) _gameHandler.HaltAttempt(message);
// _dataBearer.ResetSession();
// Clean();
// SceneManager.LoadScene("MainMenu");
//}
#endregion
#region LIFECYCLE
// Start is called before the first frame update
private void Start()
{
Debug.Log($"Start session for {Session.Rules.MaxAttempts} rounds");
_dataBearer.RecordSessionDate(DateTime.Now);
currentSessionTime = 0f;
PrepareNextRound();
}
// Awake is called before Start
protected override void Awake()
{
base.Awake();
_gameHandler = GameHandler.Instance;
_scoreHandler = ScoreHandler.Instance;
_playerHandler = PlayersHandler.Instance;
_dataBearer = DataBearer.Instance;
_gameHandler.OnAttemptEnded += ProcessNextAttempt_OnAttemptEnded;
maxAttempts = _dataBearer.Rules.MaxAttempts;
maxSessionTime = _dataBearer.Rules.MaxSessionTime;
currentAttempts = 0;
}
// Update is called once per frame
void Update()
{
currentSessionTime += Time.deltaTime;
_dataBearer.RecordSessionTime(currentSessionTime);
if (maxSessionTime > 0 && currentSessionTime >= maxSessionTime)
{
currentSessionTime = maxSessionTime;
_dataBearer.RecordSessionTime(currentSessionTime);
ExitToMenu("Temps <20>coul<75>");
}
}
//Unsubscribe events, save databearer;
private void OnDestroy()
{
Clean();
}
private void OnApplicationQuit()
{
Clean();
}
public void Clean()
{
_gameHandler.OnAttemptEnded -= ProcessNextAttempt_OnAttemptEnded;
if (!_dataBearer.DataSaved)
{
//datasaving logic
_dataBearer.RecordSessionTime(currentSessionTime);
_dataBearer.SaveData();
}
}
#endregion
}

View File

@@ -0,0 +1,299 @@
using Assets.Scripts;
using System;
using System.Collections;
using Unity.VisualScripting;
using UnityEditor.Rendering;
using UnityEngine;
using static PlayersHandler;
/// <summary>
/// GameHandler gère le fonctionnement général d'une partie de jeu. Il se charge de faire apparaître les joueurs
/// </summary>
public class GameHandler : SingletonMB<GameHandler>
{
#region PROPERTIES
int Seed
{
get { return _seed; }
set
{
_seed = value;
UnityEngine.Random.InitState(value);
}
} private int _seed;
public float FrameDistance { get; private set; }
public float TotalDistance { get; private set; }
[SerializeField] public float MapSpeed { get; private set; }
public Attempt CurrentAttempt { get; private set; }
public bool MaxDifficultyReached { get; private set; }
public bool IsPaused { get; private set; } = false;
public bool HaveHumanPlayer { get; private set; } = false;
public bool AttemptEnding { get; private set; }
public int TickAttemptDuration { get; private set; }
public float TimeAttemptDuration {get; private set;}
#endregion
#region VARIABLES
private const float DIFFICULTY_DAMP = 0.01f;
[SerializeField] private GameObject finishLine;
private ScoreHandler _scoreHandler;
private LevelSpawner _levelSpawner;
private PlayersHandler _playerHandler;
private DataBearer _dataBearer;
private TileSpawner _tileSpawner;
private Vector3 playerSpawnPosition = new Vector3(-3, 0, 0);
private float maxSpeed;
private float maxDistance;
private bool raiseScoreOnDistance;
private float difficultyMultiplier;
private bool finishLineSpawned;
private short tickCounter;
#endregion
#region EVENTS
public event EventHandler<AttemptEventArgs> OnAttemptStarted;
public event EventHandler<AttemptEventArgs> OnAttemptEnding;
public event EventHandler<AttemptEventArgs> OnAttemptEnded;
public event EventHandler<EventArgs> OnGamePaused;
public event EventHandler<EventArgs> OnGameResumed;
public event EventHandler<EventArgs> OnInstanciatingPlayers;
public class AttemptEventArgs : EventArgs
{
public Attempt attempt;
public AttemptEventArgs(Attempt attempt) { this.attempt = attempt; }
}
public void HaltAttempt_OnAllPlayersFinished(object sender, EventArgs agrs)
{
HaltAttempt("Tous les joueurs sont Out");
}
public void CheckIfHumanPlayer_OnPlayerInstanciated(object sender, PlayerEventArgs args)
{
if (args.player.IsHuman)
{
HaveHumanPlayer = true;
}
}
#endregion
#region METHODS
/// <summary>
/// Return a seed which depend on selected settings and attempt number
/// </summary>
private int SetSeed()
{
int seed;
if (_dataBearer.Session.Rules.ChangeSeedEachTry & CurrentAttempt.AttemptNumber > 1) //Get a random seed each new try
{
seed = UnityEngine.Random.Range(100000000, 1000000000); //generate a random 9 digits seed
}
else //Get either default seed or custom seed
{
seed = _dataBearer.Session.Rules.UsesDefaultSeed
? PlayerPrefs.GetInt(Settings.DefaultSeedSettingValue)
: PlayerPrefs.GetInt(Settings.CustomSeedSettingValue);
}
return seed;
}
private void RaiseScore()
{
float scoreToAdd = FrameDistance;// * Time.deltaTime;
_scoreHandler.AddScoreToAllAlivePlayers(scoreToAdd);
}
private void RaiseDifficulty()
{
if(MapSpeed > maxSpeed)
{
MapSpeed = maxSpeed;
MaxDifficultyReached = true;
}
MapSpeed += MapSpeed * difficultyMultiplier * Time.deltaTime;
}
private void SpawnPlayers()
{
if (_dataBearer.Session.Players.Human)
_playerHandler.InstanciatePlayer<HumanPlayerBehaviour>(playerSpawnPosition);
if (_dataBearer.Session.Players.OmnicientBot)
_playerHandler.InstanciatePlayer<OmnicientBehaviour>(playerSpawnPosition);
if (_dataBearer.Session.Players.ObserverBot)
_playerHandler.InstanciatePlayer<ObserverBehaviour>(playerSpawnPosition);
if (_dataBearer.Session.Players.IteratorBot)
_playerHandler.InstanciatePlayer<IteratorBehaviour>(playerSpawnPosition);
}
#endregion
#region ENDPOINTS
public void PauseGame()
{
if (!IsPaused)
{
StartCoroutine(PauseGameCoroutine());
}
}
private IEnumerator PauseGameCoroutine()
{
yield return null;
Time.timeScale = 0; // Met le jeu en pause
IsPaused = true;
OnGamePaused.Invoke(this, new EventArgs());
}
public void ResumeGame()
{
if (IsPaused)
{
Time.timeScale = _dataBearer.Rules.RushMode ? 15 : 1; // Reprend le jeu
IsPaused = false;
OnGameResumed.Invoke(this, new EventArgs());
}
}
public void InitAttempt()
{
TimeAttemptDuration = 0;
TickAttemptDuration = 0;
Debug.LogWarning("Initate Attempt !");
AttemptEnding = false;
CurrentAttempt = new Attempt();
CurrentAttempt.AttemptNumber = _dataBearer.Session.Attempts.Count + 1;
Seed = SetSeed();
CurrentAttempt.Seed = Seed;
TotalDistance = 0;
MapSpeed = _dataBearer.Rules.StartSpeed;
raiseScoreOnDistance = _dataBearer.Rules.ScoreOnDistance;
maxSpeed = _dataBearer.Rules.MaxSpeed;
difficultyMultiplier = _dataBearer.Rules.DifficultyMultiplier * DIFFICULTY_DAMP;
if(_dataBearer.Rules.RushMode){
Time.timeScale = 15;
foreach (var renderer in FindObjectsOfType<Renderer>())
{
if(renderer.gameObject.GetComponent<ScorePannelScript>()==null)
{renderer.enabled = false;}
}
}
else{
Time.timeScale = 1;
}
MaxDifficultyReached = false;
TotalDistance = 0;
FrameDistance = 0;
finishLineSpawned = false;
tickCounter = 0;
SpawnPlayers();
_scoreHandler.AddScoreToAllAlivePlayers(500);
OnAttemptStarted?.Invoke(this, new AttemptEventArgs(CurrentAttempt));
}
/// <summary>
/// Halt the current attempt
/// </summary>
public void HaltAttempt(string message)
{
Console.WriteLine(message);
AttemptEnding = true;
OnAttemptEnding?.Invoke(this, new AttemptEventArgs(CurrentAttempt));
OnAttemptEnded?.Invoke(this, new AttemptEventArgs(CurrentAttempt));
}
#endregion
#region LIFECYCLE
protected override void Awake()
{
base.Awake();
_scoreHandler = ScoreHandler.Instance;
_levelSpawner = LevelSpawner.Instance;
_playerHandler = PlayersHandler.Instance;
_dataBearer = DataBearer.Instance;
_tileSpawner = TileSpawner.Instance;
_playerHandler.OnAllPlayersFinished += HaltAttempt_OnAllPlayersFinished;
_playerHandler.OnPlayerInstanciated += CheckIfHumanPlayer_OnPlayerInstanciated;
difficultyMultiplier = _dataBearer.Rules.DifficultyMultiplier;
raiseScoreOnDistance = _dataBearer.Rules.ScoreOnDistance;
maxDistance = _dataBearer.Rules.MaxDistance;
}
void Update()
{
FrameDistance = MapSpeed * Time.deltaTime;
TotalDistance += FrameDistance;
TimeAttemptDuration += Time.deltaTime;
TickAttemptDuration += 1;
tickCounter += 1;
if (tickCounter > 1000)
{
//Debug.Log("1000 TICK " + TimeAttemptDuration);
tickCounter = 0;
_dataBearer.RecordPlayersScore();
}
if(maxDistance != 0 && TotalDistance > maxDistance)
{
HaltAttempt("Ligne d'arrivée atteinte");
}
if (raiseScoreOnDistance) RaiseScore();
if (!MaxDifficultyReached) RaiseDifficulty();
if(maxDistance != 0 && !finishLineSpawned && maxDistance-TotalDistance < 20)
{
Instantiate(finishLine, new Vector3(maxDistance - TotalDistance - 3, 0, -1), finishLine.transform.rotation);
finishLineSpawned = true;
}
if (Input.GetKeyDown(KeyCode.Escape))
(IsPaused ? (Action)ResumeGame : PauseGame)();
}
private void OnDestroy()
{
Clean();
}
private void OnDisable()
{
Clean();
}
private void Clean()
{
/*StopAllCoroutines();
_playerHandler.OnAllPlayersFinished -= HaltAttempt_OnAllPlayersFinished;
_playerHandler.OnPlayerInstanciated -= CheckIfHumanPlayer_OnPlayerInstanciated;*/
}
#endregion
}

View File

@@ -0,0 +1,215 @@
using Assets.Scripts;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using static PlayersHandler;
public class PlayersHandler : SingletonMB<PlayersHandler>
{
// Start is called before the first frame update
[SerializeField] private GameObject playerPrefab;
#region PROPERTIES
public Dictionary<int, PlayerScript> Players { get; private set; } = new();
#endregion
#region VARIABLES
private ScoreHandler _scoreHandler;
private GameHandler _gameHandler;
#endregion
#region EVENTS
/// <summary>
/// Fires when a player is instanciated in the game
/// </summary>
public event EventHandler<PlayerEventArgs> OnPlayerInstanciated;
/// <summary>
/// Fires when a player have finished their attempt
/// </summary>
public event EventHandler<PlayerEventArgs> OnPlayerFinished;
/// <summary>
/// Fires when all players have finished their attempt
/// </summary>
public event EventHandler<EventArgs> OnAllPlayersFinished;
public class PlayerEventArgs : EventArgs
{
public PlayerScript player;
public PlayerEventArgs(PlayerScript player)
{
this.player = player;
}
}
#endregion
#region ENDPOINTS
/// <summary>
/// Remove a player from the game and fire a PlayerFinished event. Fire AllPlayerFinished event if needed
/// </summary>
/// <param name="playerId"></param>
public void RemovePlayer(int playerId)
{
if (Players.TryGetValue(playerId, out PlayerScript player))
{
Players.Remove(playerId);
if(!_gameHandler.AttemptEnding)
OnPlayerFinished.Invoke(this, new PlayerEventArgs(player));
player.Dispose();
}
if(Players.Count == 0 && !_gameHandler.AttemptEnding)
{
OnAllPlayersFinished.Invoke(this, new EventArgs());
}
}
public bool InstanciatePlayer<T>(Vector3 position) where T : MonoBehaviour
{
var player = SpawnPlayer(new Vector3(position.x, position.y, -1));
PlayerScript script = player.GetComponent<PlayerScript>();
player.AddComponent<T>();
Players.Add(script.Id, script);
DecoratePlayer(player);
CreateScorePanel(script);
Debug.Log($"# Player \"{script.Name}\" instanciated !");
OnPlayerInstanciated?.Invoke(this, new PlayerEventArgs(script));
return true;
}
#endregion
#region METHODS
private GameObject SpawnPlayer(Vector3 position)
{
var player = Instantiate(playerPrefab, position, Quaternion.identity);
var script = player.GetComponent<PlayerScript>();
script.SetId(Players.Count);
return player;
}
private void CreateScorePanel(PlayerScript script)
{
_scoreHandler.AddScorePanel(script.Name, script.Id, script.Color);
}
//private GameObject InstantiatePlayer(string playerName, Vector3 position)
//{
// incrementialId++;
// var player = Instantiate(playerPrefab, position, Quaternion.identity);
// var script = player.GetComponent<PlayerScript>();
// script.SetName(playerName);
// script.SetId(incrementialId);
// _scoreHandler.AddScorePanel(script.Name, script.Id, script.Color);
// Players.Add(script);
// return player;
//}
private void DecoratePlayer(GameObject player)
{
Transform child = player.transform.Find("sprite");
PlayerScript script = player.GetComponent<PlayerScript>();
var components = child.GetComponentsInChildren<SpriteRenderer>();
var jetpackFront = child.transform.Find("JetpackFront").GetComponent<SpriteRenderer>();
var jetpackBack = child.transform.Find("JetpackBack").GetComponent<SpriteRenderer>();
float alpha = _gameHandler.HaveHumanPlayer && !script.IsHuman ? 0.4f : 1.0f;
var scriptColor = script.Color!=null ? script.Color : new Color(255, 255, 255);
scriptColor.a = alpha;
foreach ( var component in components )
{
component.color = new Color(255, 255, 255, alpha);
}
jetpackFront.color = scriptColor;
jetpackBack.color = scriptColor;
}
#endregion
#region LIFECYCLE
protected override void Awake()
{
base.Awake();
_scoreHandler = ScoreHandler.Instance;
_gameHandler = GameHandler.Instance;
}
#endregion
}
//public void InstanciateOmnicient(Vector3 position)
//{
// var bot = SpawnPlayer(position);
// PlayerScript script = bot.GetComponent<PlayerScript>();
// Players.Add(script.Id, script);
// DecoratePlayer(bot);
// CreateScorePanel(script);
// OnPlayerInstanciated?.Invoke(this, new PlayerEventArgs(script));
//}
//public void InstanciateObserver(Vector3 position)
//{
// var bot = SpawnPlayer(position);
// PlayerScript script = bot.GetComponent<PlayerScript>();
// Players.Add(script.Id, script);
// DecoratePlayer(bot);
// CreateScorePanel(script);
// OnPlayerInstanciated?.Invoke(this, new PlayerEventArgs(script));
//}
//public void InstanciateIterator(Vector3 position)
//{
// var bot = SpawnPlayer(position);
// PlayerScript script = bot.GetComponent<PlayerScript>();
// Players.Add(script.Id, script);
// DecoratePlayer(bot);
// CreateScorePanel(script);
// OnPlayerInstanciated?.Invoke(this, new PlayerEventArgs(script));
//}
//public void InstanciateHumanPlayer(Vector3 position)
//{
// Debug.Log("Instanciate Human");
// var humanPlayer = SpawnPlayer(new Vector3(position.x, position.y, -1));
// PlayerScript script = humanPlayer.GetComponent<PlayerScript>();
// script.IsHuman = true;
// humanPlayer.AddComponent<HumanPlayer>();
// DecoratePlayer(humanPlayer);
// CreateScorePanel(script);
// Players.Add(script.Id, script);
// OnPlayerInstanciated?.Invoke(this, new PlayerEventArgs(script));
//}

View File

@@ -0,0 +1,201 @@
using System.Collections.Generic;
using UnityEngine;
using Assets.Scripts;
using System;
using static GameController;
using static PlayersHandler;
using static GameHandler;
using System.Linq;
public class ScoreHandler : SingletonMB<ScoreHandler>
{
#region PROPERTIES
private Dictionary<int, PlayerScore> PlayersScores { get; set; }
private Dictionary<int, PlayerScore> OuttedPlayers { get; set; }
#endregion
#region VARIABLES
[SerializeField] private GameObject ScorePanelPrefab;
[SerializeField] private GameObject UiDocument;
private GameController _gameController;
private PlayersHandler _playersHandler;
private GameHandler _gameHandler;
private DataBearer _dataBearer;
#endregion
#region EVENTS
public event EventHandler<ScoreEventArgs> OnScoreReachingZero;
public class ScoreEventArgs : EventArgs
{
public int playerId;
public ScoreEventArgs(int id)
{
playerId = id;
}
}
internal void RemovePlayer_OnPlayerFinished(object sender, PlayerEventArgs args)
{
if (PlayersScores.TryGetValue(args.player.Id, out PlayerScore looser))
{
_dataBearer.RecordPlayerDistance(looser.PlayerName);
_dataBearer.RecordPlayerScore(looser.PlayerId, looser.PlayerName);
_dataBearer.RecordPlayerAttemptDuration(looser.PlayerName);
OuttedPlayers.Add(looser.PlayerId, looser);
PlayersScores.Remove(looser.PlayerId);
}
}
private void RemoveScores_OnAttemptEnded(object sender, AttemptEventArgs args)
{
List<int> keys;
if (PlayersScores.Count > 0)
{
keys = new List<int>(PlayersScores.Keys);
foreach(var key in keys)
{
_dataBearer.RecordPlayerDistance(PlayersScores[key].PlayerName);
_dataBearer.RecordPlayerScore(key, PlayersScores[key].PlayerName);
_dataBearer.RecordPlayerAttemptDuration(PlayersScores[key].PlayerName);
OuttedPlayers.Add(key, PlayersScores[key]);
_playersHandler.RemovePlayer(key);
}
PlayersScores.Clear();
}
keys = new List<int>(OuttedPlayers.Keys);
foreach (var key in keys)
{
_dataBearer.RecordPlayerFinalScore((int)OuttedPlayers[key].Score, OuttedPlayers[key].PlayerName);
OuttedPlayers[key].ScorePanel.Dispose();
}
OuttedPlayers = new();
PlayersScores = new();
}
#endregion
#region ENDPOINTS
public void AddScorePanel(string playerName, int playerId)
{
var newScorePanel = Instantiate(ScorePanelPrefab, UiDocument.transform);
var scoreScript = newScorePanel.GetComponent<ScorePannelScript>();
scoreScript.SetPlayerName(playerName);
PlayerScore newRecord = new PlayerScore(playerName, playerId, scoreScript);
PlayersScores.Add(playerId, newRecord);
}
public void AddScorePanel(string playerName, int playerId, Color playerColor)
{
var newScorePanel = Instantiate(ScorePanelPrefab, UiDocument.transform);
var scoreScript = newScorePanel.GetComponent<ScorePannelScript>();
scoreScript.SetColor(playerColor);
scoreScript.SetPlayerName(playerName);
PlayerScore newRecord = new PlayerScore(playerName, playerId, scoreScript);
PlayersScores.Add(playerId, newRecord);
}
public void SetScore(float score, int playerId)
{
if (PlayersScores.TryGetValue(playerId, out PlayerScore updatedScore))
{
updatedScore.Score = score;
updatedScore.ScorePanel.SetScore(score);
PlayersScores[playerId] = updatedScore;
}
if ( score <= 0) //false &&
{
OnScoreReachingZero.Invoke(this, new ScoreEventArgs(playerId));
}
}
public void AddToScore(float score, int playerId)
{
if (PlayersScores.TryGetValue(playerId, out PlayerScore updatedScore))
{
updatedScore.Score += score;
SetScore(updatedScore.Score, playerId);
}
}
public void AddScoreToAllAlivePlayers(float scoreToAdd)
{
var keys = new List<int>(PlayersScores.Keys);
foreach (var key in keys)
{
AddToScore(scoreToAdd, key);
}
}
public float GetPlayerScore(int playerId)
{
return PlayersScores.TryGetValue(playerId, out PlayerScore pscore)
? pscore.Score
: OuttedPlayers.TryGetValue(playerId, out PlayerScore oscore)
? oscore.Score
: 0;
}
#endregion
#region METHODS
#endregion
#region LIFECYCLE
protected override void Awake()
{
base.Awake();
_gameController = GameController.Instance;
_playersHandler = PlayersHandler.Instance;
_gameHandler = GameHandler.Instance;
_dataBearer = DataBearer.Instance;
_playersHandler.OnPlayerFinished += RemovePlayer_OnPlayerFinished;
_gameHandler.OnAttemptEnding += RemoveScores_OnAttemptEnded;
PlayersScores = new();
OuttedPlayers = new();
}
#endregion
struct PlayerScore
{
internal string PlayerName { get; set; }
internal int PlayerId { get; set; }
internal float Score { get; set; }
internal ScorePannelScript ScorePanel { get; set; }
public PlayerScore(string playerName, int playerId, ScorePannelScript scorePannel)
{
PlayerName = playerName;
PlayerId = playerId;
Score = 0f;
ScorePanel = scorePannel;
}
}
}
#region PROPERTIES
#endregion
#region VARIABLES
#endregion
#region EVENTS
#endregion
#region METHODS
#endregion
#region LIFECYCLE
#endregion

View File

@@ -0,0 +1,21 @@
using Assets.Scripts;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BadCoinScript : MonoBehaviour
{
[SerializeField] float SpawnChance = 1f;
private bool objectEnabled;
private void Awake()
{
objectEnabled = DataBearer.Instance.Rules.MalusCoins && Random.value < SpawnChance;
if (!objectEnabled)
{
Destroy(gameObject);
return;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FinishLineScript : MonoBehaviour
{
private GameHandler _gameHandler;
// Start is called before the first frame update
void Awake()
{
_gameHandler = GameHandler.Instance;
_gameHandler.OnAttemptEnding += Destroy_OnAttemptEnding;
}
// Update is called once per frame
void Update()
{
gameObject.transform.position += new Vector3(-_gameHandler.FrameDistance, 0, 0);
}
public void Destroy_OnAttemptEnding(object sender, EventArgs args)
{
Destroy(gameObject);
}
private void OnDestroy()
{
GameHandler.Instance.OnAttemptEnding -= Destroy_OnAttemptEnding;
}
}

View File

@@ -0,0 +1,22 @@
using Assets.Scripts;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GoodCoinScript : MonoBehaviour
{
[SerializeField] float SpawningChance = 1f;
private bool objectEnabled;
private void Awake()
{
objectEnabled = DataBearer.Instance.Rules.BonusCoins && Random.value < SpawningChance;
if (!objectEnabled)
{
Destroy(gameObject);
return;
}
}
}

View File

@@ -0,0 +1,47 @@
using Assets.Scripts;
using UnityEngine;
public class CapsulePlacer : MonoBehaviour
{
public Transform objectA;
public Transform objectB;
private bool laserEnabled;
private void Awake()
{
if(objectA == null || objectB == null)
{
laserEnabled = false;
Debug.LogError("Les boules du laser ne sont pas définis.");
}
laserEnabled = DataBearer.Instance.Rules.Lasers;
if (!laserEnabled)
{
Destroy(gameObject);
return;
}
}
private void Update()
{
Vector2 positionA = objectA.position;
Vector2 positionB = objectB.position;
// Calcul de la distance et mise à jour de l'échelle
float distance = Vector2.Distance(positionA, positionB);
Vector3 localScale = transform.localScale;
localScale.y = distance / 2f;
transform.localScale = localScale;
// Mise à jour de la position
transform.position = (positionA + positionB) / 2f;
// Calcul de la direction et de l'angle
Vector2 direction = positionB - positionA;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg + 90f;
// Mise à jour de la rotation
transform.rotation = Quaternion.Euler(0f, 0f, angle);
}
}

View File

@@ -0,0 +1,77 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MissileAlertScript : MonoBehaviour
{
#region PROPERTIES
#endregion
#region VARIABLES
private float blinkInterval = 0.2f;
private bool isBlinking = false;
private SpriteRenderer spriteRenderer;
private Vector3 position;
#endregion
#region EVENTS
#endregion
#region METHODS
private IEnumerator Blink()
{
isBlinking = true;
while (isBlinking)
{
SetSpriteVisible(true);
yield return new WaitForSeconds(blinkInterval);
SetSpriteVisible(false);
yield return new WaitForSeconds(blinkInterval);
}
}
private void SetSpriteVisible(bool isVisible)
{
spriteRenderer.enabled = isVisible;
}
#endregion
#region LIFECYCLE
private void Awake()
{
position = transform.position;
spriteRenderer = GetComponent<SpriteRenderer>();
var mainCamera = Camera.main;
position.x = mainCamera.transform.position.x + mainCamera.orthographicSize * mainCamera.aspect - 1f;
transform.position = position;
SetSpriteVisible(false);
enabled = false;
}
private void OnEnable()
{
if (!isBlinking)
{
StartCoroutine(Blink());
}
}
private void OnDisable()
{
if (isBlinking)
{
StopCoroutine(Blink());
SetSpriteVisible(false); // Assure que le sprite est invisible quand on arr<72>te le clignotement
isBlinking = false;
}
}
#endregion
}

View File

@@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MissileScript : MonoBehaviour
{
#region PROPERTIES
public int TargetId { get; set; }
#endregion
#region VARIABLES
[SerializeField] private float speed = 15.0f;
[SerializeField] private GameObject explosion;
private bool renderSprite;
private GameHandler _gameHandler;
#endregion
#region METHODS
public void Explode()
{
StopParticles();
if (renderSprite)
{
var rotation = transform.rotation;
rotation.z = 180;
Instantiate(explosion, transform.position, rotation);
}
Destroy(gameObject);
return;
}
public void SetRenderSprite(bool render)
{
renderSprite = render;
}
public void SetTarget(int id)
{
TargetId = id;
}
public void StopParticles()
{
if (transform.childCount == 0) return;
var emit = transform?.GetChild(0);
emit.parent = null;
emit.GetComponent<ParticleSystem>().Stop(true, ParticleSystemStopBehavior.StopEmitting);
// This finds the particleAnimator associated with the emitter and then
// sets it to automatically delete itself when it runs out of particles
}
#endregion
#region LIFECYCLE
void Start()
{
_gameHandler = GameHandler.Instance;
}
// Update is called once per frame
void Update()
{
if(transform.position.x < -20)
{
StopParticles();
Destroy(gameObject);
return;
}
//Go up because up is the direction of the head of the missile, even if he goes left the player's POV
transform.Translate(Vector3.up * (speed+_gameHandler.MapSpeed) * Time.deltaTime);
}
#endregion
}

View File

@@ -0,0 +1,106 @@
using System.Collections;
using System.Collections.Generic;
using Unity.MLAgents;
using UnityEngine;
public class ObstaclePatternBehaviour : MonoBehaviour
{
[SerializeField] private GameHandler gameHandler;
private float gameObjectLength;
private float gameSpeed;
private GameHandler _gameHandler;
// Start is called before the first frame update
void Awake()
{
_gameHandler = GameHandler.Instance;
}
private void Start()
{
gameObjectLength = ComputeLengthRecursive(0.0f, gameObject.transform) - gameObject.transform.position.x;
}
// Update is called once per frame
void Update()
{
// move the obstacle depending on GameHandler's offset value
gameObject.transform.position += new Vector3(-_gameHandler.FrameDistance, 0, 0);
//kill the obstacle if it reach the GameHandler's dead end value
var position = gameObject.transform.position.x;
var spawnerPosition = LevelSpawner.Instance.transform.position.x;
//Debug.Log("position : " + position+"\n"+"scale : " + gameObjectLength);
if(position < -gameObjectLength - spawnerPosition)
{
//Debug.Log("DESTROY");
MonoBehaviour.Destroy(gameObject);
return;
}
}
private float ComputeLengthRecursive(float max, Transform @object)
{
foreach (Transform child in @object)
{
if(child.position.x > max)
{
max = child.position.x;
}
max = ComputeLengthRecursive(max, child);
}
return max;
}
//private float ComputeLength()
//{
// Debug.Log("ComputeLength");
// CenterAndChildCount centerAndChildCount = new();
// centerAndChildCount = ComputeCenterRecursive(centerAndChildCount, gameObject.transform);
// Vector3 center = centerAndChildCount.center;
// int childCount = centerAndChildCount.childCount;
// center /= childCount; //center is average center of children
// Bounds bounds = new Bounds(center, Vector3.zero);
// bounds = ComputeBoundsRecursive(bounds, gameObject.transform);
// return bounds.size.x;
//}
//private CenterAndChildCount ComputeCenterRecursive(CenterAndChildCount centerAndChildCount, Transform @object)
//{
// foreach (Transform child in @object)
// {
// centerAndChildCount.center += child.gameObject.GetComponent<Renderer>().bounds.center;
// centerAndChildCount.childCount++;
// centerAndChildCount = ComputeCenterRecursive(centerAndChildCount, child);
// }
// return centerAndChildCount;
//}
//private Bounds ComputeBoundsRecursive(Bounds bounds, Transform @object)
//{
// foreach (Transform child in @object)
// {
// ComputeBoundsRecursive(bounds, child);
// bounds.Encapsulate(child.gameObject.GetComponent<Renderer>().bounds);
// }
// return bounds;
//}
//private class CenterAndChildCount
//{
// public Vector3 center = Vector3.zero;
// public int childCount = 0;
//}
}

View File

@@ -0,0 +1,166 @@
using System;
using UnityEngine;
using static ScoreHandler;
public class PlayerScript : MonoBehaviour
{
#region PROPERTIES
public bool IsHuman { get; set; } = false;
public string Name { get; private set; }
public int Id { get; private set; }
public Color Color { get; private set; } = new Color(255, 255, 255);
public bool IsGrounded { get; private set; } = false;
public bool IsFlying { get; private set; } = false;
#endregion
#region VARIABLES
private ScoreHandler _scoreHandler;
private PlayersHandler _playersHandler;
public float jumpForce = 8f;
private Rigidbody2D rb;
public float maxVelocity = 8f;
public float minVelocity = -8f;
#endregion
#region EVENTS
public event EventHandler<PrefabTouchedEventArgs> OnObstacleTouched;
public event EventHandler<PrefabTouchedEventArgs> OnBonusTouched;
public class PrefabTouchedEventArgs : EventArgs
{
public GameObject objectTouched;
public int playerId;
public string playerName;
public PrefabTouchedEventArgs(GameObject objectTouched, int playerId, string playerName)
{
this.objectTouched = objectTouched;
this.playerId = playerId;
this.playerName = playerName;
}
}
private void Lose_OnScoreReachingZero(object sender, ScoreEventArgs args)
{
if(args.playerId == Id)
_playersHandler.RemovePlayer(Id);
}
#endregion
#region METHODS
public void MoveVertically(sbyte direction,float custom_DeltaTime)
{
IsFlying=(direction>0);
rb.AddForce(Vector2.up * (direction*jumpForce) * custom_DeltaTime);
}
public void SetName(string name)
{
Name = name;
}
public void SetId(int id)
{
Id = id;
}
public void SetColor(Color color)
{
Color = color;
}
public void Dispose()
{
Destroy(gameObject);
}
#endregion
#region LIFECYCLE
void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb.freezeRotation = true;
_scoreHandler = ScoreHandler.Instance;
_playersHandler = PlayersHandler.Instance;
_scoreHandler.OnScoreReachingZero += Lose_OnScoreReachingZero;
}
private void OnTriggerEnter2D(Collider2D other)
{
// Vérifier si la collision concerne l'objet que vous souhaitez détecter
switch (other.tag)
{
case "Floor":
IsGrounded = true;
break;
case "Laser":
OnObstacleTouched.Invoke(this, new PrefabTouchedEventArgs(other.gameObject, Id, Name));
_scoreHandler.AddToScore(-200, Id);
break;
case "GoodCoin":
if (IsHuman)
other.gameObject.GetComponent<SpriteRenderer>().enabled = false;
OnBonusTouched.Invoke(this, new PrefabTouchedEventArgs(other.gameObject, Id, Name));
_scoreHandler.AddToScore(50, Id);
break;
case "BadCoin":
if (IsHuman)
other.gameObject.GetComponent<SpriteRenderer>().enabled = false;
OnObstacleTouched.Invoke(this, new PrefabTouchedEventArgs(other.gameObject, Id, Name));
_scoreHandler.AddToScore(-50, Id);
break;
case "Missile":
MissileScript missileScript = other.transform.GetComponent<MissileScript>();
if (missileScript.TargetId == Id)
{
OnObstacleTouched.Invoke(this, new PrefabTouchedEventArgs(other.gameObject, Id, Name));
other.GetComponent<MissileScript>().Explode();
_scoreHandler.AddToScore(-500, Id);
}
break;
default:
break;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Floor"))
{
IsGrounded = false;
}
}
void Update()
{
// Récupérer la vélocité actuelle du Rigidbody2D
Vector2 currentVelocity = rb.velocity;
// Limiter la vélocité verticale
float clampedVelocityY = Mathf.Clamp(currentVelocity.y, minVelocity, maxVelocity);
rb.velocity = new Vector2(currentVelocity.x, clampedVelocityY);
// Appliquer une force constante vers le haut ou le bas en fonction de la touche enfoncée
//float verticalForce = Input.GetButton("Jump") ? jumpForce : -jumpForce;
//rb.AddForce(Vector2.up * verticalForce * Time.deltaTime);
// Effectuer le raycast pour vérifier si le joueur est au sol
//RaycastHit2D hit = Physics2D.Raycast(rb.position, Vector2.down, 0.25f);
//IsGrounded = hit.collider != null && hit.transform.CompareTag("Floor");
}
#endregion
}

View File

@@ -0,0 +1,80 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
/// <summary>
/// Instanciate a ScorePannel view on the UI. This class only serves as view and does not contain any game data and does not affect the game's work
/// </summary>
public class ScorePannelScript : MonoBehaviour
{
#region PROPERTIES
#endregion
#region VARIABLES
[SerializeField] private VisualTreeAsset scorePannelTemplate; // R<>f<EFBFBD>rence au template UXML du ScorePannel
private VisualElement scoreContainer; // Le VisualElement parent o<> ajouter le ScorePannel
private VisualElement root;
private Label scoreLabel;
private Label playerNameLabel;
private VisualElement scorePanel;
#endregion
#region EVENTS
#endregion
#region ENDPOINTS
public void SetPlayerName(string playerName)
{
playerNameLabel.text = playerName;
}
public void SetScore(float score)
{
scoreLabel.text = score.ToString("0");
}
public void SetColor(Color color)
{
scorePanel.style.backgroundColor = new StyleColor(new Color(color.r, color.g, color.b, 0.43f));
}
public void Dispose()
{
scoreContainer.Remove(root);
Destroy(gameObject);
}
#endregion
#region LIFECYCLE
void OnEnable()
{
Debug.Log("SCORE Instanciatied");
var uiDocument = GetComponentInParent<UIDocument>(); // Obtenir le composant UIDocument attach<63> <20> l'objet
// V<>rifiez que les r<>f<EFBFBD>rences sont assign<67>es
if (uiDocument == null || scorePannelTemplate == null)
{
Debug.LogError("UiDocument ou scorePannelTemplate n'est pas assign<67>.");
return;
}
var rootVisualElement = uiDocument.rootVisualElement;
// V<>rifiez que ScoresContainer existe dans l'arborescence de l'UI
scoreContainer = rootVisualElement.Q<VisualElement>("ScoresContainer");
if (scoreContainer == null)
{
Debug.LogError("ScoresContainer n'a pas <20>t<EFBFBD> trouv<75> dans le UXML.");
return;
}
// Charger et instancier le ScorePannel
root = scorePannelTemplate.CloneTree();
scorePanel = root.Q<VisualElement>("panel");
scoreLabel = root.Q<Label>("ScoreValue");
playerNameLabel = root.Q<Label>("PlayerName");
scoreContainer.Add(root);
root.AddToClassList("scorePanel");
}
#endregion
}

View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
[UnityEngine.Scripting.Preserve]
public class AspectRatioPanel : VisualElement
{
[UnityEngine.Scripting.Preserve]
public new class UxmlFactory : UxmlFactory<AspectRatioPanel, UxmlTraits> { }
[UnityEngine.Scripting.Preserve]
public new class UxmlTraits : VisualElement.UxmlTraits
{
readonly UxmlIntAttributeDescription aspectRatioX = new() { name = "aspect-ratio-x", defaultValue = 16, restriction = new UxmlValueBounds { min = "1" } };
readonly UxmlIntAttributeDescription aspectRatioY = new() { name = "aspect-ratio-y", defaultValue = 9, restriction = new UxmlValueBounds { min = "1" } };
readonly UxmlIntAttributeDescription balanceX = new() { name = "balance-x", defaultValue = 50, restriction = new UxmlValueBounds { min = "0", max = "100" } };
readonly UxmlIntAttributeDescription balanceY = new() { name = "balance-y", defaultValue = 50, restriction = new UxmlValueBounds { min = "0", max = "100" } };
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get { yield break; }
}
public override void Init(VisualElement visualElement, IUxmlAttributes attributes, CreationContext creationContext)
{
base.Init(visualElement, attributes, creationContext);
var element = visualElement as AspectRatioPanel;
if (element != null)
{
element.AspectRatioX = Mathf.Max(1, aspectRatioX.GetValueFromBag(attributes, creationContext));
element.AspectRatioY = Mathf.Max(1, aspectRatioY.GetValueFromBag(attributes, creationContext));
element.BalanceX = Mathf.Clamp(balanceX.GetValueFromBag(attributes, creationContext), 0, 100);
element.BalanceY = Mathf.Clamp(balanceY.GetValueFromBag(attributes, creationContext), 0, 100);
element.FitToParent();
}
}
}
public int AspectRatioX { get; private set; } = 16;
public int AspectRatioY { get; private set; } = 9;
public int BalanceX { get; private set; } = 50;
public int BalanceY { get; private set; } = 50;
public AspectRatioPanel()
{
style.position = Position.Absolute;
style.left = 0;
style.top = 0;
style.right = StyleKeyword.Undefined;
style.bottom = StyleKeyword.Undefined;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelEvent);
}
void OnAttachToPanelEvent(AttachToPanelEvent e)
{
parent?.RegisterCallback<GeometryChangedEvent>(OnGeometryChangedEvent);
FitToParent();
}
void OnGeometryChangedEvent(GeometryChangedEvent e)
{
FitToParent();
}
void FitToParent()
{
if (parent == null) return;
var parentW = parent.resolvedStyle.width;
var parentH = parent.resolvedStyle.height;
if (float.IsNaN(parentW) || float.IsNaN(parentH)) return;
style.position = Position.Absolute;
style.left = 0;
style.top = 0;
style.right = StyleKeyword.Undefined;
style.bottom = StyleKeyword.Undefined;
if (AspectRatioX <= 0.0f || AspectRatioY <= 0.0f)
{
style.width = parentW;
style.height = parentH;
return;
}
var ratio = Mathf.Min(parentW / AspectRatioX, parentH / AspectRatioY);
var targetW = Mathf.Floor(AspectRatioX * ratio);
var targetH = Mathf.Floor(AspectRatioY * ratio);
style.width = targetW;
style.height = targetH;
var marginX = parentW - targetW;
var marginY = parentH - targetH;
style.left = Mathf.Floor(marginX * BalanceX / 100.0f);
style.top = Mathf.Floor(marginY * BalanceY / 100.0f);
}
}

View File

@@ -0,0 +1,84 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class LabelAutoFit : Label
{
[UnityEngine.Scripting.Preserve]
public new class UxmlFactory : UxmlFactory<LabelAutoFit, UxmlTraits> { }
[UnityEngine.Scripting.Preserve]
public new class UxmlTraits : Label.UxmlTraits
{
readonly UxmlIntAttributeDescription minFontSize = new UxmlIntAttributeDescription
{
name = "min-font-size",
defaultValue = 10,
restriction = new UxmlValueBounds { min = "1" }
};
readonly UxmlIntAttributeDescription maxFontSize = new UxmlIntAttributeDescription
{
name = "max-font-size",
defaultValue = 200,
restriction = new UxmlValueBounds { min = "1" }
};
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription { get { yield break; } }
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
LabelAutoFit instance = ve as LabelAutoFit;
instance.minFontSize = Mathf.Max(minFontSize.GetValueFromBag(bag, cc), 1);
instance.maxFontSize = Mathf.Max(maxFontSize.GetValueFromBag(bag, cc), 1);
instance.RegisterCallback<GeometryChangedEvent>(instance.OnGeometryChanged);
instance.style.fontSize = 1; // Triggers OnGeometryChanged callback
}
}
// Setting a limit of max text font refreshes from a single OnGeometryChanged to avoid repeating cycles in some extreme cases
private const int MAX_FONT_REFRESHES = 2;
private int m_textRefreshes = 0;
public int minFontSize { get; set; }
public int maxFontSize { get; set; }
// Call this if the font size does not update by just setting the text
// Should probably wait till the end of frame to get the real font size, instead of using this method
// Works well in OnRenderObject() too
public void SetText(string text)
{
this.text = text;
UpdateFontSize();
}
private void OnGeometryChanged(GeometryChangedEvent evt)
{
UpdateFontSize();
}
private void UpdateFontSize()
{
if (m_textRefreshes < MAX_FONT_REFRESHES)
{
Vector2 textSize = MeasureTextSize(text, float.MaxValue, MeasureMode.AtMost, float.MaxValue, MeasureMode.AtMost);
float fontSize = Mathf.Max(style.fontSize.value.value, 1); // Unity can return a font size of 0 which would break the auto fit // Should probably wait till the end of frame to get the real font size
float heightDictatedFontSize = Mathf.Abs(contentRect.height);
float widthDictatedFontSize = Mathf.Abs(contentRect.width / textSize.x) * fontSize;
float newFontSize = Mathf.FloorToInt(Mathf.Min(heightDictatedFontSize, widthDictatedFontSize));
newFontSize = Mathf.Clamp(newFontSize, minFontSize, maxFontSize);
if (Mathf.Abs(newFontSize - fontSize) > 1)
{
m_textRefreshes++;
style.fontSize = new StyleLength(new Length(newFontSize));
}
}
else
{
m_textRefreshes = 0;
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class GameUI : MonoBehaviour
{
#region PROPERTIES
#endregion
#region VARIABLES
private UIDocument _document;
private Button _pauseButton;
private Button _playButton;
private Button _exitButton;
#endregion
#region EVENTS
private void OnPauseButton(ClickEvent e)
{
GameHandler.Instance.PauseGame();
}
private void OnPlayButton(ClickEvent e)
{
GameHandler.Instance.ResumeGame();
}
private void OnExitButton(ClickEvent e)
{
GameController.Instance.ExitToMenu("Retour au menu principal");
}
private void React_OnGamePaused(object sender, EventArgs e)
{
ChangeButtonsVisibility(true);
}
private void React_OnGameResumed(object sender, EventArgs e)
{
ChangeButtonsVisibility(false);
}
#endregion
#region METHODS
private void ChangeButtonsVisibility(bool isPaused)
{
if (isPaused)
{
_pauseButton.style.display = DisplayStyle.None;
_playButton.style.display = DisplayStyle.Flex;
_exitButton.style.display = DisplayStyle.Flex;
}
else
{
_playButton.style.display = DisplayStyle.None;
_exitButton.style.display = DisplayStyle.None;
_pauseButton.style.display = DisplayStyle.Flex;
}
}
#endregion
#region LIFECYCLE
private void Awake()
{
_document=GetComponent<UIDocument>();
_pauseButton = _document.rootVisualElement.Q("PauseButton") as Button;
_pauseButton.RegisterCallback<ClickEvent>(OnPauseButton);
_playButton = _document.rootVisualElement.Q("PlayButton") as Button;
_playButton.RegisterCallback<ClickEvent>(OnPlayButton);
_exitButton = _document.rootVisualElement.Q("ExitButton") as Button;
_exitButton.RegisterCallback<ClickEvent>(OnExitButton);
ChangeButtonsVisibility(false);
GameHandler.Instance.OnGamePaused += React_OnGamePaused;
GameHandler.Instance.OnGameResumed += React_OnGameResumed;
}
#endregion
}

View File

@@ -0,0 +1,41 @@
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;
public class HoverDescriptionScript : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private string description; // La description à afficher
private TextMeshProUGUI descriptionText; // Référence au TextMeshPro pour afficher la description
private void Start()
{
// Trouver l'objet Infobox dans la scène et obtenir le TextMeshPro
GameObject infobox = GameObject.Find("Infobox");
if (infobox != null)
{
descriptionText = infobox.GetComponentInChildren<TextMeshProUGUI>();
}
else
{
Debug.LogError("Infobox not found in the scene.");
}
}
// Méthode appelée quand la souris entre dans le collider de l'objet
public void OnPointerEnter(PointerEventData eventData)
{
if (descriptionText != null)
{
descriptionText.text = description;
}
}
// Méthode appelée quand la souris sort du collider de l'objet
public void OnPointerExit(PointerEventData eventData)
{
if (descriptionText != null)
{
descriptionText.text = "Survolez un element pour en avoir une description !";
}
}
}

View File

@@ -0,0 +1,256 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
using Unity.VisualScripting;
public class MenuController : MonoBehaviour
{
[Header("Play Button")]
[SerializeField] Button playerButton;
[Header("Player Selection Settings")]
[SerializeField] Toggle omnicientBotToggler = null;
[SerializeField] Toggle observerBotToggler = null;
[SerializeField] Toggle iteratorBotToggler = null;
[SerializeField] Toggle humanPlayerToggler = null;
[Header("Level Settings")]
[SerializeField] Toggle laserToggler = null;
[SerializeField] Toggle missileToggler = null;
[SerializeField] Toggle coinToggler = null;
[SerializeField] Toggle badCoinToggler = null;
[SerializeField] Toggle surviveScoreToggler = null;
[Header("Seed Settings")]
[SerializeField] Toggle changeSeedToggler = null;
[SerializeField] Toggle useDefaultSeedToggler = null;
[SerializeField] TMP_InputField defaultSeedInputField = null;
[SerializeField] TMP_InputField customSeedInputField = null;
[Header("Session Settings")]
[SerializeField] Slider maxSpeedSlider = null;
[SerializeField] Slider startSpeedSlider = null;
[SerializeField] Slider difficultyMultiplyerSlider = null;
[SerializeField] Toggle rushModeToggler = null;
[SerializeField] TMP_InputField maxTryInputField = null;
[SerializeField] TMP_InputField maxTimeInputField = null;
[SerializeField] TMP_InputField maxDistanceInputField = null;
[Header("Menu Controller")]
public string _statsScene;
public string _playScene;
private void Start()
{
Time.timeScale = 1;
SetMaxSpeed();
SetStartSpeed();
SetDifficultyMultiplyer();
SetRushModeToggle();
SetMaxTry();
SetMaxTime();
SetMaxDistance();
SetChangeSeed();
SetUseDefaultSeed();
SetDefaultSeedValue();
SetCustomSeedValue();
SetLaserToggle();
SetMissileToggle();
SetCoinToggle();
SetBadCoinToggle();
SetSurviveScoreToggle();
SetOmnicientBotSelected();
SetObserverBotSelected();
SetIteratorBotSelected();
SetHumanPlayerSelected();
}
public void StatsButtonClicked()
{
Debug.Log("=============================================");
SceneManager.LoadScene(_statsScene);
}
public void PlayButtonClicked()
{
Debug.Log("=============================================");
SceneManager.LoadScene(_playScene);
}
void OnPlayerSelectionChanged()
{
bool omnicient = omnicientBotToggler.isOn;
bool observer = observerBotToggler.isOn;
bool iterator = iteratorBotToggler.isOn;
bool human = humanPlayerToggler.isOn;
playerButton.interactable = (omnicient || observer || iterator || human);
}
public void QuitButtonClicked()
{
Debug.Log("Game quitting");
Application.Quit();
}
// Session Rules
public void SetMaxSpeed()
{
float speed = maxSpeedSlider.value;
PlayerPrefs.SetFloat(Settings.MaxSpeedSettingValue, speed);
Debug.Log($"Game's max speed is set to {speed}");
}
public void SetStartSpeed()
{
float speed = startSpeedSlider.value;
PlayerPrefs.SetFloat(Settings.StartSpeedSettingValue, speed);
Debug.Log($"Game's starting speed is set to {speed}");
}
public void SetDifficultyMultiplyer()
{
float value = difficultyMultiplyerSlider.value;
PlayerPrefs.SetFloat(Settings.DifficultyMultiplierSettingValue, value);
Debug.Log($"Game's difficulty multiplyer is set to {value}");
}
public void SetRushModeToggle()
{
bool toggle = rushModeToggler.isOn;
PlayerPrefs.SetString(Settings.RushModeSettingToggle, toggle.ToString());
Debug.Log($"Rush Mode has been toggeled to {toggle}");
}
public void SetMaxTry()
{
int value = 0;
string text = maxTryInputField.text;
if (text != "")
{
value = int.Parse(text);
}
PlayerPrefs.SetInt(Settings.MaxTrySettingValue, value);
Debug.Log($"Game's maximum try count is set to {value}");
}
public void SetMaxTime()
{
int seconds = 0;
string text = maxTimeInputField.text;
if (text != "")
{
seconds = int.Parse(text);
}
PlayerPrefs.SetInt(Settings.MaxTimeSettingValue, seconds);
Debug.Log($"Game's session maximum time is set to {seconds} seconds");
}
public void SetMaxDistance()
{
int distance = 0;
string text = maxDistanceInputField.text;
if (text != "")
{
distance = int.Parse(text);
}
PlayerPrefs.SetFloat(Settings.MaxDistanceSettingValue, distance);
Debug.Log($"Level's max distance is set to {distance} units");
}
// Seed Rules
public void SetChangeSeed()
{
bool toggle = changeSeedToggler.isOn;
PlayerPrefs.SetString(Settings.ChangeSeedSetting, toggle.ToString());
Debug.Log($"Changing seed for each level is set to {toggle}");
}
public void SetUseDefaultSeed()
{
bool toggle = useDefaultSeedToggler.isOn;
PlayerPrefs.SetString(Settings.UseDefaultSeedSetting, toggle.ToString());
Debug.Log($"Usign default seed is set to {toggle}");
}
public void SetDefaultSeedValue()
{
int value = 0;
string text = defaultSeedInputField.text;
if(text != "")
{
value = int.Parse(text);
}
PlayerPrefs.SetInt(Settings.DefaultSeedSettingValue, value);
Debug.Log($"Default seed has been set to {value}");
}
public void SetCustomSeedValue()
{
int value = 0;
string text = customSeedInputField.text;
if (text != "")
{
value = int.Parse(text);
}
PlayerPrefs.SetInt(Settings.CustomSeedSettingValue, value);
Debug.Log($"Custom seed has been set to {value}");
}
// Level Rules
public void SetLaserToggle()
{
bool toggle = laserToggler.isOn;
PlayerPrefs.SetString(Settings.LaserSettingToggle, toggle.ToString());
Debug.Log($"Laser have been toggeled to {toggle}");
}
public void SetMissileToggle()
{
bool toggle = missileToggler.isOn;
PlayerPrefs.SetString(Settings.MissileSettingToggle, toggle.ToString());
Debug.Log($"Missiles have been toggeled to {toggle}");
}
public void SetCoinToggle()
{
bool toggle = coinToggler.isOn;
PlayerPrefs.SetString(Settings.CoinSettingToggle, toggle.ToString());
Debug.Log($"Coins have been toggeled to {toggle}");
}
public void SetBadCoinToggle()
{
bool toggle = badCoinToggler.isOn;
PlayerPrefs.SetString(Settings.BadCoinSettingToggle, toggle.ToString());
Debug.Log($"Bad Coins have been toggeled to {toggle}");
}
public void SetSurviveScoreToggle()
{
bool toggle = surviveScoreToggler.isOn;
PlayerPrefs.SetString(Settings.SurviveScoreSettingToggle, toggle.ToString());
Debug.Log($"Increasing score with surviving time has been toggeled to {toggle}");
}
// Player Selection
public void SetOmnicientBotSelected()
{
bool toggle = omnicientBotToggler.isOn;
PlayerPrefs.SetString(Settings.OmnicientBotSelected, toggle.ToString());
OnPlayerSelectionChanged();
Debug.Log($"Omnicient bot selected has been set to {toggle}");
}
public void SetObserverBotSelected()
{
bool toggle = observerBotToggler.isOn;
PlayerPrefs.SetString(Settings.ObserverBotSelected, toggle.ToString());
OnPlayerSelectionChanged();
Debug.Log($"Observer bot selected has been set to {toggle}");
}
public void SetIteratorBotSelected()
{
bool toggle = iteratorBotToggler.isOn;
PlayerPrefs.SetString(Settings.IteratorBotSelected, toggle.ToString());
OnPlayerSelectionChanged();
Debug.Log($"Iterator bot selected has been set to {toggle}");
}
public void SetHumanPlayerSelected()
{
bool toggle = humanPlayerToggler.isOn;
PlayerPrefs.SetString(Settings.HumanPlayerSelected, toggle.ToString());
OnPlayerSelectionChanged();
Debug.Log($"Human player selected has been set to {toggle}");
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayButtonBehaviour : MonoBehaviour
{
[Header("Player Selection Buttons")]
[SerializeField] Toggle omnicientBot;
[SerializeField] Toggle observerBot;
[SerializeField] Toggle iteratorBot;
[SerializeField] Toggle humanPlayer;
// Start is called before the first frame update
void Start()
{
}
}

View File

@@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class ScrollerBehavior : MonoBehaviour
{
[SerializeField] TMP_Text sliderValue = null;
[SerializeField] Slider slider = null;
[SerializeField] Slider intricatedSlider = null;
[SerializeField] bool canBeSuperiorToIntricated = false;
[SerializeField] bool canBeInferiorToIntricated = false;
float intricatedValue = 0.0f;
void Start()
{
OnIntricatedValueChanged();
OnValueChanged();
}
public void OnValueChanged()
{
float value = slider.value;
if(intricatedSlider != null)
{
if ((!canBeSuperiorToIntricated && value >= intricatedValue) || (!canBeInferiorToIntricated && value <= intricatedValue))
{
slider.value = intricatedValue;
}
}
sliderValue.text = slider.value.ToString("0.0");
}
// Call this method when an intricated slider change its value
public void OnIntricatedValueChanged()
{
if(intricatedSlider != null)
intricatedValue = intricatedSlider.value;
}
}

View File

@@ -0,0 +1,25 @@
using UnityEngine;
public class ScrollingTexture : MonoBehaviour
{
[SerializeField] private float scrollSpeedX;
[SerializeField] private float scrollSpeedY;
private CanvasRenderer canvasRenderer;
// Start is called before the first frame update
void Start()
{
canvasRenderer = GetComponent<CanvasRenderer>();
}
// Update is called once per frame
void Update()
{
Vector2 textureOffset = new Vector2(Time.realtimeSinceStartup * scrollSpeedX, Time.realtimeSinceStartup * scrollSpeedY);
if(canvasRenderer.GetMaterial() != null) {
canvasRenderer.GetMaterial().mainTextureOffset = textureOffset;
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
public struct GameState
{
public List<int> AlivePlayers { get; set; }
public float CurrentDifficultyMultiplier { get; set; }
}

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
public class GenericVector<TX, TY>
{
public TX X { get; set; }
public TY Y { get; set; }
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
public class SessionData
{
public DateTime Date { get; set; } //
public int SessionNumber { get; set; }
public double SessionTime { get; set; } //
public Players Players { get; set; } = new(); //
public Rules Rules { get; set; } = new(); //
public List<Attempt> Attempts { get;
set; }
= new();
public List<RegisteredPlayer> RegisteredPlayers { get; set; } = new(); //
}
public class Players
{
public bool OmnicientBot { get; set; }
public bool ObserverBot { get; set; }
public bool IteratorBot { get; set; }
public bool Human { get; set; }
} //
public class RegisteredPlayer
{
public string Name { get; set; }
public int Id { get; set; }
public bool Human { get; set; }
} //
public class Rules
{
public bool Lasers { get; set; }
public bool Missiles { get; set; }
public bool BonusCoins { get; set; }
public bool MalusCoins { get; set; }
public bool ScoreOnDistance { get; set; }
public bool ChangeSeedEachTry { get; set; }
public bool UsesDefaultSeed { get; set; }
public int MaxAttempts { get; set; }
public int MaxSessionTime { get; set; }
public float MaxDistance { get; set; }
public float MaxSpeed { get; set; }
public float StartSpeed { get; set; }
public float DifficultyMultiplier { get; set; }
public bool RushMode { get; set; }
} //
public class Attempt
{
public int AttemptNumber { get; set; } //
public int Seed { get; set; } //
public Dictionary<string, PlayerAttempt> PlayersAttempts { get; set; } = new();
public List<LevelPrefab> Level { get; set; } = new(); //
}
public class PlayerAttempt
{
public string PlayerName { get; set; } //
public int FinalScore { get; set; } //
public int Distance { get; set; } //
public int AttemptDurationTicks { get; set; } //
public double AttemptDurationSeconds { get; set; } //
public int TimeTouchingBorder { get; set; }
public List<GenericVector<int, int>> PlayerScoresOverTicksTime { get; set; } = new(); //
public List<TouchedGameObject> BonusTouched { get; set; } = new(); //
public List<TouchedGameObject> ObstaclesTouched { get; set; } = new(); //
}
public class TouchedGameObject //
{
public string ObjectTypeName { get; set; }
public string PrefabName { get; set; }
public string FullObjectName { get; set; }
public float GameSpeed { get; set; }
public int TickTimeWhenTouched { get; set; }
}
public class LevelPrefab
{
public string PrefabName { get; set; }
public int PrefabNumber { get; set; }
public int TickTimeWhenTouched { get; set; }
} //

View File

@@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Settings
{
public const string MaxSpeedSettingValue = "MaxSpeedSettingValue";
public const string StartSpeedSettingValue = "StartSpeedSettingValue";
public const string DifficultyMultiplierSettingValue = "DifficultyMultiplyerSettingValue";
public const string RushModeSettingToggle = "RushModeSettingToggle";
public const string MaxTrySettingValue = "MaxTrySettingValue";
public const string MaxTimeSettingValue = "MaxTimeSettingValue";
public const string MaxDistanceSettingValue = "MaxDistanceSettingValue";
public const string LaserSettingToggle = "LaserSettingToggle";
public const string MissileSettingToggle = "MissileSettingToggle";
public const string CoinSettingToggle = "CoinSettingToggle";
public const string BadCoinSettingToggle = "BadCoinSettingToggle";
public const string SurviveScoreSettingToggle = "SurviveScoreSettingToggle";
public const string ChangeSeedSetting = "ChangeSeedSetting";
public const string UseDefaultSeedSetting = "UseDefaultSeedSetting";
public const string DefaultSeedSettingValue = "DefaultSeedSettingValue";
public const string CustomSeedSettingValue = "CustomSeedSettingValue";
public const string OmnicientBotSelected = "OmnicientBotSelected";
public const string ObserverBotSelected = "ObserverBotSelected";
public const string IteratorBotSelected = "IteratorBotSelected";
public const string HumanPlayerSelected = "HumanPlayerSelected";
}

View File

@@ -0,0 +1,24 @@
using UnityEngine;
public class HumanPlayerBehaviour : PlayerBehaviour
{
protected override sbyte ChooseDirection()
{
return Input.GetKey(KeyCode.Space) ? (sbyte)1 : (sbyte)-1;
}
protected override bool ChooseIfPlayerIsHuman()
{
return true;
}
protected override Color ChoosePlayerColor()
{
return new Color(255, 255, 255);
}
protected override string ChoosePlayerName()
{
return "Joueur Humain";
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public class IteratorBehaviour : PlayerBehaviour
{
SensorsScript sensorsScript;
Rigidbody2D rb;
[SerializeField] private List<float> raycastWeight = new List<float>(){
3f, 5f, 5f, 3f, 12f, 25f, 0f, 25f, 12f, 3f, 5f, 5f, 3f
};
protected override sbyte ChooseDirection()
{
List<SensorData> sensorDataList = sensorsScript.GetObserverRaycast();
float influenceSum = 0f;
// Calculer l'influence totale en parcourant les données de capteurs
for (int i = 0; i < sensorDataList.Count; i++)
{
SensorData data = sensorDataList[i];
float angle = sensorsScript.raycastConfigurations[i].y; // Obtenir l'angle du raycast
//float horizontalDistance = data.distance * Mathf.Cos(Mathf.Deg2Rad * angle); // Composante horizontale de la distance
float DistanceForce = MinFloat(sensorsScript.raycastDistance/data.distance/10,10);
float influence = DistanceForce * raycastWeight[i];
if(data.hitLayer == 7){
influence *= 0.5f;
}
if(i <= 5){
influence *= -1;
}
influenceSum += influence;
//Debug.Log(i +" --- "+DistanceForce);
}
if (influenceSum > 0.25f && rb.velocity.y > 0f)
{
// Si la direction suggérée est la même que la direction actuelle, réduire l'influence
influenceSum -= 1f;
}
// Utiliser l'influence totale pour déterminer la direction
sbyte direction = (sbyte)(influenceSum > 0 ? 1 : -1);
//Debug.Log("influenceSum" + influenceSum);
return direction;
}
float MinFloat(float a,float b){
if(a > b)
return b;
return a;
}
void Start()
{
sensorsScript = GetComponent<SensorsScript>();
rb = GetComponent<Rigidbody2D>();
}
protected override string ChoosePlayerName()
{
return "Itérateur";
}
protected override Color ChoosePlayerColor()
{
return new Color(255, 70, 0);
}
protected override bool ChooseIfPlayerIsHuman()
{
return false;
}
}

View File

@@ -0,0 +1,110 @@
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using System.Collections.Generic;
public class ObserverAI : Agent
{
#region PROPERTIES
public sbyte LastDirectionDecision { get; private set; }
#endregion
#region VARIABLES
private PlayerScript _playerRef;
private Rigidbody2D rb;
private sbyte direction;
SensorsScript sensorsScript;
private bool isReadyToStartEpisode = false;
#endregion
#region EVENTS
public override void OnActionReceived(ActionBuffers actions)
{
LastDirectionDecision = actions.ContinuousActions[0] > 0 ? (sbyte)1 : (sbyte)-1;
}
#endregion
#region METHODS
public sbyte TakeDecision()
{
//if(isReadyToStartEpisode){
RequestDecision();
//}
return LastDirectionDecision == 0 ? (sbyte)-1 : LastDirectionDecision;
}
public override void CollectObservations(VectorSensor sensor)
{
sensor.AddObservation(rb.transform.localPosition.y);
sensor.AddObservation(rb.velocity.y);
sensor.AddObservation(GameHandler.Instance.FrameDistance);
if (sensorsScript != null)
{
List<SensorData> sensorDataList = sensorsScript.GetObserverRaycast();
// Display raycast data
foreach (SensorData data in sensorDataList)
{
sensor.AddObservation(data.hitLayer);
sensor.AddObservation(data.distance);
}
}
else
{
Debug.LogWarning("SensorsScript component is not assigned.");
}
}
public override void Heuristic(in ActionBuffers actionsOut)
{
// Heuristic method
}
#endregion
#region LIFECYCLE
void Awake()
{
rb = GetComponent<Rigidbody2D>();
sensorsScript = GetComponent<SensorsScript>();
isReadyToStartEpisode = true;
Debug.Log("Observer IA enabled !");
}
/*void Update()
{
var verticalPosition = rb.transform.position.y;
var reward = 0.1f / (0.01f + ((verticalPosition + 0.59f) * (verticalPosition + 0.59f)));
AddReward(reward);
}*/
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Laser"))
{
AddReward(-100f);
//EndEpisode();
}
else if (other.CompareTag("GoodCoin"))
{
AddReward(100f);
}
else if (other.CompareTag("BadCoin"))
{
AddReward(50f);
}
else if (other.CompareTag("Missile"))
{
AddReward(-500f);
}
}
#endregion
}

View File

@@ -0,0 +1,60 @@
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using System.Collections.Generic;
using Unity.MLAgents.Policies;
using Unity.VisualScripting;
using Unity.Barracuda;
public class ObserverBehaviour : PlayerBehaviour
{
private ObserverAI _observerAi;
private BehaviorParameters _behaviourParameters;
protected override void Awake()
{
base.Awake();
_observerAi = _playerRef.gameObject.AddComponent<ObserverAI>();
_observerAi.enabled = false;
_behaviourParameters = _playerRef.gameObject.GetComponent<BehaviorParameters>();// ?? _playerRef.gameObject.AddComponent<BehaviorParameters>();
_behaviourParameters.BehaviorName = "Observer";
_behaviourParameters.BrainParameters.VectorObservationSize = 29;
_behaviourParameters.BrainParameters.ActionSpec =
//new ActionSpec(
// numContinuousActions: 1, // Une seule action continue
// discreteBranchSizes: new int[] { 0 } // Une action discrète avec 0 choix possibles
// );
ActionSpec.MakeContinuous(1);
_behaviourParameters.Model = Resources.Load<NNModel>("Observer");
_observerAi.EndEpisode();
_observerAi.enabled = true;
}
protected override sbyte ChooseDirection()
{
var direction = _observerAi.TakeDecision();
return direction;
}
protected override string ChoosePlayerName()
{
return "Bot Observateur";
}
protected override Color ChoosePlayerColor()
{
return new Color(0, 255, 0);
}
protected override bool ChooseIfPlayerIsHuman()
{
return false;
}
}

View File

@@ -0,0 +1,44 @@
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using System.Collections.Generic;
using Unity.MLAgents.Policies;
using Unity.VisualScripting;
using Unity.Barracuda;
using System;
public class OmnicientBehaviour : PlayerBehaviour
{
public int frames = 0;
public bool boolean;
protected override sbyte ChooseDirection()
{
if (frames == 0)
{
boolean = (DateTime.Now.Millisecond) % 2 == 0;
}
frames++;
if (frames >= 5)
{
frames = 0;
}
return boolean ? (sbyte)1 : (sbyte)-1;
}
protected override string ChoosePlayerName()
{
return "Omnicient";
}
protected override Color ChoosePlayerColor()
{
return new Color(0, 0, 255);
}
protected override bool ChooseIfPlayerIsHuman()
{
return false;
}
}

View File

@@ -0,0 +1,47 @@
using UnityEngine;
public abstract class PlayerBehaviour : MonoBehaviour
{
protected PlayerScript _playerRef;
protected string playerName;
protected Color playerColor;
protected virtual void Awake()
{
playerName = ChoosePlayerName();
playerColor = ChoosePlayerColor();
_playerRef = gameObject.GetComponent<PlayerScript>();
SetPlayerName(playerName);
SetPlayerColor(playerColor);
_playerRef.IsHuman = ChooseIfPlayerIsHuman();
}
protected virtual void FixedUpdate()
{
sbyte direction = ChooseDirection();
_playerRef.MoveVertically(direction,Time.deltaTime);
}
/// <summary>
/// This method choose if the player goes up (+1) or down (-1).
/// The way the decision is made is up to you.
/// This method must return either +1 or -1 ONLY.
/// </summary>
/// <returns>Returns only +1 or -1</returns>
/// <remarks>This methof is called every frames</remarks>
protected abstract sbyte ChooseDirection();
protected abstract string ChoosePlayerName();
protected abstract Color ChoosePlayerColor();
protected abstract bool ChooseIfPlayerIsHuman();
private void SetPlayerName(string name)
{
_playerRef.SetName(name);
}
private void SetPlayerColor(Color color)
{
_playerRef.SetColor(color);
}
}

View File

@@ -0,0 +1,103 @@
using UnityEngine;
using System.Collections.Generic;
public class SensorsScript : MonoBehaviour
{
// Layer à ignorer
public int ObserverlayerToIgnore = 9;
public int IteratorlayerToIgnore = 7;
// Liste des angles et des offsets pour les raycasts
public List<Vector2> raycastConfigurations = new List<Vector2>()
{
new Vector2(1.18f, 75f),
new Vector2(1.18f, 55f),
new Vector2(1.18f, 35f),
new Vector2(1.18f, 15f),
new Vector2(1.18f, 5f),
new Vector2(0.59f, 4f),
new Vector2(0.59f, 0),
new Vector2(0.59f, -4f),
new Vector2(0f, -5f),
new Vector2(0f, -15f) ,
new Vector2(0f, -35f) ,
new Vector2(0f, -55f) ,
new Vector2(0f, -75f)
};
// Longueur des raycasts
public float raycastDistance = 20f;
// Fonction pour obtenir les données de raycasts d'observation
public List<SensorData> GetObserverRaycast()
{
List<SensorData> raycastsData = new List<SensorData>();
// Calculer le LayerMask pour ignorer le layer spécifié
int layerMask = ~(1 << ObserverlayerToIgnore);
// Pour chaque configuration de raycast
foreach (var config in raycastConfigurations)
{
// Créer une variable pour stocker la position de départ du raycast
Vector3 raycastStartPoint = transform.position;
// Obtenir l'angle et l'offset à partir de la configuration
float offset = config.x;
float angle = config.y;
// Calculer la direction du raycast en fonction de l'angle
Vector2 raycastDirection = Quaternion.Euler(0, 0, angle) * transform.right;
// Ajuster la position de départ du raycast en fonction de l'offset
if (offset == 0.59f)
{
raycastStartPoint += new Vector3(0.5f, offset, 0f);
}
else
{
raycastStartPoint += new Vector3(0f, offset, 0f);
}
// Effectuer le raycast en ignorant le layer spécifié
RaycastHit2D hit = Physics2D.Raycast(raycastStartPoint, raycastDirection, raycastDistance, layerMask);
// Dessiner une ligne pour visualiser le raycast (pour le débogage)
Debug.DrawRay(raycastStartPoint, raycastDirection * raycastDistance, Color.blue);
// Créer un objet SensorData pour stocker les informations du raycast
SensorData data = new SensorData();
// Enregistrer les informations du raycast
if (hit.collider != null)
{
data.hitLayer = hit.collider.gameObject.layer;
data.distance = hit.distance;
// Dessiner une ligne rouge jusqu'au point de collision (pour le débogage)
Debug.DrawLine(raycastStartPoint, hit.point, Color.red);
}
else
{
// Si aucun objet n'a été touché, utiliser des valeurs par défaut
data.hitLayer = -1;
data.distance = raycastDistance;
// Dessiner une ligne verte jusqu'à la fin du raycast (pour le débogage)
Debug.DrawRay(raycastStartPoint, raycastDirection * raycastDistance, Color.green);
}
// Ajouter les données du raycast à la liste
raycastsData.Add(data);
}
return raycastsData;
}
}
// Classe pour stocker les données du raycast touché
public class SensorData
{
public int hitLayer;
public float distance;
}

2
Scripts/README.md Normal file
View File

@@ -0,0 +1,2 @@
Ce dossier regroupe l'ensemble des scripts du projet.
Un diagramme de classe complet sera réalisé avec le rapport.

54
Scripts/SingletonMB.cs Normal file
View File

@@ -0,0 +1,54 @@
using UnityEngine;
namespace Assets.Scripts
{
public class SingletonMB<T> : MonoBehaviour where T : SingletonMB<T>
{
private static T instance;
private static readonly object lockObject = new object();
protected static bool Instantiated { get; private set; } = false;
public static T Instance
{
get
{
//lock (lockObject)
//{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(T).Name;
instance = obj.AddComponent<T>();
Instantiated = true;
}
}
return instance;
//}
}
}
protected virtual void Awake()
{
Debug.Log($"{typeof(T).Name} singleton component Loaded");
//lock (lockObject)
//{
if (instance == null)
{
instance = this as T;
// DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
return;
}
Instantiated = true;
//}
}
}
}

View File

@@ -0,0 +1,162 @@
using Assets.Scripts;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static GameHandler;
public class LevelSpawner : SingletonMB<LevelSpawner>
{
#region PROPERTIES
#endregion
#region VARIABLES
private GameHandler _gameHandler;
[SerializeField] private GameObject[] patternsPrefabs;
[SerializeField] private Transform levelSpawner;
[SerializeField] private float patternInterval;
[SerializeField] private GameObject PlayersContainer;
[SerializeField] private GameObject missilePrefab;
[SerializeField] private GameObject missileAlertPrefab;
[SerializeField] private float dangerMinX = -25f;
private List<MissileAlertInfo> missileAlertList = new List<MissileAlertInfo>();
private List<Transform> spawnedGameElements = new List<Transform>();
private GameObject prefabParent;
private float patternLength;
private ObstaclePatternBehaviour lastSpawnedPattern;
private float rewardValue = 0f;
#endregion
#region EVENTS
public event EventHandler<PrefabSpawnedEventArgs> OnPrefabSpawned;
public class PrefabSpawnedEventArgs : EventArgs
{
public ObstaclePatternBehaviour pattern;
public PrefabSpawnedEventArgs(ObstaclePatternBehaviour pattern)
{
this.pattern = pattern;
}
}
public void ResetLevel_OnAttemptEnding(object sender, AttemptEventArgs e)
{
foreach (Transform gameElement in spawnedGameElements)
{
if (gameElement != null)
{
Destroy(gameElement.gameObject);
}
}
if(lastSpawnedPattern)Destroy(lastSpawnedPattern.gameObject);
lastSpawnedPattern = null;
}
#endregion
#region METHODS
void PrefabSpawnerBehaviour()
{
if (lastSpawnedPattern == null)
{
SpawnObstacle();
}
}
void SpawnObstacle()
{
GameObject obstacleToSpawn = patternsPrefabs[UnityEngine.Random.Range(0, patternsPrefabs.Length)];
patternLength = obstacleToSpawn.transform.lossyScale.x;
lastSpawnedPattern = Instantiate(obstacleToSpawn, transform.position, Quaternion.identity, prefabParent.transform)
.GetComponent<ObstaclePatternBehaviour>();
OnPrefabSpawned?.Invoke(this, new PrefabSpawnedEventArgs(lastSpawnedPattern));
}
//IEnumerator SpawnMissileAfterDelay(GameObject missileAlert, GameObject player)
//{
// yield return new WaitForSeconds(3f);
// Vector3 missileSpawnPosition = missileAlert.transform.position + new Vector3(2.5f, 0, 0);
// Destroy(missileAlert);
// GameObject newMissile = Instantiate(missilePrefab, missileSpawnPosition, Quaternion.identity, prefabParent.transform);
// MissileScript missileScript = newMissile.GetComponent<MissileScript>();
// missileScript.Target = player;
// spawnedGameElements.Add(newMissile.transform);
//}
private void RemoveExpiredGameElements()
{
List<Transform> gameElementToRemove = new List<Transform>();
foreach (Transform gameElementTransform in spawnedGameElements)
{
if (gameElementTransform == null)
{
gameElementToRemove.Add(gameElementTransform);
continue;
}
if (gameElementTransform.position.x < dangerMinX)
{
gameElementToRemove.Add(gameElementTransform);
}
}
foreach (Transform gameElement in gameElementToRemove)
{
if (gameElement != null)
{
Destroy(gameElement.gameObject);
}
}
}
//private void UpdateMissileAlertPositions()
//{
// missileAlertList.RemoveAll(item => item.alertTransform == null);
// foreach (MissileAlertInfo Element in missileAlertList)
// {
// Vector3 newPosition = new Vector3(Element.alertTransform.position.x, Element.player.transform.position.y + 0.544f, 0);
// Element.alertTransform.position = newPosition;
// }
//}
#endregion
#region LIFECYCLE
void Start()
{
prefabParent = GameObject.Find("prefab");
}
protected override void Awake()
{
_gameHandler = GameHandler.Instance;
_gameHandler.OnAttemptEnding += ResetLevel_OnAttemptEnding;
}
void Update()
{
if (!GameController.Instance.IsExiting)
{
PrefabSpawnerBehaviour();
RemoveExpiredGameElements();
}
//UpdateMissileAlertPositions();
}
#endregion
public struct MissileAlertInfo
{
public Transform alertTransform;
public GameObject player;
}
}

View File

@@ -0,0 +1,120 @@
using Assets.Scripts;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static LevelSpawner;
public class MissileSpawnerScript : MonoBehaviour
{
#region PROPERTIES
#endregion
#region VARIABLES
public float alertDuration = 5f;
private int baseCountdown = 2;
private int countdown = 0;
private bool renderSprite;
private MissileAlertScript missileAlertScript;
private PlayerScript playerScript;
private GameHandler _gameHandler;
private DataBearer _dataBearer;
private PlayersHandler _playersHandler;
private LevelSpawner _leverSpawner;
[SerializeField] private GameObject missileAlertRef;
[SerializeField] private GameObject missilePrefab;
#endregion
#region EVENTS
public event EventHandler<MissileEventArgs> OnMissileAlert;
public event EventHandler<MissileEventArgs> OnMissileLaunched;
public class MissileEventArgs : EventArgs
{
//todo : eventContent
}
private void MissileLaunchCountdown_OnPrefab(object sender, PrefabSpawnedEventArgs args)
{
if (countdown == baseCountdown)
{
StartCoroutine(MissileLaunchSequence());
countdown = 0;
}
else
{
countdown++;
}
}
#endregion
#region METHODS
private IEnumerator MissileLaunchSequence()
{
if(renderSprite)
missileAlertScript.enabled = true;
yield return new WaitForSeconds(alertDuration);
missileAlertScript.enabled = false;
SpawnMissile();
}
private void SpawnMissile()
{
var position = new Vector3(20, transform.position.y, -2);
var instance = Instantiate(missilePrefab, position, missilePrefab.transform.rotation);
instance.GetComponent<MissileScript>().SetTarget(playerScript.Id);
instance.GetComponent<MissileScript>().SetRenderSprite(renderSprite);
if (!renderSprite)
{
DestroyImmediate(instance.GetComponent<SpriteRenderer>(), true);
instance.GetComponent<MissileScript>().StopParticles();
}
}
#endregion
#region LIFECYCLE
protected void Awake()
{
_dataBearer = DataBearer.Instance;
_gameHandler = GameHandler.Instance;
_playersHandler = PlayersHandler.Instance;
_leverSpawner = LevelSpawner.Instance;
if (!_dataBearer.Rules.Missiles)
{
Destroy(gameObject);
}
_leverSpawner.OnPrefabSpawned += MissileLaunchCountdown_OnPrefab;
missileAlertScript = missileAlertRef.GetComponent<MissileAlertScript>();
playerScript = GetComponentInParent<PlayerScript>();
}
private void Start()
{
renderSprite = !(_gameHandler.HaveHumanPlayer && !playerScript.IsHuman); // false if game has human and script is not human
}
private void OnDestroy()
{
Clean();
}
private void Clean()
{
StopAllCoroutines();
LevelSpawner.Instance.OnPrefabSpawned -= MissileLaunchCountdown_OnPrefab;
}
#endregion
}

View File

@@ -0,0 +1,125 @@
using Assets.Scripts;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using static GameHandler;
public class TileSpawner : SingletonMB<TileSpawner>
{
#region PROPERTIES
#endregion
#region VARIABLES
private GameHandler _gameHandler;
[SerializeField] private int maxTile = 6;
[SerializeField] private GameObject tilePrefab;
[SerializeField] private float tileLength = 15f;
[SerializeField] private float tileMinX = 15f;
private List<Transform> tileList = new List<Transform>();
private GameObject tilesParent;
private bool attemptStarted;
private float startPositionX;
#endregion
#region EVENTS
private void StartAttempt_OnAttemptStarted(object sender, AttemptEventArgs args)
{
attemptStarted = true;
}
private void EndAttempt_OnAttemptEnded(object sender, AttemptEventArgs args)
{
attemptStarted = false;
}
#endregion
#region METHODS
private void SpawnAllTiles()
{
for (int i = 1; i <= maxTile; i++)
{
SpawnTile(i);
}
}
private void SpawnTile(int tileNumber)
{
GameObject newTile = Instantiate(tilePrefab, transform.position-new Vector3((tileNumber*tileLength),0,0), Quaternion.identity, transform);
//transform.position += new Vector3(tileLength, 0, 0);
tileList.Add(newTile.transform);
}
private void ResetBackgroundPosition()
{
transform.position += new Vector3(tileLength, transform.position.y, transform.position.z);
}
private void MoveTilesHorizontally()
{
transform.position += new Vector3(-GameHandler.Instance.FrameDistance, 0, 0);
if(startPositionX- transform.position.x > tileLength)
{
ResetBackgroundPosition();
}
//List<Transform> tilesToRemove = new List<Transform>();
//foreach (Transform tileTransform in tileList)
//{
// tileTransform.position += new Vector3(GameHandler.Instance.TotalDistance,0,0);;
// if (tileTransform.position.x < tileMinX)
// {
// tilesToRemove.Add(tileTransform);
// }
//}
//// Remove tiles that have gone off screen
//foreach (Transform tileToRemove in tilesToRemove)
//{
// tileList.Remove(tileToRemove);
// Destroy(tileToRemove.gameObject);
// SpawnTile();
//}
}
private float GetStartPosition()
{
Camera mainCamera = Camera.main;
float screenRight = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, mainCamera.nearClipPlane)).x;
return screenRight+2*tileLength;
}
#endregion
#region LIFECYCLE
void Start()
{
attemptStarted = false;
_gameHandler = GameHandler.Instance;
_gameHandler.OnAttemptStarted += StartAttempt_OnAttemptStarted;
_gameHandler.OnAttemptEnding += EndAttempt_OnAttemptEnded;
startPositionX = GetStartPosition();
transform.position = new Vector3(startPositionX, transform.position.y, transform.position.z);
SpawnAllTiles();
}
void Update()
{
if(attemptStarted)
{
MoveTilesHorizontally();
}
}
#endregion
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
using Assets.Scripts;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using XCharts.Runtime;
public class GraphPanelScript : MonoBehaviour
{
#region PROPERTIES
public SessionData Session { get; private set; }
public LineChart LineChart {get; set; }
#endregion
#region VARIABLES
//public VisualTreeAsset graphPanelTemplate;
//public RenderTexture renderTexture;
public UIDocument _document;
private StatsMenuController _statsController;
private VisualElement root;
private VisualElement graphContainer;
private VisualElement graphPanel;
private VisualElement[,] anchors;
#endregion
#region EVENTS
#endregion
#region ENDPOINTS
#endregion
#region METHODS
////void UpdateChartPositionAndSize(RectTransform chartRectTransform, VisualElement chartContainer)
////{
//// // Obtenir les dimensions et la position du conteneur en pixels
//// Rect containerRect = chartContainer.worldBound;
//// // Convertir la position du VisualElement en coordonn<6E>es du Canvas
//// var canvasRectTransform = _statsController.Canvas.GetComponent<RectTransform>();
//// Vector2 containerSize = containerRect.size;
//// Vector2 containerPosition = containerRect.position - new Vector2(canvasRectTransform.rect.width / 2, canvasRectTransform.rect.height / 2);
//// // Ajuster la taille et la position du RectTransform pour qu'il corresponde au VisualElement
//// chartRectTransform.anchorMin = Vector2.zero;
//// chartRectTransform.anchorMax = Vector2.zero;
//// chartRectTransform.pivot = new Vector2(0.5f, 0.5f);
//// chartRectTransform.sizeDelta = containerSize;
//// chartRectTransform.anchoredPosition = containerPosition;
////}
void UpdateChartPositionAndSize(int anchorRow, int anchorCol)
{
//if (anchorRow < 0 || anchorRow >= 3 || anchorCol < 0 || anchorCol >= 3)
//{
// Debug.LogError("Invalid anchor position");
// return;
//}
//VisualElement anchor = anchors[anchorRow, anchorCol];
// Positionner le LineChart
//RectTransform lineChartRectTransform = LineChart.GetComponent<RectTransform>();
//lineChartRectTransform.anchoredPosition = new Vector3(480, -184, -1);
}
//private Vector2 ConvertUIToCanvasPosition(VisualElement uiElement)
//{
// // Trouver la cam<61>ra
// Camera camera = Camera.main;
// // Trouver la position de l'ancre dans l'espace du monde
// Vector3 worldPosition = uiElement.worldBound.center;
// // Convertir la position du monde en position de l'<27>cran
// Vector3 screenPosition = camera.WorldToScreenPoint(worldPosition);
// // Convertir la position de l'<27>cran en position de la toile (canvas)
// RectTransform canvasRectTransform = lineChart.GetComponentInParent<Canvas>().GetComponent<RectTransform>();
// Vector2 canvasPosition;
// RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, screenPosition, camera, out canvasPosition);
// return canvasPosition;
//}
#endregion
#region LIFECYCLE
protected void Awake()
{
_statsController = StatsMenuController.Instance;
_document = _statsController.GetComponent<UIDocument>();
root = _document.rootVisualElement;
graphContainer = root.Q<VisualElement>("ChartsParentContainer");
LineChart = gameObject.GetComponent<LineChart>();
var serie = LineChart.AddSerie<Line>();
serie.AddXYData(0, 10);
serie.AddXYData(1, 20);
serie.AddXYData(2, 15);
// Synchroniser la position et les dimensions du LineChart avec le VisualElement
RectTransform chartRectTransform = LineChart.GetComponent<RectTransform>();
graphContainer.style.width = new StyleLength(Length.Percent(100));
graphContainer.style.height = new StyleLength(Length.Percent(100));
anchors = new VisualElement[3, 3];
int k = 0;
for(int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
k++;
anchors[i, j] = graphContainer.Q<VisualElement>($"Anchor{k}");
}
}
UpdateChartPositionAndSize(1, 1);
//graphContainer.RegisterCallback<GeometryChangedEvent>(evt => UpdateChartPositionAndSize(lineChart.GetComponent<RectTransform>(), 1, 1, 300, 200));
}
#endregion
}

View File

@@ -0,0 +1,331 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace Assets.Scripts
{
public class GraphSettings : MonoBehaviour
{
#region PROPERTIES
[Header("Graph Settings")]
[Space]
public int updatePeriod = 5;
[SerializeField] private Vector2 graphSize = new Vector2(800f, 400f);
public Vector2 GraphSize
{
get { return graphSize; }
set { graphSize = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePositionAndScale | UpdateMethod.UpdateContent | UpdateMethod.UpdateGridLines); }
}
[SerializeField] private Vector2 graphScale = new Vector2(100f, 100f);
public Vector2 GraphScale
{
get { return graphScale; }
set { graphScale = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePositionAndScale | UpdateMethod.UpdateContent | UpdateMethod.UpdateGridLines); }
}
[Space]
[Header("Graph Visuals")]
[Space]
[SerializeField] private Color backgroundColor = new Color(0, 0, 0, 1f);
public Color BackgroundColor
{
get { return backgroundColor; }
set { backgroundColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateOutlines); }
}
[SerializeField] private float outlineWidth = 5f;
public float OutlineWidth
{
get { return outlineWidth; }
set { outlineWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateOutlines); }
}
[SerializeField] private Color outlineColor = new Color(0, 0.8f, 1f, 1f);
public Color OutlineColor
{
get { return outlineColor; }
set { outlineColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateOutlines); }
}
[Space]
[SerializeField] private float lineWidth = 8f;
public float LineWidth
{
get { return lineWidth; }
set { lineWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateContent); }
}
[SerializeField] private Color lineColor = new Color(1f, 0.35f, 0f, 1f);
public Color LineColor
{
get { return lineColor; }
set { lineColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateContent); }
}
[Space]
public Sprite PointSprite;
[SerializeField] private float pointRadius = 5f;
public float PointRadius
{
get { return pointRadius; }
set { pointRadius = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[SerializeField] private Color pointColor = new Color(1f, 0.35f, 0f, 1f);
public Color PointColor
{
get { return pointColor; }
set { pointColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[SerializeField] private float pointHoverRadius = 15f;
public float PointHoverRadius
{
get { return pointHoverRadius; }
set { pointHoverRadius = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
public float PointHoverSpeed = 5f;
[SerializeField] private Color pointHoverColor = new Color(1, 0.6f, 0, 1f);
public Color PointHoverColor
{
get { return pointHoverColor; }
set { pointHoverColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[SerializeField] private float pointLockedRadius = 17f;
public float PointLockedRadius
{
get { return pointLockedRadius; }
set { pointLockedRadius = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
public float PointLockedSpeed = 5f;
[SerializeField] private Color pointLockedColor = new Color(1, 0.8f, 0, 1f);
public Color PointLockedColor
{
get { return pointLockedColor; }
set { pointLockedColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[SerializeField] private float unfixedPointOutlineWidth = 10f;
public float UnfixedPointOutlineWidth
{
get { return unfixedPointOutlineWidth; }
set { unfixedPointOutlineWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[SerializeField] private Color unfixedPointOutlineColor = new Color(0, 0.8f, 1f, 1f);
public Color UnfixedPointOutlineColor
{
get { return unfixedPointOutlineColor; }
set { unfixedPointOutlineColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[SerializeField] private float unfixedPointOutlineHoverWidth = 15f;
public float UnfixedPointOutlineHoverWidth
{
get { return unfixedPointOutlineHoverWidth; }
set { unfixedPointOutlineHoverWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
public float UnfixedPointOutlineHoverSpeed = 5f;
[Space]
[SerializeField] private Color unfixedPointOutlineHoverColor = new Color(0, 0.5f, 1f, 1f);
public Color UnfixedPointOutlineHoverColor
{
get { return unfixedPointOutlineHoverColor; }
set { unfixedPointOutlineHoverColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[SerializeField] private float fixedPointOutlineWidth = 17f;
public float FixedPointOutlineWidth
{
get { return fixedPointOutlineWidth; }
set { fixedPointOutlineWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
public float FixedPointOutlineSpeed = 5f;
[SerializeField] private Color fixedPointOutlineColor = new Color(0, 0.8f, 1f, 1f);
public Color FixedPointOutlineColor
{
get { return fixedPointOutlineColor; }
set { fixedPointOutlineColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdatePointVisuals); }
}
[Space]
[Header("Grid Settings")]
[Space]
public TMP_FontAsset GridTextFont;
[SerializeField] private Vector2 gridSpacing = new Vector2(1, 1);
public Vector2 GridSpacing
{
get { return gridSpacing; }
set { gridSpacing = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private float xAxisWidth = 3f;
public float XAxisWidth
{
get { return xAxisWidth; }
set { xAxisWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private Color xAxisColor = new Color(0, 0.8f, 1f, 1f);
public Color XAxisColor
{
get { return xAxisColor; }
set { xAxisColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private Color xAxisTextColor = new Color(0, 0.8f, 1f, 1f);
public Color XAxisTextColor
{
get { return xAxisTextColor; }
set { xAxisTextColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private float xAxisTextSize = 10f;
public float XAxisTextSize
{
get { return xAxisTextSize; }
set { xAxisTextSize = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private float xAxisTextOffset = 10f;
public float XAxisTextOffset
{
get { return xAxisTextOffset; }
set { xAxisTextOffset = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private float yAxisWidth = 3f;
public float YAxisWidth
{
get { return yAxisWidth; }
set { yAxisWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private Color yAxisColor = new Color(0, 0.8f, 1f, 1f);
public Color YAxisColor
{
get { return yAxisColor; }
set { yAxisColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private Color yAxisTextColor = new Color(0, 0.8f, 1f, 1f);
public Color YAxisTextColor
{
get { return yAxisTextColor; }
set { yAxisTextColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private float yAxisTextSize = 10f;
public float YAxisTextSize
{
get { return yAxisTextSize; }
set { yAxisTextSize = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private float yAxisTextOffset = 10f;
public float YAxisTextOffset
{
get { return yAxisTextOffset; }
set { yAxisTextOffset = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private float xGridWidth = 2f;
public float XGridWidth
{
get { return xGridWidth; }
set { xGridWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private Color xGridColor = new Color(0, 0.8f, 1f, 0.6f);
public Color XGridColor
{
get { return xGridColor; }
set { xGridColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private float yGridWidth = 2f;
public float YGridWidth
{
get { return yGridWidth; }
set { yGridWidth = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[SerializeField] private Color yGridColor = new Color(0, 0.8f, 1f, 0.6f);
public Color YGridColor
{
get { return yGridColor; }
set { yGridColor = value; GH.UpdateGraphInternal(UpdateMethod.UpdateGridLines); }
}
[Space]
[SerializeField] private Color zoomSelectionColor = new Color(0, 0.8f, 1f, 0.2f);
public Color ZoomSelectionColor
{
get { return zoomSelectionColor; }
set { zoomSelectionColor = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
[SerializeField] private float zoomSelectionOutlineWidth = 5f;
public float ZoomSelectionOutlineWidth
{
get { return zoomSelectionOutlineWidth; }
set { zoomSelectionOutlineWidth = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
[SerializeField] private Color zoomSelectionOutlineColor = new Color(0, 0.8f, 1f, 0.6f);
public Color ZoomSelectionOutlineColor
{
get { return zoomSelectionOutlineColor; }
set { zoomSelectionOutlineColor = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
[Space]
[SerializeField] private Color pointSelectionColor = new Color(1, 0.35f, 0f, 0.2f);
public Color PointSelectionColor
{
get { return pointSelectionColor; }
set { pointSelectionColor = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
[SerializeField] private float pointSelectionOutlineWidth = 5f;
public float PointSelectionOutlineWidth
{
get { return pointSelectionOutlineWidth; }
set { pointSelectionOutlineWidth = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
[SerializeField] private Color pointSelectionOutlineColor = new Color(1, 0.35f, 0f, 0.4f);
public Color PointSelectionOutlineColor
{
get { return pointSelectionOutlineColor; }
set { pointSelectionOutlineColor = value; GH.UpdateGraphInternal(UpdateMethod.MouseAction); }
}
#endregion
#region VARIABLES
[Space]
public float ZoomSpeed = 5f;
public float SmoothZoomSpeed = 20f;
public float SmoothMoveSpeed = 20f;
private GraphHandler GH;
#endregion
#region EVENTS
#endregion
#region ENDPOINTS
#endregion
#region METHODS
#endregion
#region LIFECYCLE
private void Awake()
{
GH = GetComponent<GraphHandler>();
}
#endregion
}
}

View File

@@ -0,0 +1,71 @@
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UIElements;
public class SessionsListView : MonoBehaviour
{
#region PROPERTIES
#endregion
#region VARIABLES
[SerializeField] private UIDocument uiDocument;
private StatsMenuController _statsController;
#endregion
#region EVENTS
private void OnButtonClick(SessionData sessionData)
{
_statsController.OnSessionSelected(sessionData);
}
#endregion
#region METHODS
#endregion
#region LIFECYCLE
private void Awake()
{
_statsController = StatsMenuController.Instance;
}
private void Start()
{
var sessionDataList = _statsController.Sessions;
var root = uiDocument.rootVisualElement;
var listView = root.Q<ListView>("ListViewSessions");
listView.itemsSource = sessionDataList;
listView.makeItem = () => new Button();
listView.bindItem = (element, index) =>
{
var button = element as Button;
var sessionData = sessionDataList[index];
button.text = $"Session {sessionData.SessionNumber} | {sessionData.Date.ToString("ddd, dd MMM yyy")}";
button.AddToClassList("list-button");
button.style.backgroundColor = new Color(0, 0, 0, 0.2f);
button.style.height = 50;
button.style.marginLeft = 10;
button.style.marginRight = 10;
button.style.marginBottom = 2;
button.style.color = Color.white;
// Ajouter un callback pour lorsque le bouton est cliqu<71>
button.clicked += () => OnButtonClick(sessionData);
};
listView.fixedItemHeight = 52;
listView.selectionType = SelectionType.Single;
listView.style.flexGrow = 1.0f;
listView.Rebuild();
}
#endregion
}

View File

@@ -0,0 +1,180 @@
using Assets.Scripts;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using XCharts.Runtime;
public class StatsMenuController : SingletonMB<StatsMenuController>
{
#region PROPERTIES
public List<SessionData> Sessions { get; private set; }
public Canvas Canvas { get { return _canvas; } private set { _canvas = value; } }
[SerializeField] private Canvas _canvas;
#endregion
#region VARIABLES
[SerializeField] private GameObject graphPanelTemplate;
private UIDocument _document;
private Button _exitButton;
private Button _storageButton;
private List<LineChart> lineCharts;
#endregion
#region EVENTS
private void OnExitButton(ClickEvent e)
{
SceneManager.LoadScene("MainMenu");
}
private void OnStorageButton(ClickEvent e)
{
string path = Path.Combine(Application.persistentDataPath, "SessionsData");
#if UNITY_EDITOR
UnityEditor.EditorUtility.RevealInFinder(path);
#elif UNITY_STANDALONE_WIN
path = path.Replace("/", "\\"); // Remplacer les / par \ pour Windows
Process.Start("explorer.exe", path);
#elif UNITY_STANDALONE_OSX
Process.Start("open", path);
#elif UNITY_STANDALONE_LINUX
Process.Start("xdg-open", path);
#else
Debug.LogWarning("Opening the file explorer is not supported on this platform.");
#endif
}
#endregion
#region ENDPOINTS
public void OnSessionSelected(SessionData session)
{
if (lineCharts != null)
{
for (int i = 0; i < lineCharts.Count; i++)
{
Destroy(lineCharts[i]);
}
lineCharts.Clear();
}
UnityEngine.Debug.Log("Session Chart " + session.SessionNumber);
int n = 0;
foreach (var attempt in session.Attempts)
{
n++;
if (n > 4) break;
var instance = Instantiate(graphPanelTemplate, Canvas.transform).GetComponent<GraphPanelScript>().LineChart;
instance.RemoveAllSerie();
lineCharts.Add(instance);
var position = new Vector3(480, -184, -1);
var nextColumnPosition = new Vector3(instance.chartWidth + 2, 0, 0);
var nextLinePosition = new Vector3(0, -instance.chartHeight - 2, 0);
switch (lineCharts.IndexOf(instance))
{
case 0:
instance.GetComponent<RectTransform>().anchoredPosition = position;
break;
case 1:
instance.GetComponent<RectTransform>().anchoredPosition = position + nextColumnPosition;
break;
case 2:
instance.GetComponent<RectTransform>().anchoredPosition = position + nextLinePosition;
break;
case 3:
instance.GetComponent<RectTransform>().anchoredPosition = position + nextLinePosition + nextColumnPosition;
break;
}
// Definir nom de la chart
var chartName = instance.EnsureChartComponent<Title>();
chartName.text = $"Scores dans le temps | round {attempt.AttemptNumber}";
// D<>finir les noms des axes
var xAxis = instance.EnsureChartComponent<XAxis>();
xAxis.type = Axis.AxisType.Value;
AxisName xName = new();
xName.name = "Temps (ticks)";
xAxis.axisName = xName;
var yAxis = instance.EnsureChartComponent<YAxis>();
yAxis.type = Axis.AxisType.Value;
AxisName yName = new();
yName.name = "Score";
yAxis.axisName = yName;
// Ajouter une l<>gende
var legend = instance.EnsureChartComponent<Legend>();
legend.show = true;
foreach (var playerAttempt in attempt.PlayersAttempts)
{
var scoreOverTime = playerAttempt.Value.PlayerScoresOverTicksTime;
// Ajouter une nouvelle s<>rie avec une l<>gende
var serie = instance.AddSerie<Line>(playerAttempt.Value.PlayerName);
serie.serieName = playerAttempt.Value.PlayerName;
//serie.legendName = playerAttempt.Value.PlayerName;
scoreOverTime.ForEach(score =>
{
serie.AddXYData(score.X, score.Y);
});
}
}
}
#endregion
#region METHODS
private List<SessionData> LoadSessionData()
{
string directoryPath = Path.Combine(Application.persistentDataPath, "SessionsData");
var sessionDataList = new List<SessionData>();
var files = Directory.GetFiles(directoryPath, "*.json");
foreach (var file in files)
{
var json = File.ReadAllText(file);
var sessionData = JsonConvert.DeserializeObject<SessionData>(json);
sessionDataList.Add(sessionData);
}
return sessionDataList.OrderBy<SessionData, int>(s => s.SessionNumber).ToList();
}
#endregion
#region LIFECYCLE
protected override void Awake()
{
base.Awake();
Sessions = LoadSessionData();
_document = GetComponent<UIDocument>();
_exitButton = _document.rootVisualElement.Q("ExitButton") as Button;
_exitButton.RegisterCallback<ClickEvent>(OnExitButton);
_storageButton = _document.rootVisualElement.Q("StorageButton") as Button;
_storageButton.RegisterCallback<ClickEvent>(OnStorageButton);
lineCharts = new();
}
#endregion
}