不知不觉这已经是第七篇文章了,今天来谈谈python中类(class)在游戏中的应用。
老规矩,先展现一下之前的几篇博文:
用python自带的tkinter做游戏(一)—— 贪吃蛇 篇
用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇
用python自带的tkinter做游戏(三)—— 推箱子简易版 篇
用python的tkinter做游戏(四)—— 重制版 篇
用python的tkinter做游戏(五)—— 魔塔 篇
用python的tkinter做游戏(六)—— 坦克大战或射击游戏Demo 篇
自从把类给整明白了之后,整个人都通透了,感觉就像被打通了任督二脉,开辟了一个新世界!!!
就拿坦克大战来说吧,看起来简单,但如果不是用类的话,几乎很难实现,我之前折腾了很久,最终还是放弃了。
但用类的话就轻松简单多了。
在开始之前,我先谈谈最近解决的一个困扰了我很久的一个坑。
之前我做的几个小游戏,除了推箱子简易版之外,都有一个小BUG,就是关闭游戏窗口后会报错。虽然不影响程序进程,但很让人恼火,一直没找到解决的方案,这几天这个坑终于被我搞定了,哈哈。
之所以推箱子简易版里没有这个BUG,那是因为这个游戏中没有用到after函数。
没错,就是这个被用来循环的函数。
如果在关闭窗口之前没有关闭after循环的话,就会有很大的概率报错(取决于循环是否刚好结束)。所以解决的方案也很简单,在关闭窗口前将所有after函数所产生的循环全部关闭掉。
用代码说明一下:
def game_loop(self):
""" 游戏循环刷新 """
global loop_id
pass
loop_id = window.after(100, self.game_loop)
def close_w():
window.after_cancel(loop_id)
window.destroy()
window.protocol('WM_DELETE_WINDOW', close_w)
window.mainloop()
先给after函数定义成一个全局变量,代码中就是loop_id。最后在protocol中用after_cancel来取消这个循环,这样关闭窗口后就不会报错啦~
回归正题,今天做的还是一个Demo,可以说是第六篇的升级版。本是想做一个完整版的坦克大战,但实在是精力有限,加上还要去找图片资源,所以还是做一个Demo应付一下。
因为没有坦克大战的图片资源,所以就用魔塔中的人物图片来替代。反正思路是一样的,四个方位,每个方位有四张图片所形成的动图。敌方的图片也用一样的,为了区别,我特地加上了不同颜色的外框。
至于子弹,就用第六篇中飞鸟子弹的原图略微PS修改了一下,使之可以区分四个方向。
因为有四个方向,这一张是由左向右发射的。如果是往上的话,图片还需要翻转下,宽度和高度都会有变化。
既然是用到了类,先来谈谈游戏中所需要用到的类。
我方,敌方,还有子弹,这三样都会发生同一种动作,就是碰撞。所以最基础的类很简单,只需要一个关于碰撞的函数就够了。
class Sprite:
""" 精灵类 """
def sprite_collide(self,x,y,a,b,x1,y1,f,g,s=0):
"""
判断2个矩形是否相交
x, y,a,b = 矩形1的信息,xy=坐标,a,b=长宽
x1,y1,f,g = 矩形2的信息,xy=坐标,f,g=长宽
s默认为0,等于1时缩小矩形1的范围
"""
x = x + a//4*s
y = y + b//4*s
x0 = x + a - a//2*s # 矩形1最右端的坐标
y0 = y + b - b//2*s # 矩形1最底端的坐标
x2 = x1 + f # 矩形2最右端的坐标
y2 = y1 + g # 矩形2最底端的坐标
if x0 > x1 and x < x2 and y0 > y1 and y < y2:
return True
else:
return False
判断两个矩形是否相交,仅此而已。
在子弹类之前,先把地图信息和设置参数弄一下。
因为是个Demo,所以弄的就简单些,地图还是参照第六篇文章内的。
map_data = [[1, 450, 100, 50, 50, 0, 0, 0],
[1, 200, 100, 50, 50, 1, 0, 4],
[1, 500, 300, 50, 50, 1, 1, 1],
[1, 200, 300, 50, 50, 1, 2, 2],
[1, 300, 300, 50, 50, 2, 2, 3],
[1, 400, 300, 50, 50, 1, 2, 2]]
# map_data 用于存放游戏地图中各种目标对象的列表
# map_data[i][0] 显示开关,0=隐藏,1=显示
# map_data[i][1] 目标对象的X坐标
# map_data[i][2] 目标对象的Y坐标
# map_data[i][3] 目标对象的宽度
# map_data[i][4] 目标对象的高度
# map_data[i][5] 人物与目标对象的属性,0=可穿越,1=限制移动区域,2=可消除(接触后即刻消失)
# map_data[i][6] 子弹对目标对象的属性,0=可穿越,1=抵挡子弹穿越,2=可消除(接触后和子弹一起消失)
# map_data[i][7] 目标对象的线框颜色
game_setting = { '标题':'Doubles Shooting',
'窗口宽度':640,
'窗口高度':480,
}
比第六篇里的地图多了个线框颜色的设定。游戏的设定也只设置了标题和游戏窗体的宽度高度而已。
接下来看看子弹的类:
from Game_data import game_setting
from Sprite import Sprite
class Bullet(Sprite):
""" 子弹类 """
def __init__(self, x, point_x, point_y,w,h):
# 子弹有四个方向,全部弄好后根据人物的朝向再选择是哪个方向的子弹。
self.bullet_pics = ['pics/b0.png','pics/b1.png','pics/b2.png','pics/b3.png']
self.bullet_w = [ 20, 40, 40, 20]
self.bullet_h = [ 40, 20, 20, 40]
self.move_space = 15 # 子弹飞行间距
self.m = self.move_space
self.move_xx = [self.m * 0, self.m *-1, self.m * 1, self.m * 0]
self.move_yy = [self.m * 1, self.m * 0, self.m * 0, self.m *-1]
self.bullet_img = None
self.bullet_pic = self.bullet_pics[x]
self.width = self.bullet_w[x]
self.height = self.bullet_h[x]
self.point_xx = [point_x+w//2-self.width//2, point_x-self.m-30, point_x+w+self.m-10, point_x+w//2-10]
self.point_yy = [point_y+h+self.m//2, point_y+h//2-self.height//2, point_y+h//2-10, point_y-self.m-30]
self.point_x = self.point_xx[x]
self.point_y = self.point_yy[x]
self.move_x = self.move_xx[x]
self.move_y = self.move_yy[x]
def move(self):
if self.point_x > game_setting['窗口宽度'] or self.point_x + self.width < 0 \
or self.point_y + self.height < 0 or self.point_y > game_setting['窗口高度']:
return False
else:
self.point_x += self.move_x
self.point_y += self.move_y
子弹类继承了Sprite类。因为子弹有四个方向,所以各项基本参数都是四选一。
最后根据人物脸部的朝向来确定用哪一款。
Human类的和第六篇里的差不多,具体的可见上一篇,在这里我也不多做介绍了。
Hero类和Enemy类的都继承人物类,Hero类和Enemy类的区别就是Enemy类用随机函数输入来替代Hero类的键盘输入,仅此而已。
最终在主程序Main里实例化我方和敌方,就可以开始游戏了。
# 实例化两位玩家,1P可以自由移动,2P只能十字移动
player1 = Hero( 0, tk, window, 100, 100, 'w', 's', 'a', 'd', 'j', 3, cross = False )
player2 = Hero( 1, tk, window, 200, 200, 'Up', 'Down', 'Left', 'Right', '0', 4, cross = True )
player_list = [ player1, player2 ]
# 实例化四位敌军,1号2号可自由移动
enemy1 = Enemy( 2, tk, window, 300, 100, 2, cross = False )
enemy2 = Enemy( 3, tk, window, 400, 400, 2, cross = False )
enemy3 = Enemy( 4, tk, window, 400, 100, 0, cross = True )
enemy4 = Enemy( 5, tk, window, 400, 200, 0, cross = True )
enemy_list = [ enemy1, enemy2, enemy3, enemy4 ]
1P用WASD操控方向,J键开火,红色外框,可自由移动。
2P用方向键操控,按数字0键开火,蓝色外框,仅十字移动。
敌军的话绿色外框的可自由移动,粉色外框的仅十字移动。
蓝色格子:人物无法穿越,子弹可以。 —— 对应坦克大战中的河流
绿色格子:人物无法穿越,子弹可消除。—— 对应坦克大战中的砖墙
红色格子:人物和子弹均可消除。 —— 对应坦克大战中的道具(道具的话子弹可穿越)
黑色格子:人物和子弹均不可穿越。 —— 对应坦克大战中的钢墙
粉色格子:人物和子弹均可穿越。 —— 对应坦克大战中的绿地,雪地等
画面是简陋了些,不过已经能实现不少的功能了,坦克大战的雏形基本完备。
不过中弹效果没做,谁都打不死谁,子弹相互之间也无法消除。
暂且这样吧,谁让这只是个Demo呢~
因为文件较多,还是放在网盘里供下载,有兴趣的朋友可以看看,也欢迎与本人交流~(WX: znix1116)
链接:https://pan.baidu.com/s/18kWXGfyz5k7p6VE_AV3N3A
提取码:znix