// This is meant to "smooth" the data and make it less noisy by averaging the last NUMFRAMES. private AffectiveStates averageState() { AffectiveStates state = AffectiveStates.None; lastFrames[frame] = determineState(); // This gets the state for the current frame. frame = ((frame + 1) % NUMFRAMES); int nones = 0; int frust = 0; int bored = 0; int flow = 0; // Count the number of each state we've got on record. for (int i = 0; i < NUMFRAMES; i++) { switch (lastFrames[i]) { case AffectiveStates.Boredom: bored++; break; case AffectiveStates.Flow: flow++; break; case AffectiveStates.Frustration: frust++; break; default: nones++; break; } } // Most frequent wins. if ((frust > bored) && (frust > flow) && (frust > nones)) { state = AffectiveStates.Frustration; } else if ((bored > flow) && (bored > nones)) { state = AffectiveStates.Boredom; } else if (flow > nones) { state = AffectiveStates.Flow; } // else - we already have none stored in state. //Debug.Log((int)state); return(state); }
/// <summary> /// This is the hard one, where the real work happens. This is where we determine the affective state from the current frame. /// Visage gives us the emotion estimations, that's the easy part. The hard part is going from those six measures to our /// affective states. Literature, such as Craig et al. (2008), tell us that the affective states will appear as the following: /// Boredom: Neutrality, no real emotion showed. /// Frustration: High anger, low happiness. May also include disgust, but disgust proved unreliable. /// Flow: Surprise/Awe and low sadness. May also appear as neutrality, which can cause confusion when compared to boredom. /// Based on those, the measures used below were created to try and detect each of these. Feel free to modify this as /// needed, but be aware that even small changes will have a major impact on affective state detection. If you feel that /// the states are being detected incorrectly, this is the part to modify. /// </summary> private AffectiveStates determineState() { AffectiveStates state = AffectiveStates.None; // DETECT BOREDOM: If all estimations are below THRESHOLD, player is showing neutrality. bool nonBored = false; for (int i = 0; ((i < probs.Length) && (!nonBored)); i++) { if (probs[i] > THRESHOLD) { nonBored = true; } } // If player is not bored, they must be something else. if (nonBored) { // DETECT FRUSTRATION: High anger and low happiness is a sign of frustration. if ((probs[ANGER] > THRESHOLD) && (probs[HAPPINESS] < THRESHOLD)) { state = AffectiveStates.Frustration; } // Player is not frustrated, are they in flow? else { // DETECT FLOW: Noticible surprise and low sadness show flow. if ((probs[SURPRISE] > THRESHOLD) && (probs[SADDNESS] < THRESHOLD)) { state = AffectiveStates.Flow; } // NOTE! No else here. If we get here, all of our measures have failed. // This means that the player is in a state we can't recognize, so we return None. } } else { // Double check that they might be in Flow, as it can look a lot like boredom. // DETECT FLOW: If they appear bored and are currently in flow, or just were in flow, they're probably in flow still/again. if ((currentState == AffectiveStates.Flow) || (lastState == AffectiveStates.Flow)) { state = AffectiveStates.Flow; } // Nope, they are truely bored. else { state = AffectiveStates.Boredom; } } return(state); }
// Update is called once per frame void Update() { if (tracking) { if (gameObject.GetComponent <Tracker>().TrackerStatus != 0) { /////////////////////////////////////////////////////////////////////////////////////////////////////// // Emotions are acqured here! Be extremely careful about making any changes to these two lines! // The entire emotion system depends on these working successfully. IntPtr nums = VisageTrackerNative._getEmotions(); // Due to cross language type mangling, we have to marshal the result into something we can use. Marshal.Copy(nums, probs, 0, 6); /////////////////////////////////////////////////////////////////////////////////////////////////////// FileManagement.dump(probs); if (probs[0] != -9999) // -9999 is the "error" code from the plugin. { for (int i = 0; i < probs.Length; i++) { // Truncate to three places for easier display probs[i] = (float)Math.Truncate(1000 * probs[i]) / 1000; } haveRecentData = true; } else { haveRecentData = false; currentState = AffectiveStates.None; } } else { haveRecentData = false; } // If we have gotten recent data, and are not waiting for the next wave, analyse it. if (haveRecentData && !waitForWave) { analyzeData(); } } }
// Helper to translate affective state. private static string getState(AffectiveStates state) { string print; switch (state) { case AffectiveStates.Boredom: print = "Boredom"; break; case AffectiveStates.Flow: print = "Flow"; break; case AffectiveStates.Frustration: print = "Frustration"; break; default: print = "None"; break; } return(print); }
// Called when an emotion has been detected long enough for us to make judgements off of it. public static void emotionMax(AffectiveStates state) { print("EMOTION MAX REACHED - The player has been detected in " + getState(state) + " for long enough to consider it their new state."); }
// Called when a new affective state is detected. public static void stateChange(AffectiveStates state) { print("STATE CHANGE - NEW STATE -> " + getState(state)); }
// This is where we begin trying to determine the player's affective state. private void analyzeData() { // Are we waiting between changes? if (currentDelay > 0) { currentDelay--; } else { // Do we need to update the data? if (wasWaiting) { haveRecentData = false; wasWaiting = false; // Reset stored frames to ensure we don't accidentally make judgements off them. for (int i = 0; i < lastFrames.Length; i++) { lastFrames[i] = AffectiveStates.None; } FileManagement.delayEnd(); } else { AffectiveStates update = averageState(); // Are they still in the same state? if (currentState == update) { // Only increase this if we are tracking an actual emotion if (currentState != AffectiveStates.None) { currentEmotionDuration++; } } else { // State has changed. Reset everything. currentState = update; currentEmotionDuration = 0; FileManagement.stateChange(currentState); } // Have they sustained that emotion long enough to consider it their state? if (currentEmotionDuration == EMOTIONMAX) { // Reset everything. lastState = currentState; wasWaiting = true; currentDelay = ANALYSISDELAY; currentEmotionDuration = 0; FileManagement.emotionMax(currentState); waitForWave = true; Debug.Log((int)currentState); // Determine how (if at all) we need to modify difficulty. switch (currentState) { case AffectiveStates.Boredom: curDifficulty = stepUp(); //setDifficulty(1); Debug.Log("stepUp"); break; case AffectiveStates.Frustration: curDifficulty = stepDown(); Debug.Log("stepDown"); //setDifficulty(1); break; case AffectiveStates.Flow: // If they're in Flow, don't change anything for awhile. currentDelay = ANALYSISDELAY * 3; Debug.Log("Flow"); break; } DifficultyManagement.setDifficulty(curDifficulty); } } } }