From a808e73a2999a1a473791d387313591732406099 Mon Sep 17 00:00:00 2001 From: Jerome Coudron Date: Sun, 2 Apr 2023 12:27:59 +0000 Subject: [PATCH] Resolve WES-131-Feedback-REfactor --- .../PlayModeTests/CommonPlayModeTests.asmdef | 3 +- Assets/Common/Scripts/CommonScripts.asmdef | 1 - Assets/Courses/Scenes/CourseScreen.unity | 50 +++-- Assets/Courses/Scripts/CourseScripts.asmdef | 4 +- Assets/Courses/Scripts/CoursesController.cs | 174 +++++++++++++--- Assets/Hangman/Scenes/HangmanGame.unity | 8 +- Assets/Hangman/Scripts/HangManWebcam.cs | 62 ------ Assets/Hangman/Scripts/HangmanController.cs | 185 ++++++++++++++---- Assets/Hangman/Scripts/WebCam.cs | 95 --------- Assets/Hangman/Scripts/WebCam.cs.meta | 11 -- Assets/MediaPipeUnity/Interfaces.meta | 8 + Assets/MediaPipeUnity/Interfaces/Listener.cs | 14 ++ .../Listener.cs.meta} | 2 +- .../Interfaces/SignPredictorInterfaces.asmdef | 14 ++ .../SignPredictorInterfaces.asmdef.meta | 7 + Assets/MediaPipeUnity/Prefabs/Feedback.prefab | 17 -- .../ScriptableObjects/ModelList.asset | 2 +- .../Scripts/AbstractFeedback.cs | 40 ++++ .../Scripts/AbstractFeedback.cs.meta} | 2 +- Assets/MediaPipeUnity/Scripts/Feedback.cs | 182 ----------------- .../Scripts/SignPredictor.asmdef | 3 +- .../MediaPipeUnity/Scripts/SignPredictor.cs | 103 +++++----- .../SpellingBeePlayModeTests.asmdef | 3 +- .../SpellingBee/Scenes/SpellingBeeGame.unity | 32 ++- .../Scripts/SpellingBeeController.cs | 181 ++++++++++++++--- .../Scripts/SpellingBeeController.cs.meta | 16 +- .../Scripts/SpellingBeeScripts.asmdef | 4 +- 27 files changed, 663 insertions(+), 560 deletions(-) delete mode 100644 Assets/Hangman/Scripts/HangManWebcam.cs delete mode 100644 Assets/Hangman/Scripts/WebCam.cs delete mode 100644 Assets/Hangman/Scripts/WebCam.cs.meta create mode 100644 Assets/MediaPipeUnity/Interfaces.meta create mode 100644 Assets/MediaPipeUnity/Interfaces/Listener.cs rename Assets/MediaPipeUnity/{Scripts/Feedback.cs.meta => Interfaces/Listener.cs.meta} (83%) create mode 100644 Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef create mode 100644 Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef.meta create mode 100644 Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs rename Assets/{Hangman/Scripts/HangManWebcam.cs.meta => MediaPipeUnity/Scripts/AbstractFeedback.cs.meta} (83%) delete mode 100644 Assets/MediaPipeUnity/Scripts/Feedback.cs diff --git a/Assets/Common/PlayModeTests/CommonPlayModeTests.asmdef b/Assets/Common/PlayModeTests/CommonPlayModeTests.asmdef index 9e75bf8..1682fab 100644 --- a/Assets/Common/PlayModeTests/CommonPlayModeTests.asmdef +++ b/Assets/Common/PlayModeTests/CommonPlayModeTests.asmdef @@ -8,7 +8,8 @@ "CommonScripts", "InterfacesScripts", "Unity.TextMeshPro", - "AccountsScripts" + "AccountsScripts", + "SignPredictor" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/Common/Scripts/CommonScripts.asmdef b/Assets/Common/Scripts/CommonScripts.asmdef index 8b1a1ba..ed617d5 100644 --- a/Assets/Common/Scripts/CommonScripts.asmdef +++ b/Assets/Common/Scripts/CommonScripts.asmdef @@ -5,7 +5,6 @@ "GUID:6055be8ebefd69e48b49212b09b47b2f", "GUID:63c63e721f65ebb7d871cb9ef49f4752", "GUID:1631ed2680c61245b8211d943c1639a8", - "GUID:5c2b5ba89f9e74e418232e154bc5cc7a", "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25" ], "includePlatforms": [], diff --git a/Assets/Courses/Scenes/CourseScreen.unity b/Assets/Courses/Scenes/CourseScreen.unity index 8f81fd0..bb651dd 100644 --- a/Assets/Courses/Scenes/CourseScreen.unity +++ b/Assets/Courses/Scenes/CourseScreen.unity @@ -1273,7 +1273,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: modelList: {fileID: 11400000, guid: 39516e4e6e56f0f4f80647d9c4d8034c, type: 2} - model: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} modelInfoFile: {fileID: 4900000, guid: fb8b51022bdcd654a9f29c054832a1b5, type: 3} configAsset: {fileID: 4900000, guid: 6288c43cdca97374782dac1ea87aa029, type: 3} screen: {fileID: 378145456} @@ -1529,6 +1528,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6b3f784c065813a4a8364b1299284816, type: 3} m_Name: m_EditorClassIdentifier: + signPredictor: {fileID: 883853268} previewModel: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} feedbackProgressBar: {fileID: 4318122121437849759} previewMessage: {fileID: 2070775951} @@ -1544,7 +1544,9 @@ MonoBehaviour: ResultsDecription: {fileID: 100123246} CoursesButton: {fileID: 839294691} timeSpent: {fileID: 77614869} - feedback: {fileID: 1714882683} + feedbackText: {fileID: 4318122121437849762} + feedbackProgress: {fileID: 4318122121437849761} + feedbackProgressImage: {fileID: 4318122121437849760} --- !u!4 &1122267057 Transform: m_ObjectHideFlags: 0 @@ -2284,17 +2286,6 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 4318122119930585316, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} m_PrefabInstance: {fileID: 4318122121437849758} m_PrefabAsset: {fileID: 0} ---- !u!114 &1714882683 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 4318122119930585317, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} - m_PrefabInstance: {fileID: 4318122121437849758} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4318122121437849759} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 44e682a32ee15cc489bf50f3a06f717b, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!1 &1773033262 GameObject: m_ObjectHideFlags: 0 @@ -2897,3 +2888,36 @@ GameObject: m_CorrespondingSourceObject: {fileID: 4318122119930585319, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} m_PrefabInstance: {fileID: 4318122121437849758} m_PrefabAsset: {fileID: 0} +--- !u!114 &4318122121437849760 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4318122120334233319, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_PrefabInstance: {fileID: 4318122121437849758} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4318122121437849761 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4318122119968934242, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_PrefabInstance: {fileID: 4318122121437849758} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4318122121437849762 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4318122120222767928, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_PrefabInstance: {fileID: 4318122121437849758} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Courses/Scripts/CourseScripts.asmdef b/Assets/Courses/Scripts/CourseScripts.asmdef index 40b6489..53c1ab0 100644 --- a/Assets/Courses/Scripts/CourseScripts.asmdef +++ b/Assets/Courses/Scripts/CourseScripts.asmdef @@ -5,8 +5,8 @@ "Unity.TextMeshPro", "AccountsScripts", "InterfacesScripts", - "SignPredictor", - "Unity.Barracuda" + "Tween", + "SignPredictor" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/Courses/Scripts/CoursesController.cs b/Assets/Courses/Scripts/CoursesController.cs index 19446c7..219fc90 100644 --- a/Assets/Courses/Scripts/CoursesController.cs +++ b/Assets/Courses/Scripts/CoursesController.cs @@ -1,6 +1,7 @@ +using DigitalRuby.Tween; using System; +using System.Collections; using TMPro; -using Unity.Barracuda; using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; @@ -8,13 +9,10 @@ using UnityEngine.Video; /// /// TemplateCourse scene manager /// -public class CoursesController : MonoBehaviour +public class CoursesController : AbstractFeedback { - // vvv TEMPORARY STUFF vvv - public NNModel previewModel; public GameObject feedbackProgressBar; public GameObject previewMessage; - // ^^^ TEMPORARY STUFF ^^^ /// /// Reference to instructional video player @@ -112,10 +110,37 @@ public class CoursesController : MonoBehaviour /// public TMP_Text timeSpent; + /// - /// Reference to the feedback script on the Feedback prefab + /// Reference to the feedback field /// - public Feedback feedback; + public TMP_Text feedbackText; + + /// + /// Reference to the progress bar + /// + public Slider feedbackProgress; + + /// + /// Reference to the progress bar image, so we can add fancy colors + /// + public Image feedbackProgressImage; + + /// + /// 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; + /// /// This function is called when the script is initialised. @@ -124,19 +149,25 @@ public class CoursesController : MonoBehaviour /// 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 Awake() + 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]; - feedback.signPredictor.ChangeModel(course.theme.modelIndex); maxWords = course.theme.learnables.Count; - // vvv TEMPORARY STUFF vvv + // Show preview messages if there is no model feedbackProgressBar.SetActive(course.theme.modelIndex != ModelIndex.NONE); previewMessage.SetActive(course.theme.modelIndex == ModelIndex.NONE); - // Instead, the NONE-modelIndex points to Fingerspelling, which gives the same result - //feedback.signPredictor.model = previewModel; - // ^^^ TEMPORARY STUFF ^^^ // Create entry in current user for keeping track of progress userList.Load(); @@ -164,23 +195,6 @@ public class CoursesController : MonoBehaviour ResultPanel.SetActive(false); // Set the startTime startMoment = DateTime.Now; - - // Set callbacks - feedback.getSignCallback = () => - { - if (currentWordIndex < course.theme.learnables.Count) - { - return course.theme.learnables[currentWordIndex].name; - } - return null; - }; - feedback.predictSignCallback = (sign) => - { - if (sign == course.theme.learnables[currentWordIndex].name) - { - NextSign(); - } - }; } /// @@ -287,4 +301,104 @@ public class CoursesController : MonoBehaviour progress.AddOrUpdate("courseProgress", 1f); userList.Save(); } + /// + /// The updateFunction that is called when new probabilities become available + /// + /// + protected override IEnumerator UpdateFeedback() + { + // Get current sign + string currentSign = course.theme.learnables[currentWordIndex].name; + // Get the predicted sign + if (signPredictor != null && signPredictor.learnableProbabilities != null && + currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) + { + float accuracy = signPredictor.learnableProbabilities[currentSign]; + if (feedbackText != null && feedbackProgressImage != null) + { + if (accuracy > 0.90) + { + feedbackText.text = "Goed"; + feedbackText.color = Color.green; + feedbackProgressImage.color = Color.green; + } + else if (accuracy > 0.80) + { + feedbackText.text = "Bijna..."; + Color col = new Color(0xff / 255.0f, 0x66 / 255.0f, 0x00 / 255.0f); + feedbackText.color = col; + feedbackProgressImage.color = col; + } + else + { + feedbackText.text = "Detecteren..."; + feedbackText.color = Color.red; + feedbackProgressImage.color = Color.red; + } + + float oldValue = feedbackProgress.value; + // use an exponential scale + float newValue = Mathf.Exp(4 * (accuracy - 1.0f)); + feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => + { + if (feedbackProgress != null) + { + feedbackProgress.value = t.CurrentValue; + } + }); + } + + // Check whether (in)correct sign has high accuracy + foreach (var kv in signPredictor.learnableProbabilities) + { + if (kv.Value > 0.90) + { + predictedSign = kv.Key; + // Correct sign + if (predictedSign == currentSign) + { + yield return new WaitForSeconds(1.0f); + CheckEquality(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + // Incorrect sign + else + { + if (previousIncorrectSign != predictedSign) + { + timer = DateTime.Now; + previousIncorrectSign = predictedSign; + } + else if (DateTime.Now - timer > TimeSpan.FromSeconds(2.0f)) + { + CheckEquality(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + } + break; + } + } + } + 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 CheckEquality(string predicted) + { + if (predicted == course.theme.learnables[currentWordIndex].name) + { + NextSign(); + } + } } diff --git a/Assets/Hangman/Scenes/HangmanGame.unity b/Assets/Hangman/Scenes/HangmanGame.unity index ccd072b..ee3a10a 100644 --- a/Assets/Hangman/Scenes/HangmanGame.unity +++ b/Assets/Hangman/Scenes/HangmanGame.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1} + m_IndirectSpecularColor: {r: 0.37311918, g: 0.3807398, b: 0.35872716, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -5129,6 +5129,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 2db44635e0eb1e9429a2e6195785364d, type: 3} m_Name: m_EditorClassIdentifier: + signPredictor: {fileID: 1991376311} themelist: {fileID: 11400000, guid: a247e2ce790f0f746a3bc521e6ab7d58, type: 2} letterPrefab: {fileID: 4639383499500021565, guid: c3e66e8957864914cb022af914df6a28, type: 3} letterContainer: {fileID: 1870283439} @@ -5153,8 +5154,10 @@ MonoBehaviour: Scoreboard: {fileID: 1007532375} EntriesGrid: {fileID: 1391137944} scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} - feedback: {fileID: 5233312447513285388} gottogamebutton: {fileID: 1581633295} + feedbackText: {fileID: 0} + feedbackProgress: {fileID: 0} + feedbackProgressImage: {fileID: 0} --- !u!1 &1678036720 GameObject: m_ObjectHideFlags: 0 @@ -6183,7 +6186,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: modelList: {fileID: 11400000, guid: 39516e4e6e56f0f4f80647d9c4d8034c, type: 2} - model: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} modelInfoFile: {fileID: 4900000, guid: fb8b51022bdcd654a9f29c054832a1b5, type: 3} configAsset: {fileID: 4900000, guid: 6288c43cdca97374782dac1ea87aa029, type: 3} screen: {fileID: 1649505745} diff --git a/Assets/Hangman/Scripts/HangManWebcam.cs b/Assets/Hangman/Scripts/HangManWebcam.cs deleted file mode 100644 index 2690e54..0000000 --- a/Assets/Hangman/Scripts/HangManWebcam.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.UI; - -public class HangManWebcam : WebCam -{ - /// - /// The display for player 1 - /// - public RawImage display1; - - /// - /// The display for player 2 - /// - public RawImage display2; - - /// - /// We use a different awake, since we dont want the camera to start immediatelly - /// - void Awake() - { - WebCamDevice device = WebCamTexture.devices[camdex]; - tex = new WebCamTexture(device.name); - display.texture = tex; - } - - /// - /// Hangman uses two different webcam_textures, we need to be able to toggle between them - /// - public void Switch_texture() - { - if(display == display1) - { - display = display2; - } - else - { - display = display1; - } - // Give the webcamTexture to the new webcam - display.texture = tex; - } - - /// - /// Scene changing is implemented here to avoid problems with webcam - /// - public new void GotoThemeSelection() - { - //minigameList.GetIndexInMinigameList(MinigameIndex.HANGMAN); - if (tex != null) - { - if (tex.isPlaying) - { - display.texture = null; - tex.Stop(); - tex = null; - } - } - SystemController.GetInstance().BackToPreviousScene(); - } -} diff --git a/Assets/Hangman/Scripts/HangmanController.cs b/Assets/Hangman/Scripts/HangmanController.cs index f4280b2..8ce5a94 100644 --- a/Assets/Hangman/Scripts/HangmanController.cs +++ b/Assets/Hangman/Scripts/HangmanController.cs @@ -1,11 +1,13 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; +using DigitalRuby.Tween; -public class HangmanController : MonoBehaviour +public class HangmanController : AbstractFeedback { /// /// The scriptable with all the themes, will be used to select a random word for hangman. @@ -18,11 +20,6 @@ public class HangmanController : MonoBehaviour /// private string currentWord; - /// - /// All of the words that can be used in this session - /// - private string[] words; - /// /// This integer holds the total amount of wrong guesses the player has made /// @@ -200,11 +197,6 @@ public class HangmanController : MonoBehaviour /// public GameObject scoreboardEntry; - /// - /// Accuracy feeback object - /// - public Feedback feedback; - /// /// The button to go into the game /// @@ -215,8 +207,50 @@ public class HangmanController : MonoBehaviour /// private String currentsign = ""; - // Start is called before the first frame update + /// + /// Reference to the feedback field + /// + public TMP_Text feedbackText; + + /// + /// Reference to the progress bar + /// + public Slider feedbackProgress; + + /// + /// Reference to the progress bar image, so we can add fancy colors + /// + public Image feedbackProgressImage; + + /// + /// 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; + + /// + /// Start is called before the first frame update + /// void Start() + { + StartController(); + + signPredictor.SetModel(ModelIndex.FINGERSPELLING); + AddSelfAsListener(); + } + /// + /// Called at the start of the scene AND when the scene is replayed + /// + public void StartController() { // Make sure the mode starts at zero mode = 0; @@ -240,19 +274,6 @@ public class HangmanController : MonoBehaviour user.minigames.Add(progress); } userList.Save(); - - // Hangman always uses fingerspelling - feedback.signPredictor.ChangeModel(ModelIndex.FINGERSPELLING); - - // Set calllbacks - feedback.getSignCallback = () => - { - return "A"; - }; - feedback.predictSignCallback = (sign) => - { - currentsign = sign; - }; } /// @@ -281,7 +302,7 @@ public class HangmanController : MonoBehaviour DeleteWord(); DisplayWord(currentWord); - replayButton.onClick.AddListener(Start); + replayButton.onClick.AddListener(StartController); // Call to display the first image, corresponding to a clean image. ChangeSprite(); } @@ -352,7 +373,7 @@ public class HangmanController : MonoBehaviour { if (mode == 1) { - if (currentsign != "") + if (currentsign != null && currentsign != "") { char letter = currentsign.ToLower()[0]; currentsign = ""; @@ -393,7 +414,7 @@ public class HangmanController : MonoBehaviour // For the first input char given by the user, check if the letter is in the word that needs to be spelled. // Check to make sure the inputfield is not empty - if (currentsign != "") + if (currentsign != null && currentsign != "") { char firstLetter = currentsign.ToLower()[0]; currentsign = ""; @@ -543,21 +564,6 @@ public class HangmanController : MonoBehaviour } } - /// - /// Randomly shuffle the list of words - /// - private void ShuffleWords() - { - for (int i = words.Length - 1; i > 0; i--) - { - // Generate a random index between 0 and i (inclusive) - int j = UnityEngine.Random.Range(0, i + 1); - - // Swap the values at indices i and j - (words[j], words[i]) = (words[i], words[j]); - } - } - /// /// Update and save the scores /// @@ -731,4 +737,97 @@ public class HangmanController : MonoBehaviour rank++; } } + /// + /// The updateFunction that is called when new probabilities become available + /// + /// + protected override IEnumerator UpdateFeedback() + { + // Get current sign + string currentSign = "A"; + // Get the predicted sign + if (signPredictor != null && signPredictor.learnableProbabilities != null && + currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) + { + float accuracy = signPredictor.learnableProbabilities[currentSign]; + if (feedbackText != null && feedbackProgressImage != null) + { + if (accuracy > 0.90) + { + feedbackText.text = "Goed"; + feedbackText.color = Color.green; + feedbackProgressImage.color = Color.green; + } + else if (accuracy > 0.80) + { + feedbackText.text = "Bijna..."; + Color col = new Color(0xff / 255.0f, 0x66 / 255.0f, 0x00 / 255.0f); + feedbackText.color = col; + feedbackProgressImage.color = col; + } + else + { + feedbackText.text = "Detecteren..."; + feedbackText.color = Color.red; + feedbackProgressImage.color = Color.red; + } + + float oldValue = feedbackProgress.value; + // use an exponential scale + float newValue = Mathf.Exp(4 * (accuracy - 1.0f)); + feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => + { + if (feedbackProgress != null) + { + feedbackProgress.value = t.CurrentValue; + } + }); + } + + // Check whether (in)correct sign has high accuracy + foreach (var kv in signPredictor.learnableProbabilities) + { + if (kv.Value > 0.90) + { + predictedSign = kv.Key; + // Correct sign + if (predictedSign == currentSign) + { + yield return new WaitForSeconds(1.0f); + CheckEquality(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + // Incorrect sign + else + { + if (previousIncorrectSign != predictedSign) + { + timer = DateTime.Now; + previousIncorrectSign = predictedSign; + } + else if (DateTime.Now - timer > TimeSpan.FromSeconds(2.0f)) + { + CheckEquality(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + } + break; + } + } + } + else if (feedbackProgress != null) + { + + feedbackProgress.value = 0.0f; + } + + } + private void CheckEquality(string sign) + { + currentsign = sign; + } } diff --git a/Assets/Hangman/Scripts/WebCam.cs b/Assets/Hangman/Scripts/WebCam.cs deleted file mode 100644 index 7809b77..0000000 --- a/Assets/Hangman/Scripts/WebCam.cs +++ /dev/null @@ -1,95 +0,0 @@ -using UnityEngine.UI; -using UnityEngine; - -public class WebCam : MonoBehaviour -{ - /// - /// Index of the current camera - /// - protected int camdex = 0; - - /// - /// Texture to paste on the display - /// - protected WebCamTexture tex; - - /// - /// Display for the video feed - /// - public RawImage display; - - /// - /// Setup the webcam correctly - /// - void Awake() - { - WebCamDevice device = WebCamTexture.devices[camdex]; - tex = new WebCamTexture(device.name); - display.texture = tex; - - tex.Play(); - } - - /// - /// Function to toggle between stopping and starting - /// - /* - public void toggle() - { - if (tex.isPlaying) - { - tex.Stop(); - } - else - { - tex.Play(); - } - } - */ - public void PlayCam() - { - if (!tex.isPlaying) tex.Play(); - } - - public void StopCam() - { - if (tex.isPlaying) tex.Stop(); - } - - /// - /// Swap webcam by cycling through the `WebCamTexture.devices` list - /// - public void SwapCam() - { - if (WebCamTexture.devices.Length > 0) - { - // Stop the old camera - display.texture = null; - tex.Stop(); - tex = null; - - // Find the new camera - camdex += 1; - camdex %= WebCamTexture.devices.Length; - - // Start the new camera - WebCamDevice device = WebCamTexture.devices[camdex]; - tex = new WebCamTexture(device.name); - display.texture = tex; - - tex.Play(); - } - } - - /// - /// Scene changing is implemented here to avoid problems with webcam - /// - public void GotoThemeSelection() - { - display.texture = null; - tex.Stop(); - tex = null; - - SystemController.GetInstance().BackToPreviousScene(); - } -} diff --git a/Assets/Hangman/Scripts/WebCam.cs.meta b/Assets/Hangman/Scripts/WebCam.cs.meta deleted file mode 100644 index 37b2cee..0000000 --- a/Assets/Hangman/Scripts/WebCam.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5db51e2552e03de4b9e7e91b5746adbc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/MediaPipeUnity/Interfaces.meta b/Assets/MediaPipeUnity/Interfaces.meta new file mode 100644 index 0000000..80634de --- /dev/null +++ b/Assets/MediaPipeUnity/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 73c615986873dc246893879daf74c05d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MediaPipeUnity/Interfaces/Listener.cs b/Assets/MediaPipeUnity/Interfaces/Listener.cs new file mode 100644 index 0000000..05880f4 --- /dev/null +++ b/Assets/MediaPipeUnity/Interfaces/Listener.cs @@ -0,0 +1,14 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +/// +/// Listener interface with an IEnumerator as its processing-function. +/// +public interface Listener +{ + /// + /// The function that is called by the publisher. + /// + /// + public IEnumerator ProcessIncomingCall(); +} diff --git a/Assets/MediaPipeUnity/Scripts/Feedback.cs.meta b/Assets/MediaPipeUnity/Interfaces/Listener.cs.meta similarity index 83% rename from Assets/MediaPipeUnity/Scripts/Feedback.cs.meta rename to Assets/MediaPipeUnity/Interfaces/Listener.cs.meta index cd8d001..704b3ba 100644 --- a/Assets/MediaPipeUnity/Scripts/Feedback.cs.meta +++ b/Assets/MediaPipeUnity/Interfaces/Listener.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 44e682a32ee15cc489bf50f3a06f717b +guid: e4c1da9896d9ba2449549a016b5fd15e MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef b/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef new file mode 100644 index 0000000..a39877a --- /dev/null +++ b/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef @@ -0,0 +1,14 @@ +{ + "name": "SignPredictorInterfaces", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef.meta b/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef.meta new file mode 100644 index 0000000..c87212f --- /dev/null +++ b/Assets/MediaPipeUnity/Interfaces/SignPredictorInterfaces.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f55a02e98b01bc849b30d9650ccd8f15 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MediaPipeUnity/Prefabs/Feedback.prefab b/Assets/MediaPipeUnity/Prefabs/Feedback.prefab index ee82a36..47e40cf 100644 --- a/Assets/MediaPipeUnity/Prefabs/Feedback.prefab +++ b/Assets/MediaPipeUnity/Prefabs/Feedback.prefab @@ -9,7 +9,6 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 4318122119930585316} - - component: {fileID: 4318122119930585317} m_Layer: 5 m_Name: Feedback m_TagString: Untagged @@ -39,22 +38,6 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 200} m_SizeDelta: {x: 500, y: 150} m_Pivot: {x: 0.5, y: 0} ---- !u!114 &4318122119930585317 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4318122119930585319} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 44e682a32ee15cc489bf50f3a06f717b, type: 3} - m_Name: - m_EditorClassIdentifier: - feedbackText: {fileID: 4318122120222767928} - feedbackProgress: {fileID: 4318122119968934242} - feedbackProgressImage: {fileID: 4318122120334233319} - signPredictor: {fileID: 0} --- !u!1 &4318122119968934244 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/MediaPipeUnity/ScriptableObjects/ModelList.asset b/Assets/MediaPipeUnity/ScriptableObjects/ModelList.asset index 12d799c..897dae7 100644 --- a/Assets/MediaPipeUnity/ScriptableObjects/ModelList.asset +++ b/Assets/MediaPipeUnity/ScriptableObjects/ModelList.asset @@ -17,4 +17,4 @@ MonoBehaviour: - index: 0 model: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} - index: 1 - model: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} + model: {fileID: 0} diff --git a/Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs b/Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs new file mode 100644 index 0000000..ab5b0c8 --- /dev/null +++ b/Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs @@ -0,0 +1,40 @@ +using DigitalRuby.Tween; +using Mediapipe.Unity.Tutorial; +using System; +using System.Collections; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; + +/// +/// Class to display feedback during a course +/// +public abstract class AbstractFeedback : MonoBehaviour, Listener +{ + + /// + /// Reference to the sign predictor + /// + public SignPredictor signPredictor; + /// + /// The function that is called by the publisher on all its listeners + /// + /// + public IEnumerator ProcessIncomingCall() + { + yield return StartCoroutine(UpdateFeedback()); + } + /// + /// A function to add yourself as listener to the signPredictor you are holding + /// + public void AddSelfAsListener() + { + signPredictor.listeners.Add(this); + } + /// + /// The function that holds the logic to process the new probabilities of the signPredictor + /// + /// + protected abstract IEnumerator UpdateFeedback(); +} \ No newline at end of file diff --git a/Assets/Hangman/Scripts/HangManWebcam.cs.meta b/Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs.meta similarity index 83% rename from Assets/Hangman/Scripts/HangManWebcam.cs.meta rename to Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs.meta index de815e7..5fdbcf9 100644 --- a/Assets/Hangman/Scripts/HangManWebcam.cs.meta +++ b/Assets/MediaPipeUnity/Scripts/AbstractFeedback.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a62d2b8bded916443835dc19010b83c1 +guid: 7b5ac794337a54143a6e3077483d96c9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/MediaPipeUnity/Scripts/Feedback.cs b/Assets/MediaPipeUnity/Scripts/Feedback.cs deleted file mode 100644 index bbb88a0..0000000 --- a/Assets/MediaPipeUnity/Scripts/Feedback.cs +++ /dev/null @@ -1,182 +0,0 @@ -using DigitalRuby.Tween; -using Mediapipe.Unity.Tutorial; -using System; -using System.Collections; -using TMPro; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.UI; - -/// -/// Class to display feedback during a course -/// -public class Feedback : MonoBehaviour -{ - /// - /// Reference to the feedback field - /// - public TMP_Text feedbackText; - - /// - /// Reference to the progress bar - /// - public Slider feedbackProgress; - - /// - /// Reference to the progress bar image, so we can add fancy colors - /// - public Image feedbackProgressImage; - - /// - /// Reference to the sign predictor - /// - public SignPredictor signPredictor; - - /// - /// Callback for getting the correct sign - /// - public Func getSignCallback; - - /// - /// Callback to initiate the next sign - /// - public UnityAction predictSignCallback; - - /// - /// Timer to keep track of how long a incorrect sign is performed - /// - private DateTime timer; - - /// - /// Current predicted sign - /// - private string predictedSign = null; - - /// - /// Previous incorrect sign, so we can keep track whether the user is wrong or the user is still changing signs - /// - private string previousIncorrectSign = null; - - /// - /// Start is called before the first frame update - /// - void Start() - { - // Start the coroutine to update the scale every 200 milliseconds - StartCoroutine(UpdateFeedback()); - } - - /// - /// UpdateScale updates the progress bar every 200ms, updated the feedback text, and progress bar color - /// If a high enough accuracy is detected, it will go to the next sign - /// - /// - IEnumerator UpdateFeedback() - { - while (true) - { - if (getSignCallback != null && predictSignCallback != null) - { - - // Get current sign - string currentSign = getSignCallback(); - // Get the predicted sign - if (signPredictor != null && signPredictor.learnableProbabilities != null && - currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) - { - float accuracy = signPredictor.learnableProbabilities[currentSign]; - if (feedbackText != null && feedbackProgressImage != null){ - if (accuracy > 0.98) - { - // TODO: fix emojis - feedbackText.text = "✨ Perfect ✨"; - Color col = new Color(0xff / 255.0f, 0xcc / 255.0f, 0x00 / 255.0f); - feedbackText.color = col; - feedbackProgressImage.color = col; - } - else if (accuracy > 0.95) - { - feedbackText.text = "Super!"; - Color col = new Color(0x00 / 255.0f, 0xff / 255.0f, 0xcc / 255.0f); - feedbackText.color = col; - feedbackProgressImage.color = col; - } - else if (accuracy > 0.90) - { - feedbackText.text = "Goed"; - feedbackText.color = Color.green; - feedbackProgressImage.color = Color.green; - } - else if (accuracy > 0.80) - { - feedbackText.text = "Bijna..."; - Color col = new Color(0xff / 255.0f, 0x66 / 255.0f, 0x00 / 255.0f); - feedbackText.color = col; - feedbackProgressImage.color = col; - } - else - { - feedbackText.text = "Detecteren..."; - feedbackText.color = Color.red; - feedbackProgressImage.color = Color.red; - } - - float oldValue = feedbackProgress.value; - // use an exponential scale - float newValue = Mathf.Exp(4 * (accuracy - 1.0f)); - feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => - { - if (feedbackProgress != null) - { - feedbackProgress.value = t.CurrentValue; - } - }); - } - - // Check whether (in)correct sign has high accuracy - foreach (var kv in signPredictor.learnableProbabilities) - { - if (kv.Value > 0.90) - { - predictedSign = kv.Key; - // Correct sign - if (predictedSign == currentSign) - { - yield return new WaitForSeconds(1.0f); - predictSignCallback(predictedSign); - timer = DateTime.Now; - predictedSign = null; - previousIncorrectSign = null; - } - // Incorrect sign - else - { - if (previousIncorrectSign != predictedSign) - { - timer = DateTime.Now; - previousIncorrectSign = predictedSign; - } - else if (DateTime.Now - timer > TimeSpan.FromSeconds(2.0f)) - { - predictSignCallback(predictedSign); - timer = DateTime.Now; - predictedSign = null; - previousIncorrectSign = null; - } - } - break; - } - } - } - else if(feedbackProgress != null) - { - - feedbackProgress.value = 0.0f; - } - } - - // Wait for 200 milliseconds before updating the scale again - yield return new WaitForSeconds(0.2f); - } - } -} \ No newline at end of file diff --git a/Assets/MediaPipeUnity/Scripts/SignPredictor.asmdef b/Assets/MediaPipeUnity/Scripts/SignPredictor.asmdef index ba94856..2a113bb 100644 --- a/Assets/MediaPipeUnity/Scripts/SignPredictor.asmdef +++ b/Assets/MediaPipeUnity/Scripts/SignPredictor.asmdef @@ -7,7 +7,8 @@ "GUID:04c4d86a70aa56c55a78c61f1ab1a56d", "GUID:edc93f477bb73a743a97d6882ed330b3", "GUID:58e104b97fb3752438ada2902a36dcbf", - "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25" + "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", + "GUID:f55a02e98b01bc849b30d9650ccd8f15" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/MediaPipeUnity/Scripts/SignPredictor.cs b/Assets/MediaPipeUnity/Scripts/SignPredictor.cs index 2ec4263..4648e26 100644 --- a/Assets/MediaPipeUnity/Scripts/SignPredictor.cs +++ b/Assets/MediaPipeUnity/Scripts/SignPredictor.cs @@ -141,6 +141,8 @@ namespace Mediapipe.Unity.Tutorial /// private Tensor inputTensor; + public List listeners = new List(); + /// /// Google Mediapipe setup & run /// @@ -159,6 +161,7 @@ namespace Mediapipe.Unity.Tutorial webcamTexture.Play(); + yield return new WaitUntil(() => webcamTexture.width > 16); // Set webcam aspect ratio @@ -167,63 +170,63 @@ namespace Mediapipe.Unity.Tutorial float webcamAspect = (float)webcamTexture.width / (float)webcamTexture.height; screen.rectTransform.sizeDelta = new Vector2(screen.rectTransform.sizeDelta.y * webcamAspect, (screen.rectTransform.sizeDelta.y)); screen.texture = webcamTexture; - if(screen2 != null) + if (screen2 != null) { screen2.rectTransform.sizeDelta = new Vector2(screen2.rectTransform.sizeDelta.y * webcamAspect, (screen2.rectTransform.sizeDelta.y)); } - // TODO this method is kinda meh you should use - inputTexture = new Texture2D(width, height, TextureFormat.RGBA32, false); - pixelData = new Color32[width * height]; - - if (!resourceManagerIsInitialized) + if (modelList.GetCurrentModel() != null) { - resourceManager = new StreamingAssetsResourceManager(); - yield return resourceManager.PrepareAssetAsync("pose_detection.bytes"); - yield return resourceManager.PrepareAssetAsync("pose_landmark_full.bytes"); - yield return resourceManager.PrepareAssetAsync("face_landmark.bytes"); - yield return resourceManager.PrepareAssetAsync("hand_landmark_full.bytes"); - yield return resourceManager.PrepareAssetAsync("face_detection_short_range.bytes"); - yield return resourceManager.PrepareAssetAsync("hand_recrop.bytes"); - yield return resourceManager.PrepareAssetAsync("handedness.txt"); - resourceManagerIsInitialized = true; + // TODO this method is kinda meh you should use + inputTexture = new Texture2D(width, height, TextureFormat.RGBA32, false); + pixelData = new Color32[width * height]; + + if (!resourceManagerIsInitialized) + { + resourceManager = new StreamingAssetsResourceManager(); + yield return resourceManager.PrepareAssetAsync("pose_detection.bytes"); + yield return resourceManager.PrepareAssetAsync("pose_landmark_full.bytes"); + yield return resourceManager.PrepareAssetAsync("face_landmark.bytes"); + yield return resourceManager.PrepareAssetAsync("hand_landmark_full.bytes"); + yield return resourceManager.PrepareAssetAsync("face_detection_short_range.bytes"); + yield return resourceManager.PrepareAssetAsync("hand_recrop.bytes"); + yield return resourceManager.PrepareAssetAsync("handedness.txt"); + resourceManagerIsInitialized = true; + } + + stopwatch = new Stopwatch(); + + // Setting up the graph + graph = new CalculatorGraph(configAsset.text); + + posestream = new OutputStream(graph, "pose_landmarks", "pose_landmarks_presence"); + leftstream = new OutputStream(graph, "left_hand_landmarks", "left_hand_landmarks_presence"); + rightstream = new OutputStream(graph, "right_hand_landmarks", "right_hand_landmarks_presence"); + + posestream.StartPolling().AssertOk(); + leftstream.StartPolling().AssertOk(); + rightstream.StartPolling().AssertOk(); + + graph.StartRun().AssertOk(); + stopwatch.Start(); + + + keypointManager = new KeypointManager(modelInfoFile); + // check if model exists at path + //var model = ModelLoader.Load(Resources.Load("Models/Fingerspelling/model_A-L")); + worker = modelList.GetCurrentModel().CreateWorker(); + + StartCoroutine(SignRecognitionCoroutine()); + StartCoroutine(MediapipeCoroutine()); } - - stopwatch = new Stopwatch(); - - // Setting up the graph - graph = new CalculatorGraph(configAsset.text); - - posestream = new OutputStream(graph, "pose_landmarks", "pose_landmarks_presence"); - leftstream = new OutputStream(graph, "left_hand_landmarks", "left_hand_landmarks_presence"); - rightstream = new OutputStream(graph, "right_hand_landmarks", "right_hand_landmarks_presence"); - - posestream.StartPolling().AssertOk(); - leftstream.StartPolling().AssertOk(); - rightstream.StartPolling().AssertOk(); - - graph.StartRun().AssertOk(); - stopwatch.Start(); - - - keypointManager = new KeypointManager(modelInfoFile); - - // check if model exists at path - //var model = ModelLoader.Load(Resources.Load("Models/Fingerspelling/model_A-L")); - worker = modelList.GetCurrentModel().CreateWorker(); - - StartCoroutine(SignRecognitionCoroutine()); - StartCoroutine(MediapipeCoroutine()); } - - public void ChangeModel(ModelIndex index) + /// + /// Called at the start of course/Minigame, will set the model before the start of SIgnPredictor is called. + /// + /// The index of the model to be used + public void SetModel(ModelIndex index) { this.modelList.SetCurrentModel(index); - // If a worker already existed, we throw it out - worker?.Dispose(); - - // Add a new worker for the new model - worker = modelList.GetCurrentModel().CreateWorker(); } /// @@ -325,6 +328,10 @@ namespace Mediapipe.Unity.Tutorial learnableProbabilities.Add(((char)(i + 65)).ToString(), softmaxedOutput2[i]); } //Debug.Log($"prob = [{learnableProbabilities.Aggregate(" ", (t, kv) => $"{t}{kv.Key}:{kv.Value} ")}]"); + foreach(Listener listener in listeners) + { + yield return listener.ProcessIncomingCall(); + } } else { diff --git a/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef b/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef index a332ab7..3cb15ac 100644 --- a/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef +++ b/Assets/SpellingBee/PlayModeTests/SpellingBeePlayModeTests.asmdef @@ -7,7 +7,8 @@ "InterfacesScripts", "Unity.TextMeshPro", "SpellingBeeScripts", - "AccountsScripts" + "AccountsScripts", + "SignPredictor" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/SpellingBee/Scenes/SpellingBeeGame.unity b/Assets/SpellingBee/Scenes/SpellingBeeGame.unity index 64d9253..ae5d17e 100644 --- a/Assets/SpellingBee/Scenes/SpellingBeeGame.unity +++ b/Assets/SpellingBee/Scenes/SpellingBeeGame.unity @@ -1741,13 +1741,35 @@ RectTransform: m_PrefabAsset: {fileID: 0} --- !u!114 &967164045 stripped MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 4318122119930585317, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_CorrespondingSourceObject: {fileID: 4318122120334233319, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} m_PrefabInstance: {fileID: 967164043} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 44e682a32ee15cc489bf50f3a06f717b, type: 3} + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &967164046 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4318122119968934242, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_PrefabInstance: {fileID: 967164043} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &967164047 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4318122120222767928, guid: 7c71c65ecb5fe0449a8b0d178987f016, type: 3} + m_PrefabInstance: {fileID: 967164043} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} m_Name: m_EditorClassIdentifier: --- !u!1 &978093274 @@ -3584,6 +3606,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 44fbed5ae228de39b9f727def7578d06, type: 3} m_Name: m_EditorClassIdentifier: + signPredictor: {fileID: 1592592444} themeList: {fileID: 11400000, guid: a247e2ce790f0f746a3bc521e6ab7d58, type: 2} userList: {fileID: 11400000, guid: 072bec636a40f7e4e93b0ac624a3bda2, type: 2} minigame: {fileID: 11400000, guid: 8a087d241d652634eb4f6352267ea7dc, type: 2} @@ -3594,8 +3617,10 @@ MonoBehaviour: timerText: {fileID: 1843239267} bonusTimeText: {fileID: 1812475780} Scoreboard: {fileID: 862382568} - feedback: {fileID: 967164045} gameEndedPanel: {fileID: 757133117} + feedbackText: {fileID: 967164047} + feedbackProgress: {fileID: 967164046} + feedbackProgressImage: {fileID: 967164045} --- !u!1 &1499197558 GameObject: m_ObjectHideFlags: 0 @@ -3957,7 +3982,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: modelList: {fileID: 11400000, guid: 39516e4e6e56f0f4f80647d9c4d8034c, type: 2} - model: {fileID: 5022602860645237092, guid: e6d85df707405ad4f97c23b07227ee99, type: 3} modelInfoFile: {fileID: 4900000, guid: fb8b51022bdcd654a9f29c054832a1b5, type: 3} configAsset: {fileID: 4900000, guid: 6288c43cdca97374782dac1ea87aa029, type: 3} screen: {fileID: 1743003084} diff --git a/Assets/SpellingBee/Scripts/SpellingBeeController.cs b/Assets/SpellingBee/Scripts/SpellingBeeController.cs index 0da99a5..71c5fd0 100644 --- a/Assets/SpellingBee/Scripts/SpellingBeeController.cs +++ b/Assets/SpellingBee/Scripts/SpellingBeeController.cs @@ -5,8 +5,9 @@ using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; +using DigitalRuby.Tween; -public partial class SpellingBeeController : MonoBehaviour +public partial class SpellingBeeController : AbstractFeedback { /// /// All of the words that can be used in this session @@ -136,20 +137,55 @@ public partial class SpellingBeeController : MonoBehaviour /// public Transform Scoreboard; - /// - /// Accuracy feeback object - /// - public Feedback feedback; - /// /// Reference to the gameEnded panel, so we can update its display /// public GameObject gameEndedPanel; + /// + /// Reference to the feedback field + /// + public TMP_Text feedbackText; + + /// + /// Reference to the progress bar + /// + public Slider feedbackProgress; + + /// + /// Reference to the progress bar image, so we can add fancy colors + /// + public Image feedbackProgressImage; + + /// + /// 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; + /// /// Start is called before the first frame update /// public void Start() + { + StartController(); + + signPredictor.SetModel(currentTheme.modelIndex); + AddSelfAsListener(); + } + /// + /// Is called at the start of the scene AND when the game is replayed + /// + public void StartController() { correctLetters = 0; incorrectLetters = 0; @@ -182,29 +218,10 @@ public partial class SpellingBeeController : MonoBehaviour userList.Save(); currentTheme = minigame.themeList.themes[minigame.themeList.currentThemeIndex]; - feedback.signPredictor.ChangeModel(currentTheme.modelIndex); + //feedback.signPredictor.ChangeModel(currentTheme.modelIndex); words.AddRange(currentTheme.learnables); ShuffleWords(); NextWord(); - - // Set calllbacks - feedback.getSignCallback = () => - { - if (letterIndex < currentWord.Length) - { - return currentWord[letterIndex].ToString().ToUpper(); - } - return null; - }; - feedback.predictSignCallback = (sign) => - { - bool successful = sign.ToUpper() == currentWord[letterIndex].ToString().ToUpper(); - if (successful) - { - AddSeconds(secondsPerLetter); - } - NextLetter(successful); - }; } /// @@ -453,4 +470,116 @@ public partial class SpellingBeeController : MonoBehaviour { yield return new WaitForSecondsRealtime(2); } + + /// + /// The updateFunction that is called when new probabilities become available + /// + /// + protected override IEnumerator UpdateFeedback() + { + // Get current sign + string currentSign = GetSign(); + // Get the predicted sign + if (signPredictor != null && signPredictor.learnableProbabilities != null && + currentSign != null && signPredictor.learnableProbabilities.ContainsKey(currentSign)) + { + float accuracy = signPredictor.learnableProbabilities[currentSign]; + if (feedbackText != null && feedbackProgressImage != null) + { + if (accuracy > 0.90) + { + feedbackText.text = "Goed"; + feedbackText.color = Color.green; + feedbackProgressImage.color = Color.green; + } + else if (accuracy > 0.80) + { + feedbackText.text = "Bijna..."; + Color col = new Color(0xff / 255.0f, 0x66 / 255.0f, 0x00 / 255.0f); + feedbackText.color = col; + feedbackProgressImage.color = col; + } + else + { + feedbackText.text = "Detecteren..."; + feedbackText.color = Color.red; + feedbackProgressImage.color = Color.red; + } + + float oldValue = feedbackProgress.value; + // use an exponential scale + float newValue = Mathf.Exp(4 * (accuracy - 1.0f)); + feedbackProgress.gameObject.Tween("FeedbackUpdate", oldValue, newValue, 0.2f, TweenScaleFunctions.CubicEaseInOut, (t) => + { + if (feedbackProgress != null) + { + feedbackProgress.value = t.CurrentValue; + } + }); + } + + // Check whether (in)correct sign has high accuracy + foreach (var kv in signPredictor.learnableProbabilities) + { + if (kv.Value > 0.90) + { + predictedSign = kv.Key; + // Correct sign + if (predictedSign == currentSign) + { + yield return new WaitForSeconds(1.0f); + predictSign(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + // Incorrect sign + else + { + if (previousIncorrectSign != predictedSign) + { + timer = DateTime.Now; + previousIncorrectSign = predictedSign; + } + else if (DateTime.Now - timer > TimeSpan.FromSeconds(2.0f)) + { + predictSign(predictedSign); + timer = DateTime.Now; + predictedSign = null; + previousIncorrectSign = null; + } + } + break; + } + } + } + else if (feedbackProgress != null) + { + + feedbackProgress.value = 0.0f; + } + yield return null; + } + /// + /// Function to get the current letter that needs to be signed + /// + /// the current letter that needs to be signed + public string GetSign(){ + if (letterIndex + /// Function to confirm your prediction and check if it is correct. + /// + /// + public void predictSign(string sign) { + bool successful = sign.ToUpper() == currentWord[letterIndex].ToString().ToUpper(); + if (successful) + { + AddSeconds(secondsPerLetter); + } + NextLetter(successful); + } } diff --git a/Assets/SpellingBee/Scripts/SpellingBeeController.cs.meta b/Assets/SpellingBee/Scripts/SpellingBeeController.cs.meta index be352cc..6fa40d2 100644 --- a/Assets/SpellingBee/Scripts/SpellingBeeController.cs.meta +++ b/Assets/SpellingBee/Scripts/SpellingBeeController.cs.meta @@ -3,21 +3,7 @@ guid: 44fbed5ae228de39b9f727def7578d06 MonoImporter: externalObjects: {} serializedVersion: 2 - defaultReferences: - - input: {instanceID: 0} - - endText: {instanceID: 0} - - correctWordsText: {instanceID: 0} - - correctLettersText: {instanceID: 0} - - gameEndedPanel: {instanceID: 0} - - replayButton: {instanceID: 0} - - userList: {instanceID: 0} - - minigame: {instanceID: 0} - - letterPrefab: {instanceID: 0} - - letterContainer: {instanceID: 0} - - wordImage: {instanceID: 0} - - timerText: {instanceID: 0} - - Scoreboard: {instanceID: 0} - - scoreboardEntry: {fileID: 9154151134820372555, guid: d4a3a228b08d61847acc6da35b44e52c, type: 3} + defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: diff --git a/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef b/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef index d8c8edb..dd37fc8 100644 --- a/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef +++ b/Assets/SpellingBee/Scripts/SpellingBeeScripts.asmdef @@ -6,8 +6,8 @@ "GUID:1631ed2680c61245b8211d943c1639a8", "GUID:3444c67d5a3a93e5a95a48906078c372", "GUID:d0b6b39a21908f94fbbd9f2c196a9725", - "GUID:5c2b5ba89f9e74e418232e154bc5cc7a", - "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25" + "GUID:7f2d0ee6dd21e1d4eb25b71b7a749d25", + "GUID:58e104b97fb3752438ada2902a36dcbf" ], "includePlatforms": [], "excludePlatforms": [],