欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
素材文件,百度网盘下载链接 密码:y1cp
关于文件路径
计算机中的文件路径分为相对路径和绝对路径。比如说在你的桌面上有个项目文件夹dadishu
,内部包含了一个imgs
文件夹和一个main.py
文件,文件夹内有张bg.png
图片。
那么这张图片相对于main.py
的相对路径就是./imgs/bg.png
,而它实际的绝对路径大概是C:/Users/username/desktop/dadishu/imgs/bg.png
。
绝对路径不仅用起来很烦人,而且如果你把它写入了代码,那么以后整个项目文件夹移动到D盘之后代码就会出错,因为它还傻傻的认为自己在C盘,还回去C盘找文件。
下面这个函数可以非常好的解决这个问题,自动的把相对路径转换为绝对路径。你可以在单独的新文件中测试它。
import sys,os
def ipath(relative_path):
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
p = os.path.join(base_path, relative_path)
return p
print(ipath('bg.mp3'))
这个ipath
函数将输出一个完整的绝对路径。这在MacOS下将项目打包成App的时候非常关键,否则将导致运行错误。因为在运行代码的时候,绝对路径往往比相对路径更加可靠。
函数
我们经常需要把共同完成一个任务的很多行代码打包放在一起,称之为函数,然后在需要的地方整体使用这个函数,这不仅可以避免多余的重复代码,而且可以让整个代码逻辑看起来更清晰。
下面设定了一个生成随机位置的函数,它用来更新pos位置。
def randPos(): # 重置位置和时间
global ratLoopStart, pos, posAll, freeze, times
freeze = 0 # 解除冻结
times = times + 1 # 次数增加
ratLoopStart = time.time() # 重置循环时间
pos = posAll[random.randint(0, 4)] # 更新位置
在这里的global ratLoopStart...
表示在这个函数里面我们需要使用到的外部的变量数据,尽管这种使用外部数据的方法并不值得推荐,但对于我们初学小游戏来说也是可以的。
有了这个代码,我们随时执行randPos()
就能改变地鼠的位置。
时间
我们之前使用time.sleep(0.04)
这种阻碍计算机运行的方法来控制时间,但是它的问题很多。
首先,这个非常不精确,虽然每次都睡0.04秒,但是此外还有其他代码的运行耗时,所以实际上并不是我们期望的每隔0.04秒就刷新画面,肯定会多一些,但多多少呢?不知道,这也导致了我们游戏结束画面的倒计时完全不靠谱,无法按照真实的秒数计算。
其次,这个方法是浪费计算机速度的,本来它可以运行很多其他的事情,但我们却让他睡觉了,在这0.04秒中我们什么都不能做。
改进的策略就是去掉sleep
,让计算机尽情的反复运行代码,刷新画面,有多快就刷多快吧,每秒钟100张画面(100帧)都没关系,刷新越快我们的游戏玩起来越流畅。
那怎么计时?time.time()
可以获取到当前的时间,它返回1575167263.3102772这样的大数字,它表示从1970年到现在一共经过了多少秒,从小数部分.310...我们知道它实际比毫秒还精确很多,这足够我们用了。
当一局游戏结束的时候,我们记录下当前的gameOverStart=time.time()
,就像我们在比赛开始按下秒表的按键。当然这个时候画面还是一直在被我们while 1
不断的刷新,我们只要每次刷新的时候检测一下overTime=time.time()-gameOverStart
的数值,结果overTime
就是我们距离游戏过去了多少秒。然后我们在屏幕上显示int(10-overTime)
这个数字就是倒计时,当倒计时结束也就是overTime>10
的时候我们就进入下一局。
整体改进
首先我们梳理一下文件,把声音、图片和字体分别放到imgs、sounds、fonts
三个文件夹中,看起来如下:
![](https://img.haomeiwen.com/i4324074/eb48c1a205acd7ba.png)
我们使用函数,并对时间计时方法进行优化,得到下面的完整代码,你可能需要认真的完整阅读并实验它,你可以下载顶部网盘中【打地鼠v12/v12】项目来测试:
# 改进计时系统的版本
import pygame
import sys, os, time, random
from pygame.locals import * # 引入鼠标事件类型
def ipath(relative_path): # 相对路径转绝对路径
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
p = os.path.join(base_path, relative_path)
return p
pygame.init() # 初始化
window = pygame.display.set_mode([600, 400]) # 设定窗口
sur = pygame.Surface([600, 400]) # 绘制背景容器
posAll = [[100, 150], [300, 150], [500, 150], [200, 300], [400, 300]] # 六个位置
radius = 50 # 地鼠大小半径
tick = 0 # 计数器
pos = posAll[0] # 外面记录圆的位置
score = 0 # 分数计数
pygame.font.init() # 初始化文字
score_font = pygame.font.Font(ipath("fonts/MicrosoftYaqiHeiLight-2.ttf"), 30) # 设定字体和字号
score_sur = score_font.render(str(score), False, (255, 0, 0)) # 生成计数表面
pygame.mouse.set_visible(False) # 隐藏鼠标
mpos = [300, 200] # 记录鼠标位置
times = 0 # 地鼠跳出的次数
times_max = 10 # 最多次数
ditu = pygame.image.load(ipath("imgs/dds-map.jpg")) # 读取图片
rat1 = pygame.image.load(ipath("imgs/rat1.png")) # 读取地鼠图片
rat2 = pygame.image.load(ipath("imgs/rat2.png")) # 读取被砸地鼠图片
ham1 = pygame.image.load(ipath("imgs/hammer1.png")) # 读取锤子图片
ham2 = pygame.image.load(ipath("imgs/hammer2.png")) # 读取砸下锤子图片
pygame.mixer.music.load(ipath("sounds/bg.mp3")) # 载入背景音乐
pygame.mixer.music.play(-1) # 无限播放背景音乐
hitsound = pygame.mixer.Sound(ipath("sounds/hit.wav")) # 载入击打声音
hurtsound = pygame.mixer.Sound(ipath("sounds/aiyo2.wav")) # 载入地鼠叫声
ratLoopStart = time.time() # 每次地鼠跳出循环的开始时间点
ratLoopMax = 3.0 # 地鼠循环每次出现最大时间长度,秒单位
ratLoopHide = 0.2 # 循环开始不显示地鼠时间,秒单位
hamsur = ham1 # 锤子图片表面
ratsur = rat1 # 地鼠图片表面
hitLoopStart = time.time() # 锤击循环时间开始点
hitLoopMax = 0.1 # 锤击动画显示时间长度
freeze = 0 # 冻结状态,动画不变不得分。被击中后冻结一个极短时间。
gameOver = 0 # 游戏是否结束
gameOverStart = time.time() # 结束画面开始时间
gameOverMax = 3 # 结束画面最长时间,单位秒
def ratLoopRest(score_add=0): # 重置地鼠跳出循环,默认不加分score_add=0
global score, ratsur, hurtsound, freeze, ratLoopStart
if not freeze: # 非冻结状态
freeze = 1 # 冻结
score = score + score_add # 成绩增加
if score_add > 0: # 如果被击中
ratsur = rat2 # 使用被击中的地鼠
hurtsound.play() # 被击中尖角
# 将开始时间设置到ratLoopMax之前,等同立即结束当前循环
ratLoopStart = time.time() - ratLoopMax
def randPos(): # 重置位置和时间
global ratLoopStart, pos, posAll, freeze, times
freeze = 0 # 解除冻结
times = times + 1 # 次数增加
ratLoopStart = time.time() # 重置循环时间
pos = posAll[random.randint(0, 4)] # 更新位置
while 1:
ts = time.time() # 每次刷新的当前时间
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == MOUSEBUTTONDOWN and not gameOver and not freeze: # 如果是鼠标按下事件
hitLoopStart = time.time() # 重置锤击循环
hamsur = ham2 # 使用下落锤子
hitsound.play() # 播放击打声音
mpos = pygame.mouse.get_pos() # 获取鼠标位置
dis = pygame.math.Vector2(mpos[0] - pos[0], mpos[1] - pos[1]) # 计算坐标差
len = pygame.math.Vector2.length(dis) # 计算距离
if len < radius:
ratLoopRest(1)
elif event.type == MOUSEMOTION: # 当鼠标移动的时候
mpos = pygame.mouse.get_pos() # 更新鼠标位置
if times >= times_max: # 地鼠跳出次数超过限定,显示结束画面
if not gameOver: # 如果不处于结束画面
gameOverStart = time.time() # 重置成绩画面计时
gameOver = 1 # 进入结束画面状态
sur.fill((0, 0, 0)) # 结束时候仍然用黑色清空画面
pygame.mouse.set_visible(True)
end_font = pygame.font.Font(
ipath("fonts/MicrosoftYaqiHeiLight-2.ttf"), 48
) # 设定字体和字号
end_sur = score_font.render(
"你的分数是:{}/{}!".format(score, times_max), True, (255, 0, 0)
) # 生成计数表面
sur.blit(end_sur, (100, 150)) # 显示结束画面
cd = int(ts - gameOverStart)
cd_sur = score_font.render(
"重新开始倒计时{}".format(gameOverMax - cd), True, (255, 0, 0)
) # 生成计数表面
sur.blit(cd_sur, (100, 200)) # 增加分数表面
if cd > gameOverMax: # 结束画面超过设定时间,重置游戏
pygame.mouse.set_visible(False)
times = -1
score = 0
gameOver = 0 # 再次进入游戏状态
else:
sur.blit(ditu, (0, 0)) # 添加背景图片
score_sur = score_font.render(
"分数:{}/{}!".format(score, times + 1), False, (255, 0, 0)
) # 重新生成分数文字表面
sur.blit(score_sur, (200, 10)) # 增加分数表面
if ts - ratLoopStart > ratLoopMax: # 超过地鼠跳出循环时间
ratLoopRest(0)
if ts - ratLoopStart - hitLoopMax > ratLoopMax: # 超过地鼠循环时间+点击时间
randPos()
if ts - hitLoopStart > hitLoopMax: # 超过锤击动画循环
hamsur = ham1 # 使用举起的锤头
ratsur = rat1 # 使用正常地鼠
else:
hamsur = ham2 # 落下的锤头
ratsur = rat2 # 使用击中地鼠
if ts - ratLoopStart > ratLoopHide: # 开始瞬间不显示地鼠
sur.blit(ratsur, (pos[0] - 50, pos[1] - 70)) # 绘制地鼠
sur.blit(hamsur, (mpos[0] - 50, mpos[1] - 100)) # 绘制锤头
# 刷新画面
window.blit(sur, (0, 0))
pygame.display.flip() # 刷新画面
有几个地方需要注意:
-
ratLoop
是指地鼠出现到消失的循环,在这个循环的开始ratLoopHide =0.2
秒地鼠不出现,也就是每次地鼠消失到出现中间有间隔。 - 地鼠被击中后会进入
hitLoop
锤击循环时间,这个循环的目的是保持锤子落下和地鼠挨打动画一个很小的时间hitLoopMax = 0.1
秒。 - 因为地鼠被击中会有一个短暂的0.1秒停留,所以为了避免用户在这个时间重复击打地鼠得分,我们设置了
freeze
冻结状态,只有当freeze=0
解冻的时候击打才可能得分,...and not freeze
。 - 同样的
gameOver
状态用来表示进入结束画面,并有自己的计时系统gameOverStart, gameOverMax
。 -
ratLoopRest(1)
被击中后立即重置并加分,ratLoopRest
函数中freeze = 1
立即冻结,避免重复击打加分。 - 最后一大段各种
if
判断比较复杂,要仔细认真思考。
这次改进之后,游戏性能会有明显提升,原来的卡顿感没有了,而且计时系统也基本准确了。
但是现在每一局的难度是一样的,如何让游戏越来挑战越大?后面我们继续改进。
<未完待续>
欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
每个人的智能新时代
如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~
END
网友评论