Talking about Security game, and Flash.
July 27th update: part 3 released
This is the second part of the creation of a flash game like Security. In the first part we saw how to create the maze, the player and the “cop”.
Now, we’ll see how to create a “smart” cop.
I did not like how the orginal game managed the “line of sight”, so I decided to try coding something different and closer to the reality (well, if a blue circle is supposed to be a “cop” we are not so close to reality, anyway…).
The line of sight
Let’s imagine what is a “line of sight”: basically, it’s a line that connets the “cop” to the objects the cop can see. In our case, the only thing we need our cop to see, is the player. So, in next example, the line of sight is a line that goes from the cop to the player.
I created an object representing a green line and I dragged to the scene, instancing it as “line”.
Then, in the COP actions I wrote this actionscript
onClipEvent (enterFrame) {
dist_x = _root.hero._x-_x;
dist_y = _root.hero._y-_y;
angle = Math.atan(dist_y/dist_x)/(Math.PI/180);
if (dist_x<0) {
angle += 180;
}
if (dist_x>=0 && dist_y<0) {
angle += 360;
}
_root.line._x = _x;
_root.line._y = _y;
_root.line._rotation = angle;
}
Lines 2-10: Determine the angle between the cop and the player using trigonomerty basics as explained in Create a flash draw game like Line Rider or others - part 3 tutorial.
Lines 11-12: Place the line _x and _y in the same position of cop's _x and _y
Line 13: Rotates the line of angle degrees
Try to move the player, and you will see the cop line of sight following him.
Hey! our cop is not Superman! He can't see through walls!
The line of sight (limited by walls)
We have to have the cop see the player only when the line of sight hits the player before hitting any wall.
The new cop actionscript is:
onClipEvent (enterFrame) {
dist_x = _root.hero._x-_x;
dist_y = _root.hero._y-_y;
dist = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
angle = Math.atan(dist_y/dist_x)/(Math.PI/180);
if (dist_x<0) {
angle += 180;
}
if (dist_x>=0 && dist_y<0) {
angle += 360;
}
wall_collision = 0;
for (x=1; x<=dist; x++) {
point_x = _x+x*Math.cos(angle*Math.PI/180);
point_y = _y+x*Math.sin(angle*Math.PI/180);
if (_root.wall.hitTest(point_x, point_y, true)) {
wall_collision = 100;
break;
}
}
_root.line._x = _x;
_root.line._y = _y;
_root.line._rotation = angle;
_root.line._alpha = 100-wall_collision;
}
Line 12: Declare a variable called wall_collision to zero
Line 13: Loop to be executed as much times as the distance between the cop and the player, in pixels. This can be CPU expensive if you have large stages or lots of cops. In this case, you can adjust the "x++" condition with, in example, "x+=3". This will make the process faster but less accurate. It's up to you to find the right gameplay balance.
Lines 14-15: Determining the x and y coordinates of the x-th pixel from the cop in direction of the player, using trigonomerty
Line 16: Test the collision between the pixel just determined and the wall
Lines 17-18: If the test is positive, then the cop, looking in the direction of the player, won't see the player because there is a wall in the middle. So I set the wall_collision variable to 100 and break the loop to save CPU
Line 24: Set the alpha of the line at 100-wall_collision... in other words to opaque if the cop sees the player, and transparent if the cop does not see the player. This is only for testing the line of sight, of course... in a real game you can make the cop fire (we'll se how in another tutorial) or call a game over screen.
Try to move around the screen and you will see the cop will spot you only if there are no walls between the player and the cop.
You may say: why don't just perform an hit test between the line of sight and the walls, having the line of sight going exactly from the cop to the player? This is an idea, but will prevent to code special stuff such as mirrors, special walls that deflects the line of sight in a strange way, and so on.
Once the cop sees correctly, it's time to make him patrol the area
The line of sight (limited by walls) of a moving cop
To have the cop patrolling the area, I removed the cop movieclip from the stage and inserted another one, called cop_patrol.
This movieclip consists in the cop himself moving around using a guide layer.
So in the main scene we do not have the cop movieclip but the cop_patrol one. In the cop movieclip (inside the cop_patrol) the actionscript is:
onClipEvent (enterFrame) {
dist_x = _root.hero._x-(_x+_root.cop_patrol._x);
dist_y = _root.hero._y-(_y+_root.cop_patrol._y);
dist = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
angle = Math.atan(dist_y/dist_x)/(Math.PI/180);
if (dist_x<0) {
angle += 180;
}
if (dist_x>=0 && dist_y<0) {
angle += 360;
}
wall_collision = 0;
for (x=1; x<=dist; x++) {
point_x = (_x+_root.cop_patrol._x)+x*Math.cos(angle*Math.PI/180);
point_y = (_y+_root.cop_patrol._y)+x*Math.sin(angle*Math.PI/180);
if (_root.wall.hitTest(point_x, point_y, true)) {
wall_collision = 100;
break;
}
}
_root.line._x = (_x+_root.cop_patrol._x);
_root.line._y = (_y+_root.cop_patrol._y);
_root.line._rotation = angle;
_root.line._alpha = 100-wall_collision;
}
As you may notice, the actionscript is the same as before, but all pixel measurements are taken adding to the _x and _y values (the cop position) the _root.cop_patrol_x and _root.cop_patrol_y values (the position of the cop_patrol movieclip).
I had to do this because the _x and _y values are relative to the parent movieclip, in this case the cop_patrol movieclip... so I have to add to these values the position of the cop_patrol movieclip to have the real coordinates.
Now we have a patrolling cop that will see the player only if there are no walls between the player and the cop.
That's almost perfect, but I didn't know cops had eyes on their back... so we need the cop to see only in the direction they are walking.
The line of sight (limited by walls) of a moving cop seeing only in front of him
We need to determine the direction the cop is walking in. I did changing the cop's actionscript in this way:
onClipEvent (enterFrame) {
dist_x = _root.hero._x-(_x+_root.cop_patrol._x);
dist_y = _root.hero._y-(_y+_root.cop_patrol._y);
dist = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
angle = Math.atan(dist_y/dist_x)/(Math.PI/180);
if (dist_x<0) {
angle += 180;
}
if (dist_x>=0 && dist_y<0) {
angle += 360;
}
wall_collision = 0;
for (x=1; x<=dist; x++) {
point_x = (_x+_root.cop_patrol._x)+x*Math.cos(angle*Math.PI/180);
point_y = (_y+_root.cop_patrol._y)+x*Math.sin(angle*Math.PI/180);
if (_root.wall.hitTest(point_x, point_y, true)) {
wall_collision = 100;
break;
}
}
dist_old_x = _x-old_x;
dist_old_y = _y-old_y;
cop_direction = Math.atan(dist_old_y/dist_old_x)/(Math.PI/180);
if (dist_old_x<0) {
cop_direction += 180;
}
if (dist_old_x>=0 && dist_old_y<0) {
cop_direction += 360;
}
old_x = _x;
old_y = _y;
sight = angle-cop_direction;
if (((sight<30)and(sight > -30))or(sight>330)) {
_root.line._x = (_x+_root.cop_patrol._x);
_root.line._y = (_y+_root.cop_patrol._y);
_root.line._rotation = angle;
_root.line._alpha = 100-wall_collision;
} else {
_root.line._alpha = 0;
}
}
Lines 21-22: Determine the distance from the current cop position and the previous cop position. In the first frame, these values will be undefined.
Lines 23-29: Determine cop direction using trigonometry
Lines 30-31: Save the current cop position (that will be used as the old cop position next time lines 21-22 will be called)
Line 32: Determine the angle of sight, according to the cop direction and the absolute angle between the cop and the player
Lines 33-40: Determine if the angle of sight is in the cone of 60 degrees in the cop direction. In that case, show the line (if there are not walls between the cop and the player). If the angle of sight is not in the cone, don't show the line.
Now you will notice the cop will "see" you only if you are standing in front of him.
This is how a line of sight should be.
Download the source codes and give me feedback, then learn how to place exits and have a second agent in next step
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.