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.


V 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 Score a Timer.


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



Z 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) Start v Hierarchy 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).