Emotiv

2. Emotiv Epoc - softvér na vývoj

2.1. Quiz

Vytvorenie panelu – v Hierarchy vytvoríme cez pravé tlačidlo UI -> Panel. Toto bude naša obrazovka kde postupne pridáme tlačidlá a texty.



Pridanie textov – Do objektu Panel pridáme Text – TextMeshPro cez UI -> Text – TextMeshPro (ak vyskočí okno pre import TMP tak len potvrdíme). Po vytvorení ho v Hierarchy premenujeme na Question.



Objekt vieme presúvať po scéne cez Move Tool a meniť veľkosť cez Rect Tool. Presunieme teda text do hornej časti Panelu.


Inspectore upravíme text a vycentrujeme.


Takýmto štýlom pridáme ešte dva texty do horných rohov Panela, ktorým nastavíme text na 0. V Hierarchy si texty pomenujeme ScoreTimer.


Pridanie tlačidiel  - Pridáme do panela 3 tlačidlá cez UI -> Button – TextMeshPro­. Pomenujeme ich Start, Answer1, Answer2.


Tlačidlo Start presunieme do dolnej časti. Tlačidlo Answer1 presunieme na ľavú stranu a Answer2 na pravú stranu. Nastavíme im Width na 200 a Height na 100.


Každé tlačidlo obsahuje objekt Text. Text tlačidla Start nastavíme na Start.




Vytvorenie scriptu – v Assets vytvoríme C# Script, ktorý pomenujeme Quiz a otvoríme.


Na začiatok scriptu pridáme knižnice:

  1. je samotný Emotiv plugin
  2. je UI podpora tlačidiel
  3. knižnica pre Text - TMPro

using EmotivUnityPlugin;
using UnityEngine.UI;
using TMPro;


Do class Quiz pridáme premenné pre prihlásenie sa do Emotiv Cortex API (potrebné pre prácu s Emotiv zariadením)

private string _clientId = "";
private string _clientSecret = "";
private string _appName = "emotiv";
private string _appVersion = "3.3.0";

EmotivUnityItf _eItf = EmotivUnityItf.Instance;
DataStreamManager _dsManager = DataStreamManager.Instance;
float _timerDataUpdate = 0;
const float TIME_UPDATE_DATA = 1f;
bool _isDataBufferUsing = false; // default subscribed data will not saved to Data buffer


Pridáme taktiež premenné pre Quiz

// Premenné na nastavenie Textov
public TMP_Text QuestionText;  
public TMP_Text Answer1Text;  
public TMP_Text Answer2Text;  
public TMP_Text ScoreText;  
public TMP_Text TimerText;  

private bool _DataSubscribed = false;
private bool _QuizEnabled = false;

private int _AnswerBtn = -1; // cislo tlacidla so spravnym vysledkom
private int _Score = 0;
private int _SelAnswer = -1; // cislo zvoleneho tlacidla
private float _Timer;

// Maximalne cislo pri generovani prikladov
private const int _MaxMathNum = 10;


Pridáme funckiu SetQnA, ktorá bude generovať príklady

void SetQnA()
{
    int num1 = Random.Range(1, _MaxMathNum + 1);
    int num2 = Random.Range(1, _MaxMathNum + 1);
    QuestionText.text = "Kolko je " + num1 + " + " + num2 + "?";

    int answer = num1 + num2;

    // Nahodne nastavenie nespravneho vysledku (bud +1 alebo -1)
    int incorrect = (Random.Range(1, 3) == 1) ? 1 : -1;

    // Nahodny vyber tlacidla so spravnym vysledkom
    if ((_AnswerBtn = Random.Range(1, 2 + 1)) == 1) {
        num1 = answer;
        num2 = answer + incorrect;
    } else {
        num1 = answer + incorrect;
        num2 = answer;
    }
    Answer1Text.text = num1.ToString();
    Answer2Text.text = num2.ToString();
}


Pridáme funckiu AnswerButtonSetup, ktorá bude nastavovať farbu tlačidlám

void AnswerButtonSetup(int button, Color color)
{
    if (button == 1) {
        GameObject.Find("Answer1").GetComponent<Button>().GetComponent<Image>().color = color;
    }
    else if (button == 2) {
        GameObject.Find("Answer2").GetComponent<Button>().GetComponent<Image>().color = color;
    }
}

Pridáme funckiu ResetQnA, ktorá bude resetovať premenné do defaultu

void ResetQnA()
{
    _AnswerBtn = -1;
    _Timer = 8;
    _SelAnswer = -1;

    QuestionText.text = "Toto je otázka?";
    Answer2Text.text = "Odpoved";
    Answer1Text.text = "Odpoved";
    ScoreText.text = _Score.ToString();
    TimerText.text = _Timer.ToString();

    AnswerButtonSetup(1, Color.white);
    AnswerButtonSetup(2, Color.white);
}


Pridáme funkciu StartQnA, ktorá spustí celý Quiz

void StartQnA()
{
    ResetQnA();
    SetQnA();
}


Do funkcie Start pridáme ResetQnA a inicializujeme Emotiv

void Start()
{
    ResetQnA(); 
    _eItf.Init(_clientId, _clientSecret, _appName, _appVersion, _isDataBufferUsing);

    _eItf.Start(); // Start Emotiv

    // Hook pre ziskanie dat o mimike
    _dsManager.FacialExpReceived += OnFacialExpReceived;  
}


Do funkcie Update pridáme Timer pre Quiz a kontrolu ci je spustená detekcia mimiky tváre

void Update()
{
    // Ak je Quiz zapnuty, spusti sa odpocet, ktory na konci skontroluje odpovede
    if (_QuizEnabled) {
        _Timer -= Time.deltaTime;
        if (Mathf.FloorToInt(_Timer % 60) >= 0) {
            AnswerButtonSetup(_SelAnswer, Color.yellow);

            if (Mathf.FloorToInt(_Timer % 60) == 0)
            {
                // Kontrola odpovedi a nastavenie Score
                if (_AnswerBtn == _SelAnswer) {
                    _Score++;
                    AnswerButtonSetup(_AnswerBtn, Color.green);
                } else {
                    _Score--;
                    AnswerButtonSetup(_AnswerBtn, Color.green);
                    if (_SelAnswer != -1) AnswerButtonSetup(_SelAnswer, Color.red);
                }
                _Timer = -1;
            }
        } 
        else if (_Timer <= -2.0) StartQnA();
    }

    // Nastavenie textov na obrazovke
    QuestionText.text = QuestionText.text;
    Answer2Text.text = Answer2Text.text;
    Answer1Text.text = Answer1Text.text;
    ScoreText.text = _Score.ToString();
    // Text s casom bude vzdy mat hodnotu >= 0
    TimerText.text = (_Timer >= 0) ? Mathf.FloorToInt(_Timer % 60).ToString() : "0";

    // Spusti kontrolu Subscribe (spustenie detekcie v nasom pripade mimiky tvare)
    // raz za 1f (DEC: 31) aby sa nespustala kontrola kazdy frame
    _timerDataUpdate += Time.deltaTime;
    if (_timerDataUpdate < TIME_UPDATE_DATA) 
        return;

    _timerDataUpdate -= TIME_UPDATE_DATA;
    
    if (_eItf.IsAuthorizedOK && _eItf.IsSessionCreated && !_DataSubscribed) {
        SetupSubscribe();
    }
}


Pridáme funkciu, ktorá sa vyvolá vždy keď sa získajú nové dáta o mimike

private void OnFacialExpReceived(object sender, FacEventArgs data)
{
    // Ak uz je nejaka odpoved zvolená alebo nie je Quiz zapnuty -> return
    if (_SelAnswer != -1 || !_QuizEnabled) return;
    
    // Zmurknutie Lavym alebo Pravym okom
    if (data.EyeAct == "winkL") _SelAnswer = 1;
    else if (data.EyeAct == "winkR") _SelAnswer = 2;
}


Pridáme funckiu onCreateSessionBtnClick, ktorá sa vyvolá pri stlačení tlačidla Start

public void onCreateSessionBtnClick()
{
    // Pri vyuzivani Emotiv je potrebne vytvorit Session
    if (!_eItf.IsSessionCreated) {
        _eItf.CreateSessionWithHeadset(null);
    }

    // Ak klikneme na Start, zmeni sa nazov tlacidla na Stop a spusti sa Quiz
    // Ak klikneme na Stop, zmeni sa nazov tlacidla na Start a vypne sa Quiz
    if (!_QuizEnabled) {
        _QuizEnabled = true;
        StartQnA();
        GameObject.Find("Start").GetComponent<Button>().GetComponentInChildren<TMP_Text>().text = "Stop";
    }
    else {
        _QuizEnabled = false;
        _Score = 0;
        ResetQnA();
        GameObject.Find("Start").GetComponent<Button>().GetComponentInChildren<TMP_Text>().text = "Start";
    }
}


Pridanie funckie OnApplicationQuit, ktorá sa vyvolá keď vypneme hru.

void OnApplicationQuit()
{
    Debug.Log("Application ending after " + Time.time + " seconds");
    _eItf.Stop();
}

Pridanie funckie SetupSubscribe

private void SetupSubscribe()
{
    // Spustenie Subscribe pre face (mimiku)
    if (_eItf.IsSessionCreated)
    {
        List<string> _streams = new List<string> {};
        _streams.Add("fac");

        _eItf.DataSubLog = ""; // clear data subscribing log
        _eItf.SubscribeData(_streams);
        _DataSubscribed = true;
    }
    else {
        UnityEngine.Debug.LogError("Must create a session first before subscribing data.");
    }
}

Script uložíme a presunieme ho z Assets do objektu Canvas



Hierarchy presunieme všetky texty na pravú stranu do scriptu podľa názvov. Question do Question Text, Text (TMP) pod tlačidlom Answer1 do Answer1 Text ...



Klikneme na objekt (tlačidlo) StartHierarchy a na pravej strane pod Button pridáme On Click (cet +) a to políčka na objekt presunieme z Hierarchy objekt Canvas. Do políčka No Function nastavíme funckiu onCreateSessionBtnClick. Teraz vždy po stlačení tlačidla Start/Stop sa zavolá daná funkcia.



Hru už len otestujeme (pred spustením je potrebné mať pripojené zariadenie a nastavené cez Emotiv Launcher).