MODULE: IsoWalk - trying to enforce only 8 direction movments.

Started by Billbis, Thu 22/05/2014 21:30:21

Previous topic - Next topic

Billbis

Here is another module of mine, poorly solving a rare issue but that do the job and that may be useful to some. I'm introducing you:

IsoWalk v1.1
(right click -> download, ~11ko)
For people using AGS for making high resolution games, and having 8 direction loops in there views.

Let's Problem introducing you the issue:
Quote from: Problem1. Usually AGS moves the characters freely in all directions at all possible angles. This can look bad, especially if you use diagonal walkcycles. If, in your animation, the character walks at 45°, but AGS decides to move the character at 55°, it looks really awkward, as if the character is sliding. I know it's a common problem in many adventures, but are there any ways around this?
More precisely, can you limit a character's movement to 8 fixed angles somehow through scripting? I'm not talking about keyboard controls, but point & click. It doesn't look too bad at low resolutions without diagonal walkcycles, but if you make a highres game with smooth animations and diagonal views, it looks plain wrong most of the time.

My module does it (well, mostly).


If the player is in A and walks to B, my module will tell him to go to B through P1. If P1 isn't in a Walkeable Area, it will try P2. If P2 isn't available either, it will give up and will call a normal Walk.
P1 and P2 priority order customizable (see bellow). Diagonals are fixed to 45° (pi/4 radiants).
Obviously, the effect renders better with 8 direction loops, but in can be interesting even with 4 direction loops character (give it a try or another :)).

Usage:
Code: ags
cEgo.IsoWalk(100, 100);
cEgo.IsoWalk(100, 100, eBlock, eAnywhere);

IsoWalkModule.ModuleON = false;
// deactivate the module. IsoWalk will act as normal Walk function. Useful to make this module optional.

IsoWalkModule.MinDistance = 100;
// Min distance in pixel necessary to activate the module, default is 20.

IsoWalkModule.UseAlternateHorizontalPath = true;
IsoWalkModule.UseAlternateVerticalPath = true;
// Waypoint order priority, true is diagonal move first. Default is false.


/! To use this module with the Lightweight BASS Template v2.0 that come with AGS, see this post. /!

By default, the module try to override mouse clicks in eModeWalkTo to call an IsoWalk. To deactivate that behavior, simply replace line 71 in the header :
Code: ags
#define IsoW_HACKWALK true

by :
Code: ags
#define IsoW_HACKWALK false

or delete lines 173 - 183 in the script:
Code: ags
// intercept left clicks in eModeWalkto to order a IsoWalk
function on_mouse_click(MouseButton button) {  
  if (IsoW_HACKWALK) {
    if (button == eMouseLeft) {
      if (Mouse.Mode == eModeWalkto) {
          player.IsoWalk(mouse.x + GetViewportX(), mouse.y + GetViewportY(), eNoBlock, eWalkableAreas);
          ClaimEvent();
      }
    }
  }
}


Caveats: If you are doing some cEgo.Moving checks, be sure to perform them bellow the IsoWalk script (either in a script bellow in the project tree, or in a room script), otherwise it may become false at the middle of the walk.
Due to a bug in the old AGS pathfinder that is called on straight line movement, a few "+3" are lurking around in the script. Should the bug be corrected, those "+3" will be removed.

Header:
Code: ags
// Header for module 'IsoWalk', version 1.1
//
// Author: Billbis & Pidem
//
// Abstract:
//
//   Enforce walking on the 8 standard directions when possible to avoid gliding effects.
//   Contain an alternative walk function (IsoWalk(int x, int y, optional BlockingStyle, optional WalkWhere))
//   Usage: cEgo.IsoWalk(100,100);
//   By default, hacks mouse click in WalkTo mode to perform a player.IsoWalk().
//   
// Dependencies:
//
//   AGS version required:  build for AGS 3.2.1 and 3.3.0, not sure if it works with older versions.
//
//   AGS setting required: No particular configuration is needed. Not very useful if you do not have
//   8 directions in your walking views or if your game is in Low Resolution.
//
// Configuration:
//
//   Optional:
//     
//     IsoW_HACKWALK is define as true.
//     Define it as false to use normal mouse comportment for eModeWalkTo (or delete the corresponding section in IsoWalk Script).
//     
//     IsoWalkModule.moduleON (bool) can be set to false to force IsoWalk to act as normal walk function.
//     (useful to set this module as optional).
//     IsoWalkModule.UseAlternateHorizontalPath and IsoWalkModule.UseAlternateVerticalPath (bool) will change waypoints priorities.
//     (see code comments for details).
//     IsoWalkModule.MinDistance (int) is the minimum distance (in pixel) to activate the module.
//
// Caveats:
//
//   This module works better on large free WalkeableAreas than on small busy ones.
//   If no alternative way is found, characters will use normal walk comportment.
//
// Revision history:
//   verion 1.1: 2014/05/22
//      Optional variables are now encaplusated in a struct.
//   version 1.0: 2014/03/04
//      Name change from Use 8 Direction to IsoWalk. Addition of a few '+3' to avoid a strange comportment of
//      AGS pathfinder.
//      The module now support multi-character non blocking IsoWalks !
//   version beta 0.4: 2013/06/27
//      Bug correction: WA detection now take in account viewport.
//   version beta 0.3: 2013/06/12
//      Addition of Minimal Distance setting.
//   version beta 0.2: 2013/03/08
//      Code cleaning + more customizations possibles.
//   version beta 0.1: 2013/03/03
//      First release.
//  
// License:
//
//   IsoWalk is publish under the terms of the 
//   Do What The Fuck You Want To Public License, Version 2
// 
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.
//
// Thanks:
//
//   Kitai, valoulef


// Defines
//Override Mouse Click in mode WalkTo to use IsoWalk for the main character
#define IsoW_HACKWALK true // $AUTOCOMPLETEIGNORE$

//Struct definition
struct IsoWalkModule {
    
    import static attribute bool ModuleON;
    import static bool get_ModuleON(); // $AUTOCOMPLETEIGNORE$
    import static void set_ModuleON(bool input); // $AUTOCOMPLETEIGNORE$
    
    import static attribute bool UseAlternateHorizontalPath;
    import static bool get_UseAlternateHorizontalPath(); // $AUTOCOMPLETEIGNORE$
    import static void set_UseAlternateHorizontalPath(bool input); // $AUTOCOMPLETEIGNORE$    
    
    import static attribute bool UseAlternateVerticalPath;
    import static bool get_UseAlternateVerticalPath(); // $AUTOCOMPLETEIGNORE$
    import static void set_UseAlternateVerticalPath(bool input); // $AUTOCOMPLETEIGNORE$    
    
    import static attribute int MinDistance;
    import static int get_MinDistance(); // $AUTOCOMPLETEIGNORE$
    import static void set_MinDistance(int input); // $AUTOCOMPLETEIGNORE$
    
};

// Function imports

/// Walk to point (x,y) using only the 8 directions if possible.
import void IsoWalk(this Character*, int x, int y, BlockingStyle = eNoBlock, WalkWhere = eWalkableAreas);

Script:
Code: ags
// IsoWalk module script

//Variables declaration & definition
bool ModuleON = true; //if turn OFF, IsoWalk act as normal Walk. To set the module as an option.
bool UseAlternateHorizontalPath = false; //exchange waypoint priorities. False = vertical / horizontal first
bool UseAlternateVerticalPath = false; //True = diagonal first
int MinDistance = 20; //Minimum distance (in pixel) for the Module to be active.

// struct static definitions
static bool IsoWalkModule::get_ModuleON() {
    return ModuleON;
}
static void IsoWalkModule::set_ModuleON(bool input) {
    ModuleON = input;
}

static bool IsoWalkModule::get_UseAlternateHorizontalPath() {
    return UseAlternateHorizontalPath;
}
static void IsoWalkModule::set_UseAlternateHorizontalPath(bool input) {
    UseAlternateHorizontalPath = input;
}

static bool IsoWalkModule::get_UseAlternateVerticalPath() {
    return UseAlternateVerticalPath;
}
static void IsoWalkModule::set_UseAlternateVerticalPath(bool input) {
    UseAlternateVerticalPath = input;
}

static int IsoWalkModule::get_MinDistance() {
    return MinDistance;
}
static void IsoWalkModule::set_MinDistance(int input) {
    if (input >= 0 && input <= System.ViewportWidth) {
        MinDistance = input;
    }
}

// Stuff for non blocking walk
bool IsoW_noBlock = false; // optimisation of the repeatedly_execute() checks.
int IsoW_x[];
int IsoW_y[];
bool IsoW_eWalkableAreas[];
bool IsoW_character[]; 

function game_start () {
  IsoW_x = new int[Game.CharacterCount];
  IsoW_y = new int[Game.CharacterCount];
  IsoW_eWalkableAreas = new bool[Game.CharacterCount];
  IsoW_character = new bool[Game.CharacterCount];
}

//Functions definition

// Return absolute value
function IsoW_Abs(int x) { 
  if (x >= 0) return x;
  else return -x;
}

// Main module function
void IsoWalk(this Character*, int x, int y, BlockingStyle BStyle, WalkWhere WWhere) { // Alternative walk function. Core of the module.
  int ID = this.ID;
  int DeltaX = IsoW_Abs(x - this.x); //Distances between character and destination
  int DeltaY = IsoW_Abs(y - this.y);
  if ((ModuleON)&& ((DeltaX*DeltaX + DeltaY*DeltaY) > (MinDistance*MinDistance))) { //If Distance > MinDistance (Pythagore)
    int Signe;
    int xP1, xP2, xWP, yP1, yP2, yWP;
    //Calculating Waypoint Coordinates
    if (DeltaX >= DeltaY) { //Diagonal + Horizontal movement needed
      if (this.x - x >= 0) {
        Signe = 1;
      } else {
        Signe = -1;
      }
      if (!UseAlternateHorizontalPath) {
        xP1= x + DeltaY*Signe;  //Waypoint coordiantes
        yP1= this.y;
        xP2= this.x + DeltaY*(-Signe); //Alternative waypoint coordinate
        yP2= y;
      } else {// if IsoW_UseAlternateHorizontalPath P1 <-> P2
        xP1= this.x + DeltaY*(-Signe);  //Waypoint coordiantes
        yP1= y;
        xP2= x + DeltaY*Signe;//Alternative waypoint coordinate
        yP2= this.y;
      }
    } else { //DeltaX < DeltaY Diagonal + Vertical movement needed
      if (this.y - y >= 0) {
        Signe = 1;
      } else {
        Signe = -1;
      }
      if (!UseAlternateVerticalPath) {
        xP1= this.x;  //Waypoint coordiantes
        yP1= y + DeltaX*Signe;
        xP2= x; //Alternative waypoint coordinate
        yP2= this.y + DeltaX*(-Signe);
      } else {// if IsoW_UseAlternateVerticalPath P1 <-> P2
        xP1= x;  //Waypoint coordiantes
        yP1= this.y + DeltaX*(-Signe);
        xP2= this.x; //Alternative waypoint coordinate
        yP2= y + DeltaX*Signe;
      }
    }
    //Walking
    if ( WWhere == eWalkableAreas) {
      if (GetWalkableAreaAt(xP1-GetViewportX(), yP1-GetViewportY()) !=0) { //if P1 is in a WA, we use it
        xWP = xP1 + 3; // +3 are small hacks to force AGS to use Djikstra
        yWP = yP1 + 3;
      } else if (GetWalkableAreaAt(xP2-GetViewportX(), yP2-GetViewportY()) !=0) { //if P2 is in a WA but not P1, we use P2
        xWP = xP2 + 3;
        yWP = yP2 + 3;
      } else { //is not P1 neither P2 are in a WA, then we give up
        xWP = x;
        yWP = y;
      }
      if (BStyle == eBlock) {
        this.Walk(xWP, yWP, eBlock, eWalkableAreas);
        this.Walk(x, y, eBlock, eWalkableAreas);
      } else { //BStyle == eNoBlock
        this.Walk(xWP, yWP, eNoBlock, eWalkableAreas);
        IsoW_x[ID] = x;
        IsoW_y[ID] = y;
        IsoW_character[ID] = true;
        IsoW_eWalkableAreas[ID] = true;
        IsoW_noBlock = true; // 2nd part of the movement in Repeatidly_execute
      }
    } else { // WWhere == eAnywhere
      if (BStyle == eBlock) {
        this.Walk(xP1, yP1, eBlock, eAnywhere);
        this.Walk(x, y, eBlock, eAnywhere);
      } else { //BStyle == eNoBlock
        this.Walk(xP1, yP1, eNoBlock, eAnywhere);
        IsoW_x[ID] = x;
        IsoW_y[ID] = y;        
        IsoW_character[ID] = true;
        IsoW_eWalkableAreas[ID] = false;
        IsoW_noBlock = true; // 2nd part of the movement in Repeatidly_execute
      }
    }
  } else { //if module is OFF, or if too short distance
    this.Walk(x, y, BStyle, WWhere);
  }
}

//Non blocking movement
function repeatedly_execute() {
  if (IsoW_noBlock) {
    int i = 0;
    while (i < Game.CharacterCount) {
      if (IsoW_character[i] == true && character[i].Moving == false) {
        if (IsoW_eWalkableAreas[i]) {
          character[i].Walk(IsoW_x[i], IsoW_y[i], eNoBlock, eWalkableAreas);
        } else {
          character[i].Walk(IsoW_x[i], IsoW_y[i], eNoBlock, eAnywhere);
        }
        IsoW_character[i] = false;
      }
      i++;
    }
    IsoW_noBlock = false;
    i = 0;
    while (i < Game.CharacterCount) {
      if (IsoW_character[i]) {
        IsoW_noBlock = true;
      }
      i++;
    }
  }
}

// intercept left clicks in eModeWalkto to order a IsoWalk
function on_mouse_click(MouseButton button) {  
  if (IsoW_HACKWALK) {
    if (button == eMouseLeft) {
      if (Mouse.Mode == eModeWalkto) {
          player.IsoWalk(mouse.x + GetViewportX(), mouse.y + GetViewportY(), eNoBlock, eWalkableAreas);
          ClaimEvent();
      }
    }
  }
}

SMF spam blocked by CleanTalk