Graal Forums  

Go Back   Graal Forums > Development Forums > NPC Scripting > Code Gallery
FAQ Members List Calendar Today's Posts

 
 
Thread Tools Search this Thread Display Modes
Prev Previous Post   Next Post Next
  #1  
Old 07-29-2011, 12:51 PM
xXziroXx xXziroXx is offline
Malorian
xXziroXx's Avatar
Join Date: May 2004
Posts: 5,289
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
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.destinationX, temp.destinationY)
{
  if (
onwall2(temp.destinationX + X_OFFSET, temp.destinationY + Y_OFFSET, WIDTH_IN_TILES, HEIGHT_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.destinationX, temp.destinationY);
}

function 
processNodes(temp.destinationX, temp.destinationY)
{  
  
temp.lowestFScore = getLowestFScore();
  
temp.node.copyFrom(this.openNodes.(@temp.lowestFScore));
  
  
temp.pathFound = closeNode(temp.lowestFScore, temp.destinationX, temp.destinationY);
  
checkSurroundingNodes(temp.node.nodeX, temp.node.nodeY, temp.lowestFScore, temp.destinationX, temp.destinationY);
  
  if (!
temp.pathFound)
    
processNodes(temp.destinationX, temp.destinationY);
  else
    
followPath(temp.lowestFScore);
}

function 
checkSurroundingNodes(temp.startX, temp.startY, temp.parentNode, temp.destinationX, temp.destinationY)
{
  
temp.surroundingNodes = {
    { 
temp.startX - 1, temp.startY - 1 }, // top left
    
{ temp.startX, temp.startY - 1 },     // top middle
    
{ temp.startX + 1, temp.startY - 1 }, // top right
    
{ temp.startX + 1, temp.startY },     // middle right
    
{ temp.startX + 1, temp.startY + 1 }, // bottom right
    
{ temp.startX, temp.startY + 1 },     // bottom middle
    
{ temp.startX - 1, temp.startY + 1 }, // bottom left
    
{ temp.startX - 1, temp.startY },     // middle left
  
};
  
  
temp.counter = 0;
  for (
temp.node: temp.surroundingNodes) {
    
temp.nodeX = temp.node[0];
    
temp.nodeY = temp.node[1];
    
temp.identifier = temp.nodeX @ "_" @ temp.nodeY;
    
    
temp.isDiagonal = (temp.counter%2 == 0 ? true : false);
    
temp.nodeOnWall = onwall2(temp.nodeX + X_OFFSET, temp.nodeY + Y_OFFSET, WIDTH_IN_TILES, HEIGHT_IN_TILES);
    
    
temp.gScore = (temp.isDiagonal ? DIAGONAL_GSCORE : NORMAL_GSCORE);
    
temp.hScore = getNodeDistance(temp.nodeX, temp.nodeY, temp.destinationX, temp.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.identifier, temp.destinationX, temp.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.nodeX, temp.nodeY, temp.destinationX, temp.destinationY)
{
  
temp.distance = ((temp.nodeX - temp.destinationX)^2 + (temp.nodeY - temp.destinationY)^2)^0.5;
  return 
int(temp.distance);
}

function 
getLowestFScore()
{
  if (
this.lowestFScore != "")
    return 
this.lowestFScore;
  
  for (
temp.identifier: this.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.identifier: temp.nodeList) {
    
temp.node.copyFrom(this.closedNodes.(@temp.identifier));
    
    
setCharAni("walk", "");
    
move(temp.node.nodeX - this.x, temp.node.nodeY - this.y, MOVEMENT_SPEED, 16);
    
sleep(MOVEMENT_SPEED);
  }
  
  
this.trigger("MovementFinished", "");
}

function 
getPath(temp.destinationNode)
{
  
temp.nodeList = new[0];
  
temp.nodeList.insert(0, temp.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.x @ "_" @ this.y)
      
temp.nodeList.insert(0, temp.node.parentNode);
    
    
temp.identifier = temp.node.parentNode;
  }
  
  return 
temp.nodeList;
} 

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

function 
onMovementFinished()
{
  
setCharAni("idle", "");
  
this.movementCounter ++;
  
  
this.dir = 2;
  
this.chat = "Movement finished!";
  
sleep(1);
  
this.chat = "";
  
  if (
this.movementCounter == 1)
    
moveTo(45, 29);
  else if (
this.movementCounter == 2)
    
moveTo(50, 53);
  else if (
this.movementCounter == 3)
    
moveTo(25, 47);
  else if (
this.movementCounter == 4)
    
moveTo(8, 19);
} 

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.

__________________
Follow my work on social media post-Graal:Updated august 2025.
Reply With Quote
 


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +2. The time now is 11:50 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2026, vBulletin Solutions Inc.
Copyright (C) 1998-2019 Toonslab All Rights Reserved.