Resolve WES-133 "Multiple choice"

This commit is contained in:
Tibe Habils
2023-04-10 15:05:11 +00:00
committed by Jelle De Geest
parent 04d9a4bf2b
commit 4e9d801e61
49 changed files with 3310 additions and 1244 deletions

View File

@@ -12,6 +12,9 @@ using UnityEngine.Video;
/// </summary>
public class CoursesController : AbstractFeedback
{
/// <summary>
/// Reference to the objet holding the title
/// </summary>
public TMP_Text courseTitle;
/// <summary>
@@ -44,12 +47,6 @@ public class CoursesController : AbstractFeedback
/// </summary>
private int maxWords;
/// <summary>
/// Number of correct words so far
/// (can be modified to a list or something like that to give better feedback)
/// </summary>
private int correctWords = 0;
/// <summary>
/// The "finished" screen
/// </summary>
@@ -112,11 +109,55 @@ public class CoursesController : AbstractFeedback
/// </summary>
protected string previousIncorrectSign = null;
/// <summary>
/// Keeps track of what type of panel is currently being used
/// </summary>
protected int panelId = 0;
/// <summary>
/// Boolean used to check whether the user has already answered the question
/// </summary>
private bool hasAnswered = false;
/// <summary>
/// Boolean used to check whether SlideIn animation is playing
/// </summary>
private bool isNextSignInTransit = false;
/// <summary>
/// Reference to course progress bar
/// </summary>
public Slider progressBar;
/// <summary>
/// Reference to the animator of the confetti animation
/// </summary>
public Animator confettiAnimation;
/// <summary>
/// Panel with video&image prefab
/// </summary>
public GameObject panelSignWithVideoAndImagePrefab;
/// <summary>
/// Panel with image prefab
/// </summary>
public GameObject panelSignWithImagePrefab;
/// <summary>
/// Panel with multiplechoice prefab
/// </summary>
public GameObject panelMultipleChoicePrefab;
/// <summary>
/// Reference to the canvas to put the panels into
/// </summary>
public Transform canvas;
/// <summary>
/// Reference to the previous panel,
/// so it can be deleted when its done playing its exit animation
/// </summary>
private GameObject previousPanel = null;
/// <summary>
@@ -142,19 +183,24 @@ public class CoursesController : AbstractFeedback
course = courselist.courses[courselist.currentCourseIndex];
maxWords = course.theme.learnables.Count;
// Reload from disk (course may be reset)
PersistentDataController.GetInstance().Load();
// 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;
int index = 0;
foreach (Learnable learnable in course.theme.learnables)
{
progress.AddLearnable(learnable.name, index++);
}
user.AddCourseProgress(progress);
}
UserList.Save();
courseTitle.text = course.title;
progressBar.value = progress.progress;
currentWordIndex = 0;
previousPanel = SetupPanel();
@@ -165,29 +211,38 @@ public class CoursesController : AbstractFeedback
startMoment = DateTime.Now;
}
/// <summary>
/// Fetch the next sign and its panel type
/// </summary>
/// <returns>A tuple of {next sign index, panel type}</returns>
/// <remarks>
/// The different panel types:<br></br>
/// 0 : panelSignWithVideoAndImagePrefab<br></br>
/// 1 : panelMultipleChoicePrefab<br></br>
/// 2 : panelSignWithImagePrefab
/// </remarks>
private Tuple<int, int> 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);
PersistentDataController.SavedLearnableProgress learnable = progress.GetRandomLearnable();
int panelChosen;
if (course.theme.modelIndex == ModelIndex.NONE)
{
// only multiple choice works in preview mode
panelChosen = 1;
}
else if (learnable.progress > 2.0f)
{
panelChosen = 2;
}
else if (learnable.progress > 1.0f)
{
panelChosen = 1;
}
else
{
panelChosen = 0;
}
return Tuple.Create(learnable.index, panelChosen);
}
/// <summary>
@@ -197,78 +252,59 @@ public class CoursesController : AbstractFeedback
/// </summary>
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; }
// This function is also called (async) when pressing the 'Gebaar overslaan' button,
// so check for condition so we don't skip multiple signs
if (isNextSignInTransit || maxWords <= progress.completedLearnables)
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();
progress.progress = (float)progress.completedLearnables / (float)maxWords;
progressBar.value = progress.progress;
// Update UI if course is not finished yet
if (currentWordIndex < maxWords)
if (progress.completedLearnables < maxWords)
{
// Set next sign/video/image
StartCoroutine(CRNextSign());
}
// Finish course and record progress
else
if (progress.completedLearnables == maxWords)
{
FinishCourse();
}
}
/// <summary>
/// Coroutine for going to the next sign
/// </summary>
/// <returns></returns>
private IEnumerator CRNextSign()
{
isNextSignInTransit = true;
GameObject newPanel = SetupPanel();
previousPanel.transform.SetAsFirstSibling();
newPanel.GetComponent<Animator>().SetTrigger("Slide Panel");
newPanel.GetComponent<Animator>().SetTrigger("Slide Panel In");
if (previousPanel != null)
previousPanel.GetComponent<Animator>().SetTrigger("Slide Panel Out");
yield return new WaitForSeconds(1.0f);
confettiAnimation.ResetTrigger("Display Confetti");
GameObject.Destroy(previousPanel);
previousPanel = newPanel;
hasAnswered = false;
isNextSignInTransit = false;
}
/// <summary>
/// Setup a new panel
/// </summary>
/// <returns>Reference to the GameObject of the panel</returns>
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<PanelWithImage>();
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:
case 0:
{
GameObject panel = GameObject.Instantiate(panelSignWithVideoAndImagePrefab, canvas);
panel.transform.SetAsFirstSibling();
@@ -283,6 +319,41 @@ public class CoursesController : AbstractFeedback
feedbackText = script.feedbackText;
script.Display();
signPredictor.SwapScreen(script.webcamScreen);
courseTitle.text = "Voer het gebaar uit voor \"" + course.theme.learnables[currentWordIndex].name + "\"";
return panel;
}
case 1:
{
GameObject panel = GameObject.Instantiate(panelMultipleChoicePrefab, canvas);
panel.transform.SetAsFirstSibling();
PanelMultipleChoice script = panel.GetComponent<PanelMultipleChoice>();
script.signs = course.theme.learnables;
script.currentSignIndex = currentWordIndex;
script.videoPlayer = videoPlayer;
script.courseController = this;
script.progress = progress;
script.isFingerSpelling = course.theme.title == "Handalfabet";
script.Display();
signPredictor.SwapScreen(script.webcamScreen);
courseTitle.text = "Welk gebaar wordt uitgebeeld?";
return panel;
}
case 2:
{
GameObject panel = GameObject.Instantiate(panelSignWithImagePrefab, canvas);
panel.transform.SetAsFirstSibling();
PanelWithImage script = panel.GetComponent<PanelWithImage>();
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);
courseTitle.text = "Voer het gebaar uit voor \"" + course.theme.learnables[currentWordIndex].name + "\"";
return panel;
}
}
@@ -320,7 +391,8 @@ public class CoursesController : AbstractFeedback
/// <returns></returns>
protected override IEnumerator UpdateFeedback()
{
if (currentWordIndex < course.theme.learnables.Count)
// Check if the current word index is still in bounds, and if the current panel type is not multiple choice
if (currentWordIndex < course.theme.learnables.Count && panelId != 1 && !hasAnswered)
{
// Get current sign
Learnable sign = course.theme.learnables[currentWordIndex];
@@ -368,7 +440,7 @@ public class CoursesController : AbstractFeedback
float oldValue = feedbackProgress.value;
// use an exponential scale
float newValue = Mathf.Exp(4 * (accCurrentSign - 1.0f));
float newValue = Mathf.Exp(4 * (Mathf.Clamp(accCurrentSign / sign.thresholdPercentage, 0.0f, 1.0f) - 1.0f));
feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) =>
{
if (feedbackProgress != null)
@@ -419,11 +491,59 @@ public class CoursesController : AbstractFeedback
/// 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.
/// </summary>
/// <param name="predicted"></param>
private void NextSignIfCorrect(string current, string predicted)
public void NextSignIfCorrect(string current, string predicted)
{
if (current == predicted)
NextSign();
if (!hasAnswered)
{
if (current == predicted)
{
hasAnswered = true;
progress.UpdateLearnable(predicted, 1.5f);
confettiAnimation.SetTrigger("Display Confetti");
StartCoroutine(WaitNextSign());
}
else
{
// currently ignore wrong signs as "J" doesn't work well enough
}
}
// TODO: @Tibe cache the incorrect values here
}
private IEnumerator WaitNextSign()
{
// Wait for 0.75 seconds
yield return new WaitForSeconds(0.75f);
NextSign();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="predicted"></param>
public void NextSignMultipleChoice(string current, string predicted)
{
if (!hasAnswered)
{
hasAnswered = true;
if (current == predicted)
{
progress.UpdateLearnable(predicted, 1.5f);
confettiAnimation.SetTrigger("Display Confetti");
}
else
{
progress.UpdateLearnable(predicted, -1.0f);
}
}
}
/// <summary>
/// Callback for the 'back' button
/// </summary>
public void ReturnToActivityScreen()
{
UserList.Save();
SystemController.GetInstance().BackToPreviousScene();
}
}