DemoProductions / shmup

3 stars 2 forks source link

AICommands #143

Open flip40 opened 7 years ago

flip40 commented 7 years ago

While reworking the TestBossAI I found that each state required around 100-200 lines of code, as well as being very repetitive. AICommands is my attempt to fix that, while adding in a usable framework for creating a AI States with a logical and readable list of steps that is also quite modular and configurable. I have rewritten TestBossAI to inherit from AICommands.

Example:

AddState(
    First(MoveTo(Location.TopRight))
    .Then(Wait(2))
    .Then(Shoot).And(MoveTo(Location.BottomRight))
    .Then(Wait(2))
    .Then(SwitchProjectile(1))
    .Then(Shoot).For(2)
    .Then(SwitchProjectile(0))
    .Then(Wait(2))
    .Then(Shoot).And(MoveTo(Location.TopRight))
    .Then(Wait(2))
    .End()
);

The above example is the first state from TestBossAI.

Creating and Adding States

States are added by the inherited AddState function. These should be placed inside and overridden DefineStates function.

Example:

protected override void DefineStates () {
    AddState(...);
}

AddState takes an argument of List<Func>, or a list of functions that return boolean values. This list can be generated by starting a sentence with First(), which begins the first step and returns a new StateBuilder. Each word returns the StateBuilder, allowing you to chain words into a sentence.

StateBuilder Overview

Under the hood, States work in a similar logical fashion to how they did previously. Each state is simply a set of steps. Once each step is complete, the state moves on to the next step, until there are no more steps. Each step is a list of functions, or commands. All commands return true or false, to indicate whether or not they have completed (for example, a MoveTo command will have to run multiple times before the object reaches it's new location). If any command returns false, the current step is not incremented, and will run again on the next Update. Once all commands return true, the current step is incremented.

There are two sets of functions that are used in these sentences, words and commands.

Words

Words, such as First, Then, and And are functions that tie together commands. Each word takes a single command function Func<bool>. First, And, and For will modify or add commands to the current step. Then ends the current step and starts a new one. End is used to end the final step.

Word Functions

First(Func<bool>)

Starts the sentence with the first step and adds the specified command.

And(Func<bool>)

Adds the specified command to the current step.

For(int seconds)

Wraps the last added command in a time function. Continuously calls the function until the specified time as passed.

Then(Func<bool>)

Ends the previous step and starts the next step with the specified command.

End(out List<Func<bool>>)

Ends the previous step and returns the built list of steps to be added as a state.

Commands

Commands are functions Func<bool> that specify an action to take. These function MUST return a bool value. Each command indicates its completion by returning true and indicates that it is incomplete by returning false. The State will not move to the next step until all commands in the current step report completion. Note that some of the below simply functions (passed by name, not called) and some are meta-functions (functions that return a function and should be called).

Shoot

Attempts to shoot the AI's weapon. Always returns true.

Rotate(int degrees, [int degreesPerSecond])

Returns a function that will rotate the AI by the specified number of degrees at the rate of degreesPerSecond. If degreesPerSecond is not specified, it will default to 5.

MoveTo(Func<Vector2> location)

Returns a function that will move the AI to the specified location. There are a list of premade locations that can be used in the static nested Location class. Note that this takes as its argument a function, not an actual location. The returned function must be able to update the location as it moves across the screen.

Wait(int seconds)

Waits for the specified number of seconds, then returns true.

SwitchProjectile(int projectileIndex)

Attempts to call SwitchProjectile on the AI's Weapon. Always returns true.

flip40 commented 7 years ago

Added a couple of commits. Because commands making use of Timer were each attempting to Start and Stop the Timer on their own, this would cause issues if you used multiple of these commands in a single step. Timer is now Started and Stopped at the beginning and end of each step by the State object.

For function updated to properly handle the function within the allotted time.

Step execution updated to properly handle it's status checks and command execution. I had shorthanded status = status && func(), however this would cause func() not to be called if status had been set to false due to boolean logic rules. It now calls and saves the return value of func() before setting status appropriately.

Also removed the debug statements, as it prints an obscene number of statements per second.