先森林好,负基础Python游戏开发入门了解一下~
低能预警!
大扎好,没油轱天乐,我系渣渣喵,探挽教程,介四里没有学过的船新教程(简单版)。全程蹄把蹄教学,包教包会,害外面辣些妖艳教程大不一样。挤需体验三番钟,里造会干我一样,爱象节篇回答。
最终效果~
有什么能比我们一起做一款小游戏更快入门的呢?
有请主角:
先用一张图理清故事背景:
上Demo:
由于gif图大小受限,只能展示其中的几帧,更多精彩大家可以在自己的游戏里体验。废话不多说,赶快开始!
初入新手村!
开始前我们需要先安装好pygame,详细步骤可以参考:
如果遇到问题,这里是Plan B:
还有问题可以参考:pip下载地址,pygame下载地址
友情提示:如果还有问题请先点击右上角的红叉冷静一下。
当然还有模型、音效与「素材包!」:想了解更多进阶版资源吗?想要的话可以全部给你,去找吧!我把所有资源都放在最后一部分!另外沃·兹基·边德研究所发现,以下内容对零基础极其友善,阅读 + 实践大约需要60min,建议点赞收藏后分期食用。
冒险开始!
注:所有处理后的图片可以从「素材包!」中获得,开始前记得创建一个「游戏文件夹」用来收藏素材(=。=)
另外可以这里可能可以帮助大家更好的理解:
Step 1:
1.1 - 添加背景
为了方便描述,我们将代码分为三个大区域:模块区、设置区和游戏区。每个大区域中可能有若干小区域。
其中设置区中的窗口设置用于调整窗口的一些属性,例如宽度、高度、名字和背景色等。基础设置区主要用来加载图片和音乐素材,以及初始化一些参数。
# 0 - 模块区
import os
import sys
import pygame
from pygame.locals import *
# 主要的工作区域
if __name__ == '__main__':
# 1 - 设置区
# 1.1 - 窗口设置区
white = (255, 255, 255)
screen_width, screen_height = 960, 640
os.environ['SDL_VIDEO_CENTERED'] = '1'
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pas_Game")
# 1.2 - 基础设置区
pygame.init()
# 参数区:在这块区域初始化参数
fig_path = r'这里是「游戏文件夹」的路径/'
paper = pygame.image.load(fig_path + 'paper.png').convert_alpha()
# 图片区:在这块区域加载图片
# 2 - 游戏区
# 2.2 - 游戏进行区
while True:
# 2.2.1 - 游戏显示区
screen.fill(white)
screen.blit(paper, (0, 0))
# 游戏显示区的代码更新区域
pygame.display.flip()
# 2.2.2 - 游戏操作区
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 2.2.3 - 结束判断区
1.2 - 建造城墙
现在我们的任务是在200里外建造一排城墙。
首先在1.2 - 基础设置区 - 参数区更新一个参数。
distance = 200
另外在 1.2 - 基础设置区 - 图片区加载城墙的图片,并获取图片的宽和高。
wall = pygame.image.load(fig_path + 'walls.png').convert_alpha()
wall_width = wall.get_width()
wall_height = wall.get_height()
最后在 2.2.1 - 游戏显示区 - 代码更新区域显示城墙。
for height in range(0, screen_height, wall_height):
screen.blit(wall, (distance, height))
Step 2:
2.1 - 你好,乞丐子!
前两个步骤和之前建造城墙相同,在对应的区域初始化乞丐子的移动速度和初始位置。
rect_bg = Rect(50, 50, 133, 142)
bg_speed = 2
begger_pos = []
并加载乞丐子的图片,获取其宽度和高度。
begger = pygame.image.load(fig_path + 'beggers.png').convert_alpha()
begger_width = begger.get_width()
begger_height = begger.get_height()
不同的是,在这个阶段,我们还要在模块区与主要工作区域之间加上这样一段代码来通过WASD控制乞丐子的移动,同时将他的移动范围限定在城墙以内。
class Begger(object):
def __init__(self, img, rect, speed):
self.ful_img = img
self.imgs = [self.ful_img.subsurface(Rect((i*83, 0), (83, 93))) for i in range(3)]
self.rect = rect
self.speed = speed
self.num = 0
def update(self, screen, press_keys):
if press_keys[K_a]:
self.rect.left -= self.speed
if self.rect.left <= 0:
self.rect.left = 0
if press_keys[K_d]:
self.rect.left += self.speed
if self.rect.right >= 200:
self.rect.right = 200
if press_keys[K_w]:
self.rect.top -= self.speed
if self.rect.top <= 0:
self.rect.top = 0
if press_keys[K_s]:
self.rect.top += self.speed
if self.rect.bottom >= 640:
self.rect.bottom = 640
self.num += 1
if self.num % 3 == 0:
self.num = 0
return [(self.rect.left + self.rect.right)/2, (self.rect.top + self.rect.bottom)/2], self.imgs[self.num]
注:需要注意的是「素材包!」里面的乞丐子的图片由三张相同的子图组成,大家可以自行改变任意两张子图来实现动态效果。
同时,在 游戏显示区的代码更新区域新增一段代码来实现乞丐子随鼠标移动而旋转的效果。
press_keys = pygame.key.get_pressed()
begger_pos, begger_img = bg.update(screen, press_keys)
position = pygame.mouse.get_pos()
angle = math.atan2(position[1] - (begger_pos[1] + begger_height),
position[0] - (begger_pos[0] + begger_width))
begger_rot = pygame.transform.rotate(begger_img, 360 - angle * 57.29)
begger_pos1 = (begger_pos[0] - begger_rot.get_rect().width / 2,
begger_pos[1] - begger_rot.get_rect().height / 2)
screen.blit(begger_rot, begger_pos1)
友情提醒:要先在模块区里多加入一行。
import math
由于原理不是很困难,这里就不证明了。贴一张图,意思一下,溜了~
最后在2 - 游戏区 与 2.2 - 游戏进行区之间加上这段代码:
bg = Begger(begger, rect_bg, bg_speed)
看看效果:
好的,那么现在我们有了一个能旋转,但不能发射能量波的乞丐子。建议您在这里休息一下~
2.2 - 能量波?
参数设置和图片加载不再赘述,设置波速为0.8,还限制了屏幕上最多只能同时存在三个波。
wave_set = []
wave_speed = 0.8
wave_max = 3
图片加载:
waves = pygame.image.load(fig_path + 'waves.png').convert_alpha()
sub_wave = waves.subsurface(Rect((0, 0), (waves.get_width() / 5, waves.get_height())))
sub_wave_width = sub_wave.get_width()
sub_wave_height = sub_wave.get_height()
注:这里只取了能量波图片的第一部分,有兴趣的童鞋可以做一个随时间渐弱的能量波。
接下来在 游戏显示区的代码更新区域加上...
for wave in wave_set:
index = 0
vel_x = math.cos(wave[0]) * wave_speed
vel_y = math.sin(wave[0]) * wave_speed
wave[1] += vel_x
wave[2] += vel_y
if wave[1] < - sub_wave_width or wave[1] > screen_width \
or wave[2] < - sub_wave_height or wave[2] > screen_height:
wave_set.pop(index)
index += 1
for projectile in wave_set:
wave1 = pygame.transform.rotate(sub_wave, 360 - projectile[0] * 57.29)
screen.blit(wave1, (projectile[1], projectile[2]))
以及第一次在 游戏操作区的更新:
if event.type == pygame.MOUSEBUTTONDOWN and len(wave_set) < wave_max:
position = pygame.mouse.get_pos()
wave_set.append([math.atan2(position[1] - (begger_pos1[1] + begger_height),
position[0] - (begger_pos1[0] + begger_width)),
begger_pos1[0], begger_pos1[1]])
观察一下效果:
Step 3:
天启四骑士?
同样的,我们略过第一段,设置了健康值和骑士前进的速度。
health_value_max = 194
health_value = 194
monsters = []
monster_speed = 1
monster_img1 = pygame.image.load(fig_path + 'monster1.png').convert_alpha()
monster_width = monster_img1.get_width()
monster_height = monster_img1.get_height()
monster_img = monster_img1
然后在 游戏显示区的代码更新区域更新一手。
(1)如果能量波的矩形与骑士有交叉,则同时移除两者。
(2)如果天启兵临城下,则扣除20~50健康值。
实际效果可以参照一开始的Demo的中段~
for monster in monsters:
if monster[0] < - monster_width:
monsters.pop(index)
monster[0] -= monster_speed
monster_rect = pygame.Rect(monster_img.get_rect())
monster_rect.top = monster[1]
monster_rect.left = monster[0]
if monster_rect.left < wall_width + distance:
health_value -= random.randint(20, 50)
monsters.pop(index)
index1 = 0
for wave in wave_set:
wave_rect = pygame.Rect(sub_wave.get_rect())
wave_rect.left = wave[1]
wave_rect.top = wave[2]
if monster_rect.colliderect(wave_rect):
wave_set.pop(index1)
try:
monsters.pop(index)
except IndexError as error:
print("IndexError: " + str(error))
index1 += 1
index += 1
for monster in monsters:
screen.blit(monster_img, monster)
友情提醒:当然别忘了在 模块区加上这个。
import random建议您在这里休息一下~
Step 4:
Win or lose?
设定一个结束时间(秒),在到达结束时间前健康值小于等于零则判负。反之,达到胜利条件。
在 设置区设置结束时间,获取当前时间并加载血条和游戏时间。
fin_time = 20
start_time = datetime.datetime.now()
health_bar_img = pygame.image.load(fig_path + "health_bar.png")
health_bar_height = health_bar_img.get_height()
health_img = pygame.image.load(fig_path + "health.png")
health_height = health_img.get_height()
以及 游戏显示区的代码更新区域最后一次更新。
font = pygame.font.Font(None, 42)
cur_time = datetime.datetime.now()
play_time = (cur_time - start_time).seconds
if play_time % 60 < 10:
time_str = ":0"
else:
time_str = ":"
survived_text = font.render(
str(play_time // 60) +
time_str +
str(play_time % 60),
True, (0, 0, 0)
)
text_Rect = survived_text.get_rect()
text_Rect.topright = [screen_width - 5, 5]
screen.blit(survived_text, text_Rect)
health_bar_img = pygame.transform.scale(health_bar_img,
(health_value_max, health_bar_height))
screen.blit(health_bar_img, [0, 5])
if health_value < 0:
health_value = 0
health_img = pygame.transform.scale(health_img,
(health_value, health_height))
screen.blit(health_img, [0, 5])
在 模块区加上
import datetime
在 基础设置区加上
running = 1
win = 0
把 游戏进行区的
while True:
改为
while running:
最后在2.2.3 - 结束判断区加上:
if pygame.time.get_ticks() >= fin_time * 1000:
running = 0
win = 1
if health_value == 0:
running = 0
win = 0
Step 5:
添加封面与结尾~
首先是结尾部分:
在 基础设置区将running的初始值改为0,并加上:
start = 0
running = 0
加载图片不提了~
victory = pygame.image.load(fig_path + 'victory.png')
game_over = pygame.image.load(fig_path + 'game_over.png')
最后将下面的代码加到 游戏区的‘后面’作为 结束区。
while not running and start:
pygame.mixer.music.stop()
if win:
screen.blit(victory, (0, 0))
pygame.font.init()
font = pygame.font.Font(None, 84)
text = font.render("Victory !",
True, (250, 50, 200))
text_Rect = text.get_rect()
text_Rect.centerx = screen.get_rect().centerx + 20
text_Rect.centery = screen.get_rect().centery - 250
screen.blit(text, text_Rect)
if not win:
screen.blit(game_over, (0, 0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
接下来是封面部分(含背景音乐):
加载封面图~
start_img = pygame.image.load(fig_path + 'start.png').convert_alpha()
最后在游戏区和游戏进行区之间加上下面的代码就大功告成啦!
while not start:
screen.fill(white)
screen.blit(start_img, (160, 0))
pygame.font.init()
font = pygame.font.Font(None, 84)
text = font.render("Press Space to Start !",
True, (250, 50, 200))
text_Rect = text.get_rect()
text_Rect.centerx = screen.get_rect().centerx
text_Rect.centery = screen.get_rect().centery + 200
screen.blit(text, text_Rect)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == K_SPACE:
start = 1
running = 1
pygame.mixer.music.load(fig_path + "Checkpoint.mp3")
pygame.mixer.music.play(1, 0.0)
pygame.mixer.music.set_volume(0.15)
start_time = datetime.datetime.now()
资源区:
素材包链接已失效
如果需要更多的图片和音频素材,可以到这里:
另外如果您希望加上一些别的元素,例如:
可以到这里看看:
最后贴上完整的代码以供参考,完结撒花~
# 0 - 模块区
import os
import sys
import math
import random
import pygame
import datetime
from pygame.locals import *
class Begger(object):
def __init__(self, img, rect, speed):
self.ful_img = img
self.imgs = [self.ful_img.subsurface(Rect((i*83, 0), (83, 93)))
for i in range(3)]
self.rect = rect
self.speed = speed
self.num = 0
def update(self, screen, press_keys):
if press_keys[K_a]:
self.rect.left -= self.speed
if self.rect.left <= 0:
self.rect.left = 0
if press_keys[K_d]:
self.rect.left += self.speed
if self.rect.right >= 200:
self.rect.right = 200
if press_keys[K_w]:
self.rect.top -= self.speed
if self.rect.top <= 0:
self.rect.top = 0
if press_keys[K_s]:
self.rect.top += self.speed
if self.rect.bottom >= 640:
self.rect.bottom = 640
self.num += 1
if self.num % 3 == 0:
self.num = 0
return [(self.rect.left + self.rect.right)/2, (self.rect.top + self.rect.bottom)/2], self.imgs[self.num]
# 主要的工作区域
if __name__ == '__main__':
# 1 - 设置区
# 1.1 - 窗口设置区
white = (255, 255, 255)
screen_width, screen_height = 960, 640
os.environ['SDL_VIDEO_CENTERED'] = '1'
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pas_Game")
# 1.2 - 基础设置区
pygame.init()
start = 0
win = 0
running = 0
distance = 200
health_value = 194
health_value_max = 194
fin_time = 20
start_time = datetime.datetime.now()
rect_bg = Rect(50, 50, 133, 142)
bg_speed = 2
begger_pos = []
monsters = []
monster_speed = 1
wave_set = []
wave_speed = 0.8
wave_max = 3
fig_path = r'D:\Pas_Game/'
paper = pygame.image.load(fig_path + 'paper.png').convert_alpha()
wall = pygame.image.load(fig_path + 'walls.png').convert_alpha()
wall_width = wall.get_width()
wall_height = wall.get_height()
begger = pygame.image.load(fig_path + 'beggers.png').convert_alpha()
begger_width = begger.get_width()
begger_height = begger.get_height()
waves = pygame.image.load(fig_path + 'waves.png').convert_alpha()
sub_wave = waves.subsurface(Rect((0, 0), (waves.get_width() / 5, waves.get_height())))
sub_wave_width = sub_wave.get_width()
sub_wave_height = sub_wave.get_height()
monster_img1 = pygame.image.load(fig_path + 'monster1.png').convert_alpha()
monster_width = monster_img1.get_width()
monster_height = monster_img1.get_height()
monster_img = monster_img1
health_bar_img = pygame.image.load(fig_path + "health_bar.png")
health_bar_height = health_bar_img.get_height()
health_img = pygame.image.load(fig_path + "health.png")
health_height = health_img.get_height()
victory = pygame.image.load(fig_path + 'victory.png')
game_over = pygame.image.load(fig_path + 'game_over.png')
start_img = pygame.image.load(fig_path + 'start.png').convert_alpha()
# 2 - 游戏区
while not start:
screen.fill(white)
screen.blit(start_img, (160, 0))
pygame.font.init()
font = pygame.font.Font(None, 84)
text = font.render("Press Space to Start !",
True, (250, 50, 200))
text_Rect = text.get_rect()
text_Rect.centerx = screen.get_rect().centerx
text_Rect.centery = screen.get_rect().centery + 200
screen.blit(text, text_Rect)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == K_SPACE:
start = 1
running = 1
pygame.mixer.music.load(fig_path + "Checkpoint.mp3")
pygame.mixer.music.play(1, 0.0)
pygame.mixer.music.set_volume(0.15)
start_time = datetime.datetime.now()
bg = Begger(begger, rect_bg, bg_speed)
# 2.2 - 游戏进行区
while running:
# 2.2.1 - 游戏显示区
screen.fill(white)
screen.blit(paper, (0, 0))
for height in range(0, screen_height, wall_height):
screen.blit(wall, (distance, height))
press_keys = pygame.key.get_pressed()
begger_pos, begger_img = bg.update(screen, press_keys)
position = pygame.mouse.get_pos()
angle = math.atan2(position[1] - (begger_pos[1] + begger_height),
position[0] - (begger_pos[0] + begger_width))
begger_rot = pygame.transform.rotate(begger_img, 360 - angle * 57.29)
begger_pos1 = (begger_pos[0] - begger_rot.get_rect().width / 2,
begger_pos[1] - begger_rot.get_rect().height / 2)
screen.blit(begger_rot, begger_pos1)
for wave in wave_set:
index = 0
vel_x = math.cos(wave[0]) * wave_speed
vel_y = math.sin(wave[0]) * wave_speed
wave[1] += vel_x
wave[2] += vel_y
if wave[1] < - sub_wave_width or wave[1] > screen_width \
or wave[2] < - sub_wave_height or wave[2] > screen_height:
wave_set.pop(index)
index += 1
for projectile in wave_set:
wave1 = pygame.transform.rotate(sub_wave, 360 - projectile[0] * 57.29)
screen.blit(wave1, (projectile[1], projectile[2]))
monster_timer = random.choice(range(200))
if monster_timer < 1:
monsters.append([screen_width,
random.randint(monster_height, screen_height - monster_height)])
index = 0
for monster in monsters:
if monster[0] < - monster_width:
monsters.pop(index)
monster[0] -= monster_speed
monster_rect = pygame.Rect(monster_img.get_rect())
monster_rect.top = monster[1]
monster_rect.left = monster[0]
if monster_rect.left < wall_width + distance:
health_value -= random.randint(20, 50)
monsters.pop(index)
index1 = 0
for wave in wave_set:
wave_rect = pygame.Rect(sub_wave.get_rect())
wave_rect.left = wave[1]
wave_rect.top = wave[2]
# 检查两个矩形块是否交叉
if monster_rect.colliderect(wave_rect):
wave_set.pop(index1)
try:
monsters.pop(index)
except IndexError as error:
print("IndexError: " + str(error))
index1 += 1
index += 1
for monster in monsters:
screen.blit(monster_img, monster)
font = pygame.font.Font(None, 42)
cur_time = datetime.datetime.now()
play_time = (cur_time - start_time).seconds
if play_time % 60 < 10:
time_str = ":0"
else:
time_str = ":"
survived_text = font.render(
str(play_time // 60) +
time_str +
str(play_time % 60),
True, (0, 0, 0)
)
text_Rect = survived_text.get_rect()
text_Rect.topright = [screen_width - 5, 5]
screen.blit(survived_text, text_Rect)
health_bar_img = pygame.transform.scale(health_bar_img,
(health_value_max, health_bar_height))
screen.blit(health_bar_img, [0, 5])
if health_value < 0:
health_value = 0
health_img = pygame.transform.scale(health_img,
(health_value, health_height))
screen.blit(health_img, [0, 5])
pygame.display.flip()
# 2.2.2 - 游戏操作区
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN and len(wave_set) < wave_max:
position = pygame.mouse.get_pos()
wave_set.append([math.atan2(position[1] - (begger_pos1[1] + begger_height),
position[0] - (begger_pos1[0] + begger_width)),
begger_pos1[0], begger_pos1[1]])
if pygame.time.get_ticks() >= fin_time * 1000:
running = 0
win = 1
if health_value == 0:
running = 0
win = 0
while not running and start:
pygame.mixer.music.stop()
if win:
screen.blit(victory, (0, 0))
pygame.font.init()
font = pygame.font.Font(None, 84)
text = font.render("Victory !",
True, (250, 50, 200))
text_Rect = text.get_rect()
text_Rect.centerx = screen.get_rect().centerx + 20
text_Rect.centery = screen.get_rect().centery - 250
screen.blit(text, text_Rect)
if not win:
screen.blit(game_over, (0, 0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()