⌂ YolkfolkThe Dizzy Fansite
Community discussion

AI for Circular Motion

Started by Noggin the Nog on 25 April 2013 • 9,569 views

14 posts
#55261

EDIT - THE CODE IN THE FIRST TWO POSTS DOESN'T DISPLAY PROPERLY. THE CORRECT CODE IS IN POST #4

Hi, I wanted to share with you all a little thing I've been working on. This is a bit of AI for moving an object in a circle.

It works much like AIUpdateTrain. You use one waypoint, which goes at the centre of the circle. O_WAYPOINTSPEED is the same as usual, but I've used O_WAYPOINTFLIP to set the direction of motion (0 = clockwise, 1 = anti) - try setting an Action Object to switch it!

I've also added another variable O_WAYPOINTRADIUS, which you'll have to add a #def for. It does what it says on the tin. You'll probably want to set it to the same as the seperation between the starting positions on the map, but the AI will correct it anyway. Again, try changing it with an Action object.

func AIUpdateCircle( idx )
{
	if(idx==-1) return;
	if(ObjGet(idx,O_DISABLE)) return;
	if(!IsUpdate(ObjGet(idx,O_DELAY))) return;
	if(ObjGet(idx,O_STATUS)!=1) return;
	id2 = ObjGet(idx,O_TARGET);
	if(id2==0) return;
	idx2 = ObjFind(id2);
	if(idx2==-1) return;

	speed = ObjGet(idx2,O_WAYPOINTSPEED);
	clock = ObjGet(idx2,O_WAYPOINTFLIP);

	radius = ObjGet(idx2,O_WAYPOINTRADIUS);
	rad = (float)radius;
	x2 = ObjGet(idx2,O_ X) ;
	y2 = ObjGet(idx2,O_Y);
	
	x = ObjGet(idx,O_ X) ;
	y = ObjGet(idx,O_Y);
	
	count = 0;
	label:
	if(speed - count==0)
	{
		ObjSet(idx,O_X,x);
		ObjSet(idx,O_Y,y);
		return;
	}
	else
	{
	if(clock==0){	if(y>y2){ xt = x - 1;}
			if(y<=y2){ xt = x + 1;}
			if(x>=x2){ yt = y + 1;}
			if(x<x2){ yt = y - 1;} }
	if(clock==1){	if(y>=y2){ xt = x + 1;}
			if(y<y2){ xt = x - 1;}
			if(x>x2){ yt = y - 1;}
			if(x<=x2){ yt = y + 1;} }
	
	dis1 = (float)(((xt - x2)*(xt - x2)) + ((y - y2)*(y -y2)));
	rad1 = gs_sqrt(dis1);
	dis2 = (float)(((x - x2)*(x - x2)) + ((yt - y2)*(yt -y2)));
	rad2 = gs_sqrt(dis2);
	
	if(gs_abs(rad1 - rad) < gs_abs(rad2 - rad))
	{x = xt;}
	if(gs_abs(rad1 - rad) > gs_abs(rad2 - rad))
	{y = yt;}
	count++;
	goto label;
	}
}	

So, please give it a go and let me know what you think. :smile:

👍 0
#79895

I've made a small improvement. :smile: The function still works the same way.

func AIUpdateCircle2( idx )
{
	if(idx==-1) return;
	if(ObjGet(idx,O_DISABLE)) return;
	if(!IsUpdate(ObjGet(idx,O_DELAY))) return;
	if(ObjGet(idx,O_STATUS)!=1) return;
	id2 = ObjGet(idx,O_TARGET);
	if(id2==0) return;
	idx2 = ObjFind(id2);
	if(idx2==-1) return;

	speed = ObjGet(idx2,O_WAYPOINTSPEED);
	clock = ObjGet(idx2,O_WAYPOINTFLIP);
	radius = ObjGet(idx2,O_WAYPOINTRADIUS);
	rad = (float)radius;
	x2 = ObjGet(idx2,O_ X) ;
	y2 = ObjGet(idx2,O_Y);
	
	x = ObjGet(idx,O_ X) ;
	y = ObjGet(idx,O_Y);
	
	count = 0; xcount = 0; ycount = 0; extra = 0;
	label:
	if(speed + extra - count==0)
	{
		ObjSet(idx,O_X,x);
		ObjSet(idx,O_Y,y);
		return;
	}
	else
	{
	if(clock==0){	if(y>y2){ xt = x - 1;}
			if(y<=y2){ xt = x + 1;}
			if(x>=x2){ yt = y + 1;}
			if(x<x2){ yt = y - 1;} }
	if(clock==1){	if(y>=y2){ xt = x + 1;}
			if(y<y2){ xt = x - 1;}
			if(x>x2){ yt = y - 1;}
			if(x<=x2){ yt = y + 1;} }
	
	dis1 = (float)(((xt - x2)*(xt - x2)) + ((y - y2)*(y -y2)));
	rad1 = gs_sqrt(dis1);
	dis2 = (float)(((x - x2)*(x - x2)) + ((yt - y2)*(yt -y2)));
	rad2 = gs_sqrt(dis2);
	
	if(gs_abs(rad1 - rad) < gs_abs(rad2 - rad))
	{x = xt; if(xcount + ycount <= count) xcount++;}
	if(gs_abs(rad1 - rad) > gs_abs(rad2 - rad))
	{y = yt; if(xcount + ycount <= count) ycount++;}
	count++;
	
	if(xcount==0||ycount==0) {num = 0; if(num >= extra) extra = 0;}
	else
	{ if(xcount==ycount) {num = (int)((float)speed*0.4); if(num >= extra) extra = num;}
	  else {num = (int)((float)speed*0.3); if(num >= extra) extra = num;}
	}
	goto label;
	}
}	
👍 0
#83203

Sorry I didn't comment on this sooner; I wanted to wait until I'd actually tried it out, and I was too obsessed with my own ideas for my first game! I just tried it out, and it shows a "parse error" around these two lines (I always forget if it's the one before!):

if(x>=x2){ yt = y + 1;}
if(x=y2){ xt = x + 1;}

I can't work it out, as (to my untrained eye) they look fine. I did of course set the define. Do you think it's because DizzyAGE has been updated?

👍 0
#83204

Hi Andy,

I've had a good look at this and it turns out the forum's not displaying the code properly. The problem seems to be the "less than" (eg. x < y) and "greater than" (eg. x > y) symbols. The forum reads them as HTML brackets or something, and doesn't display anything between the two symbols. Anyway, to get round it I've put the problem <'s in brackets and coloured them red. When you copy the text into notepad, delete the brackets and the code should work fine.

func AIUpdateCircle2( idx )
{
	if(idx==-1) return;
	if(ObjGet(idx,O_DISABLE)) return;
	if(!IsUpdate(ObjGet(idx,O_DELAY))) return;
	if(ObjGet(idx,O_STATUS)!=1) return;
	id2 = ObjGet(idx,O_TARGET);
	if(id2==0) return;
	idx2 = ObjFind(id2);
	if(idx2==-1) return;

	speed = ObjGet(idx2,O_WAYPOINTSPEED);
	clock = ObjGet(idx2,O_WAYPOINTFLIP);
	radius = ObjGet(idx2,O_WAYPOINTRADIUS);
	rad = (float)radius;
	x2 = ObjGet(idx2,O_ X) ;
	y2 = ObjGet(idx2,O_Y);
	
	x = ObjGet(idx,O_ X) ;
	y = ObjGet(idx,O_Y);
	
	count = 0; xcount = 0; ycount = 0; extra = 0;
	label:
	if(speed + extra - count==0)
	{
		ObjSet(idx,O_X,x);
		ObjSet(idx,O_Y,y);
		return;
	}
	else
	{
		if(clock==0)
		{
			if(y&gt;y2){ xt = x - 1;}
			if(y&lt;=y2){ xt = x + 1;}
			if(x&gt;=x2){ yt = y + 1;}
			if(x(&lt;)x2){ yt = y - 1;}
		}
		if(clock==1)
		{	
			if(y&gt;=y2){ xt = x + 1;}
			if(y(&lt;)y2){ xt = x - 1;}
			if(x&gt;x2){ yt = y - 1;}
			if(x&lt;=x2){ yt = y + 1;}
		}
	
		dis1 = (float)(((xt - x2)*(xt - x2)) + ((y - y2)*(y -y2)));
		rad1 = gs_sqrt(dis1);
		dis2 = (float)(((x - x2)*(x - x2)) + ((yt - y2)*(yt -y2)));
		rad2 = gs_sqrt(dis2);
	
		if(gs_abs(rad1 - rad) &lt; gs_abs(rad2 - rad))
		{x = xt; if(xcount + ycount &lt;= count) xcount++;}
		if(gs_abs(rad1 - rad) &gt; gs_abs(rad2 - rad))
		{y = yt; if(xcount + ycount &lt;= count) ycount++;}
		count++;
		
		if(xcount==0||ycount==0) {num = 0; if(num &gt;= extra) extra = 0;}
		else
		{ if(xcount==ycount) {num = (int)((float)speed*0.4); if(num &gt;= extra) extra = num;}
		  else {num = (int)((float)speed*0.3); if(num &gt;= extra) extra = num;}
		}
		goto label;
	}
}
👍 0
#83206

Excellent! That works perfectly :smile: . The reason I started looking is that I wanted to make a figure-8 movement, similar to the swordfish in Fantastic Dizzy. For some reason I find AI really hard and I can't get even the most basic movements to work. So with your complicated circular movement I was able to make a figure-8 with 2 waypoints (radius 30, speed 4) like this:

func UpdateRoom_3_0()	
{
idx = ObjFind(3003);   // baddie
AIUpdateCircle2(idx);
ObjSet(idx,O_COUNTER,ObjGet(idx,O_COUNTER)+1);  // counter increment
if(ObjGet(idx,O_COUNTER)==150)
{
ObjSet(idx,O_TARGET,3005);  // new target
ObjSet(ObjFind(3005),O_WAYPOINTFLIP,1);  // anti-clockwise
}

if(ObjGet(idx,O_COUNTER)==300)
{
ObjSet(idx,O_TARGET,3004);
ObjSet(ObjFind(3005),O_WAYPOINTFLIP,0); // clockwise
ObjSet(idx,O_COUNTER,0);
}
}

Thanks a lot :smile:

👍 0
#83208
Glad to help mate. I'm a little disappointed you're the first person to use it actually. :sad:

The figure of eight thing's a really smart idea - the counter must have taken a fair bit of trial and error tho?
👍 0
#83209
To be honest, I got lucky and got it exactly right on my second try! (My first try was 80.) I also plan to use circular motion for moving platforms
👍 0
#83210
I've always used status checks to move objects around in a specific way - getting the O_X and O_Y coordinates and using the status check to move them a bit at a time either sideways, up or diagonally. Is this method any smoother?
👍 0
#83212
Have you been able to move something in a circle? I may have to pick your brains and some point because I'm OK working with the code here (which I don't really understand!), but certain much more simple movements I'm having trouble with.
👍 0
#83213
Personally I've never bothered with waypoints when using AI, it's a lot easier just to use targets as user #32 and #33 will work on the 'object' moving (regardless of what it says in the AI.gs file) and I always use the 'object' tile as the target tiles so that I can see exactly where the object if moving to.

What exactly is it that you want to move in a circle (or figure of eight) and if it's in one room how much of the room do you want it to occupy ...and is it a killing/hurting object or something like a block to jump onto? The reason I ask is that I've just finished my latest 'blockbuster' and may have some spare time to knock up a small template using the different methods to make it work.

In the meantime maybe I'll visit youtube as I can't remember a swordfish in fantastic dizzy.
👍 0
#83215
Andy, having watched the video of Fantastic Dizzy, making a fish do a figure of eight is very simple just using AI as long as you remember a few basic rules.

The Moving Object has to have a status of 1 for the room update to work.

Set the Object tile with a target of the next ID number up (if the object tile is ID 200, set it's target to 201).

Use the Object tile as the targets, making them dynamic and mat - so the first one, would be 201 with a target of 202. It does make it easier to see exactly where the object tile is moving to on the map.

#def 32 is the speed at which the object tile is moving towards the next target tile. Generally, setting #32 to 4 is usually fine if the 'delay' of the object tile is 3. But you can make them different speeds, so if you set the first target tile (201) to def32 = 4 and then set the following one (202) to def32 = 6, the moving tile will speed up when it's moving towards target tile 202.

#def 33 is the direction the moving object tile is facing as it travels towards the next target tile. If the original tile is facing left, then as long as you want the object tile move left, leave def33 as 0. When you need it to flip set def33 as 1 for the target tile it's moving to.

Remember that when the next target file is at a higher or lower 'y' setting than the previous target tile, the engine will make the object tile move diagonally up or down till it reaches the 'y' setting and then continue in a straight line.

Finally make sure the last target tile has a target of the first target tile (I used 8 target tiles to make a figure of eight, so set target tile 208 to have a target of 201). For the center point of the figure of eight you can place target tiles on top of each other.

Obviously the room update is just setting the moving object tile as idx and using 'AIUpdateTrain(idx);' as the next line. As long as all the action is taking place in one room you don't need to add 'ObjPresent(idx);'.

Hope this doesn't sound like I'm treating you like an idiot. Best of luck! {yolkfolk}:thumb:
👍 0
#83216

Thanks for that :smile: You can treat me like an idiot all you like, you'll usually be right. I knew about using waypoints like that (mostly through hours of trial and error) but I don't know why the thought didn't occur to me to use them like this. Actually, what I was mostly after was a way to control the speed like that, so it gradually slows down until it's going backwards, and so on in a loop. I was trying something like this:

func UpdateRoom_1_1()
{
idx = ObjFind(80);
speedinc = 10;

ObjSet(idx,O_Y,ObjGet(idx,O_Y) + speedinc);
speedinc -=1;

ObjPresent(idx);
}

This doesn't work because the speedinc variable is reset every update, which of course makes sense. So it's a work in progress. I know you can adjust the speed with waypoints, but that would mean setting 10+ waypoints (and if I can set this as a function, I can call on it whenever and save lots of time!) Maybe I'm trying to be too clever...

👍 0
#83217
Wait... I can assign the variable in the object properties in the map! I am indeed very stupid {yolkfolk}:joy:
👍 0
#83218

it is possible to use a function without using AI but it can get longwinded as you would have to define each movement.

func fish(id, aa, ab)
{
idx = ObjFind(id);
x = ObjGet(idx, O_ X) ;
y = ObjGet(idx, O_Y);
if (ObjGet(idx, O_STATUS) == 0)
{
x-=4;
y-=1;
ObjSet(idx, O_X, x);
ObjSet(idx, O_Y, y);
if (x&lt;= aa) ObjSet(idx, O_STATUS, 1);
}
if (ObjGet(idx, O_STATUS) == 1)
{
x-=3;
y-=1;
ObjSet(idx, O_X, x);
ObjSet(idx, O_Y, y);
if (x&lt;=ab) ObjSet(idx, O_STATUS, 2);
}

etc, and setting the status back to 0 to move back to the start. NB - just in case this post gets the same problem as Simon, the code after setting the object to 'y' is if the x axis is greater or less then aa, ab etc.

Then you could use the bits in brackets to define the object being used and aa and ab etc as the start points to change status.

func UpdateRoom_1_1()
{
fish(100, 4200, 4100)
}

Of course, you would have to make each fish separate, but as the define is generic, you'd just have to change the the title to fish1, fish2 etc and set the coordinates in the room update.

Told you it was longwinded {yolkfolk}:eek:

👍 0