A reusable library for adding screen reader accessibility to Unity games. Works with MelonLoader, BepInEx, or any Unity mod framework. Provides UniversalSpeech integration with SAPI fallback, text cleaning, and speech management.
$ dotnet add package UnityAccessibilityLibA reusable library for adding screen reader accessibility to Unity games. Works with MelonLoader, BepInEx, or any Unity mod framework.
<color>, <size>, <b>, etc.) with extensible custom replacementsdotnet add package UnityAccessibilityLib
Or add to your .csproj:
<PackageReference Include="UnityAccessibilityLib" Version="2.0.0" />
Add the project to your solution and reference it:
<ProjectReference Include="..\UnityAccessibilityLib\UnityAccessibilityLib.csproj" />
Build the library and reference the DLL:
<Reference Include="UnityAccessibilityLib">
<HintPath>path\to\UnityAccessibilityLib.dll</HintPath>
</Reference>
using MelonLoader;
using UnityAccessibilityLib;
public class MelonLoggerAdapter : IAccessibilityLogger
{
private readonly MelonLogger.Instance _logger;
public MelonLoggerAdapter(MelonLogger.Instance logger)
{
_logger = logger;
}
public void Msg(string message) => _logger.Msg(message);
public void Warning(string message) => _logger.Warning(message);
public void Error(string message) => _logger.Error(message);
}
public class MyAccessibilityMod : MelonMod
{
public override void OnInitializeMelon()
{
AccessibilityLog.Logger = new MelonLoggerAdapter(LoggerInstance);
if (SpeechManager.Initialize())
{
LoggerInstance.Msg("Speech system ready");
}
}
}
using BepInEx;
using BepInEx.Logging;
using UnityAccessibilityLib;
public class BepInExLoggerAdapter : IAccessibilityLogger
{
private readonly ManualLogSource _logger;
public BepInExLoggerAdapter(ManualLogSource logger)
{
_logger = logger;
}
public void Msg(string message) => _logger.LogInfo(message);
public void Warning(string message) => _logger.LogWarning(message);
public void Error(string message) => _logger.LogError(message);
}
[BepInPlugin("com.example.mymod", "MyAccessibilityMod", "1.0.0")]
public class MyPlugin : BaseUnityPlugin
{
void Awake()
{
AccessibilityLog.Logger = new BepInExLoggerAdapter(Logger);
if (SpeechManager.Initialize())
{
Logger.LogInfo("Speech system ready");
}
}
}
// Dialogue with speaker name
SpeechManager.Output("Phoenix", "Hold it!", TextType.Dialogue);
// Output: "Phoenix: Hold it!"
// Narrator text
SpeechManager.Output(null, "The court fell silent.", TextType.Narrator);
// Output: "The court fell silent."
// Menu/system announcements
SpeechManager.Announce("Save complete", TextType.System);
// Repeat last dialogue (bind to a key)
SpeechManager.RepeatLast();
| Method | Description |
|---|---|
Initialize() | Initialize the speech system. Returns true on success. |
Output(speaker, text, textType) | Speak text with optional speaker name. |
Announce(text, textType) | Speak text without a speaker name. |
RepeatLast() | Repeat the last dialogue/narrator text. |
Stop() | Stop current speech. |
ClearRepeatBuffer() | Clear stored repeat text. |
| Property | Description |
|---|---|
DuplicateWindowSeconds | Time window for duplicate suppression (default: 0.5s) |
EnableLogging | Whether to log speech output (default: true) |
EnableBraille | Whether to output to braille displays (default: true) |
FormatTextOverride | Custom delegate for text formatting (see Extensibility) |
ShouldStoreForRepeatPredicate | Custom predicate for repeat storage (see Extensibility) |
TextTypeNames | Dictionary mapping text type IDs to names for logging |
| Value | Description |
|---|---|
Dialogue | Character dialogue (formatted as "Speaker: text") |
Narrator | Narrator/descriptive text |
Menu | Menu item text |
MenuChoice | Menu selection |
System | System messages |
CustomBase | Base value (100) for defining custom text types |
Low-level access to UniversalSpeech:
UniversalSpeechWrapper.Initialize(); // Initialize (called by SpeechManager)
UniversalSpeechWrapper.Speak("text", true); // Speak with interrupt
UniversalSpeechWrapper.DisplayBraille("text"); // Output to braille display
UniversalSpeechWrapper.Stop(); // Stop speech
UniversalSpeechWrapper.IsScreenReaderActive(); // Check if screen reader is running
Basic usage:
string clean = TextCleaner.Clean("<color=#ff0000>Red text</color>");
// Result: "Red text"
string combined = TextCleaner.CombineLines("Line 1", "<b>Line 2</b>", "Line 3");
// Result: "Line 1 Line 2 Line 3"
Custom text replacements (applied after tag removal):
// Simple string replacement
TextCleaner.AddReplacement("♥", "heart");
TextCleaner.AddReplacement("→", "arrow");
// Regex replacement
TextCleaner.AddRegexReplacement(@"\[(\d+)\]", "footnote $1");
// Clear custom replacements
TextCleaner.ClearReplacements(); // Clear string replacements only
TextCleaner.ClearRegexReplacements(); // Clear regex replacements only
TextCleaner.ClearAllCustomReplacements(); // Clear all
// Set your logger implementation
AccessibilityLog.Logger = new MelonLoggerAdapter(loggerInstance);
// or
AccessibilityLog.Logger = new BepInExLoggerAdapter(loggerInstance);
// Or create a simple console logger for testing
AccessibilityLog.Logger = new ConsoleLogger();
public class ConsoleLogger : IAccessibilityLogger
{
public void Msg(string message) => Console.WriteLine(message);
public void Warning(string message) => Console.WriteLine($"[WARN] {message}");
public void Error(string message) => Console.WriteLine($"[ERROR] {message}");
}
The library includes Net35Extensions with polyfills for methods not available in .NET 3.5:
Net35Extensions.IsNullOrWhiteSpace(string) - Use instead of string.IsNullOrWhiteSpaceDefine custom text types for game-specific content:
public static class MyTextTypes
{
public const int Tutorial = TextType.CustomBase + 1; // 101
public const int Combat = TextType.CustomBase + 2; // 102
public const int Inventory = TextType.CustomBase + 3; // 103
}
// Register names for logging
SpeechManager.TextTypeNames = new Dictionary<int, string>
{
{ TextType.Dialogue, "Dialogue" },
{ TextType.Narrator, "Narrator" },
{ MyTextTypes.Tutorial, "Tutorial" },
{ MyTextTypes.Combat, "Combat" },
};
// Use custom types
SpeechManager.Announce("Press A to jump", MyTextTypes.Tutorial);
Override how text is formatted before output:
SpeechManager.FormatTextOverride = (speaker, text, textType) =>
{
// Custom formatting logic
if (textType == MyTextTypes.Combat)
return $"Combat: {text}";
if (!string.IsNullOrEmpty(speaker))
return $"{speaker} says: {text}";
return text;
};
Control which text types are stored for repeat functionality:
SpeechManager.ShouldStoreForRepeatPredicate = (textType) =>
{
// Store dialogue, narrator, and tutorial text for repeat
return textType == TextType.Dialogue
|| textType == TextType.Narrator
|| textType == MyTextTypes.Tutorial;
};
UniversalSpeech.dll (32-bit or 64-bit depending on the game's architecture) to the game's root directorySupported screen readers:
If you're upgrading from the old MelonAccessibilityLib package:
MelonAccessibilityLib to UnityAccessibilityLibusing statements from using MelonAccessibilityLib; to using UnityAccessibilityLib;No API changes - all classes and methods remain the same.
MIT License - see LICENSE for details.