Skip to content

A reusable library for adding screen reader accessibility to Unity games via MelonLoader or BepInEx mods. Provides UniversalSpeech integration with SAPI fallback, text cleaning, and speech management.

License

Notifications You must be signed in to change notification settings

AccessMods/UnityAccessibilityLib

Repository files navigation

UnityAccessibilityLib

A reusable library for adding screen reader accessibility to Unity games. Works with MelonLoader, BepInEx, or any Unity mod framework.

Features

  • UniversalSpeech integration - P/Invoke wrapper for the UniversalSpeech library with SAPI fallback
  • Braille display support - Automatic output to braille displays via screen reader
  • High-level speech manager - Duplicate prevention, repeat functionality, speaker formatting
  • Text cleaning - Strips Unity rich text tags (<color>, <size>, <b>, etc.) with extensible custom replacements
  • Logging abstraction - Integrate with any logging system (MelonLoader, BepInEx, custom)
  • Multi-target support - Builds for net6.0, net472, and net35 for broad compatibility with various games

Requirements

  • UniversalSpeech.dll in the game directory
  • One of:
    • .NET 6.0 (IL2CPP games with MelonLoader 0.6+)
    • .NET Framework 4.7.2 (Mono games)
    • .NET Framework 3.5 (older Mono games, e.g. Unity 2017)

Installation

Option 1: NuGet Package (Recommended)

dotnet add package UnityAccessibilityLib

Or add to your .csproj:

<PackageReference Include="UnityAccessibilityLib" Version="2.0.0" />

Option 2: Project Reference

Add the project to your solution and reference it:

<ProjectReference Include="..\UnityAccessibilityLib\UnityAccessibilityLib.csproj" />

Option 3: DLL Reference

Build the library and reference the DLL:

<Reference Include="UnityAccessibilityLib">
  <HintPath>path\to\UnityAccessibilityLib.dll</HintPath>
</Reference>

Quick Start

MelonLoader Setup

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");
        }
    }
}

BepInEx Setup

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");
        }
    }
}

Output Speech

// 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();

API Reference

SpeechManager

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

TextType Constants

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

UniversalSpeechWrapper

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

TextCleaner

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

AccessibilityLog

// 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}");
}

.NET 3.5 Compatibility

The library includes Net35Extensions with polyfills for methods not available in .NET 3.5:

  • Net35Extensions.IsNullOrWhiteSpace(string) - Use instead of string.IsNullOrWhiteSpace

Extensibility

Custom Text Types

Define 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);

Custom Text Formatting

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;
};

Custom Repeat Storage

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 Setup

  1. Download UniversalSpeech from GitHub
  2. Copy UniversalSpeech.dll (32-bit or 64-bit depending on the game's architecture) to the game's root directory
  3. The library will automatically use any active screen reader, or fall back to Windows SAPI

Supported screen readers:

  • NVDA
  • JAWS
  • Window-Eyes
  • System Access
  • Supernova
  • ZoomText
  • SAPI (fallback)

Migrating from MelonAccessibilityLib

If you're upgrading from the old MelonAccessibilityLib package:

  1. Update your NuGet package reference from MelonAccessibilityLib to UnityAccessibilityLib
  2. Update your using statements from using MelonAccessibilityLib; to using UnityAccessibilityLib;

No API changes - all classes and methods remain the same.

License

MIT License - see LICENSE for details.

About

A reusable library for adding screen reader accessibility to Unity games via MelonLoader or BepInEx mods. Provides UniversalSpeech integration with SAPI fallback, text cleaning, and speech management.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages