Chapter 3: Shooting
It’s time to add the part that basically defines an FPS, without it it’s just an FP, we need that S damnit! So, we’re going to add shooting, here’s what will be covered in this Chapter:
- Clicking the mouse to shoot
- Reducing ammo as we shoot
- Forcing us to reload when the clip has run out
- Disallowing us to shoot/reload when we’ve run out of clips
- Obtaining ammo to increase the amount of clips we have
- Changing weapons
- Killing an enemy after shooting them a given amount of times
- Adding mouse looking for the First Person part of FPS
- Adding a reticule to the screen to show where we’re aiming
- Showing how much ammo and clips we have left on screen as well
Part I: Adding a Limb
Its time to add shooting. So, since bullets (at least now-a-days) go so fast you can barely see them, there’s no need to be creating bullet objects. Instead, we’re going to use the Intersect Object command (DBP only). This command checks to see if two vertexes cross each other in the program. What we’re going to do is position a limb far away from the object, and cast an intersection ray from that limb to the object (our character) and check if anything is in between the two points. If so and the user is clicking the mouse, we’ll handle appropriately.
So, go way back to the beginning of the code, just before we positioned/pointed the camera. Click the blank line above the camera code and enter down a few times. Now type this code:
MAKE OBJECT SPHERE 9999,30
MAKE MESH FROM OBJECT 1,9999
DELETE OBJECT 9999
ADD LIMB 1,1,1
OFFSET LIMB 1,1,0,0,500
This code will add a limb numbered 1 to our character object and position it far away from the character (500 units away). The sphere number is 9999 because we don’t want to create an object that already exists, so we’re starting this one at 9999. Just to be safe. Before we go any further I should explain what a limb is first.
Every complex object is built up from limbs. Say you had a skeleton model for your game, and the skeleton had all the usual components (arms, legs, backs, feet, hands, head, torso). The parent object would be the body or torso, this would be the object you move around in a game. In our case, object 1. Then, we would add limbs to the parent object to create arms and legs and the other ligaments.
Limbs take on the exact position, rotation, and movement of any object. So if we were to add a limb to object 1, it would follow and turn wherever they went, kind of like an arm. We can then manipulate limbs to be offset from the center position of the user.
Limbs need to have a mesh number so they know what they’ll look like, so we obtain a mesh number by making a mesh from the previously created sphere. Once we have the mesh, we can delete the sphere for less lag. Now that we have the mesh object, we can add a limb. The ADD LIMB syntax requires an object number, a limb number and a mesh number, in that order respectively. We’ve added the limb to object number 1, given the limb number 1, and created the limb from our mesh number 1.
Now that we have a limb, we can delete the mesh for even less lag. Add this code:
Now, run the game again and you’ll notice that the sphere will be positioned far in front of you, and rotate as you turn. Try moving in all directions, it will stay with you all the time. When you’re done, come back here!!!
Alright, now its time to reduce some lag. In our case we aren’t using the limbs as parts to be shown like arms, but instead a position to shoot a ray back towards us to detect when there’s an object in front of us. Thus, since the limb won’t need to be seen, we will make it as small as possible and as simple as possible and then hide it to reduce lag. Plains are the simplest objects since they only have one side, so change the object creation code from making a sphere to this:
MAKE OBJECT PLAIN 9999,1,1
And then, add this code to the end of the section of limb code:
Now run the code again and you’ll notice that the limb doesn’t appear, this is because we’ve hidden it.
Delete the HIDE LIMB code for now, we’re going to want to see the limb for the next while, we’ll hide it later on once we have a target reticule in place.
Part II: Clicking the Mouse to Shoot
So, we have a limb in place, now we’re going to implement shooting at a very basic level, and then slowly make it better. Currently, we’re going to just delete the object when we shoot with the mouse, instead of reducing health, and the user will have unlimited ammo.
Scroll yourself to the DO/LOOP section, and start typing right after the movement commands. Enter down some more times to separate this from the movement code so we don’t get confused.
Add this code:
IF OBJECT EXIST(2)=1
IF INTERSECT OBJECT (2, LIMB POSITION X(1,1), LIMB POSITION Y(1,1), LIMB POSITION Z(1,1), OBJECT POSITION X(1),OBJECT POSITION Y(1), OBJECT POSITION Z(1))>0
DELETE OBJECT 2
ENDIF
ENDIF
There’s that intersect object command. Intersect Object can be quite confusing, here’s the syntax of how we call it:
Return Float=INTERSECT OBJECT(Object Number, X, Y, Z, ToX, ToY, ToZ)
We replace the object number with the object we want to check for intersection with, in this case it being the enemy or red sphere, so object 2. The x, y, and z specify where the intersection ray will start, and the ToX, ToY, and ToZ specify where the intersection ray will travel to to detect intersection between.
Where it says Return Float will be the number the compiler will return when an intersection occurs. This number equals the distance from the intersection to the beginning X, Y, and Z positions.
In our case, we’re sending the ray from our limb to the user’s object. So by using the LIMB POSITION commands DBP has, we can find the X, Y and Z position of our limbs by specifying the object number and limb number in the brackets. Then we use the OBJECT POSITION commands to give the destination point by putting a 1 in the brackets.
So, now that you understand the intersection command (I think), we can move on. If you’re still having troubles with the command just use the Index in DBP to find the intersection command and get a more detailed explanation of it, or just ask in this forum.
The rest of the code is pretty simple, basically IF the INTERSECTION distance is >0 (> means greater), we delete the object. The other command at the top, IF OBJECT EXIST(2)=1 checks to see if Object 2 exists, if it does then it will go through with the rest of the code, if not it will skip the checking. This is handy because it reduces lag and will stop the computer from freezing when it tries to delete an object that doesn’t exist.
Now you can run the game again. You’ll notice that when you face the red sphere, it will be deleted. Once again, when you’re done, get yer’ ass back here.
If you’ve returned...
FPS’s usually don’t just delete objects when you face them, this is a very basic form of deletion. Now, we’re going to customize the process even more and require the user to click the left mouse button and be facing the object in order to delete it.
Add this code before the IF OBJECT EXIST(2)=1 part:
IF MOUSECLICK()=1
...and then add another ENDIF below the 2 ENDIFS you currently have.
Run the code once again, and face the red object. Then, press the left mouse button to delete the object.
The code is pretty self explanatory, it checks IF the MOUSE is CLICK()ed then follow through with the rest of the code. Now it’s time to add some ammo and reloading.
Part III: Ammo and Reloading
Ok, we want even more realism to this game, so we’re going to add limitations to how much ammo the user has and a reloading capability to stop the user from shooting all their bullets at once, and give the enemy some health. Plus we’ll show some onscreen information.
Go back to the camera positioning, and click below it. Enter down a few times. We’re going to define some variables, but before that Ill explain what variables are.
You can think of variables like cards. Each card has a distinct name and a piece of information along with it. There are several types of variables: Strings, Integers, and Real Numbers. You distinguish variables by the symbol at the end of the variable name. Strings have a $ at the end of their name, Integers have a # sign at the end of them, and Real Numbers have no symbol. We won’t be using Real Numbers.
Here’s an example of an Integer variable called IntegerVariable, along with how we would define it.
This code tells the compiler that there’s a variable named IntegerVariable, and that it is of the type Integer, and that it equals 200. We can further define variables by stating if they’re global, but that’s another subject. Here’s an example of a String Variable:
StringVariable$=”This is a string variable”
String Variables can contain text and must be defined in the quotes. Integer variables can’t have any text in them. This code tells the compiler that there is now a String variable named StringVariable$ that equals “This is a string variable”.
Now then, with your typing mark below the camera positioning junk, type the following:
EnemyHP#=5000
Ammo#=1000
MaxAmmo#=1000
Now, go back to the shooting code with the intersection command and delete all of the code that had to do with shooting in the loop, we’re going to neaten it up and add more.
Replace with this:
IF MOUSECLICK()=1
IF Ammo#>0
DEC Ammo#,1
ENDIF
IF OBJECT EXIST(2)=1
IF Ammo#>0
IF INTERSECT OBJECT (2, LIMB POSITION X(1,1), LIMB POSITION Y(1,1), LIMB POSITION Z(1,1), OBJECT POSITION X(1),OBJECT POSITION Y(1), OBJECT POSITION Z(1))>0
DEC EnemyHP#,1
ENDIF
ENDIF
ENDIF
ENDIF
Here’s an explanation of what’s going on now:
If the user clicks the mouse and the enemy object exists and the user’s AMMO# variable is greater than 0 (which means the user can then shoot) and there’s an object intersecting between the user and the limb (thus something is in front of the user) and the object is the enemy object, decrease the EnemyHP# variable by 1 (DEC stands for decrease, same as INC stands for increase), and decrease the user’s AMMO# variable by 1. Then end all of the if statements to wrap up.
Like said above, DEC decreases a variable by the amount given, the syntax is DEC variable, amount. Above we’re checking if the Ammo# variable is greater than 0 which means the user has ammo left, so they can shoot, and we always decrease the Ammo# variable by 1 even if the user isn’t facing an enemy, as long as the mouse is being clicked.
Now, below this code, add this:
IF EnemyHP#=0
IF OBJECT EXIST(2)=1
DELETE OBJECT 2
ENDIF
ENDIF
This code checks if the EnemyHP# variable equals 0, if it does the enemy must be dead so we delete the object.
Finally, we’re going to display some onscreen information. Enter the following:
IF OBJECT EXIST(2)=1
CENTER TEXT OBJECT SCREEN X(2),OBJECT SCREEN Y(2)-70,"Enemy Health: "+str$(EnemyHP#)
ENDIF
This now introduces the TEXT command. Using the TEXT command we can specify the X and Y coordinate of where we want to display text, and then specify what text we want to show. The coordinates of a screen are much different than the coordinates of the 3D world. For example:
The X, Y and Z coordinates in the 3D world start at 0,0,0, but on the screen the X and Y coordinates start in the upper left, and there’s no Z since the screen has no depth.
The TEXT command comes with a lot of customizable commands for it, things like BOLD, ITALIC, UNDERLINE, or what we’re using, CENTER. Using the CENTER TEXT command we find the middle point of the text string and position it accordingly.
So, what we’re doing above is checking if the enemy object exists (because if it isn’t we cant display text over it), then if it does, using the TEXT command to display “Enemy Health: “and then the variable EnemyHP#. Since EnemyHP# is an Integer and not a String, we need to convert it to a String. That’s what the +str$() command does.
Now, the final part of the code to explain is the OBJECT SCREEN X() and OBJECT SCREEN Y(). Like I said above, the screen coordinates are not the same as the world coordinates, the SCREEN X and SCREEN Y commands take over the task of converting world coordinates to screen coordinates. We specify what object in the brackets. The reason we’re subtracting 70 so the text will be above the object, not right in it’s position.
Ok ok I know I didn’t explain this bunch of code too well so if you have any questions feel free to post.
Finally, we’re going to display the user’s Ammo# variable on the screen as well, but this won’t be following our character, instead it will be in the bottom left. Type the following below the last bunch of code you typed:
TEXT 0,SCREEN HEIGHT()-50,"Ammo: "+str$(Ammo#)
Once again using the TEXT command we place the cursor position at the far left, and to the bottom. The SCREEN HEIGHT() command returns the screen’s height for the current screen being used, then we subtract 50 so it’s not displayed right at the bottom. Using the screen height command we can now position the text in the same place no matter what the screen resolution. The rest is the same as the EnemyHP# bit, but instead with Ammo#
Ok, if you want you can test it, basically you’ll be able to shoot the enemy until your ammo equals 0. So, we need to code in a reload button. Here’s how will do it. Just above the text display code, type the following:
IF KEYSTATE(19)=1 AND Ammo#=0 THEN Ammo#=MaxAmmo#
That little bit of code checks if the user is pressing the R button and if the Ammo# variable equals 0, if so it automatically reload the Ammo# to the MaxAmmo# amount.
Like I said way back in the beginning, only the main keys are given actual names. But keys like letters aren’t, instead they’re given scan codes. Each key has a distinct scan code, and R just happens to have 19 as it’s scan code. Using the keystate command we can check if a key is being pressed with the scan code 19.
Before we go any further, here’s what we have so far:
`Created by <your name>
`Date Started: <current date>
SYNC ON:SYNC RATE 0:HIDE MOUSE
MAKE OBJECT SPHERE 1,50:COLOR OBJECT 1,RGB(000,255,000)
MAKE OBJECT SPHERE 2,50:COLOR OBJECT 2,RGB(255,000,000):POSITION OBJECT 2,130,0,0
MAKE OBJECT BOX 3,100,100,5:POSITION OBJECT 3,0,0,150
MAKE OBJECT SPHERE 9999,30
MAKE MESH FROM OBJECT 1,9999
DELETE OBJECT 9999
ADD LIMB 1,1,1
OFFSET LIMB 1,1,0,0,500
DELETE MESH 1
POSITION CAMERA 0,500,-500:POINT CAMERA 0,0,0
EnemyHP#=5000
Ammo#=1000
MaxAmmo#=1000
DO
IF UPKEY()=1 THEN MOVE OBJECT 1,.5
IF DOWNKEY()=1 THEN MOVE OBJECT 1,-.5
IF LEFTKEY()=1 THEN TURN OBJECT LEFT 1,.7
IF RIGHTKEY()=1 THEN TURN OBJECT RIGHT 1,.7
IF MOUSECLICK()=1
IF Ammo#>0
DEC Ammo#,1
ENDIF
IF OBJECT EXIST(2)=1
IF Ammo#>0
IF INTERSECT OBJECT (2, LIMB POSITION X(1,1), LIMB POSITION Y(1,1), LIMB POSITION Z(1,1), OBJECT POSITION X(1),OBJECT POSITION Y(1), OBJECT POSITION Z(1))>0
DEC EnemyHP#,1
ENDIF
ENDIF
ENDIF
ENDIF
IF EnemyHP#=0
IF OBJECT EXIST(2)=1
DELETE OBJECT 2
ENDIF
ENDIF
IF KEYSTATE(19)=1 AND Ammo#=0 THEN Ammo#=MaxAmmo#
IF OBJECT EXIST(2)=1
CENTER TEXT OBJECT SCREEN X(2),OBJECT SCREEN Y(2)-70,"Enemy Health: "+str$(EnemyHP#)
ENDIF
TEXT 0,SCREEN HEIGHT()-50,"Ammo: "+str$(Ammo#)
SYNC
LOOP
But there’s an awful lot of ENDIF statements in there that aren’t needed. Remember the colon? Meet its brother: AND. Using AND we can combine IF statements, like this:
`Created by <your name>
`Date Started: <current date>
SYNC ON:SYNC RATE 0:HIDE MOUSE
MAKE OBJECT SPHERE 1,50:COLOR OBJECT 1,RGB(000,255,000)
MAKE OBJECT SPHERE 2,50:COLOR OBJECT 2,RGB(255,000,000):POSITION OBJECT 2,130,0,0
MAKE OBJECT BOX 3,100,100,5:POSITION OBJECT 3,0,0,150
MAKE OBJECT SPHERE 9999,30
MAKE MESH FROM OBJECT 1,9999
DELETE OBJECT 9999
ADD LIMB 1,1,1
OFFSET LIMB 1,1,0,0,500
DELETE MESH 1
POSITION CAMERA 0,500,-500:POINT CAMERA 0,0,0
EnemyHP#=5000
Ammo#=1000
MaxAmmo#=1000
DO
IF UPKEY()=1 THEN MOVE OBJECT 1,.5
IF DOWNKEY()=1 THEN MOVE OBJECT 1,-.5
IF LEFTKEY()=1 THEN TURN OBJECT LEFT 1,.7
IF RIGHTKEY()=1 THEN TURN OBJECT RIGHT 1,.7
IF MOUSECLICK()=1
IF Ammo#>0 THEN DEC Ammo#,1
IF OBJECT EXIST(2)=1
IF Ammo#>0 AND INTERSECT OBJECT (2, LIMB POSITION X(1,1), LIMB POSITION Y(1,1), LIMB POSITION Z(1,1), OBJECT POSITION X(1),OBJECT POSITION Y(1), OBJECT POSITION Z(1))>0 THEN DEC EnemyHP#,1
ENDIF
ENDIF
IF EnemyHP#=0 AND OBJECT EXIST(2)=1 THEN DELETE OBJECT 2
IF KEYSTATE(19)=1 AND Ammo#=0 THEN Ammo#=MaxAmmo#
IF OBJECT EXIST(2)=1 THEN CENTER TEXT OBJECT SCREEN X(2),OBJECT SCREEN Y(2)-70,"Enemy Health: "+str$(EnemyHP#)
TEXT 0,SCREEN HEIGHT()-50,"Ammo: "+str$(Ammo#)
SYNC
LOOP
Alright, run the code and see what happens. When you reach 0 ammo, you can reload with R. If you look at the enemy for the entire time you shoot then it should take 5 reloads. Next up on the list, adding clip limitation and making it harder for the user to reload by adding a delay between reloads.
I’m off to watch a movie, Ill get back to work on this tomorrow. For now, try:
- Changing the speed the player moves and turns at
- Increasing/Decreasing the amount of ammo the user has
- Changing the program to killing the wall and not the red sphere