Kan-Kan is an engine agnostic state and actions system.
Please note: KanKan is still in early development so things might change without warning. Hopefully for the better. It currently has not been optimised and uses Reflection, so performance may vary
A KanKan will run an action - known as a Karass. Each Karass is made up of a series of Frames which are run sequentially. Karass can be combined KarassThree = KarassOne + KarassTwo
to be run in parallel or passed into a KanKan as an Array new []{ KarassOne, KarassTwo }
to be run sequentially.
-
To have a easily maintainable state/actions system that can handle complex behaviours.
-
Testable: Built through Test Driven Development and ready for TDD systems.
- Has a number of helper methods for Test Driving production systems that will return the current state of the KanKan. This means you can test a KanKan over a number of frames quickly.
KanKanTestHelper
currently includes: - Run Until:
Has / Will Receive Message
Last / Next Frames Receive <T> Payload
- Run For:
X Frames
- Has a number of helper methods for Test Driving production systems that will return the current state of the KanKan. This means you can test a KanKan over a number of frames quickly.
-
Plays nice with modern development good practices. DI is passed into KarassFrame via a factory to help with dependency inversion.
-
Avoid spaghetti and lasagne code (See Why?) and not be tied to God Objects
-
A Karass:
- can be run over multiple frames
- can be combined with another Karass. eg:
Karass quickDash = DashToPoint + PlayDashParticles + ShakeScreen
OnButtonPress(Player.KanKan.Run(quickDash));
- can be run sequentially with other Karass. eg:
NPC.Kankan(new[]{RunToPlayer,PunchPlayer,Dance)
- can take messages and sync with external systems. eg:
NPC.Kankan(RunAwayFromPlayer)
OnPlayerHit(Kankan.SendMessage(“punch NPC"));
-
A KarassFrame:
- can have reusable logic with access to DI
- can be passed different object types (usually as a unique constructor class/struct)
- can receive messages from Karass
- can be run alongside different object types
- is created through a factory
-
A KanKan:
- Can run within any engine with a frame update method. Note: Focus has been on Unity and getting KanKan to play nice with
StartCoroutine
. Feedback on improving compatibility further is greatly appreciated. - Is lightweight and doesn't drop frames dropped between actions.
- Should have no knowledge of the outside system
- Can run within any engine with a frame update method. Note: Focus has been on Unity and getting KanKan to play nice with
A Karass always has:
- Setup Action(s)
- Array of FrameRequests
- Teardown Action(s)
An array of Action
or void Setup(){}
An array of Action
or void Setup(){}
A FrameRequest is a tidy way of passing a RequestType object (such as a struct or class) to a IKarassFrame. Before a IKarassFrame can be run it must be:
IKarassFrame<RequestType>
Registed with theKarassDependancy
- Have
RequestType
linked toIKarassFrame<RequestType>
withFrameFactory.RegisterRoute<RequestType,<IKarassFrame<RequestType>
You can then new up a RequestType something = new RequestType()
add some properties. The FrameRequest can be created FrameRequest frameRequest = new FrameRequest(something)
and added to a Karass.
All Setup Actions will be called before first Frame. All Teardown Actions will be called after last Frame. If there are no Frames, Setup and Teardowns Actions will be called.
You might want an IKarassFrame to apply damage to a unit. We will want to change the amount of damage and, let's say, the impact message each time this is frame is called. It'll also hold information on which Unit did the damage and an array of all Units it should apply the damage to.
-
- Create a struct to store the damage amount:
public struct DamageRequest
{
public int OwnerID;
public int[] HitUnitsID;
public int Damage;
public string DamageMessage;
}
-
- Create a IKarassFrame class. This should be as generic as possible so we can reuse it.
class ApplyDamage : IKarassFrame<DamageRequest>
{
public DamageRequest RequestData { get; private set; }
public string Message { get; }
public bool Execute(string message, DamageRequest payload)
{
bool goToNextFrame = true;
RequestData = payload;
foreach (int hitUnitID in RequestData.HitUnitsID)
{
IUnit unit = dependencies.Get<IUnits>().GetUnit(hitUnitID);
unit.ApplyDamage(RequestData.Damage, RequestData.DamageMessage);
}
return goToNextFrame;
}
}
-
- Register
IKarassFrame<DamageRequest>
->ApplyDamage
with DI and set RouteDamageRequest
->IKarassFrame<DamageRequest>
inFrameFactory
. Below is a typicalStart
method (note: Start is run before any other frames)
- Register
private IKarassFactory _karassFactory;
private IDependencies _dependencies;
private IFrameFactory _frameFactory;
private void Setup()
{
_dependencies = new KarassDependencies();
_frameFactory = new FrameFactory(_dependencies);
_karassFactory = new KarassFactory(_frameFactory);
}
-
- When adding a frame to a Karass all you need to do is create a new
DamageRequest
and make aFrameRequest
. Note the below example has no Setup and Teardown and only one frame.
- When adding a frame to a Karass all you need to do is create a new
KarassFactory _karassFactory = new KarassFactory(_dependencies, _frameFactory);
DamageRequest applyDamage = new DamageRequest()
{
HitUnitsID = new []{42,21,45},
OwnerID = 42,
Damage = 99,
DamageMessage = "KAPOW!!"
};
FrameRequest applyDamageFrameRequest = new FrameRequest(applyDamage);
Karass applyDamageKarass = _karassFactory.Get(new List<Action>(), new List<Action>, new []{ applyDamageFrameRequest})
-
- Fire up a KanKan to run the Karass:
KanKan kankan = new KanKan(applyDamageKarass, _frameFactory);
kankan.MoveNext();
In systems with multiple, complex, moving parts a state machine can become the glue that binds your components together. It can end up knowing everything about the system and - as it becomes more complex - becomes fragile and incredibly hard to test. We found this when developing Quote. The SM was perfect for what we wanted at the beginning but, over the development duration, it grew, morphed and eventually became unmanageable. The moment we stopped attending to it daily it started to rot and - after some time away from the project - became unworkable.
The words KanKan and Karass were taken from Kurt Vonneguts 'Cats Cradle'.
KanKan: the instrument that brings individuals into their karass (Cat's Cradle - Kurt Vonnegut)
The CanCan is also a dance (👯♂) which ties in quite nicely...
Karass: "We Bokononists believe that humanity is organized into teams, teams that do God's Will without ever discovering what they are doing. Such a team is called a karass by Bokonon"`
Detailed instructions coming soon. In the meantime please read the example and:
- Clone repo
- Compile to .dll
( Note for Unity you should:
dotnet publish -c Release --framework netstandard2.0
) - Import to engine