# Collision & Hit Boxes

Subscribe to Tech With Tim!

In many games we need to check for different types of collision. Whether that be collision of our mouse with a button or collision between images and/or objects. In our case we want to check if our goblin has hit the player or if our bullets have hit the goblin.

### Hit Boxes

The term "hit box" is often used to represent the box around an object which represents its "hittable space". Since we often use complex objects and shapes to depict characters or other items in a game we create a hit box for all of these items. This makes it much easier to check for collision as collision between non-rectangular shapes is extremely complicated. We attempt to make these boxes fit the characters shape as precisely as possible but it is difficult to make them perfect.

We will start by creating hit boxes for all of our objects. Then we check if these boxes collide with one another using some basic math.

The first hit box we define will be for our player class.

```# This goes inside the player class in the __init__ method
self.hitbox = (self.x + 20, self.y, 28, 60)
# The elements in the hitbox are (top left x, top left y, width, height)
```

Since our player moves we will have to constantly redefine the hit box from within the draw method of our player class. To do this we will simply copy the line from above into the draw method.

Our player class should now look like the following.

```class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52) # NEW

def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0

if not(self.standing):
if self.left:
win.blit(walkLeft[self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight, (self.x, self.y))
else:
win.blit(walkLeft, (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52) # NEW
pygame.draw.rect(win, (255,0,0), self.hitbox,2) # To draw the hit box around the player
```

We will repeat this process in the enemy class.
And after adding in the hit box we will also define a new method called hit.

```class enemy(object):

def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57) # NEW

def draw(self,win):
self.move()
if self.walkCount + 1 >= 33:
self.walkCount = 0

if self.vel > 0:
win.blit(self.walkRight[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
self.hitbox = (self.x + 17, self.y + 2, 31, 57) # NEW
pygame.draw.rect(win, (255,0,0), self.hitbox,2) # Draws the hit box around the enemy

def move(self):
if self.vel > 0:
if self.x + self.vel < self.path:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0

# NEW METHOD
def hit(self):  # This will display when the enemy is hit
print('hit')
```

If we run the program we can see the hit boxes for our character. ### Collision

The fist collision we will check for is between the bullets and the enemy. Every time we move a bullet we will check if it has collided with the enemy. Since we already have a for loop setup to check if the bullets leave the screen we will do our collision check in there.

We are going to say these objects have collided if the x and y coordinate of the bullet sit inside the hit box of the enemy. We check this with the following code.

```if bullet.y - bullet.radius < goblin.hitbox + goblin.hitbox and bullet.y + bullet.radius > goblin.hitbox: # Checks x coords
if bullet.x + bullet.radius > goblin.hitbox and bullet.x - bullet.radius < goblin.hitbox + goblin.hitbox: # Checks y coords
goblin.hit() # calls enemy hit method
bullets.pop(bullets.index(bullet)) # removes bullet from bullet list
```

### Bullet Glitch

There is a small glitch you may have noticed which causes our bullets to stick together or shoot multiple at the same time. To fix this we must do the following.

First we are going to create a variable called shootLoop outside of our main loop.

```shootLoop = 0
```

After that we will place the following code at the top of our while loop.

```if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
```

Then we will modify our space bar event check the following way.

```if keys[pygame.K_SPACE] and shootLoop == 0:
...

# Add the "and shootLoop == 0"
```

### Full Code

After all this the final code should look like:

```import pygame
pygame.init()

win = pygame.display.set_mode((500,480))

pygame.display.set_caption("First Game")

clock = pygame.time.Clock()

class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52)

def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0

if not(self.standing):
if self.left:
win.blit(walkLeft[self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight, (self.x, self.y))
else:
win.blit(walkLeft, (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)

class projectile(object):
self.x = x
self.y = y
self.color = color
self.facing = facing
self.vel = 8 * facing

def draw(self,win):

class enemy(object):

def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)

def draw(self,win):
self.move()
if self.walkCount + 1 >= 33:
self.walkCount = 0

if self.vel > 0:
win.blit(self.walkRight[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)

def move(self):
if self.vel > 0:
if self.x + self.vel < self.path:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0

def hit(self):
print('hit')

def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)

pygame.display.update()

#mainloop
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 450)
shootLoop = 0
bullets = []
run = True
while run:
clock.tick(27)

if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0

for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False

for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox + goblin.hitbox and bullet.y + bullet.radius > goblin.hitbox:
if bullet.x + bullet.radius > goblin.hitbox and bullet.x - bullet.radius < goblin.hitbox + goblin.hitbox:
goblin.hit()
bullets.pop(bullets.index(bullet))

if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))

keys = pygame.key.get_pressed()

if keys[pygame.K_SPACE] and shootLoop == 0:
if man.left:
facing = -1
else:
facing = 1

if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))

shootLoop = 1

if keys[pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0

if not(man.isJump):
if keys[pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10

redrawGameWindow()

pygame.quit()
```