2D Directional Programming

BlitzMax Forums/BlitzMax Programming/2D Directional Programming

xcessive(Posted 2011) [#1]
I've been working on a space RPG for fun and practice a little like the Escape Velocity (http://www.ambrosiasw.com/games/evn/) series recently in Blitzmax. Using my limited knowledge of vectors/physics I managed to get the player object functioning, with rotation, inertia and acceleration. But I have run into a problem programming basic AI. How Do I made the AI ships face the player, or any object for that matter? I am at a loss working out the maths behind determining the right direction.

Can anyone help?


H&K(Posted 2011) [#2]
Learn Trigonometry


(Hint: Everything is a right angled triangle away from everything else)


Warpy(Posted 2011) [#3]
How to find the angle from the AI to the player:
dx# = playerX - aiX
dy# = playerY - aiY
angle# = ATan2(dy,dx)


You won't want the AI ship to turn instantly to face the player, so here's a function to work out the difference between two angles:
Function andiff#(an1#,an2#)
	dan#=(an1-an2) Mod 360
	If dan>180 dan:-360
	If dan<-180 dan:+360
	Return dan
End Function


So to make the AI turn at a constant rate towards a desired angle:
aiAn = aiAn + turnspeed*Sgn(andiff(angle,aiAn))


Here are some links to things you might want to look at once you've got the above sorted out:

fly-by-wire code
how to aim at moving targets


xcessive(Posted 2011) [#4]
Thanks warpy. I am desperately trying to use and understand your code. But its not working at all! The AI is not moving or rotating at all!

Heres my code:
//stuff thats inside the AI object thats of interest
Method Draw()
		SetRotation( Direction )
		DrawImage( Image,X-Player.X,Y-Player.Y ) //this is for viewpoint offset
		SetRotation( 0 )
EndMethod
Method FacePlayer()
		Local dx# = Player.X - Self.X
		Local dy# = Player.Y - Self.Y
		Local angle# = ATan2(dy,dx)
		Self.Direction = Self.Direction + Sgn(0.1*andiff(angle,Self.Direction))
EndMethod

/////////

//functions outside AI object
Function andiff#(an1#,an2#)
	Local dan#=(an1-an2) Mod 360
	If dan>180 dan:-360
	If dan<-180 dan:+360
	Return dan
End Function


In fact just doing this doesnt even work!
Method FacePlayer()
		Local dx# = Player.X - Self.X
		Local dy# = Player.Y - Self.Y
		Local angle# = ATan2(dy,dx)
		Self.Direction = angle
	EndMethod


I would muchly appreciate any help!

Last edited 2011

Last edited 2011

Last edited 2011


zambani(Posted 2011) [#5]
@xcessive
Below is a complete code for facing an image towards a 2d point. Just copy & paste this and you should see a chessy looking tank face wherever you click your mouse. I tried coding it in your style of coding. Hopefully it will be easy for you to understand.


It might take a few sec for the image to load.

SuperStrict
Graphics(640, 480, 0, 30)
SetBlend(ALPHABLEND)

Global aiImage:TImage = LoadImage(LoadBank("http::www.reflectivelayer.com/tutorials/facing/aiTank.png"))
MidHandleImage(aiImage)
Global tank:ai = New ai


While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
	Cls()
	DrawText("Click on a point to make tank turn in that direction", 5, 5)
	If MouseHit(1)
		tank.lookAtLocation(MouseX(), MouseY())
	End If
	tank.draw()
	Flip(-1)
Wend


Type ai
	Field X:Int = 320
	Field Y:Int = 240
	Field angle:Float
	Field isTurning:Int
	Field turnSpeed:Float = 5
	Field turnDelta:Float
	
	Method draw()
		If Self.isTurning > 0
			Self.angle:+Self.turnDelta
			Self.isTurning:-1
		End If
		SetRotation(Self.angle)
		DrawImage(aiImage, Self.X, Self.Y)
		SetRotation(0)
	End Method
	
	Method lookAtLocation(tx:Int, ty:Int)
		Local a:Float = getAngleToLocation(Self.X, Self.Y, tx, ty)

		Self.isTurning = (a - Self.angle) / Self.turnSpeed
		If Self.isTurning < 0
			Self.turnDelta = -1 * Self.turnSpeed
			Self.isTurning:*- 1
		Else
			Self.turnDelta = Self.turnSpeed
		End If
	End Method

End Type

Function getAngleToLocation:Float(originX:Float, originY:Float, targetX:Float, targetY:Float)
	Local dx:Float = originX - targetX
	Local dy:Float = originY - targetY
	Return ATan2(dy, dx)
End Function




Last edited 2011


Jesse(Posted 2011) [#6]
zambani's code can still be improved to find the shortest angle to it's target.


zambani(Posted 2011) [#7]
@Jesse
It does turn in the direction of the shortest angle. Is that what you mean by shortest angle to target?

edit:
Nevermind. I see what you mean. Thanks for pointing that out. Never noticed.

Last edited 2011

Last edited 2011


AdamRedwoods(Posted 2011) [#8]
Just a geek note: ATAN2 is used over ATAN because ATAN2 is a computer function, which uses all 4 quadrants in the x,y grid.

Trig for right triangles:
Tan = opposite/adjacent
angle = atan(opp/adj)
angle = atan = tan^-1

I use my left hand when trying to figure out trig stuff for games. my thumb and forfinger make the angle that I'm after. The thumb is usually the X-position, and the Y position could be the finger on my right hand, used to close the gap (makes a right triangle).

Then I just say the sin-oh, cos-ah, tan-oa and figure out what I need.


Jesse(Posted 2011) [#9]
@zambani
there are several post related to that same problem. I am sure they can easily be found by doing a search. I even post a solution in one of them. if need be, I can repost it.

Last edited 2011


xcessive(Posted 2011) [#10]
@Jesse, could you be kind enough to send some links to those threads? I Couldn't find any.

@zambani: The code you posted works well and I understand it. But I am at a loss as to why the tank occasionally does a 360 degree turn to get to a point right next too it :S


Pengwin(Posted 2011) [#11]
@AdamRedwoods Thanks for that tip. I always seem to end up searching for high school trigonometry sites to remind we whenever i need to calculate angles and stuff.


zambani(Posted 2011) [#12]
@xcessive
This the issue Jesse was talking about . Imagine a circle where the 3 o'clock position is 0 and also 360 degrees . When you're at an angle of say 350 and you need to turn clockwise by 30 degrees, that would make your new angle 380 or more like 20 degrees. The numbers wrap around. Since 380 is the same as 20 and 20 is smaller than 350, my code ends up taking the long counter clockwise route. This only happens when the target causes the total angle to be more than 360. It's not that hard to fix. I just don't have the time right now to look into it. I think Jesse posted some links to possible solutions.


xcessive(Posted 2011) [#13]
Jesse posted no such links, but hopefully he will come back and post them. I am aware that that is the problem, but Its doing my head in trying to find a solution :P.


xcessive(Posted 2011) [#14]
I seem to have got it working, I know the code is a little messy, but hey it works.


SuperStrict
Graphics 640, 480, 0,30
SetBlend(ALPHABLEND)

Global aiImage:TImage = LoadImage(LoadBank("http::www.reflectivelayer.com/tutorials/facing/aiTank.png"))
MidHandleImage(aiImage)
Global tank:ai = New ai


While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
	Cls()
	DrawText("Click on a point to make tank turn in that direction", 5, 5)
	If MouseHit(1)
		tank.lookAtLocation(MouseX(), MouseY())
	End If
	tank.draw()
	Flip(-1)
Wend


Type ai
	Field X:Int = 320
	Field Y:Int = 240
	Field angle:Float
	Field turnSpeed:Float = 5
	Field tx:Float
	Field ty:Float
	
	Method draw()
		Local a:Float = getAngleToLocation(Self.X, Self.Y, tx, ty)
		If Self.angle > 180
			Self.angle = -180 + Abs(Self.angle - 180)
		Else If Self.angle < -180
			Self.angle = 180 - Abs(Self.angle - 180)
		EndIf
		If Abs(CalcAngle(Self.angle, a)) > Ceil(turnSpeed/2)
			If CalcAngle(Self.angle, a) >= 0
				Self.angle = Self.angle + turnSpeed
			Else
				Self.angle = Self.angle - turnSpeed
			EndIf
		EndIf
		
		DrawText CalcAngle(Self.angle, a),10,90
		DrawText Self.angle,10,30
		DrawText a,10,60
				SetRotation(Self.angle)
		DrawImage(aiImage, Self.X, Self.Y)
		SetRotation(0)
	End Method
	
	Method lookAtLocation(tx:Int, ty:Int)
		Self.tx = tx
		Self.ty=ty
	End Method

End Type

Function getAngleToLocation:Float(originX:Float, originY:Float, targetX:Float, targetY:Float)
	Local dx:Float = originX - targetX
	Local dy:Float = originY - targetY
	Return ATan2(dy, dx)
End Function

Function CalcAngle:Float(Ang1:Float,Ang2:Float) 'gets the angle difference
	Local fDif:Float = Ang2-Ang1   
	If fDif >= 180.0
		fDif :- 360.0
	Else
		If fDif <= -180.0
			fDif :+ 360.0
		EndIf
	EndIf
	Return fDif
End Function



Jesse(Posted 2011) [#15]
looks good.

you don't really need it anymore but here is a link to one of the threads:
http://www.blitzmax.com/Community/posts.php?topic=92164#1049126