View Single Post
  #1  
Old 07-29-2011, 12:51 PM
xXziroXx xXziroXx is offline
Master of Puppets
xXziroXx's Avatar
Join Date: May 2004
Location: Sweden
Posts: 5,288
xXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant future
Send a message via AIM to xXziroXx Send a message via MSN to xXziroXx
A* Movement System

With this code, you can tell an NPC to move from point A to point B, and it'll calculate the best path using the Manhattan method. If you want to learn how this system works, I recommend reading this.

The only parts you might need to customize can be found in the onCreated() function. The system can be used both serverside and clientside.


Class: aStar
PHP Code:
//#CLIENTSIDE
function onCreated()
{
  
// The following settings are adjusted for useage with a "normal" showCharacter() and can be changed to your likings.
  
enum {
    
WIDTH_IN_TILES 2.5,
    
HEIGHT_IN_TILES 2,
    
MOVEMENT_SPEED 0.1,
    
X_OFFSET 0,
    
Y_OFFSET 1,
    
NORMAL_GSCORE 10,
    
DIAGONAL_GSCORE 14
  
};
  
  
showCharacter();
}

function 
moveTo(temp.destinationXtemp.destinationY)
{
  if (
onwall2(temp.destinationX X_OFFSETtemp.destinationY Y_OFFSETWIDTH_IN_TILESHEIGHT_IN_TILES))
    return 
this.chat "Error: blocking!";
  
  
this.chat "";
  
  
temp.startX this.x;
  
temp.startY this.y;
  
temp.identifier temp.startX "_" temp.startY;
  
  
this.closedNodes = new TStaticVar();
  
this.openNodes   = new TStaticVar();
  
this.openNodes.(@temp.identifier).nodeX temp.startX;
  
this.openNodes.(@temp.identifier).nodeY temp.startY;
  
this.openNodes.(@temp.identifier).parent "";
  
  
processNodes(temp.destinationXtemp.destinationY);
}

function 
processNodes(temp.destinationXtemp.destinationY)
{  
  
temp.lowestFScore getLowestFScore();
  
temp.node.copyFrom(this.openNodes.(@temp.lowestFScore));
  
  
temp.pathFound closeNode(temp.lowestFScoretemp.destinationXtemp.destinationY);
  
checkSurroundingNodes(temp.node.nodeXtemp.node.nodeYtemp.lowestFScoretemp.destinationXtemp.destinationY);
  
  if (!
temp.pathFound)
    
processNodes(temp.destinationXtemp.destinationY);
  else
    
followPath(temp.lowestFScore);
}

function 
checkSurroundingNodes(temp.startXtemp.startYtemp.parentNodetemp.destinationXtemp.destinationY)
{
  
temp.surroundingNodes = {
    { 
temp.startX 1temp.startY }, // top left
    
temp.startXtemp.startY },     // top middle
    
temp.startX 1temp.startY }, // top right
    
temp.startX 1temp.startY },     // middle right
    
temp.startX 1temp.startY }, // bottom right
    
temp.startXtemp.startY },     // bottom middle
    
temp.startX 1temp.startY }, // bottom left
    
temp.startX 1temp.startY },     // middle left
  
};
  
  
temp.counter 0;
  for (
temp.nodetemp.surroundingNodes) {
    
temp.nodeX temp.node[0];
    
temp.nodeY temp.node[1];
    
temp.identifier temp.nodeX "_" temp.nodeY;
    
    
temp.isDiagonal = (temp.counter%== true false);
    
temp.nodeOnWall onwall2(temp.nodeX X_OFFSETtemp.nodeY Y_OFFSETWIDTH_IN_TILESHEIGHT_IN_TILES);
    
    
temp.gScore = (temp.isDiagonal DIAGONAL_GSCORE NORMAL_GSCORE);
    
temp.hScore getNodeDistance(temp.nodeXtemp.nodeYtemp.destinationXtemp.destinationY) * 10;
    
temp.fScore temp.gScore temp.hScore;
    
    
temp.counter ++;
    
    
// On a wall? Ignore.
    
if (temp.nodeOnWall)
      continue;
    
// Closed already? Ignore.
    
if (temp.identifier in this.closedNodes.getDynamicVarNames())
      continue;
    
// On the open list already?
    
if (temp.identifier in this.openNodes.getDynamicVarNames()) {
      
// Is this path better?
      
if (temp.gScore >= this.openNodes.(@temp.identifier).gScore)
        continue;
    }
    
    
this.openNodes.(@temp.identifier).nodeX temp.nodeX;
    
this.openNodes.(@temp.identifier).nodeY temp.nodeY;
    
this.openNodes.(@temp.identifier).parentNode temp.parentNode;
    
this.openNodes.(@temp.identifier).gScore temp.gScore;
    
this.openNodes.(@temp.identifier).hScore temp.hScore;
    
this.openNodes.(@temp.identifier).fScore temp.fScore;
    
    if (
this.lowestFScore == "" || temp.fScore this.openNodes.(@this.lowestFScore).fScore || this.lowestFScore in this.closedNodes.getDynamicVarNames())
      
this.lowestFScore temp.identifier;
  }
}

function 
closeNode(temp.identifiertemp.destinationXtemp.destinationY)
{  
  if (
temp.identifier == this.lowestFScore)
    
this.lowestFScore "";
  
  
temp.node.copyFrom(this.openNodes.(@temp.identifier));
  
this.openNodes.(@temp.identifier).clearVars();
  
this.openNodes.clearEmptyVars();
  
  if (
temp.identifier in this.closedNodes.getDynamicVarNames())
    return;
  
  
this.closedNodes.(@temp.identifier).copyFrom(temp.node);
  
this.closedNodes.(@temp.identifier).pathIndex this.closedNodes.getDynamicVarNames().size() - 1;
  
  if (
this.closedNodes.(@temp.identifier).nodeX == temp.destinationX
   
&& this.closedNodes.(@temp.identifier).nodeY == temp.destinationY)
    return 
true;
}

function 
getNodeDistance(temp.nodeXtemp.nodeYtemp.destinationXtemp.destinationY)
{
  
temp.distance = ((temp.nodeX temp.destinationX)^+ (temp.nodeY temp.destinationY)^2)^0.5;
  return 
int(temp.distance);
}

function 
getLowestFScore()
{
  if (
this.lowestFScore != "")
    return 
this.lowestFScore;
  
  for (
temp.identifierthis.openNodes.getDynamicVarNames()) {
    
temp.node.copyFrom(this.openNodes.(@temp.identifier));
    
    if (
temp.lowestFScore == "" || temp.node.fScore this.openNodes.(@temp.lowestFScore).fScore)
      
temp.lowestFScore temp.identifier;
  }
  
  return 
temp.lowestFScore;
}

function 
followPath(temp.destinationNode)
{
  
temp.nodeList getPath(temp.destinationNode);
  
  for (
temp.identifiertemp.nodeList) {
    
temp.node.copyFrom(this.closedNodes.(@temp.identifier));
    
    
setCharAni("walk""");
    
move(temp.node.nodeX this.xtemp.node.nodeY this.yMOVEMENT_SPEED16);
    
sleep(MOVEMENT_SPEED);
  }
  
  
this.trigger("MovementFinished""");
}

function 
getPath(temp.destinationNode)
{
  
temp.nodeList = new[0];
  
temp.nodeList.insert(0temp.destinationNode);
  
temp.identifier temp.destinationNode;
  
  while (
temp.identifier != "") {
    
temp.node.copyFrom(this.closedNodes.(@temp.identifier));
    if (
temp.node.parentNode == "")
      break;
    
    if (
temp.node.parentNode != this."_" this.y)
      
temp.nodeList.insert(0temp.node.parentNode);
    
    
temp.identifier temp.node.parentNode;
  }
  
  return 
temp.nodeList;


Usage example:
PHP Code:
//#CLIENTSIDE
function onCreated()
{
  
this.join("aStar");  
  
showCharacter();
  
moveTo(488);
}

function 
onMovementFinished()
{
  
setCharAni("idle""");
  
this.movementCounter ++;
  
  
this.dir 2;
  
this.chat "Movement finished!";
  
sleep(1);
  
this.chat "";
  
  if (
this.movementCounter == 1)
    
moveTo(4529);
  else if (
this.movementCounter == 2)
    
moveTo(5053);
  else if (
this.movementCounter == 3)
    
moveTo(2547);
  else if (
this.movementCounter == 4)
    
moveTo(819);


Here's a video showing an example of usage. Note that in a lot of cases, it's better to split up the movement sequence in several shorter routes than one big, especially when your target position is close to the start from a birds eye point of view but still so far away.

The movement looks a bit choppy in the video, but it's not in-game.

__________________

"A delayed game is eventually good, but a rushed game is forever bad." - Shigeru Miyamoto
Reply With Quote