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 usingLSL.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;
StreamInfo[] streamInfos;
StreamInlet streamInlet;
float[] sample;
private int channelCount = 0;
void Update()
{
if (streamInlet == null)
{
streamInfos = LSL.resolve_stream("type", StreamType, 1, 0.0);
if (streamInfos.Length > 0)
{
streamInlet = new 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 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()
{
StreamInfo streamInfo = new StreamInfo(StreamName, StreamType, 3, Time.fixedDeltaTime * 1000, LSL.channel_format_t.cf_float32);
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 StreamOutlet(streamInfo);
currentSample = new float[3];
}
// FixedUpdate is a good hook for objects that are governed mostly by physics (gravity, momentum).
// Update might be better for objects that are governed by code (stimulus, event).
void FixedUpdate()
{
Vector3 pos = gameObject.transform.position;
currentSample[0] = pos.x;
currentSample[1] = pos.y;
currentSample[2] = pos.z;
outlet.push_sample(currentSample);
}
}