Enemy Logic
LOGIC OF THE HUMBLE SKELETON
Shout out to the sketchy scribble of a skeleton warrior in the gifs below, its a test thing so it's ok for it to look like that.
For Disavowed I've chosen for the enemies to utilise the mp_grid functions of GMS2. It makes the most sense to allow enemies to follow the player around and avoid walls. I've applied a base tile-map just to have something other than a black screen to look at whilst testing and setting up behaviors.
STATE MACHINE
There's various methods do achieve enemy logic but the form i'm most comfortable with is using an enumerated state machine. I find them easier to read, and understand, this helps a lot when the logic starts to get more cumbersome. and its easier to expand on if you have a few brain waves later on in production.
This Skeleton warrior can do a few basic things and i'll likely keep it that way, as they don't need to be too complex right off the bat. The break down of the enum is;
enum EnemyState{ ROAM, PURSUE, ATTACK, COOLDOWN, KNOCKBACK }
While in Roaming, the enemy will pick a random point and move towards it, rest for a moment, then pick another direction to move in.
switch (state) { case EnemyState.ROAM: attack_cooldown = 0; pursue_Speed = 2; if (roam_timer <= 0) { roam_direction = irandom(359); roam_timer = roam_timer_max; // Calculate a random point to roam to var roam_target_x = x + lengthdir_x(100, roam_direction); var roam_target_y = y + lengthdir_y(100, roam_direction); //make roaming path respects walls if (mp_grid_path(obj_gameControl.mp_grid, path, x, y, roam_target_x, roam_target_y, true)) { path_start(path, roam_speed, path_action_stop, false); } else { roam_timer = 1; // finding a valid path in the next step } } else { roam_timer--; } if (instance_exists(obj_player) && distance_to_object(obj_player) < 80) { // Detection range state = EnemyState.PURSUE; } break;
This lets them bumble about and not be just static. Once the player gets near enough, they will come after them, close the distance until they are in their attack range (this range will be unique to the enemy as some will be ranged enemies) but our sword Skelly here will rush in for some melee attacks. I won't break down all the code, but you can see how from the ROAM block in the state machine, its easier and tidy to keep these actions segregated.
I've added a variable in for "sword_swing_time" so this will give the player a visual cue when the skeleton raises its sword before striking. the 10 frames should be plenty to hold on a small animation with the sword glinting. Although the mechanic isn't in yet, the player will be able to block attacks if timed correctly.
Having the enumerator will allow to easily add more logic to the enemies as the game expands. this could mean three different skeleton soldiers all using the same enumerator but with certain sections blocked out for "ranged skeleton" and "shield skeleton" shield skeletons could block players sword attacks for example where as those unfortunate to be without one can only get slapped by the players steel!
MAGIC
The player will be able to collect scrolls to learn different spells. Currently you can just find them lying around to make it easier for testing, but I plan to have them in chests, or purchasable from the village area.
Spells, once acquired will use mana, you'll never lose them once learned, you just need to make sure you've got enough mana. I'll dive deeper on those in another devlog, but my reason for mentioning it here is that there is an override on the distance detection for all enemies if they get hit be a projectile. so if you decide to snipe ol bone bonce from the other side of the room, he will ignore his regular distance check and just come at you!
All the enemies base logic for moving around is being handled by a parent and the enemies are child objects of that parent. doing my very best to keep things nice and tidy so I can run the game fluidly without performance loses. There are a few things that help with that, the skeleton (and other enemies) will only check for the players position every few frames and randomises its timer so all enemies check for the position on different frames.
This may be overkill as I don't plan on the player having to face down HORDES of enemies at one time, but its a nice to have and good to set up now. Trying to account for that down the road would mean a lot of re writes of how the enemies work and its just not worth the hassle.
Development moves swiftly on, I'll likely spend some more time working on some better sprites now, as it would be good to get the logic working with those too. specifically the skeletons attack telegraph. as that would allow me to open up some testing with the player using timed blocks. This won't be anything CRAZY, this is after all a small dungeon adventure! But I do want to make it look nice!
Disavowed
retro dungeon crawler
Status | Prototype |
Author | ProjectLevel |
Genre | Adventure |
Tags | 2D, Dungeon Crawler, Pixel Art, Retro |
More posts
- Slow Progress24 days ago
- Enemy animation script29 days ago
- MP_GRID MOVEMENT WOES33 days ago
- Character Design - Morianna34 days ago
- Sprite Development35 days ago
- TILE-SET DESIGN38 days ago
Comments
Log in with itch.io to leave a comment.
I enjoyed reading this and seeing what others are up to. I had to go to your project main page to see that you're using GameMaker. I've never used it, but it looks similar to C/C++. I'm curious how come you're setting the pursue_Speed in EnemyState.ROAM?
Hey, thanks for having a read through :)
so without seeing the rest of the code I can see why that could look odd, Its set there simply because there is a cool down on the pursue speed when the enemy reaches the player. Say the enemy reaches you, but you run away, I didn't want the skeleton to immediately chase you constantly. so I just reduce the pursue speed to 0, and then there's a timer that ticks down to say when they can pursue again.
If the player moves far enough away from the enemy, they will return to their ROAM state. so in that case i wanted to just hard reset it there, so if the player gets close again the enemy would resume at the correct speed.
I could've also temporarily stopped the path action, but I didn't think it would hurt to keep that going and checking the players position for when they can move again. and instead just reduced the speed.
I should mention i'm still very much learning a lot about code, so I don't always get it right or have the most efficient approach. but i'm getting better. I've been dabbling on and off with different projects since about 2018, but i'm a silly artist that hates math, so my progress isn't the fastest.
Again thanks for commenting.
Ok, cool. When you create a second enemy, like a bat, I recommend you abstract that speed out to the enemy itself, and call a variable on the enemy. That way you can use the same state code for each enemy without having to copy and paste.
Ok, i've done this now, each child objects of the enemy parent just tells it what its own pursue speed is. so it can be unique to each enemy. its working great so far. thanks for the tip!
You're welcome!