Jump to content

User Tutorial:BCI2000Unity OUTDATED: Difference between revisions

From BCI2000 Wiki
Nluczak (talk | contribs)
Tytbutler (talk | contribs)
 
(8 intermediate revisions by 2 users not shown)
Line 3: Line 3:


==Video==
==Video==
PLACEHOLDER VIDEO
This video is currently out of date. An updated video is coming soon!
<youtube alignment="center">https://youtu.be/uLK3yl5MNu0</youtube>
 
<youtube alignment="center">https://www.youtube.com/watch?v=nZG70HSR8v8</youtube>


==Tutorial==
==Tutorial==
For information on how to use Unity itself, see the Unity [https://docs.unity3d.com/Manual/index.html manual]. This tutorial assumes knowledge of how to use Unity. It is recommended to know how GameObjects and Components work.
For information on how to use Unity itself, see the Unity [https://docs.unity3d.com/Manual/index.html manual]. This tutorial assumes knowledge of how to use Unity. It is recommended to know how GameObjects and Components work.
More in-depth detail on how UnityBCI2000 works is provided in the README.md file.
More in-depth detail on how UnityBCI2000 works is provided in the README.md file, and on this [https://bci2000.org/BCI2000Unity/classUnityBCI2000.html page].
 
 
 
This tutorial will walk through an example cursor control task, making use of the mouse position recorded from BCI2000 to control the cursor position in Unity. First, download UnityBCI2000 from this [https://github.com/neurotechcenter/UnityBCI2000 GitHub] page and download the MWE_UnityBCI2000CursorDemo tutorial project from this [https://github.com/neurotechcenter/ExperimentalDesignDemos GitHub] page. Copy the UnityBCI2000.cs and BCI2000RemoteNET.dll files into the Assets folder of your Unity project.
 
[[File:UnityMoveAssets.png|500px|alt="Unity Move Assets"|Unity Move Assets]]
 
===Connect to an Instance of BCI2000===
 
To connect to an instance of BCI2000 in Unity, you will need to create an empty GameObject and add the script UnityBCI2000 as a Component. This will serve as the central connection to the BCI2000 Operator. As of now, it is not possible to use multiple scenes with one BCI2000 connection.
 
1. Create a BCI2000 GameObject.
 
[[File:UnityBCI2000GameObject.png|600px|alt="Unity BCI2000 Game Object"|Unity BCI2000 Game Object]]


2. Add UnityBCI2000.cs to the GameObject as a Script Component.


[[File:UnityBCI2000ScriptComponent.png|600px|alt="Unity BCI2000 Script Component"|Unity BCI2000 Script Component]]


First, download UnityBCI2000 from [https://github.com/neurotechcenter/UnityBCI2000 GitHub]. As of now, UnityBCI2000 is not a full Unity package, but it works the same. Place the C# files from the Runtime folder, as well as BCI2000RemoteNET.dll, within the Assets directory of your Unity project.
3. Use the Operator Path field to specify the path to the BCI2000 Operator.exe (usually in the C:/bci2000/prog/ directory). If you already have an instance of the operator running, use the Telnet IP and Telnet Port fields to specify the IP and port it is listening on instead.


Create an empty GameObject and add the script UnityBCI2000 as a Component. This will serve as the central connection to the BCI2000 Operator. As of now, it is not possible to use multiple scenes with one BCI2000 connection.
4. Specify the names of the modules to start up alongside it. Extensions can be added by expanding the Module Args field and adding an element with the appropriate extension flag. For the example that will follow, we will log the mouse extension with  


1. Add a UnityBCI2000.cs to the GameObject as a Script Component.
<tt>
--LogMouse=1
</tt>


2.Specify the path to the operator using the Operator Path field, as well as the names of the modules to start up alongside it.  
5. Specify the name of the log file to store the BCI2000 system log. '''NOTE: There is a known issue with writing to the log file; this is remedied by changing the name of the file to which to write. This is an issue with BCI2000RemoteNET and will be fixed in upcoming updates.'''


If you have an instance of the operator already running, specify the IP and port it is listening on using the Telnet Ip and Telnet Port fields instead.
[[File:UnityBCI2000Modules.png|400px|alt="Unity BCI2000 Modules"|Unity BCI2000 Modules]]


You can also set a custom log file location, as well as tell the program to log sent states and received prompts. '''NOTE: There is a known issue with writing to the log file, this is remedied by changing the name of the file to write to. This is an issue with BCI2000RemoteNET, and will be fixed in upcoming updates.'''
===Save Unity Events in BCI2000===


3. Now, add a Script component of the script BCI2000StateSender to the object which you want to take data from.  
Next, we will modify a script to create new BCI2000 events and send event change information to BCI2000.


4. Drag the BCI2000 GameObject from step 1 into the UnityBCI2000Object field of the BCI2000StateSender.
1. Open the TargetControl script (or any script from which you would like to send information to BCI2000).


BCI2000StateSender comes with some predefined states to send, as well as values to scale them by. These are the global and screen coordinates, as well as whether the object is on screen.
2. In the class definition, instantiate BCI2000 with


===Adding Custom Variables===
<tt>
Due to the way Unity works, adding custom variables must be done from a custom script, unique to each GameObject, if the GameObjects are not identical. Unfortunately, this means that some knowledge of C# programming is required, but templates for a CustomVariableSupplier script are provided within the README.md file.
UnityBCI2000 bci;
</tt>


There are two types of custom variables: custom set variables, which set a state in BCI2000 to some value, and custom get variables, which retrieve the value of a state variable from BCI2000.
3. In the Awake() function, set the BCI2000 reference with


Custom set variables contain the name of the state to write to, a Func<int> delegate(a piece of code represented by a method or lambda expression which returns the value to send to BCI2000), a scale factor to scale the variable by, in order to avoid truncation due to the fact that BCI2000 state variables are only unsigned integers, and a type.
<tt>
Types are defined by the enum UnityBCI2000.StateType, which currently holds 5 values, which are signed integers of bit widths 16 and 32, as well as boolean, which is an unsigned integer of bit width 1. Signed numbers will be represented in BCI2000 by two state variables: The magnitude of the number and its sign, which is 0 for positive and 1 for negative.
bci = GameObject.Find("BCI2000").GetComponent<UnityBCI2000>();
</tt>


Custom get variables contain the name of the state to read from, as well as an Action<int> delegate(a piece of code which takes an int as a parameter), which will receive the value from BCI2000.
4. In the Awake() function, create new BCI2000 events with the bci.AddEvent() function. The second input specifies the number of bits assigned to the new event.
For reference on delegates see the [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/ C# Programming Guide].
Note: UnityBCI2000 uses reflection in order to reduce the amount of boilerplate code needed to implement a custom state variable, as well as simplify the process of adding and indexing of custom state variables.


Template for adding custom variables:
<tt>
<tt>
public class <ClassName> : CustomVariableBase
bci.AddEvent("t1hit", 32);
{
</tt>
    public override void AddCustomVariables()
    {
        customVariables.Add(new CustomSetVariable(  //Copy this for more set variables
            "[Name]",
            new Func<float>(() => [Code which returns variable to send]),
            [Scale],
            UnityBCI2000.StateType.[Type]
            ));
           
        customVariables.Add(new CustomGetVariable(  //Copy this for more get variables
            "[Name]", 
            new Action<int> ((int i) => [Code which uses i])
        ));


    }
5. It can be helpful to add watches for the events you have created so you can see their behavior in real time during the experiment. In the Awake() function, use the generic bci.ExecuteCommand() function (this function can be used to execute any BCI2000 operator scripting commands)
}
 
<tt>
bci.ExecuteCommand("visualize watch t1hit");
</tt>
</tt>


Example of a custom variable provider script:
6. It can be useful to reduce the verbosity of event logging, as event logs are written to the log file with every frame
 
<tt>
<tt>
public class CustomVariableSupplier1 : CustomVariableBase
bci.ExecuteCommand("Set variable LogLevel 0");
{
</tt>
    public override void AddCustomVariables()
 
    {
[[File:UnityTargetControlScript.png|500px|alt="Unity Target Control Script"|Unity Target Control Script]]
        customVariables.Add(new CustomSetVariable(
            "Custom variable 1",
            new Func<float>(() => {return 65 / 5;}),
            100,
            UnityBCI2000.StateType.SignedInt16
            ));


        customVariables.Add(new CustomSetVariable(
7. Update the event value according to some change in the experiment using the bci.SetEvent() function in the Update() function. For example, change a target hit event "t1hit" to 1 every time a target is hit by a cursor. Note that events must be set as unsigned integers.
            "Custom variable 2: Frame count",
            new Func<float>(() => Time.frameCount),
            1,
            UnityBCI2000.StateType.UnsignedInt32
            ));


        customVariables.Add(new CustomGetVariable(
<tt>
            "StateName",
bci.SetEvent("t1hit",(int)(1));
            new Action<int> ((int i) => {score = i})
        ));
    }
}
</tt>
</tt>


[[File:UnitySetEvent.png|400px|alt="Unity Set Event"|Unity Set Event]]


1. Add a new C# script to the object which you want to take data from.
===Access BCI2000 Events for Control===


2. Edit the script to change the inherited class to be CustomVariableBase instead of MonoBehavior.
We can also get event information from BCI2000 for Unity use. For example, you may want to control the Unity cursor position with a BCI2000 event, such as the mouse position. This example makes use of a BallMouseControl.cs script.


3. Implement the AddCustomVariables method, and have it add Custom Variables to the customVariables List, by calling customVariables.Add.
First you will need to instantiate BCI2000 and set the BCI2000 reference as in the TargetControl.cs script


4. Drag this new script into the Custom Variable Supplier field of the BCI2000StateSender Component.
1. Open the BallMouseControl.cs script.
 
2. In the class definition, instantiate BCI2000 with
 
<tt>
UnityBCI2000 bci;
</tt>
 
3. In the Awake() function, set the BCI2000 reference with
 
<tt>
bci = GameObject.Find("BCI2000").GetComponent<UnityBCI2000>();
</tt>
 
[[File:UnityBallMouseControlScript.png|400px|alt="Unity Ball Mouse Control Script"|Unity Ball Mouse Control Script]]
 
4. Get the value of the BCI2000 event using the bci.GetEvent() function and store the result in some variable
 
<tt>
Mpx = bci.GetEvent("MousePosX");
Mpy = bci.GetEvent("MousePosY");
</tt>
 
This value can then be used in Unity to influence the game activity.
 
[[File:UnityGetEvent.png|400px|alt="Unity Get Event"|Unity Get Event]]
 
===Compile===
 
Compile the project by navigating to file > Build and Run. This should launch the experiment and start up BCI2000!
 
==Using BCI2000 Control Signal==
 
The above example makes use of the BCI2000 Mouse Position events to control the Unity cursor position. However, the BCI2000 control signal can also be used in Unity. This is particularly useful for online experiments (for example, a neurofeedback task to move a cursor on the screen based on the decoded alpha power). In the demo script, navigate to the CursorTaskSignal Scene to see how the BCI2000 control signal can be utilized in Unity. Note that this only seems to work when the scene has been compiled!
 
Access the control signal using the function
 
<tt>
bci.GetSignal("ChannelNumber",1);
</tt>
 
[[File:UnityGetSignal.png|500px|alt="Unity Get Signal"|Unity Get Signal]]


==See also==
==See also==
Also see the [https://bci2000.org/mediawiki/index.php/BCPy2000 BCPy2000] page for more details on the installation process, APIs, and hooks.
[[Contributions:BCPy2000]]


[[Category:Tutorial]]
[[Category:Tutorial]]

Latest revision as of 19:43, 29 May 2024

Synopsis

Unity is a cross-platform game engine with support for desktop, mobile, console, and virtual reality platforms. It is both easy for beginners to use and is popular for low-cost game development. This is a Unity package which integrates BCI2000. This tutorial assumes that you have already compiled BCI2000.

Video

This video is currently out of date. An updated video is coming soon!

Tutorial

For information on how to use Unity itself, see the Unity manual. This tutorial assumes knowledge of how to use Unity. It is recommended to know how GameObjects and Components work. More in-depth detail on how UnityBCI2000 works is provided in the README.md file, and on this page.


This tutorial will walk through an example cursor control task, making use of the mouse position recorded from BCI2000 to control the cursor position in Unity. First, download UnityBCI2000 from this GitHub page and download the MWE_UnityBCI2000CursorDemo tutorial project from this GitHub page. Copy the UnityBCI2000.cs and BCI2000RemoteNET.dll files into the Assets folder of your Unity project.

"Unity Move Assets"

Connect to an Instance of BCI2000

To connect to an instance of BCI2000 in Unity, you will need to create an empty GameObject and add the script UnityBCI2000 as a Component. This will serve as the central connection to the BCI2000 Operator. As of now, it is not possible to use multiple scenes with one BCI2000 connection.

1. Create a BCI2000 GameObject.

"Unity BCI2000 Game Object"

2. Add UnityBCI2000.cs to the GameObject as a Script Component.

"Unity BCI2000 Script Component"

3. Use the Operator Path field to specify the path to the BCI2000 Operator.exe (usually in the C:/bci2000/prog/ directory). If you already have an instance of the operator running, use the Telnet IP and Telnet Port fields to specify the IP and port it is listening on instead.

4. Specify the names of the modules to start up alongside it. Extensions can be added by expanding the Module Args field and adding an element with the appropriate extension flag. For the example that will follow, we will log the mouse extension with

--LogMouse=1

5. Specify the name of the log file to store the BCI2000 system log. NOTE: There is a known issue with writing to the log file; this is remedied by changing the name of the file to which to write. This is an issue with BCI2000RemoteNET and will be fixed in upcoming updates.

"Unity BCI2000 Modules"

Save Unity Events in BCI2000

Next, we will modify a script to create new BCI2000 events and send event change information to BCI2000.

1. Open the TargetControl script (or any script from which you would like to send information to BCI2000).

2. In the class definition, instantiate BCI2000 with

UnityBCI2000 bci;

3. In the Awake() function, set the BCI2000 reference with

bci = GameObject.Find("BCI2000").GetComponent<UnityBCI2000>();

4. In the Awake() function, create new BCI2000 events with the bci.AddEvent() function. The second input specifies the number of bits assigned to the new event.

bci.AddEvent("t1hit", 32);

5. It can be helpful to add watches for the events you have created so you can see their behavior in real time during the experiment. In the Awake() function, use the generic bci.ExecuteCommand() function (this function can be used to execute any BCI2000 operator scripting commands)

bci.ExecuteCommand("visualize watch t1hit");

6. It can be useful to reduce the verbosity of event logging, as event logs are written to the log file with every frame

bci.ExecuteCommand("Set variable LogLevel 0");

"Unity Target Control Script"

7. Update the event value according to some change in the experiment using the bci.SetEvent() function in the Update() function. For example, change a target hit event "t1hit" to 1 every time a target is hit by a cursor. Note that events must be set as unsigned integers.

bci.SetEvent("t1hit",(int)(1));

"Unity Set Event"

Access BCI2000 Events for Control

We can also get event information from BCI2000 for Unity use. For example, you may want to control the Unity cursor position with a BCI2000 event, such as the mouse position. This example makes use of a BallMouseControl.cs script.

First you will need to instantiate BCI2000 and set the BCI2000 reference as in the TargetControl.cs script

1. Open the BallMouseControl.cs script.

2. In the class definition, instantiate BCI2000 with

UnityBCI2000 bci;

3. In the Awake() function, set the BCI2000 reference with

bci = GameObject.Find("BCI2000").GetComponent<UnityBCI2000>();

"Unity Ball Mouse Control Script"

4. Get the value of the BCI2000 event using the bci.GetEvent() function and store the result in some variable

Mpx = bci.GetEvent("MousePosX"); Mpy = bci.GetEvent("MousePosY");

This value can then be used in Unity to influence the game activity.

"Unity Get Event"

Compile

Compile the project by navigating to file > Build and Run. This should launch the experiment and start up BCI2000!

Using BCI2000 Control Signal

The above example makes use of the BCI2000 Mouse Position events to control the Unity cursor position. However, the BCI2000 control signal can also be used in Unity. This is particularly useful for online experiments (for example, a neurofeedback task to move a cursor on the screen based on the decoded alpha power). In the demo script, navigate to the CursorTaskSignal Scene to see how the BCI2000 control signal can be utilized in Unity. Note that this only seems to work when the scene has been compiled!

Access the control signal using the function

bci.GetSignal("ChannelNumber",1);

"Unity Get Signal"

See also