本篇是
一文帮你理清"游戏思路+实现逻辑"
专栏的第一
篇文章,我个人开此专栏的目的主要是:
- 理清代码的思路
- 增长知识覆盖面
- 记录以便于以后查看参考
在最近的学习中,突然觉得有必要去学习一些综合的小项目来增强自己的能力,而对于项目我想到了利用小游戏来学习的方法(毕竟玩游戏有意思哈哈),当然,我现在处于学习前期阶段,以我的能力来写一些游戏显得很不现实,毕竟我也没看过多少游戏代码什么的,但是我觉得可以去搜集一些小游戏的开源代码,然后阅读、理解,之后在理解的基础上结合自己的思想去做些更改,如果自己能称心如意的更改源代码并正常运行,那么我觉得能力也算是一种提高了,甚至一段时间以后自己可以根据自己的想法来做一些小游戏。
本文我要给大家带来的是一款经典的游戏-飞机大战,使用Python编写而成。
本代码是github上的开源代码,项目地址:https://github.com/CharlesPikachu/Games/tree/master/Game10
首先附上一张我对该代码总结的思维导图:
此思维导图是我根据对项目源代码的理解作出的,大家可以先浏览一下游戏的思维导图,然后再看下文思路分析,或者先看完下文再回来查看思维导图亦可,不过毕竟鄙人能力尚且不好,难免会有一些理解错误或是其它错误,还请大家包容并指出。
首先,既然是一个小游戏,一般来讲当然要用到面向对象的思想,在这个游戏中,主要分为三大对象:
当然,万物皆对象嘛,实际上代码中的界面什么的都可以被看作对象,不过在这里我们主要需要封装是以上三大对象。
首先,看一下封装飞船的代码:
class Ship(pygame.sprite.Sprite):
def __init__(self, idx):
pygame.sprite.Sprite.__init__(self)
self.imgs = ['./resources/imgs/ship.png', './resources/imgs/ship_exploded.png']
self.image = pygame.image.load(self.imgs[0]).convert_alpha()
self.explode_img = pygame.image.load(self.imgs[1]).convert_alpha()
# 位置
self.position = {'x': random.randrange(-10, 918), 'y': random.randrange(-10, 520)}
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = self.position['x'], self.position['y']
# 速度
self.speed = {'x': 10, 'y': 5}
# 玩家编号
self.playerIdx = idx
# 子弹冷却时间
self.cooling_time = 0
# 爆炸用
self.explode_step = 0
'''飞船爆炸'''
def explode(self, screen):
img = self.explode_img.subsurface((48*(self.explode_step-1), 0), (48, 48))
screen.blit(img, (self.position['x'], self.position['y']))
self.explode_step += 1
'''移动飞船'''
def move(self, direction):
if direction == 'left':
self.position['x'] = max(-self.speed['x']+self.position['x'], -10)
elif direction == 'right':
self.position['x'] = min(self.speed['x']+self.position['x'], 918)
elif direction == 'up':
self.position['y'] = max(-self.speed['y']+self.position['y'], -10)
elif direction == 'down':
self.position['y'] = min(self.speed['y']+self.position['y'], 520)
self.rect.left, self.rect.top = self.position['x'], self.position['y']
'''画飞船'''
def draw(self, screen):
screen.blit(self.image, self.rect)
'''射击'''
def shot(self):
return Bullet(self.playerIdx, (self.rect.center[0] - 5, self.position['y'] - 5))
首先,飞船作为游戏中的一大对象,其继承了pygame
中的精灵对象pygame.sprite.Sprite
,先对飞船初始化,
random
模块实现在屏幕中随机初始化位置。n人模式
给定飞船的方法:
class Bullet(pygame.sprite.Sprite):
def __init__(self, idx, position):
pygame.sprite.Sprite.__init__(self)
self.imgs = ['./resources/imgs/bullet.png']
self.image = pygame.image.load(self.imgs[0]).convert_alpha()
self.image = pygame.transform.scale(self.image, (10, 10))
# 位置
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
self.position = position
# 速度
self.speed = 8
# 玩家编号
self.playerIdx = idx
'''移动子弹'''
def move(self):
self.position = self.position[0], self.position[1] - self.speed
self.rect.left, self.rect.top = self.position
'''画子弹'''
def draw(self, screen):
screen.blit(self.image, self.rect)
子弹同样继承pygame
中的精灵对象pygame.sprite.Sprite
transform.scale
给图片做了放缩处理子弹具有的行为方法:
class Asteroid(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.imgs = ['./resources/imgs/asteroid.png']
self.image = pygame.image.load(self.imgs[0]).convert_alpha()
# 位置
self.rect = self.image.get_rect()
self.position = (random.randrange(20, WIDTH-20), -64)
self.rect.left, self.rect.top = self.position
# 速度
self.speed = random.randrange(3, 9)
self.angle = 0
self.angular_velocity = random.randrange(1, 5)
self.rotate_ticks = 3
'''移动小行星'''
def move(self):
self.position = self.position[0], self.position[1] + self.speed
self.rect.left, self.rect.top = self.position
'''转动小行星'''
def rotate(self):
self.rotate_ticks -= 1
if self.rotate_ticks == 0:
self.angle = (self.angle + self.angular_velocity) % 360
orig_rect = self.image.get_rect()
rot_image = pygame.transform.rotate(self.image, self.angle)
rot_rect = orig_rect.copy()
rot_rect.center = rot_image.get_rect().center
rot_image = rot_image.subsurface(rot_rect).copy()
self.image = rot_image
self.rotate_ticks = 3
'''画小行星'''
def draw(self, screen):
screen.blit(self.image, self.rect)
小行星当然也是继承pygame
中的精灵对象pygame.sprite.Sprite
行为方法:
好了,三大对象有了,游戏的核心就有了,接下来就是设置界面、刷新、让对象"活起来"的操作了。
源代码:
def main():
pygame.init()
pygame.font.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('飞机大战-goldsun')
#转到开始界面
num_player = start_interface(screen)
#对开始界面的操作会返回数字,即选择单双人模式
if num_player == 1:
while True:
GameDemo(num_player=1, screen=screen)
end_interface(screen)
else:
while True:
GameDemo(num_player=2, screen=screen)
end_interface(screen)
主函数非常简短,因为其它的操作都在别的模块中完成了,主函数这里只负责调用即可。
实现过程:
首先初始化游戏环境,设置游戏界面屏幕,转入开始界面,待用户操作之后程序正常运行并根据用户选择的模式进入循环过程中,此循环为大循环,即游戏界面与结束界面的循环,以实现重新开始功能。
源代码:
def start_interface(screen):
#创建一个时钟
clock = pygame.time.Clock()
while True:
button_1 = BUTTON(screen, (330, 190), '单人模式')
button_2 = BUTTON(screen, (330, 305), '双人模式')
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
if button_1.collidepoint(pygame.mouse.get_pos()):
return 1
elif button_2.collidepoint(pygame.mouse.get_pos()):
return 2
clock.tick(60)
pygame.display.update()
首先实例化一个Clock
对象,用于跟踪时间,然后进入屏幕的刷新循环中,屏幕中有两个按钮,循环中对按钮进行监听,两个按钮若有被触发时则跳出循环并返回相应的值以供主函数的大循环判断并实现相应模式。
这里的clock.tick(60)
其实也可以写作clock.tick()
,当括号中的参数被激活时会使得程序延迟,如给定60,则游戏将会以低于60帧的速率进行,给定的参数是一个程序刷新上限,如果不给定参数,程序将以本身的运行速率进行刷新。
源代码:
def GameDemo(num_player, screen):
pygame.mixer.music.load(("./resources/sounds/Cool Space Music.mp3"))
pygame.mixer.music.set_volume(0.4)
pygame.mixer.music.play(-1)
explosion_sound = pygame.mixer.Sound('./resources/sounds/boom.wav')
fire_sound = pygame.mixer.Sound('./resources/sounds/shot.ogg')
font = pygame.font.Font('./resources/font/simkai.ttf', 20)
# 游戏背景图
bg_imgs = ['./resources/imgs/bg_big.png',
'./resources/imgs/seamless_space.png',
'./resources/imgs/space3.jpg']
bg_move_dis = 0
bg_1 = pygame.image.load(bg_imgs[0]).convert()
bg_2 = pygame.image.load(bg_imgs[1]).convert()
bg_3 = pygame.image.load(bg_imgs[2]).convert()
# 玩家, 子弹和小行星精灵组
playerGroup = pygame.sprite.Group()
bulletGroup = pygame.sprite.Group()
asteroidGroup = pygame.sprite.Group()
# 产生小行星的时间间隔
asteroid_ticks = 90
for i in range(num_player):
playerGroup.add(Ship(i+1))
clock = pygame.time.Clock()
# 分数
Score_1 = 0
Score_2 = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 玩家一: ↑↓←→控制, 键盘九宫格的1射击
# 玩家二: wsad控制, j射击
pressed_keys = pygame.key.get_pressed()
i = -1
for player in playerGroup:
i += 1
direction = None
if i == 0:
if pressed_keys[pygame.K_UP]:
direction = 'up'
elif pressed_keys[pygame.K_DOWN]:
direction = 'down'
elif pressed_keys[pygame.K_LEFT]:
direction = 'left'
elif pressed_keys[pygame.K_RIGHT]:
direction = 'right'
if direction:
player.move(direction)
if pressed_keys[pygame.K_KP1]:
if player.cooling_time == 0:
fire_sound.play()
bulletGroup.add(player.shot())
player.cooling_time = 20
elif i == 1:
if pressed_keys[pygame.K_w]:
direction = 'up'
elif pressed_keys[pygame.K_s]:
direction = 'down'
elif pressed_keys[pygame.K_a]:
direction = 'left'
elif pressed_keys[pygame.K_d]:
direction = 'right'
if direction:
player.move(direction)
if pressed_keys[pygame.K_j]:
if player.cooling_time == 0:
fire_sound.play()
bulletGroup.add(player.shot())
player.cooling_time = 20
if player.cooling_time > 0:
player.cooling_time -= 1
if (Score_1 + Score_2) < 50:
background = bg_1
elif (Score_1 + Score_2) < 150:
background = bg_2
else:
background = bg_3
# 向下移动背景图实现飞船向上移动的效果
screen.blit(background, (0, -background.get_rect().height + bg_move_dis))
screen.blit(background, (0, bg_move_dis))
bg_move_dis = (bg_move_dis + 2) % background.get_rect().height
# 生成小行星
if asteroid_ticks == 0:
asteroid_ticks = 90
asteroidGroup.add(Asteroid())
else:
asteroid_ticks -= 1
# 画飞船
for player in playerGroup:
if pygame.sprite.spritecollide(player, asteroidGroup, True, None):
player.explode_step = 1
explosion_sound.play()
elif player.explode_step > 0:
if player.explode_step > 3:
playerGroup.remove(player)
if len(playerGroup) == 0:
return
else:
player.explode(screen)
else:
player.draw(screen)
# 画子弹
for bullet in bulletGroup:
bullet.move()
if pygame.sprite.spritecollide(bullet, asteroidGroup, True, None):
bulletGroup.remove(bullet)
if bullet.playerIdx == 1:
Score_1 += 1
else:
Score_2 += 1
else:
bullet.draw(screen)
# 画小行星
for asteroid in asteroidGroup:
asteroid.move()
asteroid.rotate()
asteroid.draw(screen)
# 显示分数
Score_1_text = '玩家一得分: %s' % Score_1
Score_2_text = '玩家二得分: %s' % Score_2
text_1 = font.render(Score_1_text, True, (0, 0, 255))
text_2 = font.render(Score_2_text, True, (255, 0, 0))
screen.blit(text_1, (2, 5))
screen.blit(text_2, (2, 35))
pygame.display.update()
clock.tick(60)
游戏界面是这个程序最重要的部分之一(但其实并不是最难的),因为我们既然做的是游戏,那么玩的过程肯定非常重要啊哈哈,而此过程当然就持续运行在游戏界面中。
clock.tick(60)
,如果电脑运行速度很快的话基本就是以60帧的速率在刷新,也就是说每1.5s生成一个小行星,你可以更改asteroid_ticks
的大小来加快/慢小行星的生成。源代码:
def end_interface(screen):
clock = pygame.time.Clock()
while True:
button_1 = BUTTON(screen, (330, 190), '重新开始')
button_2 = BUTTON(screen, (330, 305), '退出游戏')
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
if button_1.collidepoint(pygame.mouse.get_pos()):
return
elif button_2.collidepoint(pygame.mouse.get_pos()):
pygame.quit()
sys.exit()
clock.tick(60)
pygame.display.update()
结束界面和开始界面非常类似,也是两个按钮连接两个功能,对事件监听并处理罢了,在此不多赘述,而结束界面的入口是在主函数的大循环中,也就是当游戏界面跳出时便顺序进入结束界面。
到这里正篇文章的内容就结束了,本文章的目的并不是展示如何写这个游戏,而是用于理清游戏中的逻辑思路,这个时候你再去看前文的思维导图会不会感觉焕然一新呢,即使我们虽然看懂了这游戏的逻辑,我们可能还是写不出来什么游戏,原因就是我们看的还是太少了,因此我们还是多读游戏源代码多思考提升自己,总有一天,我们也能写出来很好的游戏!
最后,既然都看到这里了,如果感觉文章对您有帮助,不妨点个赞吧,谢谢~~