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
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
 


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 10:58 PM.


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