Skip to content

Worktable: Ability Example 1

Guerro323 edited this page Aug 9, 2021 · 5 revisions

Warning!

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.

Ability Example 1

This example has an old video (which will be remade):
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

Creating an ability that move the character, and jump when it's charged.

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

(Optional) Spawning the Provider and Script systems into your module.

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)));
});

Let's start by creating the ability description

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
};

Moving the character

Creating the script MoveNdJumpScript.cs

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 of Owner
  • 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;
    }
}

Making the character jump when charged

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.

Modify the description file MoveNdJumpAbility.cs

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>();
    }
}

Modify the script file MoveNdJumpScript.cs

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.

Modify the description file

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 list entityComponents
public override void GetComponents(PooledList<ComponentType> entityComponents)
{
    base.GetComponents(entityComponents);

    entityComponents.Add(AsComponentType<MoveNdJumpAbility.State>());
}

Modify the script file

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 called abilityState
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;
}
Clone this wiki locally