Dark Basic Collision (DBC & DBP)
Very few games can be written without some form of collision. You need a way to stop your character from walking through walls/other characters or dropping through the floor he/she is walking on. This is what the collision commands are for.
In a nutshell, the DB collision commands simpy alert you when certain objects bump into each other. This is in 3D by the way - sprites in 2D also have collision, but sprites are the topic of another tutorial.
Before we start though, I have to explain that you will find a number of third party aids to help with collision - like Sparky's external dll, because it's widely reported that DB's in-built collision commands are neither all that accurate nor particularly fast when put to the test. I've not had cause yet to stretch them that far, so I cannot confirm this from personal experience but have to believe that this is probably true.
However, having said that, I think it's important to get to know how the built-in commands are used. Try them. Once you understand how they work, implementing a third party solution is made that much easier. What's more, you may even find that the DB collision commands are actually more than enough for your needs - without you having to go away and try and learn a new set of commands.
The two basic collision commands in DB are:
OBJECT HIT(ObjNumA, ObjNumB)
OBJECT COLLISION(ObjNumA, ObjNumB)
The first command alerts you if object A bumps into object B, whereas the second lets you know if object A is overlapping object B.
The two parameters ObjNumA and ObjNumB are your main control character's object number (A) and the object you are testing for collision with (B) - both are integers.
Copy and paste the following code in the DB classic editor and run it. Use the cursor keys in this top-down view to run the red cube into the white one.
Sync On: CLS 0
Sync Rate 60
AutoCam Off
Hide Mouse
Make Matrix 1,2000,2000,20,20
Create Bitmap 1,128,128
CLS RGB(0,128,0)
Get Image 1,0,0,128,128
Set Current Bitmap 0
Delete Bitmap 1
Prepare Matrix Texture 1,1,1,1
Make Object Cube 1,3: Rem Our Cube
Position Object 1,1000,3,990
Color Object 1,RGB(255,0,0)
Make Object Cube 2,3
Position Object 2,1000,3,1000
Position Camera 1000,50.0,980
Point Camera 1000,3,980
Do
If UpKey()=1 Then Move Object 1,0.1
If DownKey()=1 Then Move Object 1,-0.1
If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)
If RightKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)+2.0)
ObjPosX#=Object Position X(1)
ObjPosY#=Object Position Y(1)
ObjPosZ#=Object Position Z(1)
Position Camera ObjPosX#,30.0,ObjPosZ#
If OBJECT HIT(1,2)=1
Color Object 2,0
Sleep 20
Repeat
Until OBJECT HIT(1,2)=0
Color Object 2,RGB(255,255,255)
Endif
Sync
Loop
End
You will have noticed that when you bump into the white cube, it flashes black. In the code, you can see that the colour of the stationary cube is set when OBJECT HIT(1,2) returns a 1. The Repeat...Until loop immediately after this executes continuously until OBJECT HIT(1,2) returns a 0, at which point the stationary cube is returned back to white.
However when the program is running and you hit the other cube, it turns black then immediately returns to white and you continue passing through the other cube.
Passing through the other cube is what should happen because we haven't yet written any code to handle what happens when a collision does occur, (it's not automatic).
As for the colour flashing, that's because OBJECT HIT does just that - it detects the first contact between two objects. It isn't triggered again while the two objects are colliding - you need to move your cube completely away from the other to end the collision and then bump back into it again to trigger another hit.
So how do we know if a collision is still occuring after the initial hit?
Enter the OBJECT COLLISION command...
This is similar to OBJECT HIT, but returns a 1 if two objects are overlapping. Try running the following program:
Sync On: CLS 0
Sync Rate 60
AutoCam Off
Hide Mouse
Make Matrix 1,2000,2000,20,20
Create Bitmap 1,128,128
CLS RGB(0,128,0)
Get Image 1,0,0,128,128
Set Current Bitmap 0
Delete Bitmap 1
Prepare Matrix Texture 1,1,1,1
Make Object Cube 1,3: Rem Our Cube
Position Object 1,1000,3,990
Color Object 1,RGB(255,0,0)
Make Object Cube 2,3
Position Object 2,1000,3,1000
Position Camera 1000,50.0,980
Point Camera 1000,3,980
Do
If UpKey()=1 Then Move Object 1,0.1
If DownKey()=1 Then Move Object 1,-0.1
If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)
If RightKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)+2.0)
ObjPosX#=Object Position X(1)
ObjPosY#=Object Position Y(1)
ObjPosZ#=Object Position Z(1)
Position Camera ObjPosX#,30.0,ObjPosZ#
If OBJECT HIT(1,2)=1
Color Object 2,0
FirstHit=1
Endif
If FirstHit=1
C=OBJECT COLLISION(1,2)
If C=0 Then Color Object 2,RGB(255,255,255): FirstHit=0
Endif
Sync
Loop
End
It's basically the same as the first example apart from inside the main Do..Loop. But this time, when you bump into the stationary cube, it stays black until you move off it. This is because once the initial 'hit' is recorded, we set a flag (change the value of a variable) to denote that a collision has occured. In this case, we set the variable FirstHit to 1.
When this is set to 1, the code in the If FirstHit=1 block is carried out each time around the loop. Before the initial collision, FirstHit was set to 0 (zero), so it was ignored.
In the If FirstHit=1 block, we test with OBJECT COLLISION. While objects 1 and 2 are overlapping, this will always return a 1. The next line checks to see if it returned 0 and when it does, (only when the objects are not colliding), it returns the other cube back to white and turns the flag back off so the If FirstHit block isn't executed again unneccessarily.
So now, we know how to detect a collision between two objects. But, have you noticed the downside?
Here we are just testing for collisions between objects 1 (our cube) and 2 using OBJECT HIT(1,2) and OBJECT COLLISION(1,2). But what if we have another object - or dozens more objects? Won't we need a line of code for each of them?
Well, no actually, we don't. Remember the ObjNumA and ObjNumB from earlier?
A useful little option is to use a 0 (zero) for ObjNumB, making it HIT(1,0) and COLLISION(1,0). With this option, the command no longer returns a 0 or a 1, but instead returns zero or the number of the object that object 1 (our cube) is colliding with! Now that's a bit more useful don't you think?...
So let's see how to detect collision with many objects when you don't know what object numbers they are...
Sync On: CLS 0
Sync Rate 60
AutoCam Off
Hide Mouse
Randomize Timer()
Make Matrix 1,2000,2000,20,20
Create Bitmap 1,128,128
CLS RGB(0,128,0)
Get Image 1,0,0,128,128
Set Current Bitmap 0
Delete Bitmap 1
Prepare Matrix Texture 1,1,1,1
Make Object Cube 1,3
Position Object 1,1000,3,990
Color Object 1,RGB(255,0,0)
For N=2 To 11
Make Object Cube N,3
Position Object N,1000+(Rnd(60)-30),3,1000+(Rnd(60)-30)
Next N
Position Camera 1000,60.0,980
Point Camera 1000,3,980
Do
If UpKey()=1 Then Move Object 1,0.4
If DownKey()=1 Then Move Object 1,-0.4
If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)
If RightKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)+2.0)
ObjPosX#=Object Position X(1)
ObjPosY#=Object Position Y(1)
ObjPosZ#=Object Position Z(1)
Position Camera ObjPosX#,60.0,ObjPosZ#
H=OBJECT HIT(1,0)
If H>0: Rem collision detected
Color Object H,0
NowColliding=H: Rem Set flag to number of object we are colliding with
Endif
If NowColliding>0
Rem Test to see if we are still colliding
C=OBJECT COLLISION(1,NowColliding)
Rem And if not, recolour cube to white and turn off flag
If C=0 Then Color Object NowColliding,RGB(255,255,255): NowColliding=0
Endif
Sync
Loop
End
Important Note: In this example, if two objects are close to each other, then it's possible to still be colliding with one object when you hit another. In such a case, the hit on the second object is not detected. This is because if OBJECT COLLISION() is returning any value other than 0 then OBJECT HIT() is not tested for. It doesn't matter that the object you hit is different to the one you are currently colliding with.
This is normal and won't be a problem when we actually come to handling the collision later.
So we now know how to detect when we hit another object - regardless of object number. Next we have to deal with it because as you have seen, detecting the other object doesn't stop you moving through it.
What method we use depends on the method used to move the object we are controlling. There are two main ways to move a 3D object around:
The first is to turn it in the required direction, then use the Move command to move it the required amount in the direction it is facing.
The second is to use variables for the X, Y and Z position and use Position Object along with the Rotate commands to move the object about.
Our examples so far use the first method. This makes it easy to handle basic object collision because we can simply use the Move command to move back to the very last position before by using a minus value.
Here's the next example...
Sync On: CLS 0
Sync Rate 60
AutoCam Off
Hide Mouse
Make Matrix 1,2000,2000,20,20
Create Bitmap 1,128,128
CLS RGB(0,128,0)
Get Image 1,0,0,128,128
Set Current Bitmap 0
Delete Bitmap 1
Prepare Matrix Texture 1,1,1,1
Make Object Cube 1,3
Position Object 1,1000,3,990
Color Object 1,RGB(255,0,0)
Make Object Box 2,80,20,3: Rem Create a wall
Position Object 2,1000,3.0,1000
Ghost Object On 2
Position Camera 1000,50.0,950
Point Camera 1000,3,990
Speed#=0
Do
If UpKey()=1 Then Speed#=0.4: Move Object 1,Speed#
If DownKey()=1 Then Speed#=0-.4: Move Object 1,Speed#
If LeftKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)-2.0)
If RightKey()=1 Then YRotate Object 1,WrapValue(Object Angle Y(1)+2.0)
ObjPosX#=Object Position X(1)
ObjPosZ#=Object Position Z(1)
Position Camera ObjPosX#,50.0,ObjPosZ#-40
Point Camera ObjPosX#,3.0,ObjPosZ#
If OBJECT COLLISION(1,0)>0: Rem collision detected
Repeat
Move Object 1,0-Speed#
Position Camera Object Position X(1),50.0,Object Position Z(1)-40
Point Camera Object Position X(1),3.0,Object Position Z(1)
Until OBJECT COLLISION(1,0)=0
Endif
Sync
Loop
End
You will notice first of all that this example does not use OBJECT HIT(). You don't have to if you know that all the objects in your program are simply objects that you cannot pass through. If this is the case - like this program - you just stop your object from passing through any them.
However, if some of your objects are not solid, then you would use OBJECT HIT() to detect the number of the object you just collided with, before deciding how to deal with the collision.
Also, the actual collision effect used here when the cube hits the wall isn't the best for a game. When a game character hits a wall at an angle, the game's continuity and smooth running is interrupted if the character comes to a dead halt. The alternative is 'Sliding Collision' which is no more realistic when you think about it - when was the last time you ran into a brick wall and 'slid' along it - but it does look and feel much better in a game.
The changes to this program from the last one are a little more substantial too.
First of all, we create a wall as object number 2. We also place the camera a little further 'South' of our cube so we can see the wall at a better angle.
We also create a new variable called Speed# with which to move our cube around.
The reason for this is that when a collision is detected, we have to move the cube backwards, away from the wall, but only if we are moving forwards! The cursor down key lets us back away from the wall, but what if we bump into the wall while moving backwards? Well, when the collision is detected we would move the cube backwards further into the wall.
So, to prevent this from happening, we use a variable to store the current direction. When we press cursor up, Speed# is set to a positive number so the cube moves forwards. When we press cursor down, Speed# is set to a negative number so the cube moves backwards.
The advantage of this is that it doesn't matter what direction the cube is heading, moving it 0-Speed# ALWAYS moves it in the opposite direction by the same amount.
So, in the example, in the loop we test to see if a collision has been triggered with:
If OBJECT COLLISION(1,0)>0
If our cube hits anything, the code in this block will be executed. In the event of a collision therefore, we use a Repeat...Until loop moving the cube back with Move Object 1,0-Speed#, which as mentioned previously moves the cube back. As this is in a repeat loop, it's repeated until the cube is no longer in collision with the wall, at which point the loop is exited.
This method isn't perfect because at certain angles, you can clip the wall with the corner of the cube when rotating it - in which case, the backing out routine kicks in and you are instantly 'transported' to the other end of the wall! For this reason, there's a little more work involved, but it's better to use the X, Y and Z variables for moving objects around if you need collision rather than Move.
So let's now take a look at Sliding Collision...
Sliding collision isn't that much different to what we've just been doing.
The basic difference is that we have to use collision boxes. We don't just move back to the last known good position, we use other DB commands to tell us where to go back to.
Collision boxes are invisible 'wrappers' that you apply to objects. These wrappers can give you feedback on collision and you simply size them to fit the object you are applying them to. If you move the object, then the collision box moves with it.
The new command we are going to use is MAKE OBJECT COLLISION BOX Object Number,x1,y1,z1,x2,y2,z2,flag and looks rather complicated with all those parameters. But believe me it's not really. Let's go through them...
Object Number
Obviously this is the number of the object that you are creating the collision box for.
X1,Y1,Z1
OK, remember that I just said that when you move an object, then the collision box moves with it? That's because the collision box is attached to the object number. As an object can be placed anywhere in your world with the Position Object X,Y,Z command, we can't use 'real' X,Y,Z world location co-ords to define the collision box because if we moved the object, the collision box would still be at the old location.
So, instead, when you define a collision box, you use X,Y,Z OFFSETS which assume that the object is at point 0,0,0 in space. This is a neat method as we can use negative and positive values which remain true wherever the object is placed.
OK, an example. Imagine a box or wall 100 wide (X), 50 tall (Y) and 10 Deep (Z). We make the object with Make Object Box 1,100,50,10 and the CENTRE of this box is positioned at 0,0,0 in space.
Therefore, the X offset to reach the left edge of the box along the X axis is -50 and this is used for the value X1. We repeat the process for the Y and Z axis. With a box 50 tall, Y1 is -25 and with a depth of 10 then Z1 is -5.
Notice that the values are the negative equivalent of the boxes X, Y and Z dimensions divided by 2. Eg: Width (X) = 100/2 = 50 which makes -50.
X2,Y2,Z2
If we follow the X axis from the centre 'til we reach the
right edge of the box we find that it is at 50, so this is used for the X2 parameter. In the same fashion, Y2 is 25 and Z2 is 5. Notice this time that if X1 = -50 then X2 is 50 and that if Y1 is -25 then Y2 will be 25 and so on.
Here's a small image to show you what exactly is happening:
In the image, our object (the wall) is green. The X, Y and Z world axis are the black lines and where they all intersect, is position 0,0,0. As you can see, this is exactly in the centre of our wall. The dimensions of the wall are shown in blue.
If the X axis is 0 at the centre of the object, the values run negative to the left and positive to the right.
The red distances are those used for the offset parameters. The important thing to realise using this method is that as mentioned earlier, once these offsets are defined, even if the wall was moved to another X,Y,Z location, the offsets would still define the collision box correctly, whereas real X,Y,Z locations would not.
The origin for the offsets is based on the X,Y,Z world position of the Object whose number you supply. Easy eh?
Let's see the code:
Set Display Mode 800,600,16
Autocam Off
Sync On: CLS 0
Sync Rate 60
Hide mouse
Make Matrix 1,2000,2000,20,20
Create Bitmap 1,128,128
CLS RGB(0,128,0)
Get Image 1,0,0,128,128
Set Current Bitmap 0
Delete Bitmap 1
Prepare Matrix Texture 1,1,1,1
Position Matrix 1,-1000,0,-1000
Make Object Cube 1,5
Color Object 1,RGB(255,0,0)
Make Object Collision Box 1,-2.5,-2.5,-2.5,2.5,2.5,2.5,0
XPos#=0: YPos#=2.5: ZPos#=0
Position Object 1,0,2.5,0
Make Object Box 2,150,50,10
Position Object 2,0,25,100
Make Object Collision Box 2,-75,-25,-5,75,25,5,0
Do
If Upkey()=1 Then Move Object 1,2
If Downkey()=1 Then Move Object 1,-2
If Leftkey()=1 Then Yrotate Object 1,Wrapvalue(Object Angle Y(1)-4)
If Rightkey()=1 Then Yrotate Object 1,Wrapvalue(Object Angle Y(1)+4)
XPos#=Object Position X(1)
YPos#=Object Position Y(1)
ZPos#=Object Position Z(1)
Rem The sliding collision bit
If Object Collision(1,0)>0
Dec XPos#,Get Object Collision X()
Dec YPos#,Get Object Collision Y()
Dec ZPos#,Get Object Collision Z()
Endif
Position Object 1,XPos#,YPos#,ZPos#
Angle#=Object Angle y(1)
CamDist#=40.0 : CamHeight#=YPos#+10.0 : Camsmooth#=3.5
Set Camera To Follow XPos#,YPos#,ZPos#,Angle#,CamDist#,CamHeight#,CamSmooth#,0
Sync
Loop
When the program is running, in the main loop, we check for the cursor keys and move the cube in the required direction or rotate it and then grab the object's new position into the variables XPos#, YPos# and ZPos#.
We then test for a collision with Object Collision(1,0) and if the result is anything but a zero then we've hit something. The value returned is the number of the object we are banging our head against.
We next use three functions we've not seen before: Get Object Collision X(), Get Object Collision Y() and Get Object Collision Z().
If you cast your mind back to when I was describing how you use the Make Object Collision Box command, I mentioned that the last parameter was called flag and that if you set it to 1 then the collision box rotated with the object. Remember? Well, if you set that flag to zero, you can use the Get Object Collision functions.
These return the sliding collision values which we use to back up our object. Remember though that they ONLY return data if that little flag is set to 0!
So, armed with the amount to back up in the X, Y and Z directions, we decrement them from our object's X, Y and Z positions and use Position Object to place the object in it's new position. The rest of the code simply handles the camera to follow the cube.
Well, that's the end of this tutorial on Simple DB Collision and armed with this new knowledge, you can investigate the many other collision commands DB supplies you with.
There are many more tutorials and example code snippets on TDKMan.com. Click on the link below - it's free to register and everyone's welcome!
Visit TDKMan.com
TDK_Man