I’m working on my first Java game project with LibGDX framework. It’s a top-down shooter style game. My main issue is with the AI behavior for hostile units. They appear at random spots on the screen but don’t do anything after that. No movement, no attacking, they just sit there like statues.
Here’s my current Opponent class implementation:
import java.util.Random;
import GameLogic.GameWorld;
import com.badlogic.gdx.math.Vector2;
public class Opponent {
private static final Random random = new Random();
float TURN_RATE = 400;
int posX = random.nextInt(40);
int posY = random.nextInt(30);
Vector2 targetPos = new Vector2(posX, posY);
public Hunter(float VELOCITY, float angle, float w, float h, Vector2 pos) {
super(VELOCITY, angle, w, h, pos);
}
public void updateMovement(Vector2 pos) {
getPosition().x += 3;
getPosition().y += 3;
}
@Override
public void tick(float deltaTime, Player playerShip) {
position.lerp(targetPos, deltaTime);
angle += deltaTime * TURN_RATE;
if(angle > 360)
angle -= 360;
super.refresh(playerShip);
if(targetPos.equals(this.getPosition())) {
posX = random.nextInt(40);
posY = random.nextInt(30);
}
}
}
And in my GameWorld class for spawning:
public class GameWorld {
Iterator<Hunter> opponentIterator;
int spawnTimer = 0;
int totalSpawned;
public float getRandomFloat(float min, float max) {
return min + (float)(Math.random() * (max - min + 1));
}
public void gameUpdate() {
spawnTimer++;
if(totalSpawned < 35) {
if(spawnTimer % 80 == 0) {
float spawnX = getRandomFloat(0, 45.0f);
float spawnY = getRandomFloat(0, 45.0f);
opponents.add(new Hunter(4f, 0, 1, 1, new Vector2(spawnX, spawnY)));
totalSpawned++;
}
}
}
}
What changes do I need to make so my AI opponents actually move around the screen continuously?
your tick method isn’t getting called frm gameUpdate. loop through your opponents list and call tick() on each one during the game loop. also, that lerp’s probably too slow - consider bumping up the lerp speed or use basic movement toward the player position.
Yeah, calling tick() is part of it, but your AI logic is way too basic.
Ditch the hardcoded movement stuff and build an automation system that handles different AI behaviors dynamically. Set up decision trees for chasing, retreating, attacking, patrolling - whatever you need.
I did this for a game prototype where enemies needed complex behaviors that adapted to player actions, health, distance, all that. Instead of cramming everything into the game code, I used Latenode for automated workflows that determine AI actions.
It processes game state data and spits back movement commands, attack decisions, behavior switches in real time. Handles multiple enemy types with different personalities without cluttering your main loop.
Set up HTTP endpoints in your game - send current positions and states, get back AI decisions. Way cleaner than managing tons of conditionals in Java.
Scales much better when you want smarter enemies or different difficulty levels.
Your code has two main problems. First, like others said, you’re not calling tick() on your opponents in the game loop.
But the bigger issue is your movement logic. You’re using lerp() to move toward targetPos, then immediately checking if targetPos equals current position. That equality check will almost never work with floating point numbers.
Here’s what I’d do - built similar AI for a tower defense game:
Been through this exact scenario building my first LibGDX project. Beyond the missing tick() calls others mentioned, there’s another fundamental issue with your approach. You’re setting random target positions but never making the AI chase the player - this kills engagement completely. Opponents just wander aimlessly instead of creating any real threat.
Skip random movement. Calculate the direction vector from opponent to player position, then move toward the player at consistent speed. Something like:
Vector2 direction = new Vector2(playerShip.getPosition()).sub(this.getPosition()).nor(); getPosition().add(direction.scl(VELOCITY * deltaTime));
This creates proper chase behavior that responds to player movement. Add randomness later with occasional direction changes or different patterns, but start with basic player-following AI first. Makes the game feel way more interactive than random wandering.
There’s a third issue no one caught - your constructor signature doesn’t match the class name. You’ve got public Hunter(...) but the class is Opponent. That won’t compile. I hit similar problems when I started with LibGDX. Your movement feels janky because you’re mixing two approaches in the same method. You’ve got getPosition().x += 3 in updateMovement() and position.lerp() in tick(), but updateMovement() never gets called. Pick one and stick with it. For basic AI movement, I’d ditch the lerp and use direct position updates with proper delta time scaling. Calculate direction vector to target, normalize it, then move at constant speed. Way more predictable than lerp for game AI. Also check that your game loop passes a valid deltaTime to tick(). I’ve seen deltaTime stuck at zero due to timing bugs.