ReplayLib
ReplayLib is a comprehensive utility library for Unity game development, providing core patterns, extensions, and systems used throughout "The Last Word" project. The library emphasizes memory management, performance optimization, and consistent coding patterns.
Install / Use
/learn @radif/ReplayLibREADME
ReplayLib
ReplayLib is a comprehensive utility library for Unity game development, providing core patterns, extensions, and systems used throughout "The Last Word" project. The library emphasizes memory management, performance optimization, and consistent coding patterns.
About
This library is used in production by The Last Word, a real-time multiplayer word puzzle game developed by Replay Digital. The game features deterministic networking with Photon Quantum, multiple competitive game modes, and cross-platform play. ReplayLib has been battle-tested in a live production environment serving thousands of players on iOS.
Download: App Store
Table of Contents
- Core Patterns
- Singleton System
- Data Persistence
- Logging and Debugging
- Extensions
- Performance Optimization
- UI Utilities
- Platform Integration
- Usage Guidelines
Core Patterns
Singleton System
ReplayLib provides three types of singleton patterns for different use cases:
ComponentSingleton<T>
Addressable-aware singleton pattern for MonoBehaviour classes requiring Unity lifecycle management.
Key Features:
- Automatic Addressables handle management
- Resource loading support
- Scene persistence with DontDestroyOnLoad
- Automatic initialization and cleanup
- Warmup support for smooth animations
Usage:
using Replay.Utils;
public class BackEndManager : ComponentSingleton<BackEndManager>
{
protected override void OnSingletonInit()
{
// Initialization code
}
protected override void OnSingletonTeardown()
{
// Cleanup code
}
}
// Access the singleton
BackEndManager.Instance.SomeMethod();
// Check if loaded without instantiation
if (BackEndManager.IsLoaded)
{
// Safe to use
}
// Warm up prefab for faster instantiation
BackEndManager.WarmupPrefab();
Important Lifecycle Patterns:
- Use
Awake()for initialization instead ofOnSingletonInit()override - Use
protected void OnDestroy()without callingbase.OnDestroy() - Check existence with
IsLoadedproperty (notIsLoaded()method) - Never compare
Instanceto null (useIsLoadedorWeakInstance)
Automatic Serialization:
When implementing IReplaySerialazable, Deserialize() is called automatically by the framework. Do NOT manually call it in Start() or Awake().
public class PlayerProfileManager : ComponentSingleton<PlayerProfileManager>, IReplaySerialazable
{
// Deserialize() called automatically - no manual call needed
public void Deserialize()
{
// Load data
}
public void Serialize()
{
// Save data - call manually as needed
}
}
ScriptableObjectSingleton<T>
Singleton pattern for ScriptableObject-based configuration and data.
Usage:
public class GameSettings : ScriptableObjectSingleton<GameSettings>
{
public float musicVolume;
public float sfxVolume;
}
// Access settings
float volume = GameSettings.Instance.musicVolume;
Singleton<T>
Basic singleton pattern for pure C# classes without Unity dependencies.
Loadable System
Components implementing ILoadable can be dynamically loaded from Resources or Addressables:
public static string GetResourcePath() => "Prefabs/MyManager";
public static string GetAddressablesIdentifier() => "MyManagerAddressable";
Data Persistence
LocalSerializer
Wrapper around Unity's PlayerPrefs with iCloud support and type-safe operations.
Features:
- Automatic iCloud synchronization on iOS
- Type-safe get/set methods
- Support for bool, int, long, float, double, string
- Device account persistence option
Usage:
using Replay.Utils;
// Save data
LocalSerializer.Instance.SetInt("score", 100);
LocalSerializer.Instance.SetString("playerName", "Alice");
// Save to device account (iCloud on iOS)
LocalSerializer.Instance.SetString("profileId", "12345", saveToDeviceAccount: true);
// Load data
int score = LocalSerializer.Instance.GetInt("score");
string name = LocalSerializer.Instance.GetString("playerName", "DefaultName");
// Persist to disk
LocalSerializer.Instance.Serialize();
// Clear all data
LocalSerializer.Instance.DeleteAll();
IReplaySerialazable
Interface for objects that need serialization support with automatic deserialization when used with ComponentSingleton.
public interface IReplaySerialazable
{
void Serialize(); // Called manually when needed
void Deserialize(); // Called automatically by ComponentSingleton
}
Logging and Debugging
Dev (Debug Logging)
Conditional debug logging that only executes in debug builds.
Features:
- Tagged logging for easy filtering
- Method name tracking
- Force logging option
- Custom tag support
Usage:
using Replay.Utils;
// Basic logging
Dev.Log("Player spawned");
// With custom tag
Dev.Log("Connection established", "Network");
// Warnings and errors
Dev.LogWarning("Low memory detected");
Dev.LogError("Failed to load asset");
// Log method name with location
Dev.LogMethod("GameState");
// Force logging even in release builds
Dev.Log("Critical error", force: true);
Logger
Persistent file-based logging system with queue management.
Features:
- Thread-safe logging
- Automatic file management
- Exception tracking
- Log filtering by tag
- Queue size management
Usage:
// Logger initializes automatically
// All Unity logs are captured
// Get logs
string logs = Logger.Instance.GetLogs();
string networkLogs = Logger.Instance.GetLogs("Network");
// Manage log file
Logger.Instance.FlushLogsToFile();
Logger.Instance.TrimLogFile();
// For debugging
string logPath = Logger.Instance.logFilePath;
IDebugLoggable
Interface for classes that need debug output with custom ToDebugString() pattern.
public class PlayerProfile : IDebugLoggable
{
public string ToDebugString()
{
return $"PlayerProfile [ID: {id}, Name: {name}, Level: {level}]";
}
}
Extensions
ReplayLib provides extensive extension methods for common Unity types and operations.
List Extensions
using Replay.Utils;
List<string> items = new List<string> { "a", "b", "c" };
// Shuffle list
items.Shuffle();
// Shuffle with seed for deterministic results
items.Shuffle(42);
// Rotate elements
items.RotateLeft();
items.RotateRight(2);
// Swap elements
items.Swap(0, 2);
// Clean up
items.RemoveNullEntries();
items.RemoveDefaultValues();
// Check index validity
if (items.HasIndex(5))
Debug.Log(items[5]);
// Destroy MonoBehaviour list contents
List<Enemy> enemies = new List<Enemy>();
enemies.DestoryContentsAndClear();
String Extensions
using Replay.Utils;
string text = "hello world";
// Character shuffling
string shuffled = text.ShuffleCharacters();
// Null/empty checks
bool isEmpty = text.IsNullOrEmpty();
bool isWhitespace = text.IsNullOrWhiteSpace();
// Parsing with defaults
int number = "123".IntValue();
long bigNumber = "9999999999".LongValue(0L);
float decimal = "3.14".FloatValue();
double precise = "3.14159".DoubleValue();
bool flag = "true".BoolValue();
// Date parsing
DateTime? date = "2024-01-01".ToDateTime();
// Case conversions
string title = "hello world".ToTitleCase();
char upper = 'a'.ToUpper();
// URL encoding
string escaped = "hello world".ToEscapeURL();
string dataEscaped = "data string".ToEscapeDataString();
// Formatting
string bracketed = "Tag".ToBracketedString(); // "[Tag]"
string display = name.GetNAOrString(); // Returns "N/A" if null/empty
GameObject Extensions
using Replay.Utils;
GameObject obj = someGameObject;
// Scene management
obj.MoveToMainScene();
obj.MoveToActiveScene();
// Hierarchy navigation
GameObject root = obj.GetRootGameObject();
GameObject[] sceneRoots = GameObjectExtensions.GetRootGameObjectsInActiveScene();
// Check if in DontDestroyOnLoad
bool persistent = obj.isDontDestroyOnLoadActivated();
// Recursive operations
obj.SetActiveRecursively(false);
// Message broadcasting
obj.BroadcastMessageToRoot("OnGameStart");
GameObjectExtensions.BroadcastMessageToRootObjectsInActiveScene("OnLevelLoad");
// Hierarchy checks
bool isChild = parent.HasChildObject(child, recursive: true);
Enum Extensions
using Replay.Utils;
public enum GameMode { Menu, Playing, Paused, GameOver }
GameMode mode = GameMode.Playing;
// Navigation
GameMode next = mode.Next();
GameMode previous = mode.Previous();
// Position checks
bool isFirst = mode.IsFirst();
bool isLast = mode.IsLast();
int index = mode.ValueIndex();
// Conversion
string name = mode.ConvertToString();
GameMode parsed = "Playing".ConvertToEnum<GameMode>();
int value = mode.intValue();
Component Extensions
using Replay.Utils;
// Get or add component
AudioSource audio = gameObject.GetOrAddComponent<AudioSource>();
// Check component existence
bool hasRigidbody = gameObject.HasComponent<Rigidbody>();
// Fix prefab clone suffix
gameObject.FixOrAppendPrefabCloneSuffix("Singleton");
Transform Extensions
using Replay.Utils;
// Reset transformations
transform.ResetLocal();
transform.ResetWorld();
// Destroy children
transform.Dest
