Pygame game game development -- small game of cake eliminating music

Development tool: pycham

The game is introduced: players eliminate the cake by exchanging the position of the cake. If there are three or more of the same cakes in a straight line or T-shaped position, the elimination will occur and a new cake will be loaded. The more cakes are eliminated in a given time, the higher the score.

Screenshot of game running:

Game making steps

1, Prepare material

Collect and process materials needed (including pictures, sounds, fonts, etc.)

2, Write code to realize game interface

1. Define the config.py file to store relevant parameters, including the width and height of the interface, the number of rows and columns of the whole grid, the total number of grids, etc.

Parameter setting:

WIDTH = 600
HEIGHT = 600
NUMGRID = 8
GRIDSIZE = 64
XMARGIN = (WIDTH - GRIDSIZE * NUMGRID) // 2
YMARGIN = (HEIGHT - GRIDSIZE * NUMGRID) // 2
ROOTDIR = os.getcwd()
FPS = 30

2. Define the classes and functions of utils.py file used to store the foundation: including the whole elimination puzzle class, game class, puzzle block moving function, coordinate setting and obtaining function, start the main function of the game, initialize the randomly generated puzzle function, time countdown display function, display score function, plus score function, elimination function, new puzzle block generation function after elimination, puzzle exchange bit Set function and so on.

1) Import library: time timing, random random number generation, pygame cross platform Python module is mainly used for game graphical interface generation and audio playback, and relevant variables defined by config itself

import sys
import time
import random
import pygame
from config import *

2) Define puzzle class

class gemSprite(pygame.sprite.Sprite):

There are mainly functions:

Initialization function: self = class materializes the object itself, img_path = the path of the picture file, size = the size of the whole interface, position = the location of the puzzle block, downlen = the number of dropped cells after the puzzle block is eliminated

def __init__(self, img_path, size, position, downlen, **kwargs):

Movement function: it mainly controls the direction of the puzzle block exchange through the mouse, which has four directions: up, down, left and right

def move(self):

Get coordinate function: get the coordinates of the current puzzle block, mainly through the left and top directions to define the coordinate distance

def getPosition(self):

Set coordinate function: set puzzle coordinates

def setPosition(self, position):

3) Define game class:

class gemGame():

The main functions are:

Initialization function: self = class concrete object itself, screen = screen, sounds = audio, font = font, gem_imgs = puzzle picture

def __init__(self, screen, sounds, font, gem_imgs, **kwargs):

Start game function: game start main cycle

def start(self):

Random puzzle block generating function: a new puzzle block generated randomly after elimination

def reset(self):

Show game countdown function:

def showRemainingTime(self):

Display score function:

def drawScore(self):

Fraction calculation function:

def drawAddScore(self, add_score):

New puzzle block generation function:

def generateNewGems(self, res_match):

Eliminate successfully matched puzzle pieces: eliminate three identical puzzle pieces in row or column

def removeMatched(self, res_match):

Interface grid drawing:

def drawGrids(self):

Draw a rectangle:

def drawBlock(self, block, color=(255, 240, 245), size=4):

The new puzzle piece has a drop effect:

def dropGems(self, x, y):

Is there a jigsaw puzzle at each location

def isFull(self):

Check if the puzzle block is selected:

def checkSelected(self, position):

Whether there are rows and columns, three pieces of puzzle are the same judgment:

def isMatch(self):

According to the coordinates, obtain the corresponding position of the puzzle object:

def getGemByPos(self, x, y):

Exchange puzzle function:

def swapGem(self, gem1_pos, gem2_pos):

3. Define main.py main function: mainly used to initialize the interface and open the game main program

1) Import library

import os
import pygame
from utils import *
from config import *

2) Definition of game main program:

def main():

Main functions:

Initialization of game interface:

pygame.init()
	size = width, height = (WIDTH,HEIGHT)
	screen = pygame.display.set_mode(size, pygame.RESIZABLE)  # Window mode
	bg = pygame.image.load("bg.jpg")
	screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
	clock = pygame.time.Clock()
	pygame.display.set_caption('Xiao Xiao le')

Load sound resources:

# Load background music
	pygame.mixer.init()
	pygame.mixer.music.load(os.path.join(ROOTDIR, "resources/audios/bg.mp3"))
	pygame.mixer.music.set_volume(0.6)
	pygame.mixer.music.play(-1)
# Loading sound effects
	sounds = {}
	sounds['mismatch'] = pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/badswap.wav'))
	sounds['match'] = []
	for i in range(6):
		sounds['match'].append(pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/match%s.wav' % i)))

Load font resources:

font = pygame.font.Font(os.path.join(ROOTDIR, 'resources/font.TTF'), 25)

Load picture resources:

gem_imgs = []
	for i in range(1, 8):
		gem_imgs.append(os.path.join(ROOTDIR, 'resources/images/gem%s.png' % i))

Main game cycle:

game = gemGame(screen, sounds, font, gem_imgs)
	while True:
		score = game.start()
		flag = False
		# After a round of games, players choose to play again or quit
		while True:
			# clock.tick(60)
			for event in pygame.event.get():
				if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
					pygame.quit()
					sys.exit()
				elif event.type == pygame.KEYUP and event.key == pygame.K_r:
					size = width, height = event.size[0], event.size[1]  # Get new size
					# screen = pygame.display.set_mode(size, pygame.RESIZABLE)
					# screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
					flag = True
			if flag:
				break
			# screen.blit(back_image, (0, 0))
			# screen.fill((135, 206, 235))
			screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
			pygame.display.flip()
			text0 = 'Final score: %s' % score
			text1 = 'Press <R> to restart the game.'
			text2 = 'Press <Esc> to quit the game.'
			text3 = 'Producer: KongXinger & HeJin'
			y = 150
			for idx, text in enumerate([text0, text1, text2, text3]):
				text_render = font.render(text, 1, (85, 65, 0))
				rect = text_render.get_rect()
				if idx == 0:
					rect.left, rect.top = (212, y)
				elif idx == 1:
					rect.left, rect.top = (122.5, y)
				elif idx == 1:
					rect.left, rect.top = (126.5, y)
				else:
					rect.left, rect.top = (130.5, y)
				y += 80
				screen.blit(text_render, rect)
			pygame.display.update()
		game.reset()

Main program:

if __name__ == '__main__':
	main()

3, Run tests

Full reference code:

config.py

import os

WIDTH = 600
HEIGHT = 600
NUMGRID = 8
GRIDSIZE = 64
XMARGIN = (WIDTH - GRIDSIZE * NUMGRID) // 2
YMARGIN = (HEIGHT - GRIDSIZE * NUMGRID) // 2
ROOTDIR = os.getcwd()
FPS = 30

utils.py

import sys
import time
import random
import pygame
from config import *


'''Jigsaw elves'''
class gemSprite(pygame.sprite.Sprite):
	def __init__(self, img_path, size, position, downlen, **kwargs):
		pygame.sprite.Sprite.__init__(self)
		self.image = pygame.image.load(img_path)
		self.image = pygame.transform.smoothscale(self.image, size)
		self.rect = self.image.get_rect()
		self.rect.left, self.rect.top = position
		self.downlen = downlen
		self.target_x = position[0]
		self.target_y = position[1] + downlen
		self.type = img_path.split('/')[-1].split('.')[0]
		self.fixed = False
		self.speed_x = 10
		self.speed_y = 10
		self.direction = 'down'
	'''Jigsaw movement'''
	def move(self):
		if self.direction == 'down':
			self.rect.top = min(self.target_y, self.rect.top+self.speed_y)
			if self.target_y == self.rect.top:
				self.fixed = True
		elif self.direction == 'up':
			self.rect.top = max(self.target_y, self.rect.top-self.speed_y)
			if self.target_y == self.rect.top:
				self.fixed = True
		elif self.direction == 'left':
			self.rect.left = max(self.target_x, self.rect.left-self.speed_x)
			if self.target_x == self.rect.left:
				self.fixed = True
		elif self.direction == 'right':
			self.rect.left = min(self.target_x, self.rect.left+self.speed_x)
			if self.target_x == self.rect.left:
				self.fixed = True
	'''Get coordinates'''
	def getPosition(self):
		return self.rect.left, self.rect.top
	'''Set coordinates'''
	def setPosition(self, position):
		self.rect.left, self.rect.top = position


'''Game class'''
class gemGame():
	def __init__(self, screen, sounds, font, gem_imgs, **kwargs):
		self.info = 'Gemgem'
		self.screen = screen
		self.sounds = sounds
		self.font = font
		self.gem_imgs = gem_imgs
		self.reset()
	'''Start the game'''
	def start(self):
		clock = pygame.time.Clock()
		# Traverse the entire game interface to update the location
		overall_moving = True
		# Specify the update location of some individual objects
		individual_moving = False
		# Define some necessary variables
		gem_selected_xy = None
		gem_selected_xy2 = None
		swap_again = False
		add_score = 0
		add_score_showtimes = 10
		time_pre = int(time.time())
		# Game main cycle
		while True:
			for event in pygame.event.get():
				if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
					pygame.quit()
					sys.exit()
				elif event.type == pygame.MOUSEBUTTONUP:
					if (not overall_moving) and (not individual_moving) and (not add_score):
						position = pygame.mouse.get_pos()
						if gem_selected_xy is None:
							gem_selected_xy = self.checkSelected(position)
						else:
							gem_selected_xy2 = self.checkSelected(position)
							if gem_selected_xy2:
								if self.swapGem(gem_selected_xy, gem_selected_xy2):
									individual_moving = True
									swap_again = False
								else:
									gem_selected_xy = None
			if overall_moving:
				overall_moving = not self.dropGems(0, 0)
				# You may be able to spell multiple 3 pieces at a time
				if not overall_moving:
					res_match = self.isMatch()
					add_score = self.removeMatched(res_match)
					if add_score > 0:
						overall_moving = True
			if individual_moving:
				gem1 = self.getGemByPos(*gem_selected_xy)
				gem2 = self.getGemByPos(*gem_selected_xy2)
				gem1.move()
				gem2.move()
				if gem1.fixed and gem2.fixed:
					res_match = self.isMatch()
					if res_match[0] == 0 and not swap_again:
						swap_again = True
						self.swapGem(gem_selected_xy, gem_selected_xy2)
						self.sounds['mismatch'].play()
					else:
						add_score = self.removeMatched(res_match)
						overall_moving = True
						individual_moving = False
						gem_selected_xy = None
						gem_selected_xy2 = None
			self.screen.fill((135, 206, 235))
			self.drawGrids()
			self.gems_group.draw(self.screen)
			if gem_selected_xy:
				self.drawBlock(self.getGemByPos(*gem_selected_xy).rect)
			if add_score:
				if add_score_showtimes == 10:
					random.choice(self.sounds['match']).play()
				self.drawAddScore(add_score)
				add_score_showtimes -= 1
				if add_score_showtimes < 1:
					add_score_showtimes = 10
					add_score = 0
			self.remaining_time -= (int(time.time()) - time_pre)
			time_pre = int(time.time())
			self.showRemainingTime()
			self.drawScore()
			if self.remaining_time <= 0:
				return self.score
			pygame.display.update()
			clock.tick(FPS)
	'''Initialization'''
	def reset(self):
		# Randomly generate blocks (i.e. initialize game map elements)
		while True:
			self.all_gems = []
			self.gems_group = pygame.sprite.Group()
			for x in range(NUMGRID):
				self.all_gems.append([])
				for y in range(NUMGRID):
					gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+x*GRIDSIZE, YMARGIN+y*GRIDSIZE-NUMGRID*GRIDSIZE], downlen=NUMGRID*GRIDSIZE)
					self.all_gems[x].append(gem)
					self.gems_group.add(gem)
			if self.isMatch()[0] == 0:
				break
		# Score
		self.score = 0
		# A reward for putting together one
		self.reward = 10
		# time
		self.remaining_time = 20
	'''Show time remaining'''
	def showRemainingTime(self):
		remaining_time_render = self.font.render('CountDown: %ss' % str(self.remaining_time), 1, (85, 65, 0))
		rect = remaining_time_render.get_rect()
		rect.left, rect.top = (WIDTH-201, 6)
		self.screen.blit(remaining_time_render, rect)
	'''Display score'''
	def drawScore(self):
		score_render = self.font.render('SCORE:'+str(self.score), 1, (85, 65, 0))
		rect = score_render.get_rect()
		rect.left, rect.top = (10, 6)
		self.screen.blit(score_render, rect)
	'''Display bonus points'''
	def drawAddScore(self, add_score):
		score_render = self.font.render('+'+str(add_score), 1, (255, 100, 100))
		rect = score_render.get_rect()
		rect.left, rect.top = (250, 250)
		self.screen.blit(score_render, rect)
	'''Generate a new puzzle block'''
	def generateNewGems(self, res_match):
		if res_match[0] == 1:
			start = res_match[2]
			while start > -2:
				for each in [res_match[1], res_match[1]+1, res_match[1]+2]:
					gem = self.getGemByPos(*[each, start])
					if start == res_match[2]:
						self.gems_group.remove(gem)
						self.all_gems[each][start] = None
					elif start >= 0:
						gem.target_y += GRIDSIZE
						gem.fixed = False
						gem.direction = 'down'
						self.all_gems[each][start+1] = gem
					else:
						gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+each*GRIDSIZE, YMARGIN-GRIDSIZE], downlen=GRIDSIZE)
						self.gems_group.add(gem)
						self.all_gems[each][start+1] = gem
				start -= 1
		elif res_match[0] == 2:
			start = res_match[2]
			while start > -4:
				if start == res_match[2]:
					for each in range(0, 3):
						gem = self.getGemByPos(*[res_match[1], start+each])
						self.gems_group.remove(gem)
						self.all_gems[res_match[1]][start+each] = None
				elif start >= 0:
					gem = self.getGemByPos(*[res_match[1], start])
					gem.target_y += GRIDSIZE * 3
					gem.fixed = False
					gem.direction = 'down'
					self.all_gems[res_match[1]][start+3] = gem
				else:
					gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+res_match[1]*GRIDSIZE, YMARGIN+start*GRIDSIZE], downlen=GRIDSIZE*3)
					self.gems_group.add(gem)
					self.all_gems[res_match[1]][start+3] = gem
				start -= 1
	'''Remove matching gem'''
	def removeMatched(self, res_match):
		if res_match[0] > 0:
			self.generateNewGems(res_match)
			self.score += self.reward
			return self.reward
		return 0
	'''Grid drawing of game interface'''
	def drawGrids(self):
		for x in range(NUMGRID):
			for y in range(NUMGRID):
				rect = pygame.Rect((XMARGIN+x*GRIDSIZE, YMARGIN+y*GRIDSIZE, GRIDSIZE, GRIDSIZE))
				self.drawBlock(rect, color=(255,240,245), size=1)
	'''Draw rectangle block frame'''
	def drawBlock(self, block, color=(255, 240, 245), size=4):
		pygame.draw.rect(self.screen, color, block, size)
	'''Drop effect'''
	def dropGems(self, x, y):
		if not self.getGemByPos(x, y).fixed:
			self.getGemByPos(x, y).move()
		if x < NUMGRID-1:
			x += 1
			return self.dropGems(x, y)
		elif y < NUMGRID-1:
			x = 0
			y += 1
			return self.dropGems(x, y)
		else:
			return self.isFull()
	'''Is there a puzzle in every position'''
	def isFull(self):
		for x in range(NUMGRID):
			for y in range(NUMGRID):
				if not self.getGemByPos(x, y).fixed:
					return False
		return True
	'''Check if the puzzle block is selected'''
	def checkSelected(self, position):
		for x in range(NUMGRID):
			for y in range(NUMGRID):
				if self.getGemByPos(x, y).rect.collidepoint(*position):
					return [x, y]
		return None
	'''Are there three consecutive blocks(nothing--Return to 0/level--Return to 1/vertical--Return to 2)'''
	def isMatch(self):
		for x in range(NUMGRID):
			for y in range(NUMGRID):
				if x + 2 < NUMGRID:
					if self.getGemByPos(x, y).type == self.getGemByPos(x+1, y).type == self.getGemByPos(x+2, y).type:
						return [1, x, y]
				if y + 2 < NUMGRID:
					if self.getGemByPos(x, y).type == self.getGemByPos(x, y+1).type == self.getGemByPos(x, y+2).type:
						return [2, x, y]
		return [0, x, y]
	'''Obtain the corresponding position of the puzzle object according to the coordinates'''
	def getGemByPos(self, x, y):
		return self.all_gems[x][y]
	'''Swap puzzle'''
	def swapGem(self, gem1_pos, gem2_pos):
		margin = gem1_pos[0] - gem2_pos[0] + gem1_pos[1] - gem2_pos[1]
		if abs(margin) != 1:
			return False
		gem1 = self.getGemByPos(*gem1_pos)
		gem2 = self.getGemByPos(*gem2_pos)
		if gem1_pos[0] - gem2_pos[0] == 1:
			gem1.direction = 'left'
			gem2.direction = 'right'
		elif gem1_pos[0] - gem2_pos[0] == -1:
			gem2.direction = 'left'
			gem1.direction = 'right'
		elif gem1_pos[1] - gem2_pos[1] == 1:
			gem1.direction = 'up'
			gem2.direction = 'down'
		elif gem1_pos[1] - gem2_pos[1] == -1:
			gem2.direction = 'up'
			gem1.direction = 'down'
		gem1.target_x = gem2.rect.left
		gem1.target_y = gem2.rect.top
		gem1.fixed = False
		gem2.target_x = gem1.rect.left
		gem2.target_y = gem1.rect.top
		gem2.fixed = False
		self.all_gems[gem2_pos[0]][gem2_pos[1]] = gem1
		self.all_gems[gem1_pos[0]][gem1_pos[1]] = gem2
		return True
	'''info'''
	def __repr__(self):
		return self.info

main.py

import os
import pygame
from utils import *
from config import *


'''Game master'''
def main():
	pygame.init()
	# screen = pygame.display.set_mode((WIDTH, HEIGHT))
	# back_image = pygame.image.load('bg.jpg')
	size = width, height = (WIDTH,HEIGHT)
	screen = pygame.display.set_mode(size, pygame.RESIZABLE)  # Window mode
	bg = pygame.image.load("bg.jpg")
	screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
	clock = pygame.time.Clock()
	pygame.display.set_caption('Xiao Xiao le')
	# Load background music
	pygame.mixer.init()
	pygame.mixer.music.load(os.path.join(ROOTDIR, "resources/audios/bg.mp3"))
	pygame.mixer.music.set_volume(0.6)
	pygame.mixer.music.play(-1)
	# Loading sound effects
	sounds = {}
	sounds['mismatch'] = pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/badswap.wav'))
	sounds['match'] = []
	for i in range(6):
		sounds['match'].append(pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/match%s.wav' % i)))
	# Load font
	font = pygame.font.Font(os.path.join(ROOTDIR, 'resources/font.TTF'), 25)
	# Picture loading
	gem_imgs = []
	for i in range(1, 8):
		gem_imgs.append(os.path.join(ROOTDIR, 'resources/images/gem%s.png' % i))
	# Main cycle
	game = gemGame(screen, sounds, font, gem_imgs)
	while True:
		score = game.start()
		flag = False
		# After a round of games, players choose to play again or quit
		while True:
			# clock.tick(60)
			for event in pygame.event.get():
				if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
					pygame.quit()
					sys.exit()
				elif event.type == pygame.KEYUP and event.key == pygame.K_r:
					size = width, height = event.size[0], event.size[1]  # Get new size
					# screen = pygame.display.set_mode(size, pygame.RESIZABLE)
					# screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
					flag = True
			if flag:
				break
			# screen.blit(back_image, (0, 0))
			# screen.fill((135, 206, 235))
			screen.blit(pygame.transform.scale(bg, size), (0, 0))  # Background energy scaling
			pygame.display.flip()
			text0 = 'Final score: %s' % score
			text1 = 'Press <R> to restart the game.'
			text2 = 'Press <Esc> to quit the game.'
			text3 = 'Producer: KongXinger & HeJin'
			y = 150
			for idx, text in enumerate([text0, text1, text2, text3]):
				text_render = font.render(text, 1, (85, 65, 0))
				rect = text_render.get_rect()
				if idx == 0:
					rect.left, rect.top = (212, y)
				elif idx == 1:
					rect.left, rect.top = (122.5, y)
				elif idx == 1:
					rect.left, rect.top = (126.5, y)
				else:
					rect.left, rect.top = (130.5, y)
				y += 80
				screen.blit(text_render, rect)
			pygame.display.update()
		game.reset()


'''test'''
if __name__ == '__main__':
	main()

Learning reference: Jack HCC, blogger

Keywords: Python

Added by candle21428 on Tue, 07 Apr 2020 18:11:29 +0300