# How do I get A.I. that follows a list of movement commands to move smoothly in a tilemap based game?

This is for a school project so it's not intended to be sold/fun/or a complete game, rather just a demonstration of stuff we've learned without resorting to frameworks and stuff.

My ai agents choose a random point in the grid and generate a sequence of actions to take to get there using breadth first search. They do this indefinitely until they're killed (something I haven't gotten to yet). Each action corresponds to one tile movement so I'm having trouble translating that into smooth movement transitioning from one tile to the next rather than just stuttering to it. What I have at the moment is limiting how many actions can be taken per second so the agent doesn't just teleport around the game because it's moving so fast. Can I modify what I have here to achieve smooth movement, or do I need to take a different approach in how to generate my action sequence?

I had smoothish movement when my MAX_VELOCITY = 1 by just rounding down if the time step made the agent move past more than one tile, but it didn't work for higher numbers, like MAX_VELOCITY = 10. The player character moves at MAX_VELOCITY = 10 which I think is perfect.

Edit: I'm thinking of something now though I don't know if it'll work or if it's the right way to do it. If I only use enough of the time step to advance to the next square, then I could use the rest of the timestep on the next action, or something like that until I'm out of time.

Edit2: It seems to work properly. There's some collision detection issues coming up, but it looks pretty much as smooth as the player character movement. I'll leave the answer unchosen for now since my solution isn't very good but I'll post what I have here once I figure out the collision detection issues.

@Override
public void move(double dt) {
this.elapsedTime += dt;
if (this.actionsIndex == this.actionsToGoal.length() || this.actionsIndex == -1) {
long startTime = System.nanoTime();
if (this.goal != null) { //Debugging stuff
startingPosition = new Point((int) this.posX, (int) this.posY);
System.out.println("Arrived at goal");
System.out.println(startingPosition.toString());
System.out.println(goal.toString());
}
chooseGoal();
this.frontier.clear();
generateActionSequence();
double timeTaken = (System.nanoTime() - startTime) / 1000000000.0;
this.elapsedTime += timeTaken;
}

if (this.actionsIndex != -1) {
while (elapsedTime >= 1.0 / this.actionsPerSecond && this.actionsIndex != this.actionsToGoal.length()) {
char action = actionsToGoal.charAt(this.actionsIndex++);
switch (action) {
case State.LEFT:
this.posX -= MAX_VELOCITY;
break;
case State.RIGHT:
this.posX += MAX_VELOCITY;
break;
case State.UP:
this.posY += MAX_VELOCITY;
break;
case State.DOWN:
this.posY -= MAX_VELOCITY;
break;
}
elapsedTime -= 1.0 / this.actionsPerSecond;
}
}

}


This is the while loop above redone. Kind of ugly and repetitive but it works. I had to use Math.floor and Math.ceil to solve the collision issues which is due to precision I guess. Any spare dt left over if the action sequence is finished is discarded so that might be something to save for accuracy but the timesteps are really small so it may not be significant.

Edit: This one is better than the one I posted first. Since the precision might cause it to either under or overshoot the +1 tile movement, it's difficult to know when to floor or ceil to get the whole number. So when there is less dt needed than what's available, just floor or ceil the current double position according to which way you're moving to get to the starting tile position and add or subtract one from that to get the proper tile position after moving. With the original my agents would move outside the walls when they shouldn't but with the second they stay inside.

while (dt > 0 && actionsIndex != actionsToGoal.length()) {
char action = actionsToGoal.charAt(actionsIndex);
double dtNeeded;
switch (action) {
case State.LEFT:
dtNeeded = (this.posX - Math.ceil(this.posX-1))/MAX_VELOCITY;
if(dtNeeded > dt) { //There's not enough time needed to fully move to the next tile, use all of what's left.
this.posX -= MAX_VELOCITY * dt;
return;
}
else{ //Less than total timestep needed to advance to next tile.
this.posX = Math.ceil(this.posX) - 1;
dt -= dtNeeded;
actionsIndex++;
}
break;
case State.RIGHT:
dtNeeded = (Math.floor(this.posX+1) - this.posX)/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posX += MAX_VELOCITY * dt;
return;
}
else{
this.posX = Math.floor(this.posX) + 1;
dt -= dtNeeded;
actionsIndex++;
}
break;
case State.UP:
dtNeeded = (Math.floor(this.posY+1) - this.posY)/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posY += MAX_VELOCITY * dt;
return;
}
else{
this.posY = Math.floor(this.posY) + 1;
dt -= dtNeeded;
this.posY = Math.ceil(this.posY);
actionsIndex++;
}
break;
case State.DOWN:
dtNeeded = (this.posY - Math.ceil(this.posY-1))/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posY -= MAX_VELOCITY * dt;
return;
}
else{
this.posY = Math.ceil(this.posY) - 1;
dt -= dtNeeded;
actionsIndex++;
}
break;
}


while (dt > 0 && actionsIndex != actionsToGoal.length()) {
char action = actionsToGoal.charAt(actionsIndex);
double dtNeeded;
switch (action) {
case State.LEFT:
dtNeeded = (this.posX - Math.ceil(this.posX-1))/MAX_VELOCITY;
System.out.println(dtNeeded);
if(dtNeeded > dt) { //There's not enough time needed to fully move to the next tile, use all of what's left.
this.posX -= MAX_VELOCITY * dt;
return;
}
else{
this.posX -= MAX_VELOCITY * dtNeeded;
dt -= dtNeeded;
this.posX = Math.floor(this.posX); //Ensuring no precision error. One action = 1 tile movement
actionsIndex++;
}
break;
case State.RIGHT:
dtNeeded = (Math.floor(this.posX+1) - this.posX)/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posX += MAX_VELOCITY * dt;
return;
}
else{
this.posX += MAX_VELOCITY * dtNeeded;
dt -= dtNeeded;
this.posX = Math.ceil(this.posX);
actionsIndex++;
}
break;
case State.UP:
dtNeeded = (Math.floor(this.posY+1) - this.posY)/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posY += MAX_VELOCITY * dt;
return;
}
else{
this.posY -= MAX_VELOCITY * dtNeeded;
dt -= dtNeeded;
this.posY = Math.ceil(this.posY);
actionsIndex++;
}
break;
case State.DOWN:
dtNeeded = (this.posY - Math.ceil(this.posY-1))/MAX_VELOCITY;
if(dtNeeded > dt) {
this.posY -= MAX_VELOCITY * dt;
return;
}
else{
this.posY -= MAX_VELOCITY * dtNeeded;
dt -= dtNeeded;
this.posY = Math.floor(this.posY);
actionsIndex++;
}
break;
}
}