Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mattak/Unidux

Redux Architecture for Unity 🎩
https://github.com/mattak/Unidux

redux unity3d

Last synced: 3 months ago
JSON representation

Redux Architecture for Unity 🎩

Awesome Lists containing this project

README

        

#

[![Join the chat at https://gitter.im/Unidux/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Unidux/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Unidux is practical application architecture for Unity3D UI.

It's inspired by Redux.

# Install

## UPM

Add following two lines to `Pacakges/manifest.json`.

```
{
"com.neuecc.unirx": "https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts",
"me.mattak.unidux": "https://github.com/mattak/Unidux.git?path=Assets/Plugins/Unidux/Scripts",
// ...
}
```

## Unity package

No longer supported.
If you need older versions, import unitypackage from [latest releases](https://github.com/mattak/Unidux/releases).

# Usage

1) Create your Unidux singleton and place it to unity scene.

```csharp
using UniRx;
using Unidux;

public sealed class Unidux : SingletonMonoBehaviour, IStoreAccessor
{
public TextAsset InitialStateJson;

private Store _store;

public IStoreObject StoreObject
{
get { return Store; }
}

public static State State
{
get { return Store.State; }
}

public static Subject Subject
{
get { return Store.Subject; }
}

private static State InitialState
{
get
{
return Instance.InitialStateJson != null
? JsonUtility.FromJson(Instance.InitialStateJson.text)
: new State();
}
}

public static Store Store
{
get { return Instance._store = Instance._store ?? new Store(InitialState, new Count.Reducer()); }
}

public static object Dispatch(TAction action)
{
return Store.Dispatch(action);
}

void Update()
{
Store.Update();
}
}
```

_Note: `ReplaySubject` is a [ReactiveX concept](http://reactivex.io/documentation/subject.html)
provided by [UniRx](https://github.com/neuecc/UniRx) in this example._

2) Create state class to store application state.

```csharp
using System;

[Serializable]
public class State : StateBase
{
public int Count = 0;
}
```

3) Define action to change state. Define Reducer to move state.

```csharp
public static class Count
{
// specify the possible types of actions
public enum ActionType
{
Increment,
Decrement
}

// actions must have a type and may include a payload
public class Action
{
public ActionType ActionType;
}

// ActionCreators creates actions and deliver payloads
// in redux, you do not dispatch from the ActionCreator to allow for easy testability
public static class ActionCreator
{
public static Action Create(ActionType type)
{
return new Action() {ActionType = type};
}

public static Action Increment()
{
return new Action() {ActionType = ActionType.Increment};
}

public static Action Decrement()
{
return new Action() {ActionType = ActionType.Decrement};
}
}

// reducers handle state changes
public class Reducer : ReducerBase
{
public override State Reduce(State state, Action action)
{
switch (action.ActionType)
{
case ActionType.Increment:
state.Count++;
break;
case ActionType.Decrement:
state.Count--;
break;
}

return state;
}
}
}
```

4) Create Renderer to display state and attach it to Text GameObject.

```csharp
[RequireComponent(typeof(Text))]
public class CountRenderer : MonoBehaviour
{
void OnEnable()
{
var text = this.GetComponent();

Unidux.Subject
.TakeUntilDisable(this)
.StartWith(Unidux.State)
.Subscribe(state => text.text = state.Count.ToString())
.AddTo(this)
;
}
}
```

5) Create dispatcher to update count and attach it to GameObject.

```csharp
[RequireComponent(typeof(Button))]
public class CountDispatcher : MonoBehaviour
{
public Count.Action Action = Count.ActionCreator.Increment();

void Start()
{
this.GetComponent()
.OnClickAsObservable()
.Subscribe(state => Unidux.Store.Dispatch(Action))
.AddTo(this)
;
}
}
```

That's it!

# Example

- [Counter](Assets/Plugins/Unidux/Examples/Counter)
- [List](Assets/Plugins/Unidux/Examples/List)
- [Todo](Assets/Plugins/Unidux/Examples/Todo)
- [Middlewares](Assets/Plugins/Unidux/Examples/Middlewares)
- [SimpleHttp](Assets/Plugins/Unidux/Examples/SimpleHttp)

# Dependencies

- [UniRx](https://github.com/neuecc/UniRx)
- [MiniJSON](https://gist.github.com/darktable/1411710) (for Unidux.Experimental.Editor.StateJsonEditor)

# API

## `StateBase`

```csharp
public class State : StateBase
{
public int Count;
}
```

### `.Clone()`

```csharp
State _state = new State();
State _clonedState = _state.Clone();
```

Create a deep clone of the current state. Useful for Immutability.

## `Store`

```csharp
IReducer[] reducers = new IReducer[]{};
Store _store = new Store(State, reducers);
// State must extend StateBase
```

### `.State`

Get the state as passed to the constructor.

### `.Dispatch(object)`

Dispatch an event of `TAction` object,
which will trigger a `Reducer`.

### `.ApplyMiddlewares(params Middleware[] middlewares)`

Apply middlewares to Store object
which implement delegate function of [Middleware](Assets/Plugins/Unidux/Scripts/IMiddleware.cs#L5).

### `.Update()`

When at least one reducer has been executed,
trigger all the renderers with a copy of the current state.

### `.ForceUpdate()`

Trigger all registered renderers with a copy of the current state
regardless of any reducers having been executed.

## `SingletonMonoBehaviour`

```csharp
public class Foo : SingletonMonoBehaviour {}
```

A singleton base class to extend.
Extends `MonoBehaviour`.

### `.Instance`

```csharp
public class Foo : SingletonMonoBehaviour {}

Foo.Instance
```

The instance of the base class.

# Performance

## `Clone()`

Default implemention of `StateBase.Clone()` is not fast, because it uses `BinaryFormatter` & `MemoryStream`.
And Unidux creates new State on every State chaning (it affects a few milliseconds).
So in case of requiring performance, override clone method with your own logic.

e.g.

```csharp
[Serializable]
class State : StateBase
{
public override object Clone()
{
// implement your custom deep clone code
}
}
```

## `Equals()`

Default implemention of `StateBase.Equals()` and `StateElement.Equals()` is not fast, because it uses fields and properties reflection.
In case of edit state on UniduxPanel's StateEditor, it calls `Equals()` in order to set `IsStateChanged` flags automatically.
So in case of requiring performance, override `Equals()` method with your own logic.

e.g.

```csharp
[Serializable]
class State : StateBase
{
public override bool Equals(object obj)
{
// implement your custom equality check code
}
}
```

# Thanks

- [@austinmao](https://github.com/austinmao) for suggestion of Ducks and UniRx.
- [@pine](https://github.com/pine) for description improvement.
- [@jesstelford](https://github.com/jesstelford) for fix document.
- [@tenmihi](https://github.com/tenmihi) for fix document.
- [@kn1cht](https://github.com/kn1cht) for fix .net 4.0 runtime error.
- [@shiena](https://github.com/shiena) for upm support.

# License

[MIT](./LICENSE.md)