0 项目简介
🔥 Hi,各位同学好呀,这里是L学长!
🥇今天向大家分享一个今年(2022)最新完成的毕业设计项目作品
热血足球游戏设计与实现
🥇 学长根据实现的难度和等级对项目进行评分(最低0分,满分5分)
-
难度系数:3分
-
工作量:3分
-
创新点:4分
项目获取:
https://gitee.com/sinonfin/L-javaWebSha/tree/master
1 游戏介绍
利用Python实现的简单的足球小游戏。
游戏规则:
比较简单,就不过多介绍了,按WASD控制上下左右,空格键射门。
2 实现效果
3 开发工具
3.1 环境配置
-
Python版本:3.6.4
-
相关模块:
-
pygame模块;
-
以及一些Python自带的模块。
3.2 Pygame介绍
简介
Pygame是一系列专门为编写电子游戏而设计的Python模块(modules)。Pygame在已经非常优秀的SDL库的基础上增加了许多功能。这让你能够用Python语言编写出丰富多彩的游戏程序。
Pygame可移植性高,几乎能在任何平台和操作系统上运行。
Pygame已经被下载过数百万次。
Pygame免费开源。它在LGPL许可证(Lesser General Public License,GNU宽通用公共许可证)下发行。使用Pygame,你可以创造出免费开源,可共享,或者商业化的游戏。详情请见LGPL许可证。
优点
-
能够轻松使用多核CPU(multi core CPUs) :如今双核CPU很常用,8核CPU在桌面系统中也很便宜,而利用好多核系统,能让你在你的游戏中实现更多东西。特定的pygame函数能够释放令人生畏的python GIL(全局解释器锁),这几乎是你用C语言才能做的事。
-
核心函数用最优化的C语言或汇编语言编写:C语言代码通常比Python代码运行速度快10-20倍。而汇编语言编写的代码(assembly code)比Python甚至快到100多倍。
-
安装便捷:一般仅需包管理程序或二进制系统程序便能安装。
-
真正地可移植:支持Linux (主要发行版), Windows (95, 98, ME, 2000, XP, Vista, 64-bit Windows,), Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX等操作系统.也能支持AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS and OS/2,但是还没有受到官方认可。你也可以在手持设备,游戏控制台, One Laptop Per Child (OLPC) computer项目的电脑等设备中使用pygame.
-
用法简单:无论是小孩子还是大人都能学会用pygame来制作射击类游戏。
-
很多Pygame游戏已发行:其中包括很多游戏大赛入围作品、非常受欢迎的开源可分享的游戏。
-
由你来控制主循环:由你来调用pygame的函数,pygame的函数并不需要调用你的函数。当你同时还在使用其他库来编写各种各种的程序时,这能够为你提供极大的掌控权。
-
不需要GUI就能使用所有函数:仅在命令行中,你就可以使用pygame的某些函数来处理图片,获取游戏杆输入,播放音乐……
-
对bug反应迅速:很多bug在被上报的1小时内就能被我们修复。虽然有时候我们确实会卡在某一个bug上很久,但大多数时候我们都是很不错的bug修复者。如今bug的上报已经很少了,因为许多bug早已被我们修复。
-
代码量少:pygame并没有数以万计的也许你永远用不到的冗杂代码。pygame的核心代码一直保持着简洁特点,其他附加物诸如GUI库等,都是在核心代码之外单独设计研发的。
-
模块化:你可以单独使用pygame的某个模块。想要换着使用一个别的声音处理库?没问题。pygame的很多核心模块支持独立初始化与使用。
最小开发框架
import pygame,sys #sys是python的标准库,提供Python运行时环境变量的操控
pygame.init() #内部各功能模块进行初始化创建及变量设置,默认调用
size = width,height = 800,600 #设置游戏窗口大小,分别是宽度和高度
screen = pygame.display.set_mode(size) #初始化显示窗口
pygame.display.set_caption("小游戏程序") #设置显示窗口的标题内容,是一个字符串类型
while True: #无限循环,直到Python运行时退出结束
for event in pygame.event.get(): #从Pygame的事件队列中取出事件,并从队列中删除该事件
if event.type == pygame.QUIT: #获得事件类型,并逐类响应
sys.exit() #用于退出结束游戏并退出
pygame.display.update() #对显示窗口进行更新,默认窗口全部重绘
代码执行流程
4 具体实现
这里简单介绍一下原理吧,首先我们整个简单的开始界面吧:
'''定义游戏开始界面'''
def StartInterface(screen, resource_loader, cfg):
clock = pygame.time.Clock()
font, flag, count = resource_loader.fonts['default30'], True, 0
font_render = font.render('按任意键开始游戏', False, cfg.RED)
while True:
count += 1
if count > 20: count, flag = 0, not flag
for event in pygame.event.get():
if event.type == pygame.QUIT:
QuitGame()
elif event.type == pygame.KEYDOWN:
return True
screen.blit(resource_loader.images['background_start'], (0, 0))
if flag: screen.blit(font_render, ((cfg.SCREENSIZE[0] - font.size('按任意键开始游戏')[0]) // 2, cfg.SCREENSIZE[1] - 200))
clock.tick(cfg.FPS)
pygame.display.update()
效果大概是这样子的:
方便起见,我们设置成了按任意键都可以开始游戏。
接下来,我们来定义一下球员类,球员分为电脑自动控制和我们手动控制两种类型,其中我们手动控制的核心代码如下:
'''更新'''
def update(self, screen_size, ball):
# 电脑自动控制
if self.auto_control:
self.autoupdate(screen_size, ball)
return
# 静止状态
if not self.is_moving: return
# 切换人物动作实现动画效果
self.switch()
# 根据方向移动人物
ori_position = self.position.copy()
speed = self.speed * self.direction[0], self.speed * self.direction[1]
self.position[0] = min(max(0, self.position[0] + speed[0]), screen_size[0] - 48)
self.position[1] = min(max(0, self.position[1] + speed[1]), screen_size[1] - 48)
self.rect.left, self.rect.top = self.position
if self.rect.bottom > 305 and self.rect.top < 505 and (self.rect.right > 1121 or self.rect.left < 75):
self.position = ori_position
self.rect.left, self.rect.top = self.position
# 设置为静止状态
self.is_moving = False
电脑自动控制的核心代码如下:
'''自动更新'''
def autoupdate(self, screen_size, ball):
# 守门员
if self.player_type == 'goalkeeper':
self.speed = 1
# --沿着门漫步
def wondering(self):
self.switch()
self.position[1] = min(max(305, self.position[1] + self.direction[1] * self.speed), 459)
self.rect.left, self.rect.top = self.position
if self.rect.top == 305 or self.rect.top == 459:
self.direction = self.direction[0], -self.direction[1]
self.setdirection(self.direction)
# --有球就随机射球
if ball.taken_by_player == self:
if self.group_id == 1:
if random.random() > 0.8 or self.prepare_for_kicking:
self.prepare_for_kicking = True
self.setdirection((1, 0))
if self.prepare_for_kicking:
self.prepare_for_kicking_count += 1
if self.prepare_for_kicking_count > self.prepare_for_kicking_freq:
self.prepare_for_kicking_count = 0
self.prepare_for_kicking = False
ball.kick(self.direction)
self.setdirection(random.choice([(0, 1), (0, -1)]))
else:
wondering(self)
else:
if random.random() > 0.8 or self.prepare_for_kicking:
self.prepare_for_kicking = True
self.setdirection((-1, 0))
if self.prepare_for_kicking:
self.prepare_for_kicking_count += 1
if self.prepare_for_kicking_count > self.prepare_for_kicking_freq:
self.prepare_for_kicking_count = 0
self.prepare_for_kicking = False
ball.kick(self.direction)
self.setdirection(random.choice([(0, 1), (0, -1)]))
else:
wondering(self)
# --没球来回走
else:
wondering(self)
# 其他球员跟着球走
else:
if ball.taken_by_player == self:
self.switch()
if self.group_id == 1:
self.direction = min(max(1150 - self.rect.left, -1), 1), min(max(405 - self.rect.top, -1), 1)
else:
self.direction = min(max(350 - self.rect.left, -1), 1), min(max(405 - self.rect.top, -1), 1)
self.setdirection(self.direction)
if (random.random() > 0.9 and self.position[0] > 350 and self.position[0] < 1150) or self.prepare_for_kicking:
if self.group_id == 1:
self.direction = min(max(1190 - self.rect.left, -1), 1), min(max(405 - self.rect.top, -1), 1)
else:
self.direction = min(max(310 - self.rect.left, -1), 1), min(max(405 - self.rect.top, -1), 1)
self.setdirection(self.direction)
self.prepare_for_kicking = True
if self.prepare_for_kicking:
self.prepare_for_kicking_count += 1
if self.prepare_for_kicking_count > self.prepare_for_kicking_freq:
self.prepare_for_kicking_count = 0
self.prepare_for_kicking = False
ball.kick(self.direction)
else:
speed = self.speed * self.direction[0], self.speed * self.direction[1]
ori_position = self.position.copy()
self.position[0] = min(max(0, self.position[0] + speed[0]), screen_size[0] - 48)
self.position[1] = min(max(0, self.position[1] + speed[1]), screen_size[1] - 48)
self.rect.left, self.rect.top = self.position
if self.rect.bottom > 305 and self.rect.top < 505 and (self.rect.right > 1121 or self.rect.left < 75):
self.position = ori_position
self.rect.left, self.rect.top = self.position
else:
self.switch()
if (ball.rect.centery > 400 and self.player_type == 'bottomhalf') or (ball.rect.centery <= 400 and self.player_type == 'upperhalf') or self.player_type == 'common':
self.direction = min(max(ball.rect.left - self.rect.left, -1), 1), min(max(ball.rect.top - self.rect.top, -1), 1)
self.direction = self.direction[0] * random.random(), self.direction[1] * random.random()
else:
if self.keep_direction_count >= self.keep_direction_freq:
self.direction = random.choice([-1, 0, 1]), random.choice([-1, 0, 1])
self.keep_direction_count = 0
else:
self.keep_direction_count += 1
self.setdirection(self.direction)
speed = self.speed * self.direction[0], self.speed * self.direction[1]
ori_position = self.position.copy()
self.position[0] = min(max(0, self.position[0] + speed[0]), screen_size[0] - 48)
self.position[1] = min(max(0, self.position[1] + speed[1]), screen_size[1] - 48)
self.rect.left, self.rect.top = self.position
if self.rect.bottom > 305 and self.rect.top < 505 and (self.rect.right > 1121 or self.rect.left < 75):
self.position = ori_position
self.rect.left, self.rect.top = self.position
逻辑比较简单,大概是这样子的:
-
守门员:就在球门边上来回走
-
负责上半场的球员:在上半场出现球的时候就往球的位置移动,如果捕获到了球,则往对方球门移动并随机射门,否则随机移动;
-
负责下半场的球员:在下半场出现球的时候就往球的位置移动,如果捕获到了球,则往对方球门移动并随机射门,否则随机移动;
-
负责全场的球员:往球的位置移动,果捕获到了球,则往对方球门移动并随机射门。
接下来定义一下足球类:
'''定义足球类'''
class Ball(pygame.sprite.Sprite):
def __init__(self, images, position):
pygame.sprite.Sprite.__init__(self)
self.images = images
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.centerx, self.rect.centery = position
self.mask = pygame.mask.from_surface(self.image)
# 球的速度
self.speed = 0
# 球的方向
self.direction = [0, 0]
# 控制球的球员
self.taken_by_player = None
# 用于切换球动作的变量
self.action_pointer = 0
self.count = 0
self.switch_frequency = 3
# 是否在运动状态
self.is_moving = False
'''更新'''
def update(self, screen_size):
# 静止状态
if not self.is_moving: return
# 切换球动作实现动画效果
self.count += 1
if self.count == self.switch_frequency:
self.count = 0
self.action_pointer = (self.action_pointer + 1) % len(self.images)
self.image = self.images[self.action_pointer]
# 如果球是被球员控制的
if self.taken_by_player is not None:
self.setdirection(self.taken_by_player.direction)
if self.taken_by_player.direction[0] < 0:
self.rect.left, self.rect.top = self.taken_by_player.rect.left - 15, self.taken_by_player.rect.top + 30
elif self.taken_by_player.direction[0] > 0:
self.rect.left, self.rect.top = self.taken_by_player.rect.left + 30, self.taken_by_player.rect.top + 30
elif self.taken_by_player.direction[1] < 0:
self.rect.left, self.rect.top = self.taken_by_player.rect.left + 15, self.taken_by_player.rect.top - 15
elif self.taken_by_player.direction[1] > 0:
self.rect.left, self.rect.top = self.taken_by_player.rect.left + 10, self.taken_by_player.rect.top + 50
return
# 根据方向移动球
ori_position = self.rect.left, self.rect.right, self.rect.top, self.rect.bottom
self.speed = max(self.speed - 1.7 * 0.05, 0.0)
if self.speed == 0.0: self.is_moving = False
vector = [self.speed * self.direction[0], self.speed * self.direction[1]]
vector[0] = vector[0] / math.pow(self.direction[0] ** 2 + self.direction[1] ** 2, 0.5)
vector[1] = vector[1] / math.pow(self.direction[0] ** 2 + self.direction[1] ** 2, 0.5)
self.rect.left = min(max(0, self.rect.left + vector[0]), screen_size[0] - 48)
if self.rect.left == 0 or self.rect.left == screen_size[0] - 48:
self.direction = self.direction[0] * -0.8, self.direction[1]
self.rect.top = min(max(0, self.rect.top + vector[1]), screen_size[1] - 48)
if ori_position[1] > 1121 or ori_position[0] < 75:
if self.rect.bottom > 305 and self.rect.top < 505:
if self.direction[1] > 0:
self.rect.bottom = 305
self.direction = self.direction[0], self.direction[1] * -0.8
elif self.direction[1] < 0:
self.rect.top = 505
self.direction = self.direction[0], self.direction[1] * -0.8
if self.rect.top == 0 or self.rect.top == screen_size[1] - 48:
self.direction = self.direction[0], self.direction[1] * -0.8
'''设置方向'''
def setdirection(self, direction):
self.is_moving = True
self.direction = direction
'''踢球'''
def kick(self, direction):
self.speed = 12
self.direction = direction
self.taken_by_player = None
self.is_moving = True
'''在屏幕上显示'''
def draw(self, screen):
screen.blit(self.image, self.rect)
比较简单,主要就是两种状态:
-
被球员捕获,跟着球员走;
-
被球员踢出去之后根据球员踢的方向和设定的初速度进行减速运动,如果碰到边界则反方向弹出。
最后写个主循环就大功告成啦:
# 游戏主循环
clock = pygame.time.Clock()
while True:
# --基础背景绘制
screen.fill(cfg.LIGHTGREEN)
pygame.draw.circle(screen, cfg.WHITE, (600, 400), 80, 5)
pygame.draw.rect(screen, cfg.WHITE, (10, 10, 600, 790), 5)
pygame.draw.rect(screen, cfg.WHITE, (600, 10, 590, 790), 5)
pygame.draw.rect(screen, cfg.WHITE, (10, 150, 300, 500), 5)
pygame.draw.rect(screen, cfg.WHITE, (890, 150, 300, 500), 5)
screen.blit(resource_loader.images['doors'][0].convert(), (8, 305))
screen.blit(resource_loader.images['doors'][1].convert(), (1121, 305))
screen.blit(score_board, (565, 15))
# --事件监听
for event in pygame.event.get():
if event.type == pygame.QUIT:
QuitGame()
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
QuitGame()
pressed_keys = pygame.key.get_pressed()
direction = [0, 0]
if pressed_keys[pygame.K_w]: direction[1] -= 1
if pressed_keys[pygame.K_d]: direction[0] += 1
if pressed_keys[pygame.K_s]: direction[1] += 1
if pressed_keys[pygame.K_a]: direction[0] -= 1
if direction != [0, 0]: player_controlled.setdirection(direction)
if pressed_keys[pygame.K_SPACE] and player_controlled == ball.taken_by_player:
ball.kick(player_controlled.direction)
# --更新玩家
for item in players_group1:
if pygame.sprite.collide_mask(item, ball) and ball.taken_by_player != item:
ball.is_moving = True
ball.taken_by_player = item
for item in players_group2:
if pygame.sprite.collide_mask(item, ball) and ball.taken_by_player != item:
ball.is_moving = True
ball.taken_by_player = item
for item in players_group1:
item.update(cfg.SCREENSIZE_GAMING, ball)
for item in players_group2:
item.update(cfg.SCREENSIZE_GAMING, ball)
# --更新球
ball.update(cfg.SCREENSIZE_GAMING)
# --更新屏幕
ball.draw(screen)
players_group1.draw(screen)
players_group2.draw(screen)
clock.tick(cfg.FPS)
pygame.display.update()
# --计算得分
if ball.rect.bottom > 305 and ball.rect.top < 505:
if ball.rect.right > 1121: return 1
elif ball.rect.left < 75: return 2