using System; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; /// /// UserProgressScreen scene manager /// public class UserProgressScreen : MonoBehaviour { /// /// Reference to the userlist /// public UserList userList; /// /// Reference to the current user /// private User user; /// /// UI reference to the username /// public TMP_Text username; /// /// UI reference to the user's avatar /// public Image avatar; /// /// UI reference to the user total playtime /// public TMP_Text playtime; /// /// Prefab of the highscore marker to display on the graph /// public GameObject highscoreMarker; /// /// Prefab of a course card /// public GameObject courseCardPrefab; /// /// UI reference to the container holding all course cards /// public GameObject coursesContainer; /// /// UI reference to the message that displays when no course progress is present /// public GameObject emptyCourses; /// /// Prefab of a minigame card /// public GameObject minigameCardPrefab; /// /// UI reference to the container holding all the minigame cards /// public GameObject minigamesContainer; /// /// UI reference to the message that displays when no minigame progress is present /// public GameObject emptyMinigames; /// /// UI reference to the plot /// public RawImage progressGraph; /// /// Left and right padding of the graph /// private const int GRAPH_PADDING_X_PX = 50; /// /// Top and bottom padding of the graph /// private const int GRAPH_PADDING_Y_PX = 50; /// /// Radius of the point on the graph /// private const int GRAPH_POINT_RADIUS = 10; /// /// Size of the line on the graph /// private const int GRAPH_LINE_SIZE = 4; /// /// Current selected activity draw to the graph /// private int selectedActivity = -1; /// /// List of activity backgrounds and indices /// private List> activities = new List>(); /// /// Start is called before the first frame update /// 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(); cpc.courseProgress = courseProgress; cpc.selectActivity = () => UpdateSelection(j); // Store reference to background so we can apply fancy coloring Image background = instance.GetComponent(); background.color = Color.gray; activities.Add(Tuple.Create(background, (int)courseProgress.Get("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(); mpc.minigameProgress = minigameProgress; mpc.selectActivity = () => UpdateSelection(j); // Store reference to background so we can apply fancy coloring Image background = instance.GetComponent(); background.color = Color.gray; activities.Add(Tuple.Create(background, (int)minigameProgress.Get("minigameIndex"))); } } /// /// Update the current selected activity /// /// Index to the new activity 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); } } /// /// Plot the graph of a course /// /// Index of the course /// TODO: create a better plot private void DisplayCourseGraph(CourseIndex index) { } /// /// Plot the graph of a minigame /// /// Index of the minigame private void DisplayMinigameGraph(MinigameIndex minigameIndex) { Progress progress = user.GetMinigameProgress(minigameIndex); List latestScores = progress.Get>("latestScores"); List highestScores = progress.Get>("highestScores"); if (0 < highestScores.Count) { PlotGraph(latestScores.ConvertAll((s) => (double)s.scoreValue), highestScores[0].scoreValue); } else { progressGraph.gameObject.SetActive(false); } } /// /// Plot points and a highscore on the graph /// /// List of score values to plot /// Highscore value (this will be plotted in a fancy color) private void PlotGraph(List 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> points = new List>(); 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(); rect.localPosition = new Vector3(0, y - 25, 0); } // Draw points for (int i = 0; i < points.Count; i++) { Tuple p = points[i]; if (0 < i) { Tuple 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; } /// /// Draw a point to a texture /// /// Texture2D to plot point on /// Center x-pos /// Center y-pos /// Radius (aka width and height) /// Color of the point 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); } } } /// /// Draw a line to a texture /// /// Texture2D to plot line on /// Starting x-pos /// Strating y-pos /// Ending x-pos /// Ending y-pos /// Size of the line (width) /// Color of the line 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; } } }