Issues with character movement angles/speeds

Started by Problem, Wed 09/04/2014 07:58:41

Previous topic - Next topic

Problem

I'm not quite sure if this is the right forum, but I have a few more advanced questions about character movement.

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

2. When I set UniformMovementSpeed to false, I can have different speed values for X and Y. But this doesn't seem to apply to diagonal movement? You can test this easily by trying to set a very low value (such as -5, which would translate to 1/5) for MovementSpeedY. Now you get a much faster y-speed by moving diagonally than by moving vertically, which just doesn't make any sense. Normally you wouldn't set the speed levels that low, so you won't always notice it, but it makes the movement inconsistent when using different speed settings for X and Y. Is this behaviour intended, or is it a bug?

So generally, while the speed settings work fine for low resolutions and simple animations, I recently got the feeling that character movement in AGS can get quite messy when you try to make it look more real. Does anyone know of any good workarounds? Have I missed something?

Billbis

#1
Regarding point 1, which is reported here, it will require somehow (source code, module or plugin) to recode the entire pathfinder to do to it properly.
However I have coded a unperfect module that mostly do the job. It is still in development and still not published in this forum, but I'm using it in my two games, and some French members are also using it. All explanation are present in the header, it simply contain an alternative walk function, and by default intercept clicks in eModeWalkTo.
Download link (~5ko)
Header:
Spoiler
Code: ags
// Header for module 'IsoWalk', version 1.0
//
// 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.
//
// 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).
//    
//     You can import a few variables to do some customizations. (Uncomment these following lines)

/*
import bool IsoW_moduleON;
import bool IsoW_UseAlternateHorizontalPath;
import bool IsoW_UseAlternateVerticalPath;
import int IsoW_MinDistance;
*/

//     IsoW_moduleON can be set to false to force IsoWalk to act as normal walk function.
//     (useful to set this module as optional).
//     IsoW_UseAlternateHorizontalPath and IsoW_UseAlternateVerticalPath will change waypoints priorities.
//     (see code comments for details).
//     IsoW_MinDistance 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:
//   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
#define IsoW_HACKWALK true //Override Mouse Click in mode WalkTo to use IsoWalk for the main character
//
// 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);
[close]
Script:
Spoiler
Code: ags
// IsoWalk module script
//Variables declaration & definition
bool IsoW_moduleON = true; //if turn OFF, IsoWalk act as normal Walk. To set the module as an option.

bool IsoW_UseAlternateHorizontalPath = false; //exchange waypoint priorities. False = vertical / horizontal first
bool IsoW_UseAlternateVerticalPath = false; //True = Diagonnale first

int IsoW_MinDistance = 20; //Minimum distance (in pixel) for the Module to be active.

// 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 ((IsoW_moduleON)&& ((DeltaX*DeltaX + DeltaY*DeltaY) > (IsoW_MinDistance*IsoW_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 (!IsoW_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 (!IsoW_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++;
    }
  }
}

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

//You may want to import these in (see Header)
export IsoW_moduleON, IsoW_UseAlternateHorizontalPath, IsoW_UseAlternateVerticalPath, IsoW_MinDistance;
[close]
Usage:
Code: ags
cEgo.IsoWalk(x, y, optional BlockingStyle, optional WalkWhere)


Problem

Thanks, this looks very interesting! I'll give it a try and report back if it works for me. :)

Billbis

For posterity reason (:grin:), I'll repeat here a few things we discussed in PM:

1)
To use my module with the Lightweight BASS Template v2.0 that come with AGS:
-Import the module and put the IsoWalk script upper the TwoClickHandler script in the project tree.
-Line 76 of IsoWalk header, replace:
Code: ags
#define IsoW_HACKWALK true 
by:
Code: ags
#define IsoW_HACKWALK false 

(alternatively, delete this line in the header and line 145-154 in the script body)
-Line 47 of TwoClickHandler script, replace:
Code: ags
ProcessClick(mouse.x, mouse.y, eModeWalkto);
by:
Code: ags
player.IsoWalk(mouse.x+GetViewportX(), mouse.y+GetViewportY(), eNoBlock, eWalkableAreas);

If you aren't doing to many fancy things in your game, it should do the job.

Alternatively, change nothing code-wise, put IsoWalk bellow TwoClickHandler and call a ClaimEvent after each ChangeRoom:
Code: ags
function hDoor_Interact()
{
    player.IsoWalk(935, 610, eBlock);
    player.ChangeRoom(2, 260, 590);
    ClaimEvent();
}


2) If I understand correctly, TheBitPriest has recoded a pathfinder for Heroine's Quest. Was it for addressing the same issue (avoid gliding effect during walks)? How he did it, module, plugin or directly in the AGS source code? Do the HQ team plan to share his work with the community? There solution might be less broken than mine. :-X

3)
I have still no simple idea for your original point 2, despite handling walking in another custom function.

Problem

Once again, thank you! ;-D

I've posted the second point in the bug tracker. I guess the movement speed calculations should better be fixed in AGS at some point in the future, and it's not that important. After playing around with the settings I got satisfactory results - not perfect, but good enough.

The fixed movement angles were more important, and thanks to your IsoWalk module the walking animations look much better than before.


Khris

I'm digging this up to show something I'm currently working on:

[embed=640,430]http://www.youtube.com/watch?v=UzadZlGZlic[/embed]

The basic idea is to add nodes to walkable areas where their edge bends inwards, because any path that itself bends will touch those. I'm currently doing this by hand; not sure this can be automated in an efficient way.
Then I'm adding nodes at the start and end of the walk.
The next step is to move outwards in all 8 directions from each node, creating a grid of intersections.
Dijkstra finds the shortest path, and finally I'm optimizing it by removing intermediate nodes and finding paths of equal length with less corners (this is deliberately slowed and shown in the video; it only takes a fraction of a second).

You might notice a glitch at around 1:10, the path contains a small "bulge". This is one of the kinks I'm still working out :)

Slasher

Hi Khris,

Lovely idea and would be much welcomed. I do hope you iron out the wrinkles and make it available sometime.

Cheers ;)





Problem

That's great, Khris! Looks like an interesting approach. I hope you can finish it, I'd love to give it a try. :)

SMF spam blocked by CleanTalk