using DigitalRuby.Tween; using System; using System.Collections; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; /// /// TemplateCourse scene manager /// public class CoursesController : AbstractFeedback { public TMP_Text courseTitle; /// /// The current user /// private User user; /// /// Current user progress for this course /// private PersistentDataController.SavedCourseProgress progress = null; /// /// ScriptableObject with list of all courses /// public CourseList courselist; /// /// Reference to Course ScriptableObject /// private Course course; /// /// Index of the current word/letter in the course.learnables list /// private int currentWordIndex = 0; /// /// This holds the amount of words in the course /// private int maxWords; /// /// Number of correct words so far /// (can be modified to a list or something like that to give better feedback) /// private int correctWords = 0; /// /// The "finished" screen /// public GameObject ResultPanel; /// /// Reference to the title on the results panel /// public TMP_Text ResultsTitle; /// /// Reference to the description on the results panel /// public TMP_Text ResultsDecription; /// /// Button to go back to courses list /// public Button CoursesButton; /// /// DateTime containint the start moment /// private DateTime startMoment; /// /// Reference to the timeSpent UI /// public TMP_Text timeSpent; /// /// Reference to the feedback field /// private TMP_Text feedbackText; /// /// Reference to the progress bar /// private Slider feedbackProgress; /// /// Reference to the progress bar image, so we can add fancy colors /// private Image feedbackProgressImage; public VideoPlayer videoPlayer; /// /// Timer to keep track of how long a incorrect sign is performed /// protected DateTime timer; /// /// Current predicted sign /// protected string predictedSign = null; /// /// Previous incorrect sign, so we can keep track whether the user is wrong or the user is still changing signs /// protected string previousIncorrectSign = null; public Animator confettiAnimation; public GameObject panelSignWithVideoAndImagePrefab; public GameObject panelSignWithImagePrefab; public GameObject panelMultipleChoicePrefab; public Transform canvas; private GameObject previousPanel = null; /// /// This function is called when the script is initialised. /// It inactivatis the popup, finds a webcam to use and links it via the WebcamTexture to the display RawImage. /// It takes the correct course from the courselist, using the courseIndex. /// Then it checks whether or not the User has started the course yet, to possibly create a new progress atribute for the course. /// Then it sets up the course-screen to display relevant information from the course-scriptable. /// void Start() { StartCourseController(); signPredictor.SetModel(course.theme.modelIndex); AddSelfAsListener(); } /// /// Holds the course-specific logic to start the controller, it is seperated to allow the course to be reset (if that would become needed) /// public void StartCourseController() { // Setting up course course = courselist.courses[courselist.currentCourseIndex]; maxWords = course.theme.learnables.Count; // Create entry in current user for keeping track of progress //PersistentDataController pdc = PersistentDataController.GetInstance(); //pdc.Load(); user = UserList.GetCurrentUser(); progress = user.GetCourseProgress(course.index); if (progress == null) { progress = new PersistentDataController.SavedCourseProgress(); progress.courseIndex = course.index; user.AddCourseProgress(progress); } UserList.Save(); courseTitle.text = course.title; currentWordIndex = 0; previousPanel = SetupPanel(); // Hide the result panel ResultPanel.SetActive(false); // Set the startTime startMoment = DateTime.Now; } private Tuple FetchSign() { // TODO: @Tibe here you need to provide other signs and there question method /************************** * TODO: @Tibe * * In deze functie beslist welk panel je nu nodig hebt * Momenteel doe ik gwn iets om te wisselen tussen de twee (moet zeker weg want je begint ALTIJD met een imageANDvideo * * Je ziet zelf maar hoe groot je de sets van woorden maakt, om de 5 ofzo * Altijd eerst video and image, nadien kan je afwisselen, mag random * * ALSO: * Hiervoor moet ge bij Dries zijn, maar man is verdwenen. (100 jaar gingen voorbij en mijn broer en ik vonden een nieuwe oorzaak voor hoofdpijn) * Progress gaat ook aangepast moeten worden, als een user terugkeert tijdens een course en later hervat ga je moeten weten welke set die zat * Zeker als ge woorden in een andere volgorde zou willen doen, gaat ge echt nog aan uw 40u geraken :) * --> dat gaat bijgehouden worden in een van die user files. (Stalk Dries indien nodig, voor andere zaken kan je mij ook vragen stellen) * * *************************/ int panelChosen = currentWordIndex % 2 + 1; return Tuple.Create(currentWordIndex, panelChosen); } /// /// This function is called when the next-sign button is pressed. /// It increased the wordindex and fetches new videos/images if index public void NextSign() { // If the currentindex >= maxwords, it indicated that the course is already finished, running the next code is the meaningless. if (currentWordIndex >= maxWords) { return; } confettiAnimation.SetTrigger("Display Confetti"); // Goto the next word/letter currentWordIndex++; // TODO: fix correct word count correctWords++; progress.progress = (float)correctWords / (float)maxWords; UserList.Save(); // Update UI if course is not finished yet if (currentWordIndex < maxWords) { // Set next sign/video/image StartCoroutine(CRNextSign()); } // Finish course and record progress else { FinishCourse(); } } private IEnumerator CRNextSign() { GameObject newPanel = SetupPanel(); previousPanel.transform.SetAsFirstSibling(); newPanel.GetComponent().SetTrigger("Slide Panel"); yield return new WaitForSeconds(1.0f); confettiAnimation.ResetTrigger("Display Confetti"); GameObject.Destroy(previousPanel); previousPanel = newPanel; } private GameObject SetupPanel() { int panelId; (currentWordIndex, panelId) = FetchSign().ToValueTuple(); switch (panelId) { case 0: return null; // TODO: @Tibe multiple choice setup /************************** * TODO: @Tibe * * Hier moet de panelMultipleChoice worden aangemaakt * Geef publieke dingen mee aan uw script. * Kan je eventueel zelf aanpassen, * naargelang hoe je het wilt implementeren * * *************************/ case 1: { GameObject panel = GameObject.Instantiate(panelSignWithImagePrefab, canvas); panel.transform.SetAsFirstSibling(); PanelWithImage script = panel.GetComponent(); script.signs = course.theme.learnables; script.currentSignIndex = currentWordIndex; script.isPreview = (course.theme.modelIndex == ModelIndex.NONE); feedbackProgress = script.feedbackProgressBar; feedbackProgressImage = script.feedbackProgressImage; feedbackText = script.feedbackText; script.Display(); signPredictor.SwapScreen(script.webcamScreen); return panel; } case 2: { GameObject panel = GameObject.Instantiate(panelSignWithVideoAndImagePrefab, canvas); panel.transform.SetAsFirstSibling(); PanelWithVideoAndImage script = panel.GetComponent(); script.signs = course.theme.learnables; script.currentSignIndex = currentWordIndex; script.isPreview = (course.theme.modelIndex == ModelIndex.NONE); script.videoPlayer = videoPlayer; feedbackProgress = script.feedbackProgressBar; feedbackProgressImage = script.feedbackProgressImage; feedbackText = script.feedbackText; script.Display(); signPredictor.SwapScreen(script.webcamScreen); return panel; } } return null; } /// /// finishcourse is called to save the "finished" progress to the user. /// public void FinishCourse() { // Show the "finished" screen ResultPanel.SetActive(true); // Set the correct title ResultsTitle.text = course.title + " voltooid!"; // Set the correct description ResultsDecription.text = "Goed gedaan! Je kan nu spelletjes spelen met " + course.title + " om verder te oefenen!"; // Set the total time spent UI TimeSpan time = DateTime.Now - startMoment; timeSpent.text = time.ToString(@"hh\:mm\:ss"); // Link button CoursesButton.onClick.AddListener(() => { SystemController.GetInstance().BackToPreviousScene(); }); progress.progress = 1.0f; UserList.Save(); } /// /// The updateFunction that is called when new probabilities become available /// /// protected override IEnumerator UpdateFeedback() { if (currentWordIndex < course.theme.learnables.Count) { // Get current sign Learnable sign = course.theme.learnables[currentWordIndex]; string currentSign = sign.name; // Get the predicted sign if (signPredictor != null && signPredictor.learnableProbabilities != null && currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) { float accCurrentSign = signPredictor.learnableProbabilities[currentSign]; // Get highest predicted sign string predictedSign = signPredictor.learnableProbabilities.Aggregate((a, b) => a.Value > b.Value ? a : b).Key; float accPredictSign = signPredictor.learnableProbabilities[predictedSign]; Learnable predSign = course.theme.learnables.Find(l => l.name == predictedSign); if (feedbackText != null && feedbackProgressImage != null) { Color col; if (accCurrentSign > sign.thresholdPercentage) { feedbackText.text = "Goed"; col = new Color(0x8b / 255.0f, 0xd4 / 255.0f, 0x5e / 255.0f); } else if (accCurrentSign > 0.9 * sign.thresholdPercentage) { feedbackText.text = "Bijna"; col = new Color(0xf2 / 255.0f, 0x7f / 255.0f, 0x0c / 255.0f); } else if (accPredictSign > predSign.thresholdPercentage) { feedbackText.text = $"Verkeerde gebaar: '{predictedSign}'"; col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); accCurrentSign = 0.0f; } else { feedbackText.text = $"Detecteren ..."; col = new Color(0xf5 / 255.0f, 0x49 / 255.0f, 0x3d / 255.0f); //accCurrentSign = 0.0f; } feedbackText.color = col; feedbackProgressImage.color = col; float oldValue = feedbackProgress.value; // use an exponential scale float newValue = Mathf.Exp(4 * (accCurrentSign - 1.0f)); feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => { if (feedbackProgress != null) { feedbackProgress.value = t.CurrentValue; } }); } if (accPredictSign > sign.thresholdPercentage) { // Correct sign if (predictedSign == currentSign) { yield return new WaitForSeconds(1.0f); NextSignIfCorrect(currentSign, predictedSign); timer = DateTime.Now; previousIncorrectSign = null; predictedSign = null; } // Incorrect sign else { if (previousIncorrectSign != predictedSign) { timer = DateTime.Now; previousIncorrectSign = predictedSign; } else if (predictedSign != null && currentSign != null && (DateTime.Now - timer).TotalSeconds > 2.0f) { NextSignIfCorrect(currentSign, predictedSign); timer = DateTime.Now; predictedSign = null; previousIncorrectSign = null; } } } } else if (feedbackProgress != null) { feedbackProgress.value = 0.0f; } } } /// /// Function to check equality between the current sign and the sign that the model predicted, if they are equal then the next sign is fetched. /// /// private void NextSignIfCorrect(string current, string predicted) { if (current == predicted) NextSign(); // TODO: @Tibe cache the incorrect values here } }