Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411418 Posts in 69362 Topics- by 58416 Members - Latest Member: timothy feriandy

April 17, 2024, 06:15:22 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)ECS and AI
Pages: [1]
Print
Author Topic: ECS and AI  (Read 1265 times)
tanis
Level 1
*


View Profile WWW
« on: December 31, 2015, 12:03:11 AM »

Hello everybody,

I've been struggling trying to design some good API for my AI logic. I'm looking for suggestions.

Here's the setup of my current game:

- Everything is managed by a plain ECS.
- My enemies are made up of the following components: Input, Physics, Grid, Health, Inventory, Sprite, AIControl.
- Input is the one that passes input data to other components, physics is just holding velocity, grid contains the coordinates on the tile map, health holds data like max health and current health, inventory holds references to entities like weapons, sprite has all the data about the sprites used for animating, AIControl is the actual holder of data like the mental state of the enemy, its action queue, sight range, current walking direction, etc...
- My AI system is designed around two main structures: the mental state which is just an enumeration like idle, alerted, panicking, etc... and an action queue that holds a list of actions to perform in order, like wait, move, fire, etc...

This setup is working quite well with a single type of enemy. As an example just think of an enemy that can move on the ground and fire with its weapon. I have added the code to manage the mental states and actions to perform based on the current mental state so that if it's idle and it sees the player, it will turn to face the player, fire, wait for the weapon to recharge, fire again if the player is still in range and so on.

If I add a new kind of enemy, let's say an alien that doesn't fire but can charge at the player to hit it by proximity, the whole AI system needs an overhaul to either handle a new action (charge) or turn the fire action into the actual charge. Either way, I need to check whether the enemy can fire or can charge. I solved this by adding a new component to the alien which is just called Charge and that has no data, it's just an empty component that I use to tell that this kind of enemy can charge.

What I'm wondering about is whether this is a good design in the long run as I might add more and more kind of actions or if there's a better way to handle this kind of logic.

Thanks in advance!
Logged

Noogai03
Level 6
*


WHOOPWHOOPWHOOPWHOOP


View Profile WWW
« Reply #1 on: December 31, 2015, 01:19:45 AM »

Well, it sounds like your program is quite small and isn't going to grow that much, so it would probably be relatively manageable to just have an EnemyType enum and when you create the AI component use a switch to decide which sequence of events to add - not pretty but it would work and would be relatively easy to implement

Or you could move the specific logic out of the main AIControl class and put different types into inheriting subclasses (can get nasty, though)

Or you could use something like Lua to implement a really simple scripting system for AI; register a bunch of different functions manually and then have it execute a code sequence every tick; this could also store data about the enemy:

(my lua is extremely patchy so sorry if my syntax is wrong but you get the idea)

Code:
--chargerAI.lua
function init()
    moveSpeed = 100
    name = "Charger"
    visionRadius = 300
end

function tick()
    if get_state() == "idle" then
        if queue_empty() then
           wait(100)
           walk_random_direction(moveSpeed)
        end
    end
end
Logged

So long and thanks for all the pi
tanis
Level 1
*


View Profile WWW
« Reply #2 on: January 01, 2016, 03:27:40 AM »

Thanks for the ideas. I won't subclass the AIControl as it would defeat the entire component system. Scripting is an interesting path but it feels like a lot of work just to get pretty much the same results I might have by planning the AI a bit more carefully.

My current solution is to add new actions as I need them. As you say, my options aren't going to grow like crazy so this should be pretty much under control. On the other hand now I'm thinking about more subtle behaviors. An example could be the way the enemies move. Some might move with a certain path, some might chase the player, others might go along a path but stray from it every now and then. I can probably get around this by adding new actions just like with the chasing behavior.
I'm still thinking out loud so if you've got any idea, please share it with me. Cheers!
Logged

Photon
Level 4
****


View Profile
« Reply #3 on: January 01, 2016, 11:44:38 AM »

Why would adding a new action be a problem? To me, I would think part of the reason for having an action type and action queue is so that you could add new actions as the game is developed and the queue/queue handler could handle all actions indiscriminately.

I guess I'm not entirely sure how actions are defined (string identifiers, actual class objects, etc.) in your game, which would also dictate how the action handler is defined. If there were defined under an interface/base class of some sort, a simple implementation could have the action handler just hit an "update" function on the action and let the it behave as it wishes. If the actions have to operate over several frames, you could add an "if_finished" function to regulate action flow. You could extend the action as necessary to suit your needs.
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #4 on: January 01, 2016, 12:41:15 PM »

Your charging example sounds like something that could make use of a bitfield. If this bit is set, we can charge on this enemy, otherwise not. Using a queue of actions is a good solution and should be able to handle most of your AI needs. Also be warned that the tinier your components are the more numerous and complex the interactions and dependencies between components will become.
Logged
tanis
Level 1
*


View Profile WWW
« Reply #5 on: January 02, 2016, 02:28:35 AM »

@Photon: to further clarify, my ActionObject class is just a data holder that has a duration (float) and an ActionType (enumeration).
Instead of adding more properties to my ActionObject I use the AIControlComponent to store all of the AI related data that doesn't fit in other components.

The reason why I think that adding more and more actions could become a problem in the long run is that the method that does the actual "thinking" of the AI becomes polluted of checks to see if it should rather charge or fire at the player, and so on. Not that it's really a problem, but I thought there might be a smarter way to split things up to avoid creating monolithic checks.


@qMopey: yes, the charge sounds like a bitfield, but in my case it's more convenient to just make it an empty component that I stick on my entity. Since I've got the ECS already covering that kind of behavior, it becomes easy to add components to specify what characteristics the enemy has.
Logged

Photon
Level 4
****


View Profile
« Reply #6 on: January 02, 2016, 07:46:48 AM »

@Photon: to further clarify, my ActionObject class is just a data holder that has a duration (float) and an ActionType (enumeration).
Instead of adding more properties to my ActionObject I use the AIControlComponent to store all of the AI related data that doesn't fit in other components.

The reason why I think that adding more and more actions could become a problem in the long run is that the method that does the actual "thinking" of the AI becomes polluted of checks to see if it should rather charge or fire at the player, and so on. Not that it's really a problem, but I thought there might be a smarter way to split things up to avoid creating monolithic checks.
Which is why I think it makes more sense to offload the actual workload into action itself via child classes: to avoid a ginormous handler class. I know that in ECS there's an emphasis on separating data and logic, but in my personal opinion the child class approach gets you a much simpler, cleaner implementation without a significant sacrifice to cohesion.
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic