用python的tkinter做游戏(七)—— 双人射击游戏Demo(类的应用) 篇

不知不觉这已经是第七篇文章了,今天来谈谈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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值