PDA

View Full Version : Crescent Dodge



Fire Wizzrobe
07-16-2008, 12:51 AM
I have a challenge? for you. (Well, it would be a challenge for me)

I've never seen this kind of movement in any of the 2d zelda-like games I've played. The Crescent Dodge.

Link moves in a forward 180 degree arc via r button in the space of a 3x3 tile grid in front of him, with solidity detection of course.
http://two.xthost.info/Fizzi/Crescent%20Dodge.png
facing right

http://two.xthost.info/Fizzi/CrescentDodgeup.png
facing up

He can arc in either side of the circle, or both with extra controls, scripter's choice. Extra internets if you get him to move over a 3x4 tile grid. It's basically like an alternate roll script.

Anyone up for this?

C-Dawg
07-16-2008, 04:24 PM
What

So like if these are tiles on screen

A-B-C-D-E
F-G-H-I-J
K-L-M-N-O

And link starts at F, facing East, you want him to loop quickly up through A, B, C, then down through H, M, N. and end up at I facing North?

Fire Wizzrobe
07-16-2008, 04:51 PM
He would start at F facing east, and go through A-B-C and end up at H facing east; OR
he would start at F facing east, and go through K-L-M and end up at H facing east

But the movement you described would look cool too. :)

Fire Wizzrobe
07-26-2008, 04:49 PM
Progress from anyone? Do I have to be more specific in what I'm looking for?

Elmensajero
07-28-2008, 10:50 PM
Hmmm, I might try this. I am pretty new at scripting, but I have a pretty good background in C, and it seems like an interesting challenge. A few questions...

1) Do you want all enemies on the screen to stop moving while Link is doing this move?
2) Do you want this to be tied in with an obtainable item selected on the subscreen, to be used as an earned skill, or to be present from the beginning of the game?
3) If Link moves in a perfect circle around an enemy (or unwalkable object), he will most likely end up having the corner of his combo moving on top of the corner of the enemy's or object's combo. Does this matter, or would you prefer he move in more of an ellipse or even box pattern?
4) With solidity detection, do you want him not able to do the move if there is an object in his path or an object anywhere in the 3x3 (or bigger) box?
5) Do you want Link's graphics to change while he is doing the move?
6) How many pixels/frame do you want Link to move (e.g. 2 pixels each frame, or 1 pixel every 3 frames, etc.)?

If I do end up trying to implement this, I will prob. have it check to see if he can do the move in one of the arcs, and if an object is blocking him, let it check the other arc to see if that one is possible. I will also prob. have a parameter to control how long Link has to wait before he can do the move again. (This will also prevent the user from using the move again in the middle of doing the move.)

Fire Wizzrobe
07-29-2008, 11:45 AM
1) No.
2) It should be an earned skill.
3) He can only move in a half circle. That's why in the diagram the circle is split. You can make him only in one half of the circle or both with extra controls.
4) I want him to stop at the object in his path.
5) It would be nice if you made him spin while doing it.
6) 2 pixels per frame

Thanks for your interest in this. :)

Elmensajero
08-02-2008, 11:46 PM
Okay, I finished the script finally. It is implemented so that when a player holds the "R" button down and then presses a directional button perpendicular to where he is looking it does the crescent dodge (for example, if he is looking up, the user presses left or right to select which arc he wants to follow). The global values at the top of the script are all you will need to change to have the script work as you want. First, change "canusecd" to false in the actual quest, and have another script change it to true when a player learns the move. Link can move in either a circle or an ellipse. Keep the diameters the same for a circle, or different for an ellipse. The default values below is for the 3x3 circle you asked about above (Link only moves across 2 tiles, which is 32 pixels). All the other global values are explained in the script. If you come across any bugs, just let me know (hopefully there should be none left, I tried everything I could think of that might cause problems).


import "std.zh"

//Global vars/constants
bool canusecd=true;
const int CD_DIAMETER_A=32; //diameter of ellipse in pixels (parallel to Link's Direction)
const int CD_DIAMETER_B=32; //diameter of ellipse in pixels (perpendicular to Link's Direction)
const int CD_LINKTOP=8; //These are for collision detection. Default values (8,0,15,15) mimics the collision detection that the games uses.
const int CD_LINKLEFT=0;
const int CD_LINKRIGHT=15;
const int CD_LINKBOTTOM=15;
const float CD_SPEED=2.0; //how many pixels Link moves per frame. values above 7 may break collision detection
const int CD_SPIN=1; //0=no spin, 1=normal spin, 2=backwards spin,3=fast spin
const int CD_WAIT=5; //number of frames user must wait between each use of CD
const int SFX_CRESDODGE=54; //default is the sound of the spin attack

//Used in arclength function.
float alfx(float x,float ra,float rb)
{
return Sqrt(ra*ra*Sin(x)*Sin(x)+rb*rb*Cos(x)*Cos(x));
}

//Takes the two radii of an ellipse (keep both vals the same for a circle) and approximates the arclength of half of it
float arclength(float radius_a,float radius_b)
{
int i;
float step=PI/10;
float arclen=0;
arclen=alfx(0,radius_a,radius_b)+alfx(PI,radius_a, radius_b);
for(i=1;i<10;i++)
{
if(i%2==1) arclen+=4*alfx(step*i,radius_a,radius_b);
else arclen+=2*alfx(step*i,radius_a,radius_b);
}
arclen*=(step/3);
return arclen;

// DETAILS (sort of) of algorithm used
// It uses the definition of arclength, which is the integral of the square root of dx/dt squared plus dy/dt squared, with
// respect to t, and it goes from c to d, where c <= t <= d. In this case, x(t)=a*cos(t) and y(t)=b*sin(t), where
// a and b are set by CD_DIAMETER_A and CD_DIAMETER_B. c and d is 0 and PI, which has t go only half the circle/ellipse. The
// integral itself is estimated using Simpson's Rule, which you can look up using pretty much any calculus book.

}

//Checks all four corners of Link's "collision box" (based on global values above) to see if any are in a solid object
bool CD_collision(int newx,int newy)
{
if(isSolid(newx+CD_LINKLEFT,newy+CD_LINKTOP)) return true;
if(isSolid(newx+CD_LINKRIGHT,newy+CD_LINKTOP)) return true;
if(isSolid(newx+CD_LINKLEFT,newy+CD_LINKBOTTOM)) return true;
if(isSolid(newx+CD_LINKRIGHT,newy+CD_LINKBOTTOM)) return true;
return false;
}

//Used in crescentdodge() to spin Link while doing the move
int getnextdir(int currentdir,float angle)
{
int ret;

if(CD_SPIN==0) return currentdir;
else if(CD_SPIN==2) angle*=-1;

if(angle>0) //spin clockwise
{
if(currentdir==DIR_UP) ret=DIR_RIGHT;
if(currentdir==DIR_LEFT) ret=DIR_UP;
if(currentdir==DIR_DOWN) ret=DIR_LEFT;
if(currentdir==DIR_RIGHT) ret=DIR_DOWN;
}
if(angle<0) //spin counter-clockwise
{
if(currentdir==DIR_UP) ret=DIR_LEFT;
if(currentdir==DIR_LEFT) ret=DIR_DOWN;
if(currentdir==DIR_DOWN) ret=DIR_RIGHT;
if(currentdir==DIR_RIGHT) ret=DIR_UP;
}

return ret;
}

//This function is pretty self-explanatory :)
void ignorelink()
{
Link->InputUp=false;
Link->InputDown=false;
Link->InputLeft=false;
Link->InputRight=false;
Link->InputL=false;
Link->InputR=false;
Link->InputA=false;
Link->InputB=false;
}

//This boolean, by Saffith (although slightly edited), checks for the solidity of combos, and is used for walkability detection
bool isSolid(int x, int y)
{
if(x<0 || x>255 || y<0 || y>175) return true; //edited to fix bug when on side of screen and using CD.
int mask=1111b;
if(x%16<8) mask&=0011b;
else mask&=1100b;
if(y%16<8) mask&=0101b;
else mask&=1010b;
if((Screen->ComboS[ComboAt(x, y)]&mask)==0) return false;
else return true;
}

//Rounds float to nearest integer
int Round(float val)
{
if(val-Floor(val) >= 0.5) return Ceiling(val);
else return Floor(val);
}

//Allows Link to travel in semicircle when using "R" plus a directional key prependicular to where he is facing
global script crescentdodge
{
void run()
{
//Script variables
float totalpixels=arclength(CD_DIAMETER_A/2,CD_DIAMETER_B/2); //Approximate number of pixels in semicircle (as if it were drawn)
float stepangle=(180*CD_SPEED)/totalpixels; //Calculates the number of degrees Link will move each frame
float currentangle;
float startangle;
float endanglemin;
float endanglemax;
float radx;
float rady;
float newangle;
float speed;
int center_x;
int center_y;
int primarydir;
int seconddir;
int cd_counter=0;
int nextx;
int nexty;
int nextdir;
int currentspin=0;
bool done=false;

while(true)
{
if(canusecd && Link->InputR && cd_counter==0)
{
center_x=0;
center_y=0;
primarydir=Link->Dir;

//Find center of circle/ellipse, set starting angle, and read which half of semicircle user wants to travel on
if(primarydir==DIR_UP || primarydir==DIR_DOWN)
{
if(primarydir==DIR_UP)
{
startangle=270;
center_y-=CD_DIAMETER_A/2;
}
else
{
startangle=90;
center_y+=CD_DIAMETER_A/2;
}
if((Link->InputUp || Link->InputDown) && !Link->InputLeft && !Link->InputRight) done=true; //fixes bug
while(!Link->InputLeft && !Link->InputRight && Link->InputR && !done) Waitframe(); //Read inputs until user selects which side to cd on
if(Link->InputLeft) seconddir=DIR_LEFT;
else if(Link->InputRight) seconddir=DIR_RIGHT;
else done=true; //R was let go
}

if(primarydir==DIR_LEFT || primarydir==DIR_RIGHT)
{
if(primarydir==DIR_LEFT)
{
startangle=180;
center_x-=CD_DIAMETER_A/2;
}
else
{
startangle=0;
center_x+=CD_DIAMETER_A/2;
}
if((Link->InputLeft || Link->InputRight) && !Link->InputUp && !Link->InputDown) done=true; //fixes bug
while(!Link->InputUp && !Link->InputDown && Link->InputR) Waitframe();
if(Link->InputUp) seconddir=DIR_UP;
else if(Link->InputDown) seconddir=DIR_DOWN;
else done=true;
}

//Read in Link's current position and fix Link's direction
center_x+=Link->X;
center_y+=Link->Y;
if(Link->Dir!=primarydir) done=true;

//Set initial current angle,the min/max endangles, and what direction Link will be moving around circle (clockwise/counterclockwise)
currentangle=startangle;
endanglemin=((startangle+180)-stepangle/2)%360;
endanglemax=(endanglemin+stepangle)%360;
if(primarydir==DIR_UP && seconddir==DIR_RIGHT) stepangle*=-1;
else if(primarydir==DIR_DOWN && seconddir==DIR_LEFT) stepangle*=-1;
else if(primarydir==DIR_LEFT && seconddir==DIR_UP) stepangle*=-1;
else if(primarydir==DIR_RIGHT && seconddir==DIR_DOWN) stepangle*=-1;

//Get the radii of the ellipse
if(primarydir==DIR_UP || primarydir==DIR_DOWN)
{
radx=-1*CD_DIAMETER_B/2;
rady=-1*CD_DIAMETER_A/2;
}
else
{
radx=-1*CD_DIAMETER_A/2;
rady=-1*CD_DIAMETER_B/2;
}

//If the move has not been cancelled yet, play the sound effect and set the counter
if(!done)
{
Game->PlaySound(SFX_CRESDODGE);
cd_counter=CD_WAIT;
}

//Move Link around the circle until he has moved 180 degrees
while(!done)
{
ignorelink();
currentangle+=stepangle;
if(currentangle<0) currentangle+=360;
else if(currentangle>360) currentangle-=360;
nextx=Round(radx*Cos(currentangle)+center_x);
nexty=Round(rady*Sin(currentangle)+center_y);

//Collision detection
if(CD_collision(nextx,nexty))
{
done=true; //Link stops moving
speed=Floor(CD_SPEED);
while(CD_collision(nextx,nexty)) //Moves Link as close to the solid object as possible.
{
newangle=currentangle-stepangle+(180*speed)/totalpixels;
if(newangle<0) newangle+=360;
else if(newangle>360) newangle-=360;
if(speed>0)
{
nextx=Round(radx*Cos(newangle)+center_x);
nexty=Round(rady*Sin(newangle)+center_y);
}
else
{
nextx=Link->X;
nexty=Link->Y;
}
speed--;
}
Link->Dir=primarydir;
}

if(nexty<0) nexty=0; //fixes bug when using CD at top of screen
if(nextx<0) nextx=0;
Link->X=nextx;
Link->Y=nexty;

//Make Link spin while doing the move
if(Abs(currentangle-startangle)>=135 && currentspin<3)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=3;
}
else if(Abs(currentangle-startangle)>=90 && currentspin<2)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=2;
}
else if(Abs(currentangle-startangle)>=45 && currentspin<1)
{
Link->Dir=getnextdir(Link->Dir,stepangle);
if(CD_SPIN!=3) currentspin=1;
}

//Stop Link when he has traveled 180 degrees
if(currentangle<=endanglemax && currentangle>=endanglemin)
{
done=true;
Link->Dir=primarydir;
}
else if(endanglemin>endanglemax && (currentangle<=endanglemax || currentangle>=endanglemin))
{
done=true;
Link->Dir=primarydir;
}

Waitframe();
}

done=false;
stepangle=Abs(stepangle);
currentspin=0;
}

if(cd_counter>0) cd_counter--;
if(Link->InputR) ignorelink(); //fixes a rather annoying, random bug
Waitframe();
}
}
// DETAILS of algorithm
// The way this is implemented is the scripter chooses initial values of the diameters of the ellipse (basically how many tiles Link does move in), which is used
// to create an ellipse. Using the arclength formula, it estimates the amount of pixels in half of the ellipse, and then uses a variable also set by the scripter to
// determine how many degrees at a time Link should move each frame, until Link has traveled the full 180 degrees. Finally, it determines how far from the center of
// the ellipse the next point for Link to go to next is in each frame. Collision detection prevents Link from moving through solid objects and off the side of the
// screen. Finally, it prevents the user from doing the move again for the number of frames again set by the scripter.
}

Fire Wizzrobe
08-05-2008, 03:28 PM
Thanks A LOT. Unfortunately I won't be able to try this out right now, maybe in a few days or so.

ShadowTiger
08-06-2008, 07:34 PM
It actually does work. It works quite beautifully, in fact. I just wonder if he should be facing the direction he's going, or the direction he just came from though when it ends.

Fire Wizzrobe
08-12-2008, 09:56 PM
Yes, it is excellent. A little weird in the edge detection- I'll have to do more testing on that to give you the specifics. This move would be more useful moving ahead 2 spaces instead of one, or being able to charge it up to move further. I'll try and see if I'm able to modify the script.

Elmensajero
08-13-2008, 12:53 PM
It actually does work. It works quite beautifully, in fact. I just wonder if he should be facing the direction he's going, or the direction he just came from though when it ends.

Thank you. :D The way Fire Wizzrobe specified it above, Link should face the same direction he is going. If you want me too add a couple more spins for you, just let me know. Tell me how many degrees you want him to spin (ie. 180, 540, etc.)


Yes, it is excellent. A little weird in the edge detection- I'll have to do more testing on that to give you the specifics. This move would be more useful moving ahead 2 spaces instead of one, or being able to charge it up to move further. I'll try and see if I'm able to modify the script.

Yes, the collision detection is going to be weird since, as Gleeok mentioned on purezc (I think that was who it was), is checked as a box, while the move is done as a circle, so the corners of the box will cause it to be strange. I might try messing around with it to see if I can make the collision detection work as a circle, I'll have to think of an efficient algorithm to do it. I might not be able to get this to work, though.

As far as moving more spaces, just play around with the CD_DIAMETER A/B parameters. For example, try a value of 48 in one at a time to see how it affects it. Charging it up should be pretty easy to implement, do you want it to be the longer the "R" button is held down, the farther he does the move?