Unity Integration
Enable players to use Cephable's personal assistant adaptive controls in your Unity game, making it accessible for players with motor or physical disabilities.
Overview
The Cephable Unity Samples demonstrate how to integrate Cephable's personal assistant adaptive controls into a Unity game to support players with motor or physical disabilities. Players configure face expressions, head movement, voice controls, and virtual buttons through their personal Cephable profile — all processed on their own device — and your game receives only the resulting command strings via the Cephable Device Hub over SignalR using ASP.NET Core SignalR for Unity.
Sample repository: github.com/Cephable/Cephable-Unity-Samples
See it in action: https://youtu.be/cwKVbYTWMt8
Prerequisites
Start a free 30-day trial at services.cephable.com/trial/developers to receive:
- OAuth Client ID
- OAuth Client Secret
- Device Type ID
No credit card required. After your trial, contact support@cephable.com to arrange a permanent license.
How the FPS sample works
User opens game → hits Tab → Settings menu appears
│
▼
"Link Account" button → Cephable OAuth flow in browser
│ (OAuth2Manager.cs)
▼
Auth code → access token → create UserDevice → create DeviceToken
│ (VirtualController.cs)
▼
SignalR connection to https://services.cephable.com/device
│
▼
DeviceCommand events → Player inputs (jump, fire, etc.)
Key scripts
The sample includes three key scripts in FPS_Full/Assets/FPS/Scripts/EnabledPlay/:
| Script | Purpose |
|---|---|
OAuth2Manager.cs |
OAuth 2.0 authorization code flow + token refresh |
VirtualController.cs |
Device creation, SignalR connection, command handling |
LoginButton.cs |
UI button that triggers sign-in / sign-out |
OAuth2Manager.cs
Handles the full OAuth flow. Configure these fields in the Unity Inspector:
public string AUTH_ENDPOINT = "https://services.cephable.com/signin";
public string TOKEN_ENDPOINT = "https://services.cephable.com/signin/token";
public string CLIENT_ID = "YOUR_CLIENT_ID";
public string CLIENT_SECRET = "YOUR_CLIENT_SECRET";
Authentication flow (from sample):
public async void Authenticate()
{
REDIRECT_URI = string.Format("http://{0}:{1}/", IPAddress.Loopback, 51772);
string authUrl = $"{AUTH_ENDPOINT}?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&response_type=code";
Application.OpenURL(authUrl);
// Listen for the OAuth callback on localhost
var http = new HttpListener();
http.Prefixes.Add(REDIRECT_URI);
http.Start();
var context = await http.GetContextAsync();
var code = context.Request.QueryString.Get("code");
StartCoroutine(GetAccessToken(code));
}
public IEnumerator GetAccessToken(string authCode)
{
UnityWebRequest www = UnityWebRequest.Post(
$"{TOKEN_ENDPOINT}?grant_type=code&code={authCode}&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}",
string.Empty
);
yield return www.SendWebRequest();
var tokenResponse = TokenResponse.CreateFromJSON(www.downloadHandler.text);
PlayerPrefs.SetString("accessToken", tokenResponse.access_token);
PlayerPrefs.SetString("refreshToken", tokenResponse.refresh_token);
StartCoroutine(controller.CreateVirtualController());
}
VirtualController.cs
Manages the user device and SignalR hub connection. Configure DeviceTypeId and DefaultDeviceName in the Inspector:
public string DeviceTypeId; // Your Device Type ID from the portal
public string DefaultDeviceName = "Unity-Sample";
Creating a virtual device (from sample):
public IEnumerator CreateVirtualController()
{
var accessToken = PlayerPrefs.GetString("accessToken");
var www = UnityWebRequest.Post(
$"https://services.cephable.com/api/Device/userDevices/new/{DeviceTypeId}?name={DefaultDeviceName}",
string.Empty
);
www.SetRequestHeader("Authorization", $"Bearer {accessToken}");
yield return www.SendWebRequest();
var deviceResponse = UserDevice.CreateFromJSON(www.downloadHandler.text);
PlayerPrefs.SetString("userDeviceId", deviceResponse.id);
StartCoroutine(CreateDeviceToken());
}
public IEnumerator CreateDeviceToken()
{
var accessToken = PlayerPrefs.GetString("accessToken");
var www = UnityWebRequest.Post(
$"https://services.cephable.com/api/Device/userDevices/{userDeviceId}/tokens",
string.Empty
);
www.SetRequestHeader("Authorization", $"Bearer {accessToken}");
yield return www.SendWebRequest();
var tokenResponse = UserDeviceToken.CreateFromJSON(www.downloadHandler.text);
PlayerPrefs.SetString("userDeviceToken", tokenResponse.token);
userDeviceToken = tokenResponse.token;
// ... proceed to ConnectToHub()
}
Connecting to the SignalR hub (from sample):
public async Task ConnectToHub()
{
var connection = new HubConnectionBuilder()
.WithUrl("https://services.cephable.com/device", options =>
{
options.AccessTokenProvider = () => Task.FromResult(userDeviceToken);
options.Headers.Add("X-Device-Token", userDeviceToken);
options.Headers.Add("User-Agent", "Unity-Sample");
})
.Build();
connection.On<string>("DeviceCommand", (command) =>
{
if (command == "jump" || command == "hotkey_jump" || command == "eyebrows_raised")
{
inputHandler.isJumping = true;
}
if (command == "fire")
{
inputHandler.isShooting = true;
}
StartCoroutine(ResetKeys());
});
connection.On<UserDevice>("DeviceProfileUpdate", (device) =>
{
currentProfile = device.currentProfile?.configuration;
});
await connection.StartAsync();
await connection.InvokeAsync("VerifySelf");
}
public IEnumerator ResetKeys()
{
yield return new WaitForSeconds(0.1f);
inputHandler.isJumping = false;
inputHandler.isShooting = false;
}
Data models (from sample)
[System.Serializable]
public class UserDevice
{
public string id;
public string nameOverride;
public bool isVerified;
public bool isConnected;
public bool isListening;
public bool isAutoListen;
public bool isOptimisticModel;
public UserDeviceProfile currentProfile;
}
[System.Serializable]
public class UserDeviceToken
{
public string id;
public string userDeviceId;
public string token;
public bool isDisabled;
}
[System.Serializable]
public class TokenResponse
{
public string access_token;
public string refresh_token;
public string access_token_expiration;
public string refresh_token_expiration;
}
Scene hierarchy
The MainScene contains these key GameObjects (from sample):
- CephableAuth → LoginButton — Triggers
OAuth2Manager.StartAuthorization() - Player — Has
VirtualControllercomponent attached withDeviceTypeIdconfigured
User flow
- User opens the game and presses Tab to access settings
- User clicks Link Account — Cephable OAuth opens in the browser
- User logs in or creates a Cephable account and authorizes the app
- The app redirects back to the game via the localhost callback
- The game creates a virtual device in the user's Cephable account
- The user opens the Cephable app and sees the virtual device — they can add a profile or use this demo share link
- The user sends commands from virtual buttons, voice, or camera controls
Next steps
- Virtual Controller Guide — Non-Unity samples (C#, JS, C++)
- Cephable Unity Samples on GitHub
- API Reference
- Swagger UI