-
Notifications
You must be signed in to change notification settings - Fork 6
Worktable: Ability Example 1
This guide goes into a lot of details, you can directly skip straight to the completed files if you have enough understanding of how game logic work.
This example has an old video:
https://www.youtube.com/watch?v=lBx_9vOeBHY
It doesn't use the worktable, but instead directly integrate the ability in the game code, but 90% of the content is the same
Completed files:
https://gist.github.com/guerro323/4bf9cacb85a99c15f72547e554e237e4
For this example, we will name our ability: "MoveNdJump"
In your worktable folder, access your Abilities folder (create it if it doesn't exist),
and create two files:
MoveNdJumpAbility.cs
MoveNdJumpScript.cs
This step doesn't exist if systems are automatically resolved on your module (see Worktable: Automatically Add Your Systems)
Later once you've created your systems you will need to spawn them, Modify your module file to make them spawn:
ApplicationTracker.Track(this, (SimulationApplication app) => {
// ...
AddDisposable(app.Data.Collection.GetOrCreate(wc => new MoveNdJumpAbilityProvider(wc)));
AddDisposable(app.Data.Collection.GetOrCreate(wc => new MoveNdJumpScript(wc)));
});
Open MoveNdJumpAbility.cs
.
Create a new component structure called MoveNdJump
and let's add a float
field inside the struct called Speed
using GameHost.Simulation.TabEcs.Interfaces;
public struct MoveNdJumpAbility : IComponentData
{
public float Speed;
}
We're halfway here, now let's add the Provider in the same file:
public class MoveNdJumpAbilityProvider : BaseRuntimeRhythmAbilityProvider<MoveNdJumpAbility>
{
public MoveNdJumpAbilityProvider(WorldCollection collection) : base(collection)
{
}
}
Now there will be two errors:
- The property MasterServerId isn't implemented
- The method GetChainingCommand() isn't implemented.
So let's implement them:
MasterServerId
public override string MasterServerId => "MoveNdJump";
If you plan to integrate an ability in the PataNext code, then you'll need to return a different string:
This is totally optional, if you just want to get started on implementing your own abilities, then don't do that yet
// We create a resource path, for an ability for taterazay.
public override string MasterServerId => resPath.Create(new[] { "ability", "tate", "move_nd_jump" }, ResPath.EType.MasterServer);
GetChainingCommand This return the rhythm command that will be used to invoke this ability. In this example, the March command will be used.
public override ComponentType GetChainingCommand()
{
return AsComponentType<MarchCommand>();
}
We will also need to set the default Speed to a custom value, let's use 1
.
Modify the parent property DefaultConfiguration
in the constructor:
DefaultConfiguration = new MoveNdJumpAbility()
{
Speed = 1
};
We will need to create a new class based on PataNext.Module.Simulation.BaseSystems.AbilityScriptModule<T>
.
Generate the missing members and it should look like that
using System;
using GameHost.Core.Ecs;
using GameHost.Simulation.TabEcs;
using PataNext.Module.Simulation.BaseSystems;
using PataNext.Module.Simulation.Components.GamePlay.Abilities;
public class MoveNdJumpScript : AbilityScriptModule<MoveNdJumpAbilityProvider>
{
public MoveNdJumpScript(WorldCollection collection) : base(collection)
{
}
protected override void OnExecute(GameEntity owner, GameEntity self, ref AbilityState state)
{
}
protected override void OnSetup(Span<GameEntityHandle> abilities)
{
}
}
We will then proceed to modify the body of OnExecute
!! All script operations in this example is done in the OnExecute
method. !!
The OnExecute method has three arguments:
-
owner
, which is the character that possess this ability -
self
, which is the ability entity -
state
, which contains the state of the entity
We first need to get our ability component MoveNdJumpAbility
var settings = GetComponentData<MoveNdJumpAbility>(self);
The goal will be to move the character when the ability is active, so add a condition
if (state.IsActive)
{
}
We then force the horizontal velocity of the owner
to the speed we chosen
- Access the component
Velocity
ofOwner
- Modify the variable
Value.X
of the component
Also, we need to enable the variable ControlOverVelocityX
in the owner
component UnitControllerState
- If we don't do that, the resulting velocity will not be what we may want since other systems may modify it.
This will be the result of the OnExecute body
protected override void OnExecute(GameEntity owner, GameEntity self, ref AbilityState state)
{
var settings = GetComponentData<MoveNdJumpAbility>(self);
if (state.IsActive)
{
GetComponentData<Velocity>(owner).Value.X = settings.Speed;
GetComponentData<UnitControllerState>(owner).ControlOverVelocityX = true;
}
}
As of now, our character only march! It would be nice if when we charge the ability it would also jump.
Such stuff is easily possible.
Add the float
field JumpPower
.
In the provider, modify the default configuration to have a default value for JumpPower
(we will use 8).
This is how our file look as of now
using System;
using GameHost.Core.Ecs;
using GameHost.Simulation.TabEcs;
using GameHost.Simulation.TabEcs.Interfaces;
using PataNext.Module.Simulation.BaseSystems;
using PataNext.Simulation.Mixed.Components.GamePlay.RhythmEngine.DefaultCommands;
public struct MoveNdJumpAbility : IComponentData
{
public float Speed;
public float JumpPower;
}
public class MoveNdJumpAbilityProvider : BaseRuntimeRhythmAbilityProvider<MoveNdJumpAbility>
{
public MoveNdJumpAbilityProvider(WorldCollection collection) : base(collection)
{
DefaultConfiguration = new MoveNdJumpAbility()
{
Speed = 1,
JumpPower = 8
};
}
public override string MasterServerId => "MoveNdJump";
public override ComponentType GetChainingCommand()
{
return AsComponentType<MarchCommand>();
}
}
We added the jump power in the settings, but it also mean that we now to make it jump for real!
But how can we know the previous command?
The ability has another component called AbilityEngineSet
and it contains a field called PreviousCommand
.
We will check whether or not this resource has the ChargeCommand
component.
Let's implement it, in the body of if (state.IsActive)
:
- Create a new variable called
previousCommand
with the result of
GetComponentData<AbilityEngineSet>(self).PreviousCommand
- Create another condition that will check for ChargeCommand
if (HasComponent<ChargeCommand>(previousCommand.Entity))
{
}
Now let's force the vertical velocity of the character to the jump power in the condition we've just created it:
GetComponentData<Velocity>(owner).Value.Y = settings.JumpPower;
This should be the end result of OnExecute:
var settings = GetComponentData<MoveNdJumpAbility>(self);
if (state.IsActive)
{
var previousCommand = GetComponentData<AbilityEngineSet>(self).PreviousCommand;
if (HasComponent<ChargeCommand>(previousCommand.Entity))
{
GetComponentData<Velocity>(owner).Value.Y = settings.JumpPower;
}
GetComponentData<Velocity>(owner).Value.X = settings.Speed;
GetComponentData<UnitControllerState>(owner).ControlOverVelocityX = true;
}
Now build and run, do your charge command and then march, and... We jum------p?
It look like more it's flying, but you can easily fix that by checking if it's the first frame.
We will need to add sub-component called State
in the ability component.
It will contains an int
field named PreviousActivation
We need to modify the provider to include this new component.
- Override the method GetComponents()
- Add the component type
MoveNdJump.State
into the listentityComponents
public override void GetComponents(PooledList<ComponentType> entityComponents)
{
base.GetComponents(entityComponents);
entityComponents.Add(AsComponentType<MoveNdJumpAbility.State>());
}
We need to get the reference our new ability state component:
- Go inside the OnExecute body
- Store the
State
reference into a new ref variable calledabilityState
ref var abilityState = ref GetComponentData<MoveNdJumpAbility.State>(self);
Right after the condition if (HasComponent<ChargeCommand>(previousCommand))
add a new condition that will check between abilityState.PreviousActivation
and state.ActivationVersion
before you set the vertical velocity.
if (abilityState.PreviousActivation != state.ActivationVersion)
GetComponentData<Velocity>(owner).Value.Y = settings.JumpPower;
If you build and run your code, you will see that now the character is jumping properly, but it also move horizontally at the same time!
You can easily fix that by introducing an else
statement after you check for the ChargeCommand:
if (HasComponent<ChargeCommand>(previousCommand.Entity))
{
if (abilityState.PreviousActivation != state.ActivationVersion)
GetComponentData<Velocity>(owner).Value.Y = settings.JumpPower;
}
else
{
GetComponentData<Velocity>(owner).Value.X = settings.Speed;
GetComponentData<UnitControllerState>(owner).ControlOverVelocityX = true;
}