370 lines
12 KiB
C#
370 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
/// <summary>
|
|
/// UserProgressScreen scene manager
|
|
/// </summary>
|
|
public class UserProgressScreen : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// Reference to the userlist
|
|
/// </summary>
|
|
public UserList userList;
|
|
|
|
/// <summary>
|
|
/// Reference to the current user
|
|
/// </summary>
|
|
private User user;
|
|
|
|
/// <summary>
|
|
/// UI reference to the username
|
|
/// </summary>
|
|
public TMP_Text username;
|
|
|
|
/// <summary>
|
|
/// UI reference to the user's avatar
|
|
/// </summary>
|
|
public Image avatar;
|
|
|
|
/// <summary>
|
|
/// UI reference to the user total playtime
|
|
/// </summary>
|
|
public TMP_Text playtime;
|
|
|
|
/// <summary>
|
|
/// Prefab of the highscore marker to display on the graph
|
|
/// </summary>
|
|
public GameObject highscoreMarker;
|
|
|
|
/// <summary>
|
|
/// Prefab of a course card
|
|
/// </summary>
|
|
public GameObject courseCardPrefab;
|
|
|
|
/// <summary>
|
|
/// UI reference to the container holding all course cards
|
|
/// </summary>
|
|
public GameObject coursesContainer;
|
|
|
|
/// <summary>
|
|
/// UI reference to the message that displays when no course progress is present
|
|
/// </summary>
|
|
public GameObject emptyCourses;
|
|
|
|
/// <summary>
|
|
/// Prefab of a minigame card
|
|
/// </summary>
|
|
public GameObject minigameCardPrefab;
|
|
|
|
/// <summary>
|
|
/// UI reference to the container holding all the minigame cards
|
|
/// </summary>
|
|
public GameObject minigamesContainer;
|
|
|
|
/// <summary>
|
|
/// UI reference to the message that displays when no minigame progress is present
|
|
/// </summary>
|
|
public GameObject emptyMinigames;
|
|
|
|
/// <summary>
|
|
/// UI reference to the plot
|
|
/// </summary>
|
|
public RawImage progressGraph;
|
|
|
|
/// <summary>
|
|
/// Left and right padding of the graph
|
|
/// </summary>
|
|
private const int GRAPH_PADDING_X_PX = 50;
|
|
|
|
/// <summary>
|
|
/// Top and bottom padding of the graph
|
|
/// </summary>
|
|
private const int GRAPH_PADDING_Y_PX = 50;
|
|
|
|
/// <summary>
|
|
/// Radius of the point on the graph
|
|
/// </summary>
|
|
private const int GRAPH_POINT_RADIUS = 10;
|
|
|
|
/// <summary>
|
|
/// Size of the line on the graph
|
|
/// </summary>
|
|
private const int GRAPH_LINE_SIZE = 4;
|
|
|
|
/// <summary>
|
|
/// Current selected activity draw to the graph
|
|
/// </summary>
|
|
private int selectedActivity = -1;
|
|
|
|
/// <summary>
|
|
/// List of activity backgrounds and indices
|
|
/// </summary>
|
|
private List<Tuple<Image, int>> activities = new List<Tuple<Image, int>>();
|
|
|
|
|
|
/// <summary>
|
|
/// Start is called before the first frame update
|
|
/// </summary>
|
|
void Start()
|
|
{
|
|
// Assign the current user
|
|
user = userList.GetCurrentUser();
|
|
|
|
// Set correct displayed items
|
|
username.text = user.username;
|
|
avatar.sprite = user.avatar;
|
|
// TODO: implement total playtime
|
|
//playtime.text = $"Totale speeltijd: {user.playtime.ToString("0.00")}";
|
|
|
|
// Set graph inactive
|
|
progressGraph.gameObject.SetActive(false);
|
|
|
|
int i = 0;
|
|
// Display courses
|
|
coursesContainer.SetActive(user.courses.Count > 0);
|
|
emptyCourses.SetActive(user.courses.Count <= 0);
|
|
foreach (Progress courseProgress in user.courses)
|
|
{
|
|
// Create instance of prefab
|
|
GameObject instance = GameObject.Instantiate(courseCardPrefab, coursesContainer.transform.Find("Viewport").Find("Content").transform);
|
|
int j = i++;
|
|
|
|
// Initialize card
|
|
CourseProgressCard cpc = instance.GetComponent<CourseProgressCard>();
|
|
cpc.courseProgress = courseProgress;
|
|
cpc.selectActivity = () => UpdateSelection(j);
|
|
|
|
// Store reference to background so we can apply fancy coloring
|
|
Image background = instance.GetComponent<Image>();
|
|
background.color = Color.gray;
|
|
activities.Add(Tuple.Create(background, (int)courseProgress.Get<CourseIndex>("courseIndex")));
|
|
}
|
|
|
|
// Display minigames
|
|
minigamesContainer.SetActive(user.minigames.Count > 0);
|
|
emptyMinigames.SetActive(user.minigames.Count <= 0);
|
|
foreach (Progress minigameProgress in user.minigames)
|
|
{
|
|
// Create instance of prefab
|
|
GameObject instance = GameObject.Instantiate(minigameCardPrefab, minigamesContainer.transform.Find("Viewport").Find("Content").transform);
|
|
int j = i++;
|
|
|
|
// Initialize card
|
|
MinigameProgressCard mpc = instance.GetComponent<MinigameProgressCard>();
|
|
mpc.minigameProgress = minigameProgress;
|
|
mpc.selectActivity = () => UpdateSelection(j);
|
|
|
|
// Store reference to background so we can apply fancy coloring
|
|
Image background = instance.GetComponent<Image>();
|
|
background.color = Color.gray;
|
|
activities.Add(Tuple.Create(background, (int)minigameProgress.Get<MinigameIndex>("minigameIndex")));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the current selected activity
|
|
/// </summary>
|
|
/// <param name="newActivity">Index to the new activity</param>
|
|
private void UpdateSelection(int newActivity)
|
|
{
|
|
if (selectedActivity < 0)
|
|
{
|
|
progressGraph.gameObject.SetActive(true);
|
|
}
|
|
else
|
|
{
|
|
activities[selectedActivity].Item1.color = Color.gray;
|
|
}
|
|
|
|
selectedActivity = newActivity;
|
|
activities[selectedActivity].Item1.color = Color.blue;
|
|
if (selectedActivity < user.courses.Count)
|
|
{
|
|
// TODO: create a better graph
|
|
//DisplayCourseGraph((CourseIndex)activities[selectedActivity].Item2);
|
|
// For now: just deactivate graph rendering
|
|
progressGraph.gameObject.SetActive(false);
|
|
}
|
|
else
|
|
{
|
|
DisplayMinigameGraph((MinigameIndex)activities[selectedActivity].Item2);
|
|
// TODO: remove line, this is only because courses deactivates the graph
|
|
progressGraph.gameObject.SetActive(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plot the graph of a course
|
|
/// </summary>
|
|
/// <param name="index">Index of the course</param>
|
|
/// <remarks>TODO: create a better plot</remarks>
|
|
private void DisplayCourseGraph(CourseIndex index) { }
|
|
|
|
/// <summary>
|
|
/// Plot the graph of a minigame
|
|
/// </summary>
|
|
/// <param name="minigameIndex">Index of the minigame</param>
|
|
private void DisplayMinigameGraph(MinigameIndex minigameIndex)
|
|
{
|
|
Progress progress = user.GetMinigameProgress(minigameIndex);
|
|
List<Score> scores = progress.Get<List<Score>>("latestScores");
|
|
PlotGraph(scores.ConvertAll<double>((s) => (double)s.scoreValue), progress.Get<List<Score>>("highestScores")[0].scoreValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Plot points and a highscore on the graph
|
|
/// </summary>
|
|
/// <param name="scores">List of score values to plot</param>
|
|
/// <param name="highscore">Highscore value (this will be plotted in a fancy color)</param>
|
|
private void PlotGraph(List<double> scores, double highscore)
|
|
{
|
|
// Remove previous marker(s)
|
|
foreach (Transform child in progressGraph.gameObject.transform)
|
|
{
|
|
Destroy(child.gameObject);
|
|
}
|
|
|
|
// Get texture reference
|
|
Texture2D tex = progressGraph.texture as Texture2D;
|
|
if (tex == null)
|
|
{
|
|
RectTransform rt = progressGraph.gameObject.transform as RectTransform;
|
|
tex = new Texture2D(
|
|
width: (int)rt.sizeDelta.x,
|
|
height: (int)rt.sizeDelta.y,
|
|
textureFormat: TextureFormat.ARGB32,
|
|
mipCount: 3,
|
|
linear: true
|
|
);
|
|
}
|
|
tex.filterMode = FilterMode.Point;
|
|
|
|
// calculate positions and offsets
|
|
int x0 = GRAPH_PADDING_X_PX, x1 = tex.width - GRAPH_PADDING_X_PX;
|
|
int y0 = GRAPH_PADDING_Y_PX, y1 = tex.height - GRAPH_PADDING_Y_PX;
|
|
double min = scores.Min();
|
|
double max = scores.Max();
|
|
|
|
List<Tuple<int, int>> points = new List<Tuple<int, int>>();
|
|
for (int i = 0; i < scores.Count; i++)
|
|
{
|
|
int x = x0 + (scores.Count > 1 ? i * ((x1 - x0) / (scores.Count - 1)) : (x1 - x0) / 2);
|
|
int y = y0 + (int)((y1 - y0) * (min != max ? (scores[i] - min) / (max - min) : 0.5));
|
|
points.Add(Tuple.Create(x, y));
|
|
}
|
|
|
|
// Calculate scaling
|
|
int mag = (int)Math.Round(Math.Log10(max));
|
|
int MAG = (int)Math.Pow(10, mag);
|
|
double c = max / MAG;
|
|
|
|
// Draw axes
|
|
if (min != max)
|
|
{
|
|
for (double d = c / 5.0; d < c; d += 0.2 * c)
|
|
{
|
|
int y = y0 + (int)((y1 - y0) * (MAG * d - min) / (max - min));
|
|
DrawLine(tex, x0, y, x1, y, 2, Color.gray);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int y = y0 + (int)((y1 - y0) * 0.5);
|
|
DrawLine(tex, x0, y0, x1, y0, 2, Color.gray);
|
|
DrawLine(tex, x0, y, x1, y, 2, Color.gray);
|
|
DrawLine(tex, x0, y1, x1, y1, 2, Color.gray);
|
|
}
|
|
|
|
// Draw highscore
|
|
if (min <= highscore && highscore <= max)
|
|
{
|
|
int y = y0 + (int)((y1 - y0) * (min != max ? (highscore - min) / (max - min) : 0.5));
|
|
DrawLine(tex, x0, y, x1, y, 3, new Color(255, 192, 0));
|
|
GameObject marker = GameObject.Instantiate(highscoreMarker, progressGraph.gameObject.transform);
|
|
RectTransform rect = marker.GetComponent<RectTransform>();
|
|
rect.localPosition = new Vector3(0, y - 25, 0);
|
|
}
|
|
|
|
// Draw points
|
|
for (int i = 0; i < points.Count; i++)
|
|
{
|
|
Tuple<int, int> p = points[i];
|
|
if (0 < i)
|
|
{
|
|
Tuple<int, int> q = points[i - 1];
|
|
DrawLine(tex, p.Item1, p.Item2, q.Item1, q.Item2, GRAPH_LINE_SIZE, Color.blue);
|
|
}
|
|
DrawPoint(tex, p.Item1, p.Item2, GRAPH_POINT_RADIUS, Color.blue);
|
|
}
|
|
|
|
// Apply to graph GameObject
|
|
tex.Apply();
|
|
progressGraph.texture = tex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw a point to a texture
|
|
/// </summary>
|
|
/// <param name="tex">Texture2D to plot point on</param>
|
|
/// <param name="xc">Center x-pos</param>
|
|
/// <param name="yc">Center y-pos</param>
|
|
/// <param name="r">Radius (aka width and height)</param>
|
|
/// <param name="color">Color of the point</param>
|
|
private void DrawPoint(Texture2D tex, int xc, int yc, int r, Color color)
|
|
{
|
|
for (int y = yc - r; y < yc + r; y++)
|
|
{
|
|
for (int x = xc - r; x < xc + r; x++)
|
|
{
|
|
tex.SetPixel(x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw a line to a texture
|
|
/// </summary>
|
|
/// <param name="tex">Texture2D to plot line on</param>
|
|
/// <param name="x0">Starting x-pos</param>
|
|
/// <param name="y0">Strating y-pos</param>
|
|
/// <param name="x1">Ending x-pos</param>
|
|
/// <param name="y1">Ending y-pos</param>
|
|
/// <param name="size">Size of the line (width)</param>
|
|
/// <param name="color">Color of the line</param>
|
|
private void DrawLine(Texture2D tex, int x0, int y0, int x1, int y1, int size, Color color)
|
|
{
|
|
int w = x1 - x0;
|
|
int h = y1 - y0;
|
|
|
|
int length = Mathf.Abs(x1 - x0);
|
|
if (Mathf.Abs(y1 - y0) > length)
|
|
{
|
|
length = Mathf.Abs(h);
|
|
}
|
|
|
|
double dx = w / (double)length;
|
|
double dy = h / (double)length;
|
|
|
|
double x = x0;
|
|
double y = y0;
|
|
double r = size / 2;
|
|
for (int i = 0; i <= length; i++)
|
|
{
|
|
for (int j = (int)(y - r); j < y + r; j++)
|
|
{
|
|
for (int k = (int)(x - r); k < x + r; k++)
|
|
{
|
|
tex.SetPixel(k, j, color);
|
|
}
|
|
}
|
|
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
}
|
|
}
|