The LSL4Unity repository provides a more featureful integration of LSL into Unity. However, it is not as well-maintained as this repository. The recommended approach to integrating LSL into a Unity project is to use liblsl-Csharp's LSL.cs, and use LSL4Unity as a reference for more advanced use of LSL in Unity.
LSL.cs includes a Unity interface to liblsl as a Unity native plug-in. The following instructions outline how to add a cube to a Unity scene that uses the LSL native plugin to pull in samples that modify its movement vector. These instructions were written while using Unity 2020.1.0f1.
- Download the latest liblsl release for your platform(s) (e.g. liblsl-1.14.0-Win64.zip) and extract the library (e.g. lsl.dll) into the project's Assets/Plugins/lib folder (you may have to create this folder).
- If you plan to build for more than one target platform then you may wish to further subdivide the folders.
- If you will deploy to Android, the easiest way to get the lib files is to follow the instructions in the "Building for Android" LSL docs.
- On recent versions of MacOS, if the dylib fails to load for security reasons, you will have to allow it manually in "Security & Privacy Settings". More info.
- Drag and drop LSL.cs into the project's Assets/Plugins folder.
- In Unity, use the Project view and navigate to the Assets/Plugins/lib folder. For each library file:
- Set the platforms for the plug-in. See here.
- In Unity, use the menu to place a cube in the scene: GameObject > 3D Object > Cube
- When the cube is selected, in the Inspector click on "Add Component", and create a new script called LSLInput.
- In the Project viewer, double click on LSLInput.cs. This should launch Visual Studio or another IDE.
- Fill in the script. Use LSL4Unity AInlet for inspiration.
- There is currently a bug that prevents liblsl in Unity from resolving streams from other computers while running in editor, and also the built product but only when using
ContinuousResolver
. For this reason we recommend usingliblsl.resolve_stream
instead.
- There is currently a bug that prevents liblsl in Unity from resolving streams from other computers while running in editor, and also the built product but only when using
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LSL;
public class LSLInput : MonoBehaviour
{
public string StreamType = "EEG";
public float scaleInput = 0.1f;
liblsl.StreamInfo[] streamInfos;
liblsl.StreamInlet streamInlet;
float[] sample;
private int channelCount = 0;
void Update()
{
if (streamInlet == null)
{
streamInfos = liblsl.resolve_stream("type", StreamType, 1, 0.0);
if (streamInfos.Length > 0)
{
streamInlet = new liblsl.StreamInlet(streamInfos[0]);
channelCount = streamInlet.info().channel_count();
streamInlet.open_stream();
}
}
if (streamInlet != null)
{
sample = new float[channelCount];
double lastTimeStamp = streamInlet.pull_sample(sample, 0.0f);
if (lastTimeStamp != 0.0)
{
Process(sample, lastTimeStamp);
while ((lastTimeStamp = streamInlet.pull_sample(sample, 0.0f)) != 0)
{
Process(sample, lastTimeStamp);
}
}
}
}
void Process(float[] newSample, double timeStamp)
{
var inputVelocity = new Vector3(scaleInput * (newSample[0] - 0.5f), scaleInput * (newSample[1] - 0.5f), scaleInput * (newSample[2] -0.5f));
gameObject.transform.position = gameObject.transform.position + inputVelocity;
}
}
- Elsewhere, run one of the LSL outlet examples. For example, from a conda environment with pylsl installed:
python -m pylsl.examples.SendData
- Run the Unity game and watch that cube shake!
- Attach a new component called LSLPosOutput to the cube.
- Edit it as follows:
using System.Collections; using System.Collections.Generic; using UnityEngine; using LSL; public class LSLOutput : MonoBehaviour { private liblsl.StreamOutlet outlet; private float[] currentSample; public string StreamName = "Unity.ExampleStream"; public string StreamType = "Unity.StreamType"; public string StreamId = "MyStreamID-Unity1234"; // Start is called before the first frame update void Start() { liblsl.StreamInfo streamInfo = new liblsl.StreamInfo(StreamName, StreamType, 3, Time.fixedDeltaTime * 1000, liblsl.channel_format_t.cf_float32); liblsl.XMLElement chans = streamInfo.desc().append_child("channels"); chans.append_child("channel").append_child_value("label", "X"); chans.append_child("channel").append_child_value("label", "Y"); chans.append_child("channel").append_child_value("label", "Z"); outlet = new liblsl.StreamOutlet(streamInfo); currentSample = new float[3]; } // Update is called once per frame void FixedUpdate() { Vector3 pos = gameObject.transform.position; currentSample[0] = pos.x; currentSample[1] = pos.y; currentSample[2] = pos.z; outlet.push_sample(currentSample); } }