Write the "Alien Invasion" game in python: armed spaceship > 1

In yesterday's article, we set up the local environment.

Now, we have python, pip to install pygame, and pygame. Then we start to enter the project phase.

start game project

First create an empty pygame window for later drawing game elements such as spaceships and aliens. We'll also make the game respond to user input, set the background color, and load the spaceship image.


Create pygame windows and respond to user input
#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 

def run_game():
	#初始化背景设置
	pygame.init()
	#设置窗口
	screen = pygame.display.set_mode((1200,800))
	pygame.display.set_caption("兔C:外星人入侵")
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		pygame.display.flip()
		
run_game()

First, the sys and pygame modules are imported. pygame contains the functions needed to develop games. When the player exits, we will use the module sys to exit the game.

run_game() function:

​ pygame.init() initializes the background settings so that python can work correctly.

​ Call pygame.display.set_mode() to create a display window named screen, in which all graphical elements of the game will be drawn. The argument (1200,800) is a tuple specifying the size of the game window. By passing these dimensions to pygame.sisplay.set_mode() we create a game window that is 1200 pixels wide and 800 pixels high.

​ The object screen is a surface. In python, a surface is the part of the screen that displays game elements. In this game, each element (such as an alien or a spaceship) is a surface. The surface returned by display.set_mode() represents the entire game window. After we activate the game's animation loop, the entire surface will be automatically redrawn every time the loop passes through.

The game is controlled by a while loop, which contains an event loop and code that manages screen updates. Events are actions the user performs while playing the game, such as pressing a key or moving the mouse. In order for the program to respond to events, we write an event loop, listen to events, and perform corresponding tasks according to the events that occur. The for loop is an event loop.

To access the events detected by pygame, we use the method pygame.enent.get(). All keyboard and mouse events will cause the for loop to run. In this loop, we will write a series of if statements to detect and respond to specific events. For example, when the close button of the player's stand-alone game window is detected, the pygame.QUIT event will be detected, and we call sys.exit() to exit the game.

​ pygame.display.flip(), instructs pygame to make the most recently drawn screen visible. Here, it draws an empty screen each time the while loop executes, and erases the old screen so that only the new screen is visible. As we move game elements, pygame.display.flip() will constantly update the screen to show the new position of the element and hide the game in its original position, creating a smooth movement effect.

Throughout the basic game structure, the last line calls run_game(), which initializes the game and starts the main loop.

If we run this code at this point, we will see an empty pygame window.

set background color

insert image description here

Let's make some settings for the game background.

#在run_game()方法中,设置颜色参数
bg_color = (230,230,230)

#在循环中的每次更新屏幕前设置颜色
screen.fill(bg_color)

Here we store the set background color in the variable bg_color, which only needs to be specified once, so we define it before entering the main loop.
In pygame, colors are specified as RGB values. This color consists of red, green, and blue values, each of which has a possible value range from 0 to 255. A color value of (255,0,0) represents red, (0,255,0) represents green, and (0,0,255) represents blue. By combining different RGB values, 16 million colors can be created. With a color value of (230,230,230), equal amounts of red, blue, and green, it sets the background to a light gray.
Next, we call the method screen.fill() to fill the screen with the background color; this method takes only one argument: a color.

insert image description here


Create settings class

Every time a new feature is added to the game, some new settings will usually be introduced as well. Below we write a module called settings that contains a class called Settings to store all settings in one place so as not to add settings all over the code. This way, we can pass one settings object instead of many different settings. Additionally, this makes function calls simpler and easier to modify.

#定义settings.py模块的Settings类
class Settings():
	#存储《外星人入侵》的所有设置的类
	
	def __init__(self):
		self.screen_width = 1200
		self.screen_height = 800
		self.bg_color = (230,230,230)

Now that we have the setting class, we need to change the code of the alien_invasion module,
because we need to set the parameters by calling the setting class.

#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		screen.fill(ai_settings.bg_color)
		
		pygame.display.flip()
		
run_game()

In the main program file, we import the Settings class, call pygame.init(), create a Settings instance, and store it in the variable ai_settings. When creating the screen, the attributes screen_width and screen_height of ai_settings are used; when filling the screen next, ai_settings is also used to access the background color.

Add spaceship image

Almost any type of image file can be used in the game, but it is easiest to use a bitmap (.bmp) file, since python loads bitmaps by default. While pygame can be configured to use other file types, some file types require you to have the corresponding image library installed on your computer. Most images are in .jpg, .png, or .gif format, but they can be converted to bitmaps using tools such as photoshop, GIMP, Paint, etc.
Note:
When choosing an image, try to use one with a transparent background, as this allows you to set its background to any color using an image editor.

Let's first create a folder in the project and name it: images.
Put the image of the spaceship in the images folder.
insert image description here

Create the Ship class

Once the image to represent the spaceship is selected, it needs to be displayed on the screen.
We create a module called ship that contains the Ship class, which manages most of the ship's behavior.

import pygame

class Ship():
	def __init__(self,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		#表示屏幕的矩形
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
	def blitme(self):
		#在指定位置绘制飞船
		self.screen.blit(self.image,self.rect)

Let's illustrate this code,

On the first line of the module page, there is still the import of pygame.

​ The method __init__() of Ship() accepts two parameters: a reference to self and screen, where the latter specifies where to draw the spaceship. To load the image, we call pygame.image.load(), which returns a surface representing the spaceship, and we store this surface in self.image.

After loading the image, we use get_rect() to get the property rect() of the corresponding surface. The reason why pygame is efficient is because it allows you to handle game elements like rectangles (rect objects), even if their shape is not a rectangle. Treating game elements like rectangles is tall because rectangles are simple geometric shapes. This works so well that gamers hardly notice that we're dealing with actual shapes that aren't game elements.

When dealing with rect objects, you can use the x and y coordinates of the four corners and the center of the rectangle. The position of the rectangle can be specified by setting these values.

​ To center a game element, set the properties center, centerx, or centery of the corresponding rect object. To align the game element with the edge of the screen, use top, bottom, left, or right; to adjust the horizontal or vertical position of the game element, use the properties x and y, which are the x and y coordinates of the upper left corner of the corresponding rectangle. These properties save you from having to do calculations that game developers would otherwise have to do manually, and you'll use them a lot.

.Note
: In pygame, the origin (0,0) is located at the upper left corner of the screen, and the coordinate value will increase when moving to the lower right. On a 1200*800 screen, the origin is in the upper left corner, and the coordinates of the lower right corner are (1200,800)

.
​ We place the spaceship at the bottom center of the screen. To do this, first store the rectangle representing the screen in self.screen_rect, then set self.rect.centerx (the x coordinate of the center of the spaceship) to the attribute centerx of the rectangle representing the screen, and then set self.rect.bottom (the spaceship under The y-coordinate of the edge) is set to the bottom property of the rectangle representing the screen. pygame will use these rect properties to position the ship image so it is aligned with the bottom edge of the screen and centered horizontally.

The blitme() method draws the image to the screen at the position specified by self.rect.

draw spaceship on screen

Next update alien_invasion.py so that it creates a spaceship and calls the method blitme():

#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings
from ship import Ship #飞船类

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	#创建飞船
	ship = Ship(screen)
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		pygame.display.flip()
		
run_game()

We import the Ship class and create a Ship instance called ship after the screen is created. This instance must be created before the main while loop to avoid creating a spaceship every time the loop loops. After filling the background, we call ship.blitme() to draw the ship to the screen, making sure it appears in front of the background.


Refactored module game_functions

In large projects, it is often necessary to refactor existing code before adding new code blocks. Refactoring aims to shorten the structure of existing code and make it easier to extend. We need to create a new module called game_functions which will store a lot of functions that make the game Alien Invasion run. By creating a module game_functions, you can keep alien_invasion.py from being too long and make its logic easier to understand.

Function check_events()

We first moved the code for managing events into a function called check_events() to simplify run_game() and isolate the event management loop. By isolating the event loop, event management is decoupled from other aspects of the game, such as updating screens.

Put check_events() in a module called game_functions:

import sys
import pygame

def check_events():
     #响应按键和鼠标事件
	for event in pygame.event.get():
		if event == QUIT:
			sys.exit()

This module imports sys and pygame to be used by the event check loop. Currently, the function check_events() does not require any formal parameters, and its function body duplicates the event loop of alien_invasion.py.

Next, we continue to modify alien_invasion.py to import the game_functions we just created, and replace the event loop with a call to the function check_events():

#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings
from ship import Ship #飞船类
import game_functions as gf

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	#创建飞船
	ship = Ship(screen)
	
	# while 循环来控制游戏
	while True:
		
		gf.check_events()
				
		#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		pygame.display.flip()
		
run_game()

In the main program file, there is no need to import sys directly, as it is currently only used in the module game_fuctions. For simplicity, we have given the imported game_functions the alias gf.

function update_screen()

To further simplify run_game(), the code for updating the screen is moved into a function called update_game() and placed in the module game_functions:

// In order to optimize the article and reduce redundancy, only the newly added lines of code are placed in the content of the following code part.

def update_screen(ai_settings,screen,ship):
	#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		#让最近绘制的屏幕可见
		pygame.display.flip()

The new function update_screen() contains three formal parameters:
ai_settings, screen, ship. The code that updates the screen in the while loop of alien_invasion.py now needs to be replaced with a call to the function update_screen().

gf.update_screen(ai_settings,screen,ship)

These two functions make the while loop simpler and make subsequent development easier: do most of the work in the module game_functions instead of run_game().

pilot spaceship

Next we're going to make the spaceship move left and right, so we'll write code that responds when the user presses the left or right arrow key.

Let's move to the right first, and then move to the left.

Response to keys

Whenever the user presses a key, an event will be registered in python. Events are obtained through the method pygame.event.get(). So in check_events(), we need to specify which classes of events to check. Each key press is registered as a KEYDOWN event. When a KEYDOWN event is detected, we need to check if a specific key was pressed. For example, if the right arrow key was pressed, we increment the ship's rect.centerx value, moving the ship to the right.

Here, we only need to add new monitoring logic in the game_functions module.

#game_functions模块的 check_events 方法
def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.rect.centerx += 1

We set a formal parameter for check_events, so we need to pass an actual parameter at the call site. Don't forget to modify the parameters at the call site.
The reason why this parameter is added is that when we monitor the player's button action, we need to make the spaceship perform the corresponding movement operation. Here we add the elif block to
respond when python detects a KEYDOWN event.
Read the event.key property to check if the right arrow key (pygame.K_RIGHT) was pressed. If the right arrow key was pressed,
+1 to ship.rect.centerx.

If we run our alive_invasion.py now, every time the right arrow key is pressed, the spaceship will move 1 pixel to the right. It's a start, but not an efficient way to control a ship. Next, let's improve the control method to allow continuous movement.

allow constant movement

When the player holds down the right arrow key, we want the ship to keep moving to the right until the player releases it. We'll have the game detect the pygame.KEYUP event so we know when the player releases the right arrow key; we'll then use a combination of the KEYDOWN and KEYUP events, along with a metric called moving_right to enable continuous movement.
When the ship is not moving, the flag moving_right will be False. We set this flag to True when the player presses the right arrow key, and reset it to False when the player releases it.
The properties of the spaceship are all controlled by the ship class, so we will add a property called moving_right and a method called update() to this class. The method update() checks the state of the flag moving_right, and if this state is True, adjusts the position of the spaceship. We call this method whenever we need to adjust the position of the spaceship.

Modify the ship class:

class Ship():
		
	def __init__(self,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
		#移动标志
		relf.moving_right = False
		
	def update(self):
		#根据移动标志调整飞船的位置
		if self.moving_right:
			self.rect.centerx +=1	
		
	def blitme(self):

Next we go ahead and modify check_enents()
to set moving_right to True when the player presses the right arrow key, and set moving_rigth to Fasle when the player releases it:

def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = True
		elif event.type == pygame.KEYUP:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = False

We first changed the way the game responds when the player presses the right arrow: instead of directly adjusting the position of the ship, we just set moving_right to True. We then added a new elif block that responds to the KEYUP event; when the player releases the right arrow key (K_RIGHT), we set moving_right to False.

Finally,
we modify the while loop in alien_invasion.py to call the ship's method update() each time the loop executes:

# while 循环来控制游戏
	while True:
		
		gf.check_events(ship)
		ship.update()		
		gf.update_screen(ai_settings,screen,ship)
move left and right

Once the spaceship is able to move constantly to the right, it's easy to add logic to move to the left. We will modify the ship class and the function check_events() again. The relevant modifications made to the methods __init__() and update of the Ship class are shown below.

	#ship 类的 init方法中添加一个记录向左移动状态的标志
		#移动标志
		self.moving_right = False
		self.moving_left =False
		
	def update(self):
		#根据移动标志调整飞船的位置
		if self.moving_right:
			self.rect.centerx += 1	
		if self.moving_left:
			self.rect.centerx -= 1

Adjust check_events():

def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = True
			elif event.key == pygame.K_LEFT:
				ship.moving_left = True
		elif event.type == pygame.KEYUP:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = False
			elif event.key == pygame.K_LEFT:
				ship.moving_left = False
Adjust the speed of the spaceship

Currently, the spaceship moves up to 1 pixel each time the while loop is executed, but we can add the property ship_speed_factor to the Settings class
to control the speed of the spaceship. We will use this property to determine the maximum distance the spaceship can move each cycle. Let's start
by adding this new property in settings.py:

class Settings():
	#存储《外星人入侵》的所有设置的类
	
	def __init__(self):
		self.screen_width = 1200
		self.screen_height = 800
		self.bg_color = (230,230,230)
		
		#飞船的设置
		self.ship_speed_factor = 1.5

We set the initial value of ship_speed_factor to 1.5. When we need to move the spaceship, we will move it by 1.5 pixels instead of 1 pixel.

By specifying a small value for the speed setting, you can fine-tune the speed of the ship later when speeding up the game. However, attributes such as rect's centerx can only store integer values, so we need to
make some changes to the Ship class:

import pygame

class Ship():
		
	def __init__(self,ai_settings,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		self.ai_settings = ai_settings
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
		#在飞船的属性centerx中存储小数值
		self.center = float(self.rect.centerx)
		
		#移动标志
		self.moving_right = False
		self.moving_left =False
		
	def update(self):
		#根据移动标志调整飞船的位置
			#更新飞船的centerx值,而不是rect
		if self.moving_right:
			self.center += self.ai_settings.ship_speed_factor	
		if self.moving_left:
			self.center -= self.ai_settings.ship_speed_factor	
			
		#根据self.center更新rect值
		self.rect.centerx = self.center
		
	def blitme(self):
		#在指定位置绘制飞船
		self.screen.blit(self.image,self.rect)

In the ship class, we first add ai_settings in the __init__() method, so that the ship can get its speed settings. Next, we
store the value of the formal parameter ai_settings in an attribute to be able to use it in update(). Since adjusting the position of the spaceship now adds or subtracts a fractional value in pixels, the position needs to be stored in a variable capable of storing fractional values. Decimals can be used to set properties of the rect, but the rect will only store the integer portion of the value. To accurately store the position of the spaceship, we define a new property
self.center that can store decimal values. We
convert the value of self.rect.centerx to a decimal using the function float() and store the result into self.center.
.Now
when adjusting the position of the ship in update(), add or subtract the value of ai_settings.ship_speed_factor from the value of self.center. After updating self.center, we update self.rect.centerx which controls the position of the spaceship according to it. self.rect.centerx will only store the integer part of self.center, but for displaying the spaceship this is not a problem.

Finally don't forget to pass the actual parameters at the call of the alien_invasion.py module.

As long as the value of ship_speed_factor is greater than 1, the ship will move faster than before. This helped make the spaceship responsive enough to shoot the aliens down, and also allowed us to pick up the pace of the game as it progressed.

Guess you like

Origin blog.csdn.net/tianlei_/article/details/129528204