skills/unity-app-ui-redux/SKILL.md
Expert for App UI Redux state management - Store, Slices, Reducers, Actions, AsyncThunks, middleware, and Redux DevTools. Use this skill whenever the user wants to manage global state, create a Redux store, define actions and reducers, dispatch events, subscribe to state changes, implement async data fetching with thunks, add middleware for logging or analytics, use the Redux DevTools window, or architect a predictable state management layer in their Unity App UI project. Also trigger when the user mentions ActionCreator, StoreFactory, PartitionedState, or asks about centralized state, immutable records, or state slices.
npx skillsauth add cuozg/oh-my-skills unity-app-ui-reduxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert assistant for implementing Redux state management in Unity using the App UI Redux framework.
Redux is a predictable state management pattern for Unity applications. It provides a centralized store to manage application state, making it easier to debug, test, and maintain complex state logic. App UI implements Redux using C# with support for slices, async thunks, middleware, and Redux DevTools.
Use Redux when you need to:
using Unity.AppUI.Redux;
var store = StoreFactory.CreateStore(new[]
{
StoreFactory.CreateSlice(
"sliceName",
new MyState(),
builder => { /* reducers */ })
});
var store = StoreFactory.CreateStore(new[]
{
StoreFactory.CreateSlice("counter", new CounterState(), counterBuilder => { }),
StoreFactory.CreateSlice("user", new UserState(), userBuilder => { }),
StoreFactory.CreateSlice("todos", new TodosState(), todosBuilder => { })
});
var store = StoreFactory.CreateStore(
slices: new[] { /* slices */ },
enhancer: Store.ApplyMiddleware(
LoggerMiddleware(),
ThunkMiddleware()
)
);
Always use immutable records with init properties:
public record CounterState
{
public int Count { get; init; } = 0;
public string Status { get; init; } = "idle";
}
Without Payload:
public static readonly ActionCreator Increment = "counter/Increment";
With Payload:
public static readonly ActionCreator<int> AddAmount = "counter/AddAmount";
public static readonly ActionCreator<string> SetName = "user/SetName";
builder.AddCase(Actions.Increment, (state, action) =>
state with { Count = state.Count + 1 });
builder.AddCase(Actions.AddAmount, (state, action) =>
state with { Count = state.Count + action.payload });
Wrong pattern (do not use):
// DO NOT USE Add() - use AddCase() instead
builder.Add(Actions.Increment, reducer); // INCORRECT
AsyncThunk creates pending/fulfilled/rejected actions automatically:
public static readonly AsyncThunkCreator<int, string> FetchUserName =
new("user/fetchName", async (userId, api) =>
{
await Task.Delay(1000);
return $"User_{userId}";
});
// In extra reducers:
builder.AddCase(FetchUserName.pending, (state, action) =>
state with { IsLoading = true, Error = null });
builder.AddCase(FetchUserName.fulfilled, (state, action) =>
state with { IsLoading = false, Name = action.payload });
builder.AddCase(FetchUserName.rejected, (state, action) =>
state with { IsLoading = false, Error = "Failed to fetch" });
StoreFactory.CreateSlice(
name: "counter",
initialState: new CounterState(),
reducer: builder =>
{
builder.AddCase(Actions.Increment, IncrementReducer);
builder.AddCase(Actions.Decrement, DecrementReducer);
},
extraReducers: builder =>
{
// Handle async thunk actions here
builder.AddCase(MyAsyncThunk.pending, PendingReducer);
builder.AddCase(MyAsyncThunk.fulfilled, FulfilledReducer);
}
);
var subscription = store.Subscribe<CounterState>("counter", state =>
{
Debug.Log($"Counter: {state.Count}");
});
// Unsubscribe when done
subscription.Dispose();
Simple Actions:
store.Dispatch(Actions.Increment.Invoke());
store.Dispatch(Actions.AddAmount.Invoke(5));
Async Thunks:
var action = MyAsyncThunk.Invoke(123);
store.Dispatch(action);
// Or wait for completion
await store.DispatchAsyncThunk(action);
var state = store.GetState<CounterState>("counter");
Debug.Log($"Current count: {state.Count}");
Create selector functions for derived state:
public static class Selectors
{
public static bool IsCounterPositive(CounterState state) =>
state.Count > 0;
public static string FormatCount(CounterState state) =>
$"Count: {state.Count}";
}
// Usage
var isPositive = Selectors.IsCounterPositive(state);
public static Middleware<TStore, TStoreState> LoggerMiddleware<TStore, TStoreState>()
where TStore : IStore
{
return (store) => (next) => (action) =>
{
Debug.Log($"[Redux] Dispatching: {action.type}");
var result = next(action);
Debug.Log($"[Redux] Action completed");
return result;
};
}
public static Middleware<TStore, TStoreState> CustomMiddleware<TStore, TStoreState>()
where TStore : IStore
{
return (store) => (next) => (action) =>
{
// Before action
Debug.Log($"Before: {action.type}");
var result = next(action);
// After action
Debug.Log($"After: {action.type}");
return result;
};
}
The Redux DevTools is available in the Unity Editor:
public interface IStoreService
{
Store Store { get; }
}
public class StoreService : IStoreService
{
public Store Store { get; }
public StoreService()
{
Store = StoreFactory.CreateStore(/* slices */);
}
}
// Register in app builder
public class MyAppBuilder : UIToolkitAppBuilder<MyApp>
{
protected override void OnConfiguringApp(AppBuilder builder)
{
base.OnConfiguringApp(builder);
builder.services.AddSingleton<IStoreService, StoreService>();
}
}
// Use in ViewModel
public class MyViewModel : ObservableObject
{
readonly IStoreService m_StoreService;
public MyViewModel(IStoreService storeService)
{
m_StoreService = storeService;
m_StoreService.Store.Subscribe<MyState>("mySlice", state =>
{
UpdateUI(state);
});
}
}
Always use the with keyword for immutable updates:
// Good
return state with { Count = state.Count + 1 };
// Bad - mutates state
state.Count++;
return state;
public static readonly AsyncThunkCreator<int, string> FetchData =
new("app/fetchData", async (id, api) =>
{
try
{
var result = await SomeApiCall(id);
return result;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed: {ex.Message}");
}
});
var thunk = new AsyncThunkCreator("operation", async (arg, api) =>
{
api.Dispatch(MyAction.Invoke("starting"));
// Do work
api.Dispatch(MyOtherAction.Invoke("done"));
return result;
});
YourProject/
├── Redux/
│ ├── Store/
│ │ └── StoreConfiguration.cs
│ ├── Slices/
│ │ ├── CounterSlice.cs
│ │ ├── UserSlice.cs
│ │ └── ...
│ ├── Actions/
│ │ ├── CounterActions.cs
│ │ └── ...
│ ├── Reducers/
│ │ ├── CounterReducers.cs
│ │ └── ...
│ ├── Selectors/
│ │ ├── CounterSelectors.cs
│ │ └── ...
│ └── Middleware/
│ └── CustomMiddleware.cs
Issue: Reducer mutations not reflecting in UI
state with { ... } for immutable updatesIssue: AsyncThunk not updating state
Issue: Middleware not being applied
Issue: Subscriptions memory leaks
Consult reference.md when you need exact API signatures for Store, StoreFactory, ActionCreator, AsyncThunkCreator, or Middleware types, full property/method lists, or advanced patterns like custom enhancers and thunk cancellation. See examples/redux-store.cs for a complete multi-slice store with async operations.
tools
Generate Unity raster image assets through Unity MCP: game sprites, item art, backgrounds, UI icons, portraits, concept images, transparent cutouts, image edits, upscales, background removal, and Unity scene or Game View screenshots. Use when a Unity project needs image files imported under Assets or screenshots captured from the editor. Do not use for meshes, audio, animation, materials, gameplay code, UI Toolkit layout, or generic non-Unity image generation.
tools
Create Unity technical solution documents from user requirements, feature ideas, bug goals, specs, or codebase problems. Use when the user asks for a technical approach, architecture, implementation strategy, solution options, feasibility analysis, system design, or "how should we build/fix this" for Unity runtime, Editor, tools, assets, data, UI, WebGL, SDKs, or production pipelines.
tools
Orchestrate Unity Editor via MCP (Model Context Protocol) tools and resources. Use when working with Unity projects through MCP for Unity - creating/modifying GameObjects, editing scripts, managing scenes, running tests, or any Unity Editor automation. Provides best practices, tool schemas, and workflow patterns for effective Unity-MCP integration.
development
Convert a spec document into an implementation TODO list in the same spec folder. U se when the user says goal-todo, todo from spec, generate tasks from spec, turn this spec into todos, create implementation checklist, extract tasks, or asks to read a Docs/Specs design doc and produce what must be implemented. Includes UI/UX review and codebase investigation before writing the checklist. Do not use for implementing the tasks, creating new goal files, writing test cases, or verifying completed work.