1
\$\begingroup\$

I’m trying to implement a top-down Zelda-like enemy movement system. Consider a screen of x tiles wide by y tiles tall, each tile being 16x16 pixels.

I’m not asking for code here. I’m asking for direction. I’m using Godot, but perhaps the answer isn’t engine specific. Should I use something akin to NavigationAgent2D? Or manual movement? Based on the requirements below what is the best practice for an enemy movement system?

Requirements:


  • Enemies can only move in cardinal directions (up, down, left right).
  • Enemies cannot enter tiles that have physics colliders.
  • Enemies are confined to the current screen and must not leave the screen.
  • Enemies may or may not pass through each other (based on enemy type).
  • Enemies may or may not be inclined to move toward the player (based on enemy type). Even if they move toward the player, they must still respect cardinal direction movement.
  • Enemies can be hit with melee or ranged weapons which apply a force to them. The force should apply to them if hit from the same or opposite direction that they’re moving. 


Most tutorials just have pathfinding where enemies take the shortest route to the player. But I want my enemies to move around the screen in confined cardinal directions and be mostly oblivious to the the player, like in Zelda. But I keep running into problems where they get stuck or do not keep their movement aligned with the tile grid.


 As I see it, enemies decide a direction to move and how many tiles to move, and then when they reach their destination tile they must decide again. But I could be going about this wrong. Other enemies (or the player) could get in the way and they’d be forced to recalculate their next move. It’s important that they stay aligned with the grid.

Is there some built-in node or system in Godot that can be used to satisfy these requirements?

Any suggestions are truly appreciated.

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

Two common patterns for implementing simple AI behaviors like you saw them in the classic 2D Zelda games are:

  • State Machines. State machines are a general architectural pattern in software development with great potential for senseless overengineering. So if you ask 10 programmers to implement a state machine, you get 20 different solutions. But a simple one is to give every enemy a variable that says in which state it currently is (moving, turning, attacking, etc.). Your _process / _physics_process would then be a if/elif chain with separate code for each possible state to perform whatever the enemy is supposed to do during that state and checking the conditions to switch to a different state.
  • Behavior Trees. Another common pattern for simple enemy AI. The main difference between BT and SM is that a behavior tree doesn't remember which state it's in but rather determines the state again on each frame. You can express that logic using a data-driven approach as explained in the article I just linked. Or you can do it the simple way using an if/else tree checking all the relevant information and then behaves according to the result.
\$\endgroup\$
0
\$\begingroup\$

I'm new to this forum and new to this, so just a disclaimer before you read this.

I saw you were asking for a best practice approach and I definitely don't know the answer to that question but what's best practice for your project might depend on your project so... but I think you should just implement what you want in the dumbest, fastest and simplest way possible first and iterate once you know more what works and what doesn't from implementing.

Not sure how you've defined your tilemap and if collision is handled through tileIDs or you've added a node for specific tiles for collision or you just have object nodes for collisions - so I'm assuming you just have 1 tilemap:

So for every delta, your enemy will roll a direction (up, down, left, right, idle) and you should conduct a check on the next tile it's going to move and if it fails either reroll or just idle:

Checks I can think of:

  • if (nextPos.x < screenEdgeX),
  • if (nextTile.collision),
  • for enemy in enemyList: if (enemy.pos === nextPos && enemy.type === passable ),
  • if (beenHit),
  • any other conditions.

Not sure what other systems need to be accounted for in your game but that's the dumbest way I would approach what you said.

\$\endgroup\$
0
\$\begingroup\$

Create a 2D array to hold Map Data Map(x,y)

Create functions to:

CanMove(x,y) // Can a unit move here ValidMove(x,y) // Is this a valid game tile on Map RandomMove(x,y) // returns a random tile in cardinal direction from passed x,y (calls ValidMove() to ensure move is valid IsPLayerInTile(x,y) // returns true if player is in tile Then for each enemy for each movement point call RandomMove() to pick a random direction and do move check if enemy collides with player and do battle etc next next 

Keep adding and map functions you need like:

LineOfSight(x,y,x1,y1) // returns true if LOS exists between passed tiles Range(x,y,x1,y1) // returns range in movement between passed tiles 

Write a function to handle enemy movement, and put your logic in there.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.