Arc Of Sight, Included in guard-like A.I. example |
This forum is for clear and concise tutorials and well documented examples. Tutorials and examples posted here must follow the format in the pinned Rules and Guidelines.
All new topics must be approved by a moderator before they appear. Don't post twice!
![]() ![]() |
Arc Of Sight, Included in guard-like A.I. example |
Apr 28 2008, 05:30 AM
Post
#1
|
|
|
The Game Boy Group: GMC Member Posts: 2123 Joined: 9-March 04 From: Yes Member No.: 6861 |
QUOTE (Local Moderators) Replies to topics in this forum are held to a high standard. Reviews must have a critique or suggestion for use to be approved. Read the rules here. Author: SoulRed12 (my name on YYG) Title: Arc of Sight Partner file: Link Version: GM7, either Pro or Lite. However, if you have the Lite version, go to the constants section of the game settings and set "drawrotated" to 0. That will turn off the use of draw_sprite_ext. The sprites won't rotate as the objects move in different directions, but that's okay because it doesn't have to do with what's actually being described in the tutorial. As long as you keep the drawarc constant set to 1, you'll still be able to easily observe the intended effect inside this example. Updates to example: V2 (5/17/08): Example now supports multiple players and multiple instances of a single player. Because the enemies search for an instance of "GoodGuy", which is now the parent of "Player1" and "Player2", no matter how many players you have, as long as they all have "GoodGuy" as their parent object, the enemies will still search for all of them. Comments: Hello everyone, I've written up a new tutorial in which you will learn how to make an object test for another object inside an "arc of sight". The first thing that probably comes to your mind is the similarity to "line of sight". A line of sight is a line usually drawn from one object to another object to make sure nothing is in the way. For example, an enemy must shoot towards the player, but only if a wall is not between them. There's collision_line() for that, and collision_* for other types of tests. But we want to use more of a circle sector shape. Essentially, an arc of sight. This is very useful for effects such as a guard carrying around a flashlight; if the light shines on you, you're seen and the guard chases after you. But you can't get that effect without being a little creative. I have included a partner gmk file, so you can see the effect visually. In this file you play as a little blue guy walking around trying to find the diamonds. Also in the area are red guys sauntering around, guarding these diamonds. If they see you, they chase after you and if they catch you, you die. Run out of their sight and they continue pacing around again. First I'd like you to download and run this file to see the final effect. Note: this tutorial assumes you have at least a basic knowledge of geometry. More specifically, you must know what a circle's radius is. The partner file includes most of the direct info for a practical example, but here I will describe the concept behind this arc of sight. The basic theory is that we need a different shape than can be provided by Game Maker's functions. This is the circle sector. The orange portion is the shape I'm referring to: How do we achieve that? Quite simply, we fake it. What do you notice about this circle, and circles in general? The length of any line starting from their center (marked with a black dot in the picture) and ending on the circle's rim is equal to the circle's radius. Therefore, no matter where the object is inside the circle sector, the distance from the object to the center point must always be smaller than the radius of the full circle. Imagine the small red circle is the object, and the "r"s marks the circle's radii (plural of radius): Then there's the issue of being inside the circle, but not being outside the desired orange region. We only want to "see" the object when it's inside the orange region. If the distance from the object to the center point is smaller than the radius, then the object is inside the "circle" somewhere, but not necessarily in the region we want. It could be like this: As you can see, the object is still inside the full circle (the distance from the object to the centerpoint is smaller than the circle's radius), but the object is not inside the orange region. So we have to make sure the direction does not stray too far from the direction that would point straight through the center of the circle segment. Shown by the white line here: Diagram 4 (the board won't let me post any more images) There is an equal amount of space on each side of the white line, so the angles formed at the center point on either side of the white line are also equal. Thus we need to define an angle offset. The greater this value, the more of the circle is included in the sector. So now we have the basic theory. For the object to be inside the circle at all, the distance from the object to the circle's centerpoint must be less than the circle's radius. For the object to then be also inside the circle sector, we must make sure the angle from the center point to the object does not differ from the center angle (white line) by more than the offset value. Note that the circle sector does not have to be in the top part of the circle. The "center angle" could point to the right and the same idea still comes into play: I encourage you now to examine the .gmk file which explains how all this is done in GML coding. Basically we use point_distance() and point_direction() to "make" the arc of sight as described above. I think I did a good job of over-commenting, so you should be able to find your way around it. There are some other goodies in the gmk that I hope will also be helpful to you. These include: -A single script for testing an arc of sight which can be used in any game, including non-topdown games -Another script which draws a circle sector on the screen -Topdown guard-like A.I. Enjoy the tutorial. If you use it, some credit would be cool. Thanks. This post has been edited by gmXpert2000: May 17 2008, 10:53 PM |
|
|
|
May 13 2008, 01:22 PM
Post
#2
|
|
|
GMC Member Group: GMC Member Posts: 20 Joined: 4-November 07 Member No.: 92240 |
Hi, I really like this and have implemented it in my test game.
A problem which I found is that the "bad guy" will only kill 1 object of the "good guy". What I was experimenting was to put serveral "good guys" in the level and the "bad guy" would kill all of them if it can see them. However the "bad guy" can only see the first created object of the "good guy" and not the others... |
|
|
|
May 14 2008, 01:12 AM
Post
#3
|
|
|
GMC Member Group: Posts: 1121 Joined: 13-April 08 Member No.: 104330 |
Loop through all existing instances of the "good guy."
|
|
|
|
May 14 2008, 10:38 PM
Post
#4
|
|
|
The Game Boy Group: GMC Member Posts: 2123 Joined: 9-March 04 From: Yes Member No.: 6861 |
This example was built under the basis of a basic standard game; a single player and enemies. However, it can be easily adapted to having multiple players.
If you want to have more than one player object, for example so you can have "objPlayer1" and "objPlayer2", or "GoodGuy1" and "GoodGuy2" for the case of this example, simply use the function twice. The first time use it with argument0=GoodGuy1, and the second time with argument0=GoodGuy2. If either one returns true, then store that object index in a variable. CODE var followobj; //Tells the bad guy which player to follow followobj=noone; //Initialize to "noone" //Pick an object to follow, depending on which one we can see if (ArcOfSight(GoodGuy1,sight_maxdist,dir,sight_angleoff)) followobj=GoodGuy1; else if (ArcOfSight(GoodGuy2,sight_maxdist,dir,sight_angleoff)) followobj=GoodGuy2; Finally change "if (cansee)" to "if (followobj!=noone)", because we've switched methods. Then, in inside that code block, find the line with point_direction() and change it to use your variable instead of just "GoodGuy". So that whole part would now look like this: CODE //Now check if we can see the player. var followobj; //Tells the bad guy which player to follow followobj=noone; //Initialize to "noone" //Pick an object to follow, depending on which one we can see if (ArcOfSight(GoodGuy1,sight_maxdist,dir,sight_angleoff)) followobj=GoodGuy1; else if (ArcOfSight(GoodGuy2,sight_maxdist,dir,sight_angleoff)) followobj=GoodGuy2; if (followobj!=noone) { running=1; move=1; dirto=point_direction(x,y,followobj.x,followobj.y); //<--This line is changed [...then the rest of the code...] ---------------------------------------------------------------------------------- I can't think of any reasons why in a game there would be more than one instance of a particular player object in the room, but if you do need to do so, try something like this: Loop through each instance of "Good Guy", as overman stated. Use instance_nearest(), then the script, and finally move the instance of Good Guy far away so it won't be found with instance_nearest() until we're done. You're going to use the same principle as above (using a variable to control who the enemy follows) but this time the variable is going to contain an instance id. CODE //Now check if we can see the player. followobj=noone; //Go through each Good Guy and test if we can "see" them while (instance_exists(GoodGuy)) //Repeat endlessly (we use "break" later) { //Get an instance. This will return noone when //all instances are at the same position. inst=instance_nearest(x,y,GoodGuy); if (inst==noone) break; //If we can "see" this instance of player, store it's id and break from the loop if (ArcOfSight(inst,sight_maxdist,dir,sight_angleoff)) {followobj=inst; break;} //Store instance's old x value and then move it far away with (inst) { oldx=x; x=-999999; } } //Now reset all the Good Guy's x positions with (GoodGuy) x=oldx; This whole code snippet would replace the old "cansee=ArcOfSight(...)" line, just as the first method I mentioned in this post did. Finally, again, instead of "if (cansee)" you would use "if (followobj!=noone)" and "followobj" instead of "GoodGuy" inside the point_direction(). Hope that helps. EDIT: Removed unnecessary part of while loop expression =p (shouldn't actually change anything) EDIT 2: Ok, through personal messages, AR_85 has alerted me of a bug in the multiple-instances-of-one-player-object code that I wrote inside this post. I have changed it so that it works for any instances of the player. I also updated the main GMK in the main post because I realized this code can be used with parents to let an object search for multiple instances of one player object and multiple player objects. "GoodGuy" is now the parent of all the player objects (Player1, Player2, and however many more players you want), therefore the enemies will search for any and all of them. This post has been edited by gmXpert2000: May 17 2008, 10:55 PM |
|
|
|
May 16 2008, 10:40 PM
Post
#5
|
|
|
GMC Member Group: GMC Member Posts: 71 Joined: 24-April 08 Member No.: 105196 |
You explain this better than my maths teacher
NAVg |
|
|
|
Jul 22 2008, 07:21 PM
Post
#6
|
|
|
GMC Member Group: GMC Member Posts: 51 Joined: 20-July 08 Member No.: 111177 |
I love this!!
But i do have some questions; Question nr1: Since i suck at scripting i used d&d to make the bot with the arc stop when it reaches object: obj_wall. But the stupid bot still tries to run thrue the wall so how can i make it turn when it reaches the wall? Question nr2: Is it possible to make the bot shoot while following the player? Question nr3: And how can i stop the bot from seeing the player thrue solid objects? |
|
|
|
Nov 15 2008, 11:42 PM
Post
#7
|
|
|
GMC Member Group: GMC Member Posts: 297 Joined: 18-June 08 Member No.: 109188 |
This is cool! definately want to find a way to use it.
Ive noticed however, that when the enemies see the player, he sort of freezes up, stops moving. Is there any way to avoid that happening, because ive looked through the code and cant find whats causing it |
|
|
|
Nov 16 2008, 07:34 AM
Post
#8
|
|
|
Nocturne Games Group: GMC Member Posts: 3713 Joined: 25-April 08 From: Scotland/Spain Member No.: 105300 |
This is an incredibly useful and well written tutorial... The code (for the arc) is simple, clean and fast, the comments perfectly written and the explanation in the first post one of the best I´ve seen...
Good job, and I will recommend this on the forums to all who ask about AI and line of sight... (and may even use it myself to replace my current collision triangle script!!!) |
|
|
|
Dec 10 2009, 12:31 PM
Post
#9
|
|
|
GMC Member Group: GMC Member Posts: 1294 Joined: 24-January 09 Member No.: 127212 |
This is a great example. The script is what I've been looking for, well... for ages...
Job well done! -MoK This post has been edited by masterofkings: Dec 15 2009, 08:45 AM |
|
|
|
Dec 16 2009, 07:39 AM
Post
#10
|
|
|
GMC Member Group: GMC Member Posts: 2 Joined: 15-December 09 Member No.: 147402 |
QUOTE (Local Moderators) Replies to topics in this forum are held to a high standard. Reviews must have a critique or suggestion for use to be approved. Read the rules here. Author: SoulRed12 (my name on YYG) Title: Arc of Sight Partner file: Link Version: GM7, either Pro or Lite. However, if you have the Lite version, go to the constants section of the game settings and set "drawrotated" to 0. That will turn off the use of draw_sprite_ext. The sprites won't rotate as the objects move in different directions, but that's okay because it doesn't have to do with what's actually being described in the tutorial. As long as you keep the drawarc constant set to 1, you'll still be able to easily observe the intended effect inside this example. Updates to example: V2 (5/17/08): Example now supports multiple players and multiple instances of a single player. Because the enemies search for an instance of "GoodGuy", which is now the parent of "Player1" and "Player2", no matter how many players you have, as long as they all have "GoodGuy" as their parent object, the enemies will still search for all of them. Comments: Hello everyone, I've written up a new tutorial in which you will learn how to make an object test for another object inside an "arc of sight". The first thing that probably comes to your mind is the similarity to "line of sight". A line of sight is a line usually drawn from one object to another object to make sure nothing is in the way. For example, an enemy must shoot towards the player, but only if a wall is not between them. There's collision_line() for that, and collision_* for other types of tests. But we want to use more of a circle sector shape. Essentially, an arc of sight. This is very useful for effects such as a guard carrying around a flashlight; if the light shines on you, you're seen and the guard chases after you. But you can't get that effect without being a little creative. I have included a partner gmk file, so you can see the effect visually. In this file you play as a little blue guy walking around trying to find the diamonds. Also in the area are red guys sauntering around, guarding these diamonds. If they see you, they chase after you and if they catch you, you die. Run out of their sight and they continue pacing around again. First I'd like you to download and run this file to see the final effect. Note: this tutorial assumes you have at least a basic knowledge of geometry. More specifically, you must know what a circle's radius is. The partner file includes most of the direct info for a practical example, but here I will describe the concept behind this arc of sight. The basic theory is that we need a different shape than can be provided by Game Maker's functions. This is the circle sector. The orange portion is the shape I'm referring to: How do we achieve that? Quite simply, we fake it. What do you notice about this circle, and circles in general? The length of any line starting from their center (marked with a black dot in the picture) and ending on the circle's rim is equal to the circle's radius. Therefore, no matter where the object is inside the circle sector, the distance from the object to the center point must always be smaller than the radius of the full circle. Imagine the small red circle is the object, and the "r"s marks the circle's radii (plural of radius): Then there's the issue of being inside the circle, but not being outside the desired orange region. We only want to "see" the object when it's inside the orange region. If the distance from the object to the center point is smaller than the radius, then the object is inside the "circle" somewhere, but not necessarily in the region we want. It could be like this: As you can see, the object is still inside the full circle (the distance from the object to the centerpoint is smaller than the circle's radius), but the object is not inside the orange region. So we have to make sure the direction does not stray too far from the direction that would point straight through the center of the circle segment. Shown by the white line here: Diagram 4 (the board won't let me post any more images) There is an equal amount of space on each side of the white line, so the angles formed at the center point on either side of the white line are also equal. Thus we need to define an angle offset. The greater this value, the more of the circle is included in the sector. So now we have the basic theory. For the object to be inside the circle at all, the distance from the object to the circle's centerpoint must be less than the circle's radius. For the object to then be also inside the circle sector, we must make sure the angle from the center point to the object does not differ from the center angle (white line) by more than the offset value. Note that the circle sector does not have to be in the top part of the circle. The "center angle" could point to the right and the same idea still comes into play: I encourage you now to examine the .gmk file which explains how all this is done in GML coding. Basically we use point_distance() and point_direction() to "make" the arc of sight as described above. I think I did a good job of over-commenting, so you should be able to find your way around it. There are some other goodies in the gmk that I hope will also be helpful to you. These include: -A single script for testing an arc of sight which can be used in any game, including non-topdown games -Another script which draws a circle sector on the screen -Topdown guard-like A.I. Enjoy the tutorial. If you use it, some credit would be cool. Thanks. Good tutorial,Its good that you are telling that it is WORKING in lite edition=) |
|
|
|
Dec 29 2009, 09:19 PM
Post
#11
|
|
|
GMC Member Group: GMC Member Posts: 12 Joined: 16-June 08 Member No.: 109001 |
Uhhhh,
I got game maker 8 and I can't set constants in lite. I used them in 7.0, but now they're pro. |
|
|
|
Dec 29 2009, 10:36 PM
Post
#12
|
|
|
GMC Member Group: GMC Member Posts: 448 Joined: 13-March 08 From: AK Member No.: 102182 |
I love this!! But i do have some questions; Question nr1: Since i suck at scripting i used d&d to make the bot with the arc stop when it reaches object: obj_wall. But the stupid bot still tries to run thrue the wall so how can i make it turn when it reaches the wall? Question nr2: Is it possible to make the bot shoot while following the player? Question nr3: And how can i stop the bot from seeing the player thrue solid objects? Yeah I'm wondering the same thing as question 3 |
|
|
|
Dec 30 2009, 05:41 PM
Post
#13
|
|
|
GMC Member Group: GMC Member Posts: 118 Joined: 3-August 08 From: Slovenia/Koper Member No.: 112088 |
Its cool , but cinda hard for new guys...
but i found an easyer and faster way to do it..il post it soon as il make examples and tutorials for it. |
|
|
|
Dec 31 2009, 08:01 AM
Post
#14
|
|
|
GMC Member Group: GMC Member Posts: 111 Joined: 11-September 09 Member No.: 139642 |
I don't get it. Why not use a simple "pizza" object with a collision mask? It could follow the enemy.x,enemy.y image_angle=direction. And then you would just need a collision event.
And to prevent the enemy from seeing through walls, you could just add a: CODE if (place_meeting(x,y,objwall)) and distance_to_object(player) > distance_to_object(objwall) {seeplayer=false} else {seeplayer=true} Like, if the distance between the enemy and the wall in range is smaller than the distance between the enemy and the player in range, then that means the player is at the other side of the wall, right? Logically, yes. But I know that in some cases, the enemy should still be able to see the player even if a wall is in his LOS. That would be tougher to manage. I dunno. I'd have to test that. EDIT: OR... Another idea instead of a pizza, would be a "beam of sight". A beam that would go through humans and low leveled objects. But that would be stopped by walls (collide). What I'm suggesting here is that the "Line Of Sight" could work like a gun that shoots infinite bullets (at random 90 degree angle). And when a bullet hits another player, then the enemy sees him (real bullets kill, don't try this at home). Of course, the "eye bullets" could not go through walls because they are solid objects. And the LOS should not bounce either. And when you turn the light off for example, then the eyegun would temporarily run out of bullets ! This post has been edited by Pesmerga: Dec 31 2009, 08:28 AM |
|
|
|
Dec 31 2009, 08:47 AM
Post
#15
|
|
|
GMC Member Group: GMC Member Posts: 118 Joined: 3-August 08 From: Slovenia/Koper Member No.: 112088 |
EDIT: OR... Another idea instead of a pizza, would be a "beam of sight". A beam that would go through humans and low leveled objects. But that would be stopped by walls (collide). What I'm suggesting here is that the "Line Of Sight" could work like a gun that shoots infinite bullets (at random 90 degree angle). And when a bullet hits another player, then the enemy sees him (real bullets kill, don't try this at home). Of course, the "eye bullets" could not go through walls because they are solid objects. And the LOS should not bounce either. And when you turn the light off for example, then the eyegun would temporarily run out of bullets ! your first pizza slice idea is what im talking about , and its simple to edit and easy to understand without any other usless codes... ..but your second idea is bad...if you use objects then the lag will be huge. |
|
|
|
Jan 2 2010, 12:21 PM
Post
#16
|
|
|
GMC Member Group: GMC Member Posts: 1294 Joined: 24-January 09 Member No.: 127212 |
Question nr3: And how can i stop the bot from seeing the player thrue solid objects? collision_line() is what you need, but it's code. I have no idea how to it in D&D. @pesmerga: First idea: Pros: - simple to use - easy to learn Cons: - big pizza means big mask which equals a big file which you don't need - wouldn't work correctly. It may see a wall closer to you, and/or a wall at all, but that doesn't mean the enemy can't see you (wall could be on other side of arc) Second idea: Pros: - relatively effective in detection (might not do very well if written wrong) Cons: - extremely processor heavy - unnecessary processing - lots of object going to waste, and using cpu and memory Overall Neither idea really matches up to speed and capability of this script (although I've written a better one [two actually], which will be going in my TDS v2 example) -MoK |
|
|
|
Jan 3 2010, 04:18 PM
Post
#17
|
|
|
GMC Member Group: GMC Member Posts: 111 Joined: 11-September 09 Member No.: 139642 |
Yeah, you're probably right about the second idea. I didn't think about lag issues because I thought lag was caused by visual effects and well you would only be using one invisible object really.
Anyway, I can't wait to see your newer and improved scripts. btw, I had another idea that might work that is similar to the second one but does not require the use of objects at all. Instead of creating instances, maybe we could "draw" collision lines/rectancles and test what object it comes in contact with first. Using: collision_ellipse collision_line collision_point collision_rectangle But I don't know if that would work because I have never handled collision this way. I'm not even sure how to use them properly but I'll try and play with them a bit. |
|
|
|
Jan 29 2010, 08:15 AM
Post
#18
|
|
|
The Game Boy Group: GMC Member Posts: 2123 Joined: 9-March 04 From: Yes Member No.: 6861 |
QUOTE I don't get it. Why not use a simple "pizza" object with a collision mask? Well, because that allows for no dynamic changes to the sight area, unless you want to make multiple subimages or sprites to represent each possible size you'd need for the mask. Much more work than necessary, especially when with this code, you only have to modify a few parameters. (two, in fact) It also eliminates the need for an extra object for every enemy on the screen. QUOTE What I'm suggesting here is that the "Line Of Sight" could work like a gun that shoots infinite bullets (at random 90 degree angle). And when a bullet hits another player, then the enemy sees him (real bullets kill, don't try this at home). Of course, the "eye bullets" could not go through walls because they are solid objects. And the LOS should not bounce either. And when you turn the light off for example, then the eyegun would temporarily run out of bullets ! Why force Game Maker to perform that many calculations each step? The only two calculations needed for the arc of sight script are difference in distance and difference in direction. Quite frankly the arc of sight script is relatively simply stuff; it's really the AI in the example that's the hard part. QUOTE btw, I had another idea that might work that is similar to the second one but does not require the use of objects at all. Instead of creating instances, maybe we could "draw" collision lines/rectancles and test what object it comes in contact with first collision_* functions can't replicate the shape that arc of sight uses, unless you use many of them to approximate it in which case you're still doing more than you need. You could use collision_triangle from gmlscripts.com, but even that has way more calculations than the arc of sight script. I appreciate the responses, but you guys should really check out the code inside the script, and see what it actually does. The explanation in the first post seems long due to explaining the theory, but the script itself is pretty simple. Again, it can just seem complicated at times because of the guard AI. |
|
|
|
![]() ![]() |
|
Lo-Fi Version | Time is now: 9th February 2010 - 02:24 PM |