Posted By
|
Message
|
Deleted User
|
5th January, 2009 at 04:05:51 -
http://www.create-games.com/article.asp?id=1761
I think the author kind of just skips a few steps, well to me. Especially the part where he talks about collecting underpants and then jumping all the way to profit.
But seriously, as far as the angles are concerned, I'm not sure how you're supposed to actually determine what is the AI's angle of sight, maybe I missed it, but I'm lost. Also, I'm not exactly getting how to get the detector to just be an XY value. I set two alterable values on my AI's character, but I don't know how it's supposed to detect collisions.
Help?
|
Deleted User
|
5th January, 2009 at 23:57:15 -
Ba bum bump bump.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 02:18:52 -
Yeah, that article's a little harder to follow without the diagrams
As some people point out in the comments at the bottom, the article does some things in the wrong order, making it less efficient than it could be.
I'm going to describe it as though there is just one enemy, and one player, to keep things simple - in practice, you would need to use fastloops to check the line-of-sight between every combination of enemy and player objects.
DISTANCE
Anyway, contrary to the order of the original article, the first thing you need to do, is calculate the distance between the enemy and the player (you do this first because MMF can calculate it quickly, and it will immediately "rule-out" a lot of objects).
The easy way to calculate the distance is to use Pythagoras' theorum:
Distance("Enemy") = SQR(((X("Enemy") - X("Player")) POW 2) + ((X("Enemy") - X("Player")) POW 2))
Alternatively, you can use an extension, or there is a more complicated but slightly faster forumla in my article "Useful Formulae".
Once you've found the distance, you compare it to your enemy's maximum vision range. If the player is too far away, it means they remain undetected. There is no need then to continue checking the angles / obstacles etc. If you have multiple enemies, you move immediately onto the next one, and start over.
ANGLE
Assuming the enemy doesn't literally have eyes in the back of their heads, you want them only to see the player whilst they are more or less infron of them. If you've ever played "Commandos: Behind Enemy Lines" then you'll know exactly what I'm talking about.
The first thing you need to do, is find the angle from the enemy to the player - I'm calling it "Bearing" to stop things getting confusing later on.
Bearing("Enemy") = ATan2(Y("Player") - Y("Enemy"), X("Enemy") - X("Player"))
I may have got the player and enemy the wrong way round in that formula? Again, the Advanced Direction extension makes it much easier.
Now, you need to compare that to the enemy's heading. Let's say anything within 45 degrees either side of the direction in which they are facing, might be visible.
If Bearing("Enemy") < EnemyHeading - 45
or
If Bearing("Enemy") > EnemyHeading + 45
...then the player is not within their field of view. Again, there is no need to move onto the final step.
OBSTRUCTIONS
If you wan't only background objects marked as "obstacle" to block the line of sight, then you don't need to use detectors (you can use the "collision mask" instead). Otherwise, detectors will be required, but it's really not a problem. I'm going to assume you're doing the former.
You need to use fastloops and some simple trigonometry for this.
Since we're not using detectors, we need to store some things in alterable values / counters / etc.
Always(?) (probably "always" would make the game run too slow - experiment)
* start fastloop("Scan"), "Distance("Enemy") / "Accuracy") times
* set flag0 to "off"
On Loop("Scan")
* set XScan to X("Enemy") + (Cos(Bearing("Enemy"))*LoopIndex("Scan")*Accuracy)
* set YScan to Y("Enemy") - (Sin(Bearing("Enemy"))*LoopIndex("Scan")*Accuracy)
If XScan,YScan is an obstacle (use the collision mask, under "storyboard controls")
* stop fastloop("Scan")
* set flag0 to "on"
If flag0 is "on"
* -> a clear line-of-sight does not exist (player is not detected)
In case you're not familiar with it, "LoopIndex" is just a variale to tell you how many times the fastloop has repeated so far.
Using an "accuracy" value makes the process more efficient. The larger the number, the faster it will work, but the more likely it will be to fail to detect small obstructions. A value of 1 will check every single pixel between the enemy and the player - however a value of 5+ is probably sufficient.
If you want to use detectors, just set their position to XScan, YScan and detect for collisions in the normal way.
Also, Pixelthief made a really good line-of-sight example, that's definitely worth a look.
Now I need to go throw up (forgot I had a mild peanut allergy )
Edited by Sketchy
n/a
|
Deleted User
|
6th January, 2009 at 07:09:30 -
Oh god, thank you. That explains things alot better. I was going about it all wrong. The author gave no indication to weather or not the line of sight was a condition or a product. I got most of it down pat now, although I have to change some of my code up because apparantly I've got it backwards, instead of checking if there is line of sight, it seems you're leaning more towards if the enemy does not. I was pretty sure the obstacle check was in the wrong spot, I haven't even really tinkered with it yet, but eh, who knows. Thanks again.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 13:29:01 -
A couple of corrections:
Always(?) (probably "always" would make the game run too slow - experiment)
* set flag0 to "off"
* start fastloop("Scan"), "Distance("Enemy") / "Accuracy") times
On Loop("Scan")
* set XScan to X("Enemy") + (Cos(Bearing("Enemy"))*LoopIndex("Scan")*Accuracy)
* set YScan to Y("Enemy") - (Sin(Bearing("Enemy"))*LoopIndex("Scan")*Accuracy)
On Loop("Scan")
+ If XScan,YScan is an obstacle (use the collision mask, under "storyboard controls")
* stop fastloop("Scan")
* set flag0 to "on"
If flag0 is "on"
* -> a clear line-of-sight does not exist (player is not detected)
I've also noticed another potential problem with the Angle part - if heading is say, 30 degrees, the player should be visible if bearing = 310. However, 310 > 30+45 so it wouldn't be.
Instead, you need to find the difference between Bearing and Heading:
If Abs(((Bearing - Heading + 540) mod 360) - 180) > 45
* -> player is outside enemy field of view
Sorry to make it confusing again
Edited by Sketchy
n/a
|
Deleted User
|
6th January, 2009 at 16:24:53 -
Oh no, I'm following you completely. The only thing I'm not familiar with is the collision mask. What exactly is that? And what's the difference between or filtered and or logical?
Edited by an Administrator
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 16:53:10 -
The collision mask is a feature built into MMF, that works kind of like a detector object.
You can look for it under the "storyboard controls" (the chessboard icon in the event editor).
It provides two conditions; "Is Obstacle" and "Is Ladder". Both of these ask for an X- and Y- coordinate. They then check those screen coordinates to see whether there is an obstacle (or ladder) there. Both conditions can of course be negated (ie. "Is NOT Obstacle").
The other way to use the collision mask, is to retrieve the "Value of collision mask".
CollisionMask(>Enter X coordinate<, >Enter Y coordinate<
Again, this checks the coordinates you specified. If there are no obstacles there, it returns "0". If there is an obstacle it returns "1". If there is a ladder, it returns "2".
It can't detect active objects - for that you'd need to use detectors.
Hopefully clickteam will fix that sometime - I'd like it to return the fixed value of an active if one is present.
Filtered/logical - No idea. Never really understood that, so I don't worry about it. Does it say in the help file?
Edited by Sketchy
n/a
|
Deleted User
|
6th January, 2009 at 16:58:07 -
Alright, the angles lost me again.
"I've also noticed another potential problem with the Angle part - if heading is say, 30 degrees, the player should be visible if bearing = 310. However, 310 > 30+45 so it wouldn't be.
Instead, you need to find the difference between Bearing and Heading:
If Abs(((Bearing - Heading + 540) mod 360) - 180) > 45
* -> player is outside enemy field of view"
It makes perfect sense, but I have no idea where I'm supposed to plug everything in. I have the Bearing figured everytime the player enters within 200 pixels of the enemy and then it calculates the viewing angle by the Absolute value formula then a separate condition checks that if the player is within 200 pixels and the viewing angle is lower than 45 than the character is within view.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 17:07:52 -
Ok, once the player is within 200 pixels;
* calculate bearing using an extension or
Bearing("Enemy") = ATan2(Y("Player") - Y("Enemy"), X("Enemy") - X("Player"))
* calculate difference between heading and bearing
Abs(((Bearing - Heading + 540) mod 360) - 180)
-> if this value is >45, the player is outside the field of view.
-------------------------------------------------
This part we ge rid of altogether:
If Bearing("Enemy") < EnemyHeading - 45
or
If Bearing("Enemy") > EnemyHeading + 45
n/a
|
Deleted User
|
6th January, 2009 at 17:14:17 -
You see, I got it backwards again. I did just that, but instead of it being outside of the view, I said it was inside of the view. Duy.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 17:25:20 -
If you don't mind waiting a day or so, I could probably make an example, if you want (I'm quite tempted to make a massively simplified Commandos:BEH clone myself).
Edited by Sketchy
n/a
|
Deleted User
|
6th January, 2009 at 18:12:48 -
I don't mind. I think I've got it okay, but just in case that might be pretty good to look at.
|
Deleted User
|
6th January, 2009 at 20:01:37 -
Agh. Everything works except the angle thing. I'm debuging it and it comes up with all kinds of large numbers, but it's like the AI notices my player no matter where he's looking.
EDIT: Here's my current Code
Detection process
Always
--Distance Algorithm
--set flag 1 off
Pick one enemy with a distance of less than or equal to 200
--Calculate Beariing
--Calculate Viewing Angle
--set flag 2 off
Pick one enemy over the distance of 200 and internal flag 1 off
--set internal flag 1 on
Pick one enemy with a viewing angle less than 45 and internal flag 2 off
--set internal flag 2 on
If internal flag 1 and 2 are off
--start scan loop
--set internal flag 0 off
On loop scan
--Set the detector x and y
On loop scan when detector touches obstacle
--stop loop scan
--set internal flag 0 on
if flags 0 1 2 are off and no targets
--create target at player
if flags 0 1 2 are off and there is a target
--center target at player
Edited by an Administrator
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 22:09:25 -
Well, I made an example but my usual filehost is down, so I emailed it you (at the address in your profile).
Hope it helps
n/a
|
-J-
Registered 05/10/2008
Points 228
|
6th January, 2009 at 22:12:19 -
It's good to know the differences between the Filtered and Logical OR operators, it doesn't really seem like much of a difference but it can be really useful in some situations
So here they are, I'll explain Logical OR's first as they're easier:
Logical:
----------------------------------------------
+User clicks with left button on "apple"
+ OR (Logical)
+User clicks with left button on "orange"
> Destroy apple
> Destroy orange
----------------------------------------------
In this event, if the user clicks either the apple or the orange, both of them will be destroyed. The OR Logical operator makes all the actions occur if the conditions above and/or below are true. If the user clicked the apple and the orange at the same time, both would still be destroyed. This would have the same outcome if you used the OR Filtered operator...
Filtered:
----------------------------------------------
+User clicks with left button on "apple"
+ OR (Filtered)
+User clicks with left button on "orange"
> Destroy apple
> Destroy orange
----------------------------------------------
In this event, if the user clicked the apple then only the apple will be destroyed! Same goes for orange, so no matter which fruit the user clicks, the one that is clicked will be the only one destroyed. The filtered OR operator will go through all the conditions of the event, and mark all the conditions as "inspected". The conditions that have returned false are also marked, but the action related to them will not occur. So only the actions related to the true condition above or below the OR Filtered operator will occur. Which also means that if the conditions on both sides of the OR Filtered operator return true, then every event will occur. So if the player clicks the apple and the orange at the same time, both will be destroyed, which would give the same outcome no matter which OR operator you used.
n/a ...
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
6th January, 2009 at 22:24:59 -
Thanks
That explanation might be worthy of being posted in the articles section?
Here's the new and slightly improved example file:
http://cid-b1e7ee094271bbda.skydrive.live.com/self.aspx/Public/LoSeg.mfa
There's one minor flaw that I'm aware of - let's see how long it takes you to figure it out...
Edited by Sketchy
n/a
|
-J-
Registered 05/10/2008
Points 228
|
6th January, 2009 at 23:48:43 -
Thanks I was thinking of writing an article on a couple of things (that included) which reminds me, (this is slightly off topic) but do you have any idea on how to get vitalize working within an article?
Edited by -J-
n/a ...
|
Deleted User
|
7th January, 2009 at 00:18:05 -
@sketchy: thanks alot man, I'll take a look at it tonight.
@julian: That's very useful to know actually. I figured it was either something like that or if you had multiple commands for each or statement such as:
if player does such and such
and such
or
this
if player does such and such
and such
or
if player does such and such
and this
|
-J-
Registered 05/10/2008
Points 228
|
7th January, 2009 at 01:21:19 -
Just testing something. Word.
Edited by -J-
n/a ...
|
Deleted User
|
7th January, 2009 at 06:32:34 -
I don't know if I'm retarded or what but I've done everything exactly as the example shows, but it just gets all jacked up. It's the whole angle part, for whatever reason it calculates stupid, it's not even close. I can't really even describe it. Does the fact that I'm using 32 directions count for anything? I hope that's all it is. I'll give it a shot tommorrow. Thanks anyways for your help guys.
|
-J-
Registered 05/10/2008
Points 228
|
7th January, 2009 at 10:51:36 -
Using 32 directions and then setting the "direction" of the object through the event editor won't give you a 360 degree angle number. Instead of using "set direction to", under the Direction tab, go down to the Scale/Angle tab and choose "set angle" and then enter the angle calculations, and then 0 or 1 depending on whether you want speed or quality.
Not sure if that helps but I hope it does
n/a ...
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
7th January, 2009 at 13:06:57 -
Does my example work for you?
If it does, and it's only your implementation that doesn't, then I'd probably need to see your file to be able to help.
My example uses only angles (not MMFs "directions").
If for some reason, you only want 32-directions, it's easy to convert between angles and directions:
Angle = Direction * 11.25
Direction = Angle / 11.25 (kind of obviously)
n/a
|
Deleted User
|
7th January, 2009 at 18:43:53 -
Nevermind, I've got it. Using the advanced math objects convert directions to angle function, I set the current angle to always be that conversion of the enemies direction. I also needed to tweak the distance calculation a bit, but now it seems to work fine.
For the example though, during each LoS check, I would add "turn flag 0 off" anytime you "deactivate group line of sight" so that way the enemy will stop blinking when you walk through the area. Also, I don't know if you need to, but the last line, "if flag 0 is on" I added the ID check before that, rotations using the direction calculator and "activate group line of sight". I don't know if the ID check or the group activation is necessarry. I'll test it though. I'm sure you know this already but just in case someone stumbles across this thread and goes "gdam it what ter hell!>?lol"
Once again, thanks alot for your help guys, you have no idea how exciting it is to actually have a project going that looks and feels very professional. I'll be sure to credit both of you when I complete this game.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
7th January, 2009 at 21:36:02 -
Okay, I'll come clean
After further experimentation, it may not be quite as stable as I'd have liked
Yes, you should turn flag0 off, when the player isn't visible. Have you tried it though? I couldn't make it work. To be honest, I've been trying to improve it, and small changes to completely unrelated events seem to totally screw everything up. Good luck though
Using extensions is a good idea. I couldn't get my example to run at all with HWA, until I replaced the angle formula with an extension (weird, cos it worked fine with the normal build).
I think "machine independant speed" is essential. I get the impression it goes wrong if it can't maintain the proper framerate.
There's no need to add "if flag 0 is on" or "activate group line of sight", on the last line - I guess it won't do any harm either though.
The way it works, it only checks the line of sight for one enemy each frame. This means that with 10 enemies and a frame rate of 50fps, it can take up to 0.2 seconds for the player to be detected after they enter the enemy's line-of-sight (0.1 secs on average).
You *could* make it check every enemy, every frame, but there are two main problems with that;
1.) 10x as many enemies take 10x as long to calculate LoS for. It could easily end up slowing down the whole game if you have enough enemies (I suspect that would make it pretty unstable too).
2.) It's a humongous pain in the arse to code. It would require nested fastloops (fastloops inside fastloops inside fastloops). I spent quite a while trying, but it's beyond me. Don't let that stop you trying though.
I'm quite impressed with it myself actually - I made some of the enemies move around a bit and rotate, and it was almost like a game in itself
I don't know what kind of a game you're making, but you should make it so you can crawl - increasing the minimum range at which you're detected, but making you move slower and unable to perform actions until you stand up. All it would take is 1 extra event and some more frames of animation.
Incidentally, I found pixelthiefs example on my harddrive - couldn't make it work - just kept crashing (again, weird, cos it always used to work).
Edited by Sketchy
n/a
|
Deleted User
|
7th January, 2009 at 22:28:15 -
Well, I messed with it a bit more, and instead of having the enemy look directly at the player, I've created a target object, that I will most likely replace with a an xy val similar to the detection process. So when the player is in the line of sight, a target object is either placed on the player or created based on if there is one or not. If the enemy is in attack mode when it reaches the target, the target gets destroyed, runing a looking script. If the enemy is not, the target simply goes to the next spot that is currently just a random value around the area of the enemy. Also, instead of telling the flag to turn off during the line of sight checked, I did one of those follow counters.
I also added another event that if you are within say 50 pixels of the enemy and the player speed is greater than 15, then the enemy will turn around. I might make another for the longer distance, but I'm going to have to revamp the code alot.
The biggest problem I'm seeing is that, like you said, there is a lag in the calculations, several times the enemy just kind of forgets what he's doing and starts going "OOH SHINY OBJECT" and stops following the player. While maybe that might be hilarious to have as a lower difficulty style enemy, I'd like to work with it to find a better solution. Also I can see with the visual target object that the target detection is going much slower than the gameplay itself.
Unfortunatly the way it looks is that I'm going to have to get rid of the spread values and make it specific to each enemy, replace the calculations with extentions and see how many enemys I can ultimatly have on screen before it makes a noticable difference. So about 2. Haha.
If you can get pixeltheif's system to work, that'd be awesome, I have a node detection example of his that I'm going to try to work into it somehow.
As far as the style of game I'm making, maybe when I finish up the system a bit more and get something other than test gfx I'll upload it to the projects section.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
7th January, 2009 at 23:14:27 -
To be honest, I didn't really understand most of that (or any of the first paragraph infact)
"I also added another event that if you are within say 50 pixels of the enemy and the player speed is greater than 15, then the enemy will turn around. I might make another for the longer distance, but I'm going to have to revamp the code alot."
That part I get. It's like the enemy would be able to hear you running, right?
There are lots of little things like that, which you could add very easily for extra realism. Crawling was one such idea. Also, the enemy could have a narrower field-of-vision when the player is not moving - it's much easier to notice things in your peripheral vision if they're moving. Other ideas taken from "Commandos" - have the player leave tracks in snow/sand; make the player able to move and hide bodies.
I'm not sure why the lag should be a problem. Every 1/10th of a second is pretty often. If the enemies are getting distracted too easily, can't you just put in some kind of a delay before they stop following?
"Unfortunatly the way it looks is that I'm going to have to get rid of the spread values and make it specific to each enemy, replace the calculations with extentions and see how many enemys I can ultimatly have on screen before it makes a noticable difference. So about 2. Haha."
btw: It turned out, the reason it wasn't working in the HWA build of MMF, is that the latest "regular" build is actually more recent - HWA just doesn't have the ATan2 function yet
TRY THIS: http://cid-b1e7ee094271bbda.skydrive.live.com/self.aspx/Public/LoSeg3.mfa
Edited by Sketchy
n/a
|
Deleted User
|
8th January, 2009 at 01:37:01 -
Oh well basically with the target object, it's just a separate object that acts as the enemy's current goal. Right now it just kind of goes around randomly and the enemy chases it, I want to make up a kind of network of active objects that the target moves to, then when the enemy reaches that target it moves on to the next one. But it sees the player, the target moves to the player and should stay there untill the player is out of the line of sight, and if the player gets far enough away, the enemy will run into that target, stop, look around, then go do whatever. It's basically a precursor to more complicated tasks for the enemies, like reporting in, taking a break, whatever I think of in the future, and also most importantly, taking the players to jail.
This build looks alot better. I'll see if I can implement most of it into my system.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
8th January, 2009 at 18:06:13 -
Yay!
The definitive version;
http://cid-b1e7ee094271bbda.skydrive.live.com/self.aspx/Public/myLoS.mfa
I finally fixed all the bugs - it's now rock solid, and more efficient too.
It comfortably handled 20 on screen enemies (didn't try with more than that), and that's on my old 700mhz pc!
I don't remember everything I changed, but I know I added lots of "flag 0 is on" conditions all over the place, so it doesn't go through the whole rest of the process once it's already found a LoS impossible.
Also, and this one took me FOREVER to figure out - "CurrentHeading" mustn't get too big. Basically any time you change it, add "mod 360" to the end and it'll be ok.
eg;
to rotate...
Always -> Set CurrentHeading to (CurrntHeading + 1) mod 360
Edited by Sketchy
n/a
|
Deleted User
|
8th January, 2009 at 19:02:47 -
Top notch my friend. Thank you for all your help. You should upload it to the download section for the rest of the other chitlins to look at.
|
Sketchy Cornwall UK
Registered 06/11/2004
Points 1971
|
8th January, 2009 at 19:14:05 -
Thanks. I probably will.
I've done some more thorough testing - on my pc (700mhz like I said before), it can maintain 50fps with up to about 275 enemy objects on screen. That's with no "cone-of vision" objects, and with the enemies just slowly rotating. (now I'm just boasting )
Still, I reckon if the rest of your game is well coded, I don't think the LoS part is going to slow it down much.
Better solution to "CurrentHeading" problem - add a line at the beginning of the Line of sight group:
always -> set "CurrentHeading" to "CurrentHeading" mod 360
much simpler, and definitely won't mess anything else up
Edited by Sketchy
n/a
|
|
|
Post Reply
|
|