QQ技术分享交流互助群:
QQ群号:566341887 (2024/5刚建的群,可能没什么人一开始,相信会越来越好哒。大家一起互帮互助,共同进步,交流分享,奔赴未来!)
序
根据“上”和“下”,我们已经可以粗略地构建一个游戏了。这一篇,我们需要更加完善游戏,对一些边边角角进行补充。
一.状态机
这个东西哩,如果你比较有个性的话,或者是游戏不太需要的话,也没必要看。
通俗点来说,就像是菜单一样的东西,清楚地,列个表。
从图中看,根据状态机的状态标记不同,窗口中展现的游戏状态也不同。不但有登录,也有主页,甚至可以有角色,战斗,剧情……以此类推。
把它转化为程序的话,可以理解为对于一个自变量mark,不同的mark值对应了不同的界面,使整个程序变得更加整洁,后期改BUG也方便许多。
mark=0
while True:
if mark==1:
#登录
elif mark==2:
#主页
elif mark==3:
#角色
elif mark==4:
#商店
#......以此类推
大概上就是这么个意思。
当然,mark在玩家的选择中,也会改变mark的值,因此达到不同状态的效果。
二.大地图
1.小区块问题
前面的游戏中,玩家都是在一个固定的区域中活动,相当于一个关卡,如果说许多个这样的区块构成一个大区块,从而在一个较大的区块中运行,那么就会有一个问题非常影响游戏体验。

比如这4个区块合在一起,黄色的区块为玩家所处的区块,红色和紫色都是看不到的区块,在游戏中为了流畅性,各个区块之间不存在黑色的条条框框。那么,问题来了,玩家在黄色区块中,假设紫色区块中存在可以远程伤害的敌对生物,玩家在敌对生物的攻击范围中。照理来说,怪物应该对玩家发起攻击,但是玩家又看不到怪,就是说,不是很连贯,有种断开的感觉。
2.大区块思路
解决这个问题其实很简单,把玩家固定不动,移动背景和怪物等。相对,就是说相对喵,理解吧。
本来是,玩家往右走,玩家的x值增加,相反的,要改成背景的x值左移,包括怪物和其他实体的移动。玩家下落改成背景、怪物和实体上移。
所以嘞,对于一个800×600的窗口,背景可能就需要做到3000×2000。而且不能把背景的最左上角的点放于窗口的(0,0),不然玩家一旦一开始往左移动一下就出现问题了。
3.大区块代码
backx,backy=50,50 #背景的坐标
while True:
keys=pygame.key.get_pressed()
if keys[pygame.K_d]:
backx-=5
if keys[pygame.K_a]:
backx+=5
其中一部分差不多就这样啦,当然,熟能生巧,其他部分就自己写啦。跳跃部分也一样的。
(敌对生物移动时,这样写不会对其产生影响,只需要在移动时,调用类中的一个函数,移动保持与屏幕一致。)
(1)举例
讲讲可能,额,就是说,不好理解,啊啊啊啊啊啊,烦死了,慢慢讲。
(2)慢慢讲
从问题——>思路——>方案。
①问题
问题呢,在于玩家移动时,生物保持自己原有的移动,又要时玩家的移动对生物产生影响,体现出玩家在移动的效果。
②思路
思路一
假设敌对生物在往左移动,x持续减小,x-=1,玩家一按下"D"键,相对于玩家要左移,生物要右移,那么当玩家按下之后,生物的x-=5(假设玩家每步长为5)二者要连贯在一起,就要分开成两个def来写,互不干扰。
一个def用于生物自己原本的运行,另外一个def用于玩家对生物的干涉,从而二者叠加,不会造成冲突。
思路二
本质上差不多,就是为了保证生物本身与玩家干涉不冲突。
生物移动自己原本x数值,除此之外还保持x+=dx,dx为屏幕移动距离,若玩家不按移动键,则dx=0,屏幕既不会移动,生物也照样保持原有移动。如果dx有了数值,x本身的改变和dx的数值二者叠加,导致出一个综合性的效果。
③方案
理清了思路,我们就是说直接上代码。
(以下为本喵从自己乱写的一个游戏中的举例,运用的是思路二)
class enemy(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image=pygame.image.load("P/enemy/enemy.png")
x=randint(winflagx,5000)
self.rect=self.image.get_rect(center=(x+25,by+540))
def update(self):
self.rect.x-=randint(2,5)
self.rect.x+=dx
self.rect.y=by+540
咱们就是说主要看update部分。
self.rect.x-=randint(2,5)
self.rect.x+=dx
可以看到,这两行分开来,互不干涉,最后结果综合。
(3)静态物品
同理,根据上面的部分,删掉物品自己本身的移动就行了。
比如,以下。
class dyh(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image=pygame.image.load("P/tool/7.62dyh.png")
self.rect=self.image.get_rect(center=(waitx,waity))
def update(self):
self.rect.x+=dx
self.rect.y+=g
三.帧率
这个玩意,相当影响游戏体验感的呀喵。
而且还可以从中反推出运行一遍程序,所需要的时间,以及关系到一些现实中与游戏中时间的关联。
1.例子
zf=pygame.time.Clock()
while True:
zf.tick(30)
主要就这么两个,但是注意,这个"zf.tick(30)"必须放在while里面,放外面没用(帮你们试过了已经)
2.影响
对于之前写的代码,如果你们照着写下来,运行的时候就会发现,这个速度很快啊,所以说我数值才那么小。加上这个之后完全就不一样了。
3.意义
那么对于上面这个例子,是什么意思呢。
zf.tick(30)
1s之内,该程序运行30遍,也就是说30帧/s。
四.抽奖机制
这个的话,主打的就是靠randint随机函数。
嗯额……说实话没什么好展开讲的,先放个例子。
from random import randint
gailv=0
gailv=randint(0,100)
if gailv<=10:
print("中奖啦")
以上中奖概率就是10%。更多的细枝末节,自己添些elif即可。
五.存档
对于一次性的游戏,就是那种开一次就要重来的那种,没必要这种存档。
1.思路
根据状态机,在登录界面时,输入相应账号和密码,如果在文档中有匹配的,那么读取紧随其后的数据,把那些游戏中的初始值代替掉。并且在游戏中,后来,即使保存创建新的存档,或者替换掉原来的存档,进行一个更新的措施。
2.(小插曲)
之前都是用C++写的文件读取,改写之类的。
python这玩意本喵自己就浅浅看了一下,不是很明白,所以说接下来讲的方法可能比较落后,若有更好的还请指出,谢谢。
3.创建存档
首先要有有一个.txt文档用来存数据。
先讲个前提:对于这种数据要有一个格式,我们假设为如下。
名称
密码
等级
金币
name=input("名称:")
mima=input("密码:")
f=open("档.txt","a")
duqu=open("档.txt","r")
li=-1
mark=0
for line in duqu.readlines():
li+=1
line=line.rstrip("\n") #带上换行
if line==name and li%4==1:
mark=-1
if mark==-1 and li%4==2:
mark=1
if mark==0:
f.write("\n"+name)
f.write("\n"+mima)
f.write("\n"+"0")
f.write("\n"+"0")
这个里面f用于书写追加,duqu用于读取识别。mark=0则为判断没有该存档,进行自动创建存档,如果要改成报错也行,详细后面讲。对于新的账号,自动在文件档.txt后面进行追加名称、密码、等级(初始为0)和金币。
4.读取存档
当检测到存档中有玩家输入的名称,并且密码符合,那么就进行读取数据。
name=input("名称:")
mima=input("密码:")
f=open("档.txt","a")
duqu=open("档.txt","r")
li=-1
mark=0
level=0
money=0
for line in duqu.readlines():
li+=1
line=line.rstrip("\n")
if line==name and li%4==1:
mark=-1
if mark==-1 and li%4==2:
mark=1
if mark==1 and li%4==3:
level=int(line)
if mark==1 and li%4==0:
money=int(line)
break
if mark==0:
f.write("\n"+name)
f.write("\n"+mima)
f.write("\n"+str(level))
f.write("\n"+str(money))
这个没什么多的可以讲的,就是这么个样子,给你们看一下效果。
5.保存数据
(额啊啊啊,本喵翻了书,翻了网,找不到可以修改.txt其中某一行的办法,那只能暴力解决啦!)
一般来说,指针不是在开头就是在结尾,那么我们还需要另外一个来存储原有文档,在碰到要修改的数据处写入修改后的数值。
(1)思路
1.因为要重新覆盖写,所以说在一开始就要对当初的.txt进行存档读取,把他们存放到一个列表里面。
2.为了后期for写入,我们需要一个变量li=-1用于存储。
3.在for中,为了把保存数据和抄写原有数据不冲突,我们另外要一个xieru=0作为标记。
4.当检测到原有存档中有与目前登录的名称相同时,则接下来的判断中,根据位置输入程序中的level和money的str形式,并且xieru=1。
5.每次for的结尾都追加一个if,如果xieru为0的话,就在文档中保存原有数据。因此每次for开头,都要先将xieru重置为0。
(2)代码
name=input("名称:")
mima=input("密码:")
duqu=open("档.txt","r")
li=-1
mark=0
level=0
money=0
xieru=0
cun=[]
for line in duqu.readlines():
li+=1
line=line.rstrip("\n")
cun.append(line)
if line==name and li%4==0:
mark=-1
if mark==-1 and li%4==1:
mark=1
if mark==1 and li%4==2:
level=int(line)
if mark==1 and li%4==3:
money=int(line)
mark=-2
if mark==0:
cun.append(str(name))
cun.append(str(mima))
cun.append(str(level))
cun.append(str(money))
li+=4
fugai=open("档.txt","w")
for i in range(0,li+1):
xieru=0
if cun[i]==name and i%4==0:
mark=2
fugai.write(name+"\n")
xieru=1
if mark==2 and i%4==1:
fugai.write(mima+"\n")
xieru=1
if mark==2 and i%4==2:
xieru=1
fugai.write(str(level)+"\n")
if mark==2 and i%4==3:
mark=3
xieru=1
fugai.write(str(money)+"\n")
if xieru==0:
fugai.write(str(cun[i])+"\n")
这个为了方便,我把一整串的代码全放上来了。
本喵的建议话,最好自己在草稿本上列个表,数据对应进去,纸头上算一遍。
(3)答疑
这个相对于前面的代码,有很多改动,我自己纸上算完,改动之后变这样的。下面的答疑呢,不可能面面俱到,只能说一下常见的,对于上述代码的。
疑1:为什么li的初始值为-1?
喵:因为列表的一个变量的位置在列表中为第0个,在第一个for开头中一开始就有li+=1,所以li=0时,刚好存储cun[0],二者相对应。
疑2: 为什么第一个for中,判断名称符合后,mark=-1?
喵:可能存在名称匹配,但是密码不匹配的情况。这样要满足两个条件,才能读取下方的level和money。
疑3:为什么money读取完之后,mark=2?
喵:因为要把所有的内容先读取保存到cun里面,读取到需要的数据,不是意味着后面的不要读取。
哎,非常麻烦,这个东西没地方学模板,本喵搞了一整天才弄清楚,累死了……烦死了……经常出现数据串台,漏一个呀,多一个呀,乱存储。哎……自己好好看吧,不懂再问。
六.撒花
非常好,看到这里,学完了算是,差不多已经可以做一个像样的游戏了,加油吧!