In one of my last posts we created the game pong, which is a very basic one file based game. As for a next game I considered to create a breakout game. Hence, I am able to reuse different classes which I have created during the pong game. I.e. the Bar and the Ball class, which only needs some little modifications to be used within a breakout game. To be able to complete the breakout an additional class is needed the Block, which is our enemy in this case. To win we would only need to destroy all our enemies. After adding the the Block class and some additional programming the pong game looked like breakout:
The game is very basic, you have to hit the monkey with the fist, so that he is able to eat ALL of his bananas, if you miss him he falls into the pit and you have to start again. There is currently, no score counting or some kind menu involved in this version. Even if all the bananas are eaten by the monkey the game needs to be restarted, because there is no automatic restart currently implemented. I figured out that for learning basic game development creating menus is not essential.
The programming included steps, like rotating the fist and adding the Block class. A big portion of the programming to this point was splitting up from one game file to multiple files, one for each different class. Also I have created a GameElement as a Basic class, which is the parent of the Bar, Ball and the Block. Also a lot of time was spent for the collision detection and how to decide if the box was hit either on the left or the right side. The source code is available on github For more implementation details continue reading.
The implementation details of breakout
During the implementation details I only will state and explain code which in my opinion is very interesting or which uses something important, everything else could be read in the comments of the source code.
The main function
The main function in in this case is used to prepare the game elements. Especially in this case is the creation of the block area, which sets up all the block which are later eaten by the monkey:
#block area should occupy 80% of the width and 50% of height blockAreaWidth = int(width * 0.8) blockAreaHeight = int(height * 0.5) maxBlockInX = int(blockAreaWidth/aBlock.rect.width) #update the block area width to the correct size blockAreaWidth = (maxBlockInX*aBlock.rect.width) # get start position for x blockStartX = int((width - (blockAreaWidth)) / 2) blockStartY = int(blockStartX) # same difference above as on the sides # create the blocks start at the x value and go until the max width has reached # there should be no space between the current and the next block gameAreaSprites = pygame.sprite.RenderPlain() for blockPosX in xrange(blockStartX, (width - blockStartX), aBlock.rect.w): for blockPosY in xrange(blockStartY, (blockAreaHeight + blockStartY), aBlock.rect.h): gameAreaSprites.add(Block((blockPosX, blockPosY)))
The block area is defined relatively to the width and height of the game window. after that the maximum amount of blocks in x direction is calculated. The amount is then used to calculate the exact width of the block area. Afterwards the x position of the first block is calculated and also used for the first y position. In the loop each blocks are added for every position in the block area.
Also I implemented the input handling as described in the game piece article:
for event in pygame.event.get(): # quit the game if escape is pressed if event.type == QUIT: return elif event.type == KEYDOWN and event.key == K_ESCAPE: return # move the handles according to the object elif event.type == MOUSEMOTION: gameBar.handleEvent(event) playBall.handleEvent(event) elif event.type == MOUSEBUTTONDOWN: playBall.handleEvent(event)
The details on the collision detection
The collision detection is as well being handled by the pygame engine, which does a lot in our case. But for the pong game some additional tests are needed for the ball:
def checkHit(self, targets): """ Checks for multiple Blocks, if the ball collides with any of these blocks """ for target in targets.sprites(): hitbox = target.rect if hitbox.colliderect(self.rect): # check if hit from side, this is when the center of the ball # is within the top and bottom of the block #hitbox = target.rect.inflate(0, 0) if self.rect.centery < hitbox.top and \ self.rect.centery > hitbox.bottom: self.speed = -self.speed else: self.slope = -self.slope return target return None
First of all all game area sprites are delivered to this check function. Hence we loop over every single item and do a check if the balls hits any of these blocks. If the balls has collided with a block, an additional check is performed to see, if the center of the ball is within the range of the block. If this is the case the left or right side of the bar was hit. Hence the ball needs to be reflected, which in our case means that the ball is reversed by negating its speed. If the bottom of a bar is hit, the balls slope is negated, which means that the ball travels down instead of up or vice versa.