Prtri Purho的经典游戏搞死战争的复刻


介绍

“搞死战争”,是一款射击小游戏的名称,又名“稿纸战争”。玩家以三百六十度自由射击模式与虚拟世界中的外星人展开大战,最终将外星人全部打死,才能保卫地球。这款游戏可以使玩家提高自己的自信心,让自己的形象高大起来。


一、编译准备

1、Python安装

使用了Python进行游戏的编程,需要安装Python环境的小伙伴可以去看我的另一篇博客
https://blog.csdn.net/m0_51406479/article/details/112848945

2、PyQt5的安装

图形化界面的实现用到了PyQt5,安装命令如下:

pip install PyQt5

安装完成后便可开始PyQt的图形化界面的编写

二、QGraphics的介绍

1、模块介绍

主要介绍三个模块:
1、QGraphicsView
2、Scene
3、QGraphicsItem
这三个模块之间的关系就好像是画板画布以及画布上图形之间的关系
QGraphicsView可以视为画板
scene可以看成画布
QGraphicsItem可以看成是画布上的图形
在画图之前要先有块画板(QGraphicsView)然后在在上面添加画布,之后就是把图形(QGraphicsItem)往画布上放就可以了。值得注意的是各个GraphicsItem之间相互独立,意味着刷新(update())某个QGraphicItem对象其他对象不受影响

2、QGraphicsItem

使用时继承QGraphicsItem便可

import sys
from PyQt5.QtWidgets import *
class Guy(QGraphicsItem):
	def __init__(self, scene):#变量scene用来添加QGraphics
		super(Guy,self).__init__()
		scene.addItem(self) #将自身添加到scene中

boundingRect

这个函数用来确定图元(QGraphicsItem)的坐标系

paint

用来绘制,每次调用函数advance或者update时自动调用此函数
示例:

def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: typing.Optional[QWidget] = ...):
        if self.isdeath == 0:
            painter.setOpacity(self.opacity)
            painter.drawPixmap(-self.pix.width() * 0.5, -self.pix.height() * 0.5, self.pix)
        else:
            painter.drawPixmap(-self.guydeadbody.width() * 0.5, -self.guydeadbody.height() * 0.5, self.guydeadbody)

advance

在GraphicsView调用advace槽函数那么其包括的所有的QGraphicsItem中重写的advance都会被调用一遍
示例:

 def advance(self, phase: int):
        self.setPos(QPointF(self.mapToScene(0,0).x()+self.speedx,self.mapToScene(0,0).y()+self.speedy))

collidesWithItem

这个函数用于碰撞检测
示例

Bullet.collidesWithItem(Enemy)#检测Bullet是否和Enemy碰撞

其他

	def setPos(self, pos: typing.Union[QPointF, QPoint]):
        super(Bullet, self).setPos(pos)

    def setRotation(self, angle: float):
        super(Bullet, self).setRotation(angle)

    def setVisible(self, visible: bool):
        super(Bullet, self).setVisible(visible)

    def isVisible(self):
        return super(Bullet, self).isVisible()

这里有篇博客写得蛮好的,可以看看作为参考
https://blog.csdn.net/u013475704/article/details/50301307.

3、信号和槽

这是Qt中的机制,将信号和槽绑定之后,一旦触发了信号(例如时间到了等)与信号绑定的槽函数就会被调用
下面给出参考链接自行学习
https://www.cnblogs.com/jfzhu/p/13501678.html?tvd.

三、QThread

这个不多介绍

四、实现思路

首先搭建画板(QGraphicsView)之后再将图片一一放入画布中,再向其中加入碰撞检测,需要动画的地方使用QTimer进行动画的播放,并且使用QTimer5毫秒刷新一次界面中的元组

1、小人的实现

import sys
import typing
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtWidgets
from Bullet import Bullet


class Guy(QGraphicsItem):
    def __init__(self, scene):
        super(Guy, self).__init__()
        self.root = QFileInfo(__file__).absolutePath()
        self.shootspeed = 20
        self.shootrecord = 0
        # 初始化弹夹
        self.bulletlist = []
        for i in range(0, 20, 1):
            self.bulletlist.append(Bullet(scene))

        # 用来存储小人走路三种状态的图片
        self.state = [['1.png', '2.png', '3.png'],
                      ['4.png', '5.png', '6.png']]
        self.state_index1 = 0  # 记录小人面向的方向
        self.state_index2 = 0  # 记录小人当前处于哪一种移动状态
        self.state_time = 0

        self.position = QPointF(0, 0)
        self.rotate = 0
        self.opacity = 0
        self.isdeath = 0

        self.pix = QPixmap(self.root + "/res/doom_guy_frame" + self.state[self.state_index1][self.state_index2]).scaled(
            85, 95)
        # 死亡后的尸体
        self.guydeadbody = QPixmap(self.root + "/res/doom_guy_frame7.png").scaled(85, 95)
        self.scene = scene
        self.scene.addItem(self)

    def boundingRect(self):
        return QRectF(-self.pix.width() * 0.5 - 20, -self.pix.height() * 0.5 - 20, self.pix.width() - 20,
                      self.pix.height() - 20)

    def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: typing.Optional[QWidget] = ...):
        if self.isdeath == 0:
            painter.setOpacity(self.opacity)
            painter.drawPixmap(-self.pix.width() * 0.5, -self.pix.height() * 0.5, self.pix)
        else:
            painter.drawPixmap(-self.guydeadbody.width() * 0.5, -self.guydeadbody.height() * 0.5, self.guydeadbody)

    def setState(self, rotate):
        # print(rotate)
        # self.state_time = (self.state_time+1)%10    #延时更新
        if self.state_time == 0:
            self.rotate = rotate
            if -90 < rotate < 90:
                self.state_index1 = 0
                self.position = self.mapToScene(30, 7)
                self.setRotation(rotate)
            else:
                self.state_index1 = 1
                self.position = self.mapToScene(-30, 7)
                self.setRotation(180 + rotate)
            self.state_index2 = (self.state_index2 + 1) % 3
            # print(self.state_index2)
            self.pix = QPixmap(
                self.root + "/res/doom_guy_frame" + self.state[self.state_index1][self.state_index2]).scaled(85, 95)
            self.update()

    def Shooting(self):
        self.shootrecord += 1
        if self.shootrecord > self.shootspeed:
            self.shootrecord = 0
            for bullet in self.bulletlist:
                if not bullet.isVisible():
                    bullet.setPos(self.position)
                    bullet.setRotation(self.rotate)
                    bullet.setVisible(True)
                    break

    def updateAnimation(self):
        self.opacity += 0.02

    def setPos(self, pos: typing.Union[QPointF, QPoint]):
        super(Guy, self).setPos(pos)

    def setRotation(self, angle: float):
        super(Guy, self).setRotation(angle)

小人的朝向不同绘制以及移动要分情况讨论

2、子弹的实现

import sys
import math
import typing
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtWidgets
from EnemySpark import EnemySpark


class Bullet(QGraphicsItem):
    def __init__(self, scene):
        super(Bullet, self).__init__()
        self.root = QFileInfo(__file__).absolutePath()

        self.setVisible(False)
        self.speed = 7
        self.speedx = 0
        self.speedy = 0
        self.damage = 200    # 子弹伤害

        self.pix = QPixmap(self.root + "/res/_bullet.png").scaled(18, 9)
        self.scene = scene
        self.scene.addItem(self)

        self.pix_map = []
        for i in range(5):
            self.pix_map.append(QPixmap(self.root + "/res/smoke_0"+str(i+1)+".png"))
        self.Times = 0
        self.Symbol = False

        self.EnemySpark = EnemySpark(self.scene)

    def boundingRect(self):
        return QRectF(-self.pix.width() * 0.5, -self.pix.height() * 0.5, self.pix.width(), self.pix.height())

    def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: typing.Optional[QWidget] = ...):

        if self.Times <= 50 and self.Times>0:
            x = self.Times%5
            painter.drawPixmap(-self.pix_map[x].width()*0.5,-self.pix_map[x].height()* 0.5,self.pix_map[x])
            self.Times = self.Times+1
            
        elif self.Times==0:
            painter.drawPixmap(-self.pix.width() * 0.5, -self.pix.height() * 0.5, self.pix)
        else:
            self.setVisible(False)
    
    def CallSmoke(self):
        self.Times = 1

    def advance(self, phase: int):
        # self.setPos(QPointF(self.mapToScene(0,0).x()+self.speedx,self.mapToScene(0,0).y()+self.speedy))
        if self.isVisible():
            if self.mapToScene(0, 0).x() < 0 or self.mapToScene(0, 0).y() < 0 or \
                    self.mapToScene(0, 0).x() > self.scene.width() or self.mapToScene(0, 0).y() > self.scene.height():
                self.setVisible(False)
            elif self.Times==0:
                self.setPos(self.mapToScene(self.speed, 0))
                self.collidingItems()
            elif self.Times<=50 and self.Times>0:
                self.setPos(self.mapToScene(0,1))
            else:
                self.Times = 0

    def setPos(self, pos: typing.Union[QPointF, QPoint]):
        super(Bullet, self).setPos(pos)

    def setRotation(self, angle: float):
        super(Bullet, self).setRotation(angle)

    def setVisible(self, visible: bool):
        super(Bullet, self).setVisible(visible)

    def isVisible(self):
        return super(Bullet, self).isVisible()

3、敌人的实现

敌人种类较多,所以下面只给出了一种敌人的实现方式,其余敌人请参考文末链接中的内容

import sys
import math
import typing
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtWidgets


# 创建Enemy类
class Small_Moving(QGraphicsItem):
    def __init__(self, scene):
        super(Small_Moving, self).__init__()
        self.root = QFileInfo(__file__).absolutePath()

        self.isshowblood = 0
        self.blood = 1000
        self.current_blood = 1000
        self.setVisible(False)  # 设为可视
        self.speed = 1  # 设置速度
        self.Rotate = 0
        self.current_blood_front_x = 59
        self.blood_front_x = 59
        self.blood_gb = QPixmap(self.root + "/res/doom_slider_back.png").scaled(61, 8)   # 血条背景
        self.blood_front = QPixmap(self.root + "/res/doom_slider_front.png").scaled(self.current_blood_front_x, 6)  # 血条前景

        self.pix = []
        for i in range(9):
            self.pix.append(QPixmap(self.root + "/res/small_moving_frame_0" + str(i + 1) + ".png"))
        for i in range(7):
            self.pix.append(QPixmap(self.root + "/res/small_moving_frame_" + str(i + 10) + ".png"))

        self.state = 0

        self.stopTime = 0

        self.scene = scene
        self.scene.addItem(self)

        self.MuchSet = 0  # 表示第几集合

    # 设置第几集合
    def setSet(self, MuchSet):
        self.MuchSet = MuchSet

    # 暂时不知道啥用的函数
    def boundingRect(self):
        return QRectF(-self.pix[0].width() * 0.5, -self.pix[0].height() * 0.5, self.pix[0].width(),
                      self.pix[0].height()-16)

    # 绘制
    def paint(self, painter: QPainter, option: 'QStyleOptionGraphicsItem', widget: typing.Optional[QWidget] = ...):
        if self.isshowblood == 1:
            painter.drawPixmap(-self.blood_gb.width() * 0.5, -self.pix[0].height() * 0.5 - 10, self.blood_gb)
            painter.drawPixmap(-self.blood_gb.width() * 0.5 + 1, -self.pix[0].height() * 0.5 - 9, self.blood_front.scaled(self.current_blood_front_x, 6))
        if -90 < self.Rotate < 90:
            painter.drawPixmap(-self.pix[self.state + 8].width() * 0.5, -self.pix[self.state + 8].height() * 0.5,self.pix[self.state + 8])
        else:
            painter.drawPixmap(-self.pix[self.state].width() * 0.5, -self.pix[self.state].height() * 0.5,self.pix[self.state])
        if self.stopTime % 10 == 0:
            self.state = (self.state + 1) % 8

    def advance(self, phase: int):
        self.stopTime = (self.stopTime + 1) % 20  # 延迟进行
        if self.stopTime % 10 == 0:
            if self.isVisible():
                if self.mapToScene(0, 0).x() < -150 or self.mapToScene(0, 0).y() < -150 or \
                                self.mapToScene(0, 0).x() > self.scene.width()+150 or self.mapToScene(0,
                                                                                                  0).y() > self.scene.height()+150:
                    self.setVisible(False)

                    self.isshowblood = 0
                    self.current_blood = self.blood
                    self.current_blood_front_x = self.blood_front_x

                else:
                    if self.Rotate < -90 or self.Rotate > 90:  # 朝向左下的敌人
                        self.setPos(self.mapToScene(-self.speed, 0))
                    else:
                        self.setPos(self.mapToScene(self.speed, 0))

    def setBlood(self, damage):
        self.isshowblood = 1
        self.current_blood -= 200
        a = damage/self.blood
        self.current_blood_front_x -= self.blood_front_x * a
        self.update()

    def setPos(self, pos: typing.Union[QPointF, QPoint]):
        super(Small_Moving, self).setPos(pos)

    def setRotation(self, angle: float):
        self.Rotate = angle
        if self.Rotate < -90 or self.Rotate > 90:  # 朝向左下的敌人
            super(Small_Moving, self).setRotation(angle + 180)
        else:
            super(Small_Moving, self).setRotation(angle)

    def setVisible(self, visible: bool):
        super(Small_Moving, self).setVisible(visible)

    def isVisible(self):
        return super(Small_Moving, self).isVisible()

这里要注意的是,敌人的方向不同导致绘制(paint)和移动(advance)的时候要分情况讨论

碰撞函数的实现

自己实现或者看文末链接中的源代码

其他细节修饰

参见文末源代码

四、问题解决

1、使用KeyPress时只能进行上下左右移动无法组合移动

解决方法如下:

def keyPressEvent(self, QKeyEvent):  # 方向控制
        # 8个移动方向

        if QKeyEvent.key() == Qt.Key_A:
            self.MoveSign += 1
            # self.Keyx -= 20
        elif QKeyEvent.key() == Qt.Key_W:
            self.MoveSign += 2
            # self.Keyx += 20
        elif QKeyEvent.key() == Qt.Key_S:
            self.MoveSign += 4
        elif QKeyEvent.key() == Qt.Key_D:
            self.MoveSign += 6
    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_A:
            self.MoveSign -= 1
        elif event.key() == Qt.Key_W:
            self.MoveSign -= 2
        elif event.key() == Qt.Key_S:
            self.MoveSign -= 4
        elif event.key() == Qt.Key_D:
            self.MoveSign -= 6
if self.guy.isdeath == 0 and self.updatekeyword == 1: #这个函数需要用QTimer定时刷新
            # 角色移动
            if self.MoveSign == 1:
                self.Keyx -= 8 #表示小人位置x坐标
            elif self.MoveSign == 2:
                self.Keyy -= 8 #表示小人位置y坐标
            elif self.MoveSign == 3:
                self.Keyx -= 6
                self.Keyy -= 6
            elif self.MoveSign == 4:
                self.Keyy += 8
            elif self.MoveSign == 5:
                self.Keyx -= 6
                self.Keyy += 6
            elif self.MoveSign == 6:
                self.Keyx += 8
            elif self.MoveSign == 8:
                self.Keyx += 6
                self.Keyy -= 6
            elif self.MoveSign == 10:
                self.Keyx += 6
                self.Keyy += 6

2、卡顿问题

可以考虑使用多线程解决,但是其实我这代码的卡顿问题也不是解决得很好。

3、QGraphicsItem在QGraphicsView的坐标的获取

使用QGraphicsItem自带的函数:
mapToScene
用法如下:

self.mapToScene(x, y) #代表此Item向正前方移动x,正下方移动y个单位得到的坐标

所以如果想要图像前进只要这样做:

self.setPos(self.mapToScene(self.speed, 0))

最后

这个游戏和杨某写了整整一个月(寒假写写停停,基本都是划水),想到什么写什么,所以代码结构有点乱,且大学新手初学PyQt也有很多理解不到位的地方,还请各位读者指正
代码两天后上传,注释较少(随想随写,有点随意),还在做最后的debuug以及整理两天后将附上代码链接!
https://gitee.com/wujiiii/NewGaoZhiZhanZheng.

对PyQt以及Python感兴趣的同学可以参考如下链接

https://blog.csdn.net/happygaohualei/article/details/82659180.
https://blog.csdn.net/memory_qianxiao/article/details/112151561.
https://blog.csdn.net/chenxuezhong0413/article/details/89223038.
https://blog.csdn.net/xfyangle/article/details/75007658.
https://blog.csdn.net/Wang_Jiankun/article/details/83113750.
最后附上作品图片
在这里插入图片描述

以及原版图片
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值