Python游戏引擎开发(二):创建窗口以及重绘界面

Python】PyQt5 在窗口中使用自定义的QWidget,并写paintEvent 窗口中放置一个QWidget,用于画图(使用QPainter),其余位置可以放置别的控件。自定义QWidget类,写PaintEvent后。可以单独作为一个控件放在其他的窗口中。 阅读详情

开发本地应用之前,我们得先有个窗口,用于显示界面。其次我们还得实现重绘机制,使游戏不停地刷新,达到动态化的目的。所以我们的引擎开发的第一个阶段就是创建窗口和重绘界面。

以下是之前的文章:
Python游戏引擎开发(一):序

Qt的渲染机制

在上一章《序》中我们讲到本次开发用到了PyQt,也就是Qt的Python版。在开始实现引擎功能之前我们要先了解一下Qt,这里先了解渲染机制。
在Qt中,绘画用到的类叫做QPainter,顾名思义,就是个画家类吧。在这个类中,提供了非常多的方法用于操控这个“画家”,比如说叫他去画个圆,叫他去测量文字的尺寸,画家先生当然是不会推辞的。
在使用QPainter时,需要给其一个绘画设备,也就类似于画家的画板。这个绘画设备就是一个QWidget,部件类。这个类又提供了很多方法,比如说设置部件尺寸等。在Qt中,如果只有一个QWidget,那么这个QWidget就会成为窗口对象。

Qt中的事件以及信号和槽

Qt中的事件和JavaScript dom事件有点不同,不是通过注册一个事件类型和所对应的监听器来进行事件回调的,而是Qt直接在类中写好了监听器,通过重写这个监听器方法来执行自己的代码,也就是是说Js的事件是加了才会调用的,而Qt是自动加上了事件,通过修改监听器来执行自定义的代码。不过Qt对Js添加事件这一功能换了种说法——信号和槽。这个就好比《基督山伯爵》中菲尔兰出卖阿里国王那段一样——送来的是匕首则引爆炸弹炸掉所有的财产和家人,送来的是戒指则平安无事。这里的“送来匕首”就是信号,“引爆炸弹”就是槽;“送来戒指”是信号,“平安无事”为槽。你还可以把信号理解为事件名,槽理解为回调函数。
上文所提到的绘画需要在QWidgetpaintEvent中进行。

QApplication

QApplication是整个应用程序的入口。这个类有点特别,具体特别在哪里呢?请看如下的代码:

app = QtGui.QApplication(sys.argv)

# ...

app.exec_()

也就是说,如果你要使用Qt里一系列的功能,请在实例化QApplication后,调用exec_方法前使用这些功能,也就是程序的入口是在代码中# ...的位置。另外,QApplication要接受一个参数,也就是代码中的sys.argv,因此你需要在使用前引入sys模组。
这里就顺便提一下Qt的一些模组。Qt作为一款强大的引擎,难免有很多模组,不过我们大致要用的就只有QtGuiQtCoreQtWebKit这些模组。开发游戏的话,QtGuiQtCore这两个模组就已经够用了。

库件基础类

基础部分大致讲完了,开始封装吧,首先封装的就是Stage类。这个类大致就是管理一些全局的事物吧,比如说获取窗口的大小,设置窗口刷新频率,向最低层加入显示对象等等。不过在实现Stage前,我们得先创建一个库件中最底层的Object类:

class Object(object):
    latestObjectIndex = 0

    def __init__(self):
        Object.latestObjectIndex += 1

        self.objectIndex = Object.latestObjectIndex

这个类作为库件中所有类的基类,方便以后给所有类扩展功能。然后是Stage类的构造器部分:

class Stage(Object):
    def __init__(self):
        super(Stage, self).__init__()

        self.parent = "root"
        self.width = 0
        self.height = 0
        self.speed = 0
        self.app = None
        self.canvasWidget = None
        self.canvas = None
        self.timer = None
        self.childList = []
        self.backgroundColor = None

可以看到,这个类中包含了许多全局属性。比如说widthheight就是用来获取游戏界面宽高的,backgroundColor用来设置底色。
接下来是_setCanvas方法,用来完成创建绘画设备和QPainter以及重绘计时器:

def _setCanvas(self, speed, title, width, height):
        self.speed = speed
        self.width = width
        self.height = height

        self.canvas = QtGui.QPainter()

        self.canvasWidget = CanvasWidget()
        self.canvasWidget.setWindowTitle(title)
        self.canvasWidget.setFixedSize(width, height)
        self.canvasWidget.show()

        self.timer = QtCore.QTimer()
        self.timer.setInterval(speed)
        self.timer.start();

        QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.canvasWidget, QtCore.SLOT("update()"))

Qt的QTimer是一个计时器类,通过调用setInterval方法来设置计时时间,并循环计时。start方法开启计时器。然后还用到了connect方法来连接信号和槽,信号是“计时完毕”,槽是“刷新窗口”。这样一来,我们就可以通过这个信号槽装置来实现定时刷新界面的功能了。

在上面的代码中,我们还保存了绘画类QPainter,方面在以后的代码中调用这个画笔。

上面的代码中还出现了CanvasWidget类,它派生自QWidget类,在这个类中,我们还会加入其他的功能,例如重写绘画事件监听器,键盘事件监听器等。构造器代码如下:

class CanvasWidget(QtGui.QWidget):
    def __init__(self):
        super(CanvasWidget, self).__init__()

当然,Qt为我们提供了不只是QWidget这一种基础部件,还有输入框,按钮,窗口等高级部件,不过我们暂时用不上这些部件,更何况Qt在没有其他窗口部件的情况下,会默认把第一个QWidget设置为窗口对象,所以这里的CanvasWidget继承自QWidget,实例化后,就是窗口对象。
上面用到的setWindowTitlesetFixedSize方法分别是用来设置窗口标题,窗口固定大小的。然后使用show方法把这个部件显示出来。

因为整个游戏中只能有一个Stage对象,为了方便全局公开使用,我们直接在库件里实例化这个类,作为唯一的舞台对象:

stage = Stage()

在上面的_setCanvas方法中,我们用到了信号槽来完成界面的刷新,那么到底是怎么刷新的呢?当我们的计时器计时到指定的时间后会调用部件的update槽,在这个槽里会调用到paintEvent中,正如我们在“Qt中的事件以及信号和槽”中提到的,这个事件是在QWidget中已经写好了的,我们只需要在CanvasWidget中重写这个函数即可:

def paintEvent(self, event):
        stage._onShow()

调用到Stage对象的_onShow方法中:

def _onShow(self):
        self.canvas.begin(self.canvasWidget)

        if self.backgroundColor is not None:
            self.canvas.fillRect(0, 0, self.width, self.height, getColor(self.backgroundColor))
        else:
            self.canvas.eraseRect(0, 0, self.width, self.height)

        self._showDisplayList(self.childList)

        self.canvas.end()

在这个函数中,我们首先用QPainterbegin方法绑定绘画设备。然后进行擦除界面,再通过_showDisplayList方法进行重绘,最后结束绘画。值得注意的是_showDisplayList方法,它通过遍历显示列表,将遍历得到对象进行重画:

def _showDisplayList(self, childList):
        for o in childList:
            if hasattr(o, "_show") and hasattr(o._show, "__call__"):
                o._show(self.canvas)

我们通过调用显示对象的_show方法就可以完成绘制对象了。在后续的开发中,我们只需要为某个对象添加_show方法,然后加入显示列表,就可以重绘该对象了。如果在_show中加入绘画图片的功能,那么就得到了图片类,添加绘画文字的功能,就成了文本类。

_onShow方法中我们还用到了getColor这个全局函数,用于把颜色名字字符串或者16进制值转化为QColor,毕竟Qt这个文盲,认颜色就只认QColor

def getColor(color):
    if isinstance(color, QtGui.QColor):
        return color
    elif not color:
        return QtCore.Qt.transparent
    else:
        colorObj = QtGui.QColor()
        colorObj.setNamedColor(color)

        return colorObj

由于Stage类是用来作为最底层显示列表的,所以加入addChildremoveChild方法用来完成添加和删除显示对象:

def addChild(self, child):
        if child is not None:
            child.parent = self

            self.childList.append(child)
        else:
            raise ValueError("parameter 'child' must be a display object.")

    def removeChild(self, child):
        if child is not None:
            self.childList.remove(child)

            child.parent = None
        else:
            raise ValueError("parameter 'child' must be a display object.")

最后添加init全局函数用于初始化界面:

def init(speed, title, width, height, callback):
    stage.app = QtGui.QApplication(sys.argv)

    stage._setCanvas(speed, title, width, height)

    if not hasattr(callback, "__call__"):
        raise ValueError("parameter 'callback' must be a function.")

    callback()

    stage.app.exec_()

其中的QApplication在上文已经有了详细的描述。
调用init函数并传入对应的参数就可以看到空空的窗口了。

本文全部代码:

import sys
from PyQt4 import QtGui, QtCore

class Object(object):
    latestObjectIndex = 0

    def __init__(self):
        Object.latestObjectIndex += 1

        self.objectIndex = Object.latestObjectIndex


class CanvasWidget(QtGui.QWidget):
    def __init__(self):
        super(CanvasWidget, self).__init__()

        self.setMouseTracking(True)

    def paintEvent(self, event):
        stage._onShow()


class Stage(Object):
    def __init__(self):
        super(Stage, self).__init__()

        self.parent = "root"
        self.width = 0
        self.height = 0
        self.speed = 0
        self.app = None
        self.canvasWidget = None
        self.canvas = None
        self.timer = None
        self.childList = []
        self.backgroundColor = None

    def _setCanvas(self, speed, title, width, height):
        self.speed = speed
        self.width = width
        self.height = height

        self.canvas = QtGui.QPainter()

        self.canvasWidget = CanvasWidget()
        self.canvasWidget.setWindowTitle(title)
        self.canvasWidget.setFixedSize(width, height)
        self.canvasWidget.show()

        self.timer = QtCore.QTimer()
        self.timer.setInterval(speed)
        self.timer.start();

        QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.canvasWidget, QtCore.SLOT("update()"))

    def _onShow(self):
        self.canvas.begin(self.canvasWidget)

        if self.backgroundColor is not None:
            self.canvas.fillRect(0, 0, self.width, self.height, getColor(self.backgroundColor))
        else:
            self.canvas.eraseRect(0, 0, self.width, self.height)

        self._showDisplayList(self.childList)

        self.canvas.end()

    def _showDisplayList(self, childList):
        for o in childList:
            if hasattr(o, "_show") and hasattr(o._show, "__call__"):
                o._show(self.canvas)

    def addChild(self, child):
        if child is not None:
            child.parent = self

            self.childList.append(child)
        else:
            raise ValueError("parameter 'child' must be a display object.")

    def removeChild(self, child):
        if child is not None:
            self.childList.remove(child)

            child.parent = None
        else:
            raise ValueError("parameter 'child' must be a display object.")

stage = Stage()


def init(speed, title, width, height, callback):
    stage.app = QtGui.QApplication(sys.argv)

    stage._setCanvas(speed, title, width, height)

    if not hasattr(callback, "__call__"):
        raise ValueError("parameter 'callback' must be a function.")

    callback()

    stage.app.exec_()

def getColor(color):
    if isinstance(color, QtGui.QColor):
        return color
    elif not color:
        return QtCore.Qt.transparent
    else:
        colorObj = QtGui.QColor()
        colorObj.setNamedColor(color)

        return colorObj

预告:下一篇我们实现显示图片。


欢迎大家继续关注我的博客

转载请注明出处:Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

Python游戏引擎开发(三):显示图片 在前面的章节中,我们屡次提到了显示对象这个东西,那显示对象到底是什么呢?顾名思义,它是一个可视的物体,比如说游戏中的人物,地图等。例如`list`,`tuple`等,这些对象是不可以显示的,它们只用于内部的数据存储,所以不是显示对象。同理,游戏中的资源加载器也不是显示对象。程序开发可以看作一个归类的过程(所以`class`成为了一种主要的程序语句)。如果我们以对象的尺寸,或者颜色来分类显示对象,那么可能会出现这些 阅读详情

相关推荐

Python游戏引擎开发(一):序

写了这么久的html5,感觉html5学得差不多了,是时候去接触更多的语言来扩充自己的能力了。我先后看了Swift,Java等语言。首先开发Swift需要一台mac,对于我这个寒士而言,过于奢华了一些;Java吧,又感觉太胖了,不够苗条,身材不好,也看不上。最后遇到了Miss Python,先前也和她打过交道,不过感觉语法怪怪的,总是出现>>>这类符号(当时没有深入接触,不晓得是命令输入提示),实在是太高冷了

Yorhom's Game Box 2万+

python3 PyQt5 实现在GUI界面中点击按钮,进行

环境:Python 3.8.5 最终效果:点击下面的PushButton,可以实现制一幅图。图在一个GroupBox中,该GroupBox放在哪里,图就会装在它里面,从而可以实现指定位置新画图的功能。 下面是主文件的源码,源码并不复杂,该工程是在链接1和链接2的基础上修改得到的,就只当做一个简单demo吧,读者可以根据这个程序思路进行修改,比如实现点击按钮,或者定时来制自己后台中的数据。完整的工程我已传到了GitHub,需要的同学可以去取哦。 from Ui_DataDisplayUI i

Jason Bourne的博客 2657

Python游戏引擎开发(七):制矢量图

开始制命令需要如下几个参数:图形填充色、填充色透明度。 在上面的代码中,我们初始化了`__currentGraphics`属性,可以看到,他是一个`dict`对象,其中的`path`成员是一个`QPainterPath`对象,这个对象来自`Qt`,通过调用这个类中的一些方法,可以创建一些图形,然后调用`QPainter`的`drawPath`方法就可以把这个对象里创建的所有图形画出来。

Yorhom's Game Box 7214

游戏开发引擎(Pygame)核心方法

Pygame是Python最经典的2D游戏开发第三方库,机巧围棋基于Pygame游戏开发引擎实现围棋游戏运行和交互逻辑,并搭建围棋游戏可视化界面。 本文不是Pygame的API文档或教程,但是本文将介绍在机巧围棋中所涉及的Pygame游戏开发引擎相关知识和方法,并详细讲解机巧围棋中基于Pygame实现的按钮控件、信息滚动显示器、音乐播放器等。 1. Pygame基本知识 1.1 Pygame游戏开发框架 Pygame游戏开发框架共包含4个部分: 第一部分:引入pygame和sys; 第部分:初始化pyg

RuizhiHe 9850

python系列11:python游戏引擎

2D的有pygame(可以做飞机大战之类的小游戏)、renpy(用来制作视觉小说游戏,也就是经典模拟恋爱游戏) 3D的有panda3d,完全免费。也可以用webgl和3djs做一些网页游戏。

kittyzc的博客 4180

python游戏开发引擎_3D游戏引擎的原理和架构

阅读:3,1903D游戏引擎的原理和架构随着 IT 产业的蓬勃发展,越来越多的幵源 3D 引擎、商业 3D 引擎都涌现出来了,其中最具代表性的有用于移动端开发的 Unity3D、用于 PC 端开发的虚幻4(Unreal Engine 4,UE4)以及幵源图形学引擎 OGRE。Unity 引擎在市面上比较流行,相信大多数 IT 开发者都使用过,先以 Unity3D 引擎为例介绍引擎的原理,Unity...

weixin_39952502的博客 1047

python游戏引擎开发_Python游戏引擎开发(一):创建窗口以及界面

这是一组给大家介绍Python游戏引擎开发的系列教程,使用Python开发的优点就不多说,下面进入引擎开发的第一个阶段,关于创建窗口界面。。在开发本地应用之前,我们得先有个窗口,用于显示界面。其次我们还得实现机制,使游戏不停地刷新,达到动态化的目的。Qt的渲染机制在上一章《序》中我们讲到本次开发用到了PyQt,也就是QtPython版。在开始实现引擎功能之前我们要先了解一下Qt,这里先...

weixin_39979167的博客 694

python怎么制作游戏图片_Python游戏引擎开发):显示图片

本篇文章是Python游戏引擎开发系列的第篇文章,主要介绍如何显示图片,大家可以学习下。在上一章中我们讲了如何创建窗口以及对界面进行。可能有朋友不理解为什么要进行全窗口呢?我在这里可以大致讲一下原因:由于我们的游戏是动态的,所以我们每次更改数据后(例如播放动画时切换图片),要让界面显示更改后的结果,一般的想法是:首先进行擦除原先要改的地方,然后再把变更的内容画出来。不过这个看似简单,...

weixin_39637059的博客 509

Python游戏引擎开发(五):Sprite精灵类和鼠标事件

本次来实现Sprite类和鼠标事件。 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它。它中文原意是“精灵”,不过在不同人的眼中,它所表示的意义不同。比如说在cocos2d中,它可以是一张图片。不过在flash中,Sprite是一个类似于“层”的家伙。当然你把它定义为层并不是很准确,实际上它是一个含显示列表的显示对象。什么意思呢?各位看官如果阅读了前面的章节,那对显示列表并不陌生

Yorhom's Game Box 1万+

Python游戏引擎开发(六):动画的小小研究

播放动画的时候,像播放电影一样,这张图就是胶卷。我们可以弄一个放映机,放映机的镜头大小就是每个动作小图的大小。如果我们的胶卷不停地移动,那么就会连成动画。在播放动画时,我们的“胶卷”就是一个`Bitmap`图片显示对象,其中包含了一个`BitmapData`对象,我们通过调用这个对象的上述两个方法,就能实现动画播放。

Yorhom's Game Box 8618

Python开发接水果小游戏

我研发的Python游戏引擎Pylash已经更新到1.4了。现在我们就来使用它完成一个极其简单的小游戏:接水果。游戏操作说明:点击屏幕左右两边或者使用键盘方向键控制人物移动,使人物与水果接触得分,碰到非水果的物品,如碎玻璃,就会game over。接下来是详尽的开发过程,篇幅较长,请看官耐心阅读。由于本次开发用到了pylash,大家可以先去Github上对引擎进行

Yorhom's Game Box 1万+

C++游戏引擎开发指南:Blender Python开发环境配置详解

游戏引擎开发过程中,3D建模工具与引擎的集成至关要。本文将详细介绍如何为C++游戏引擎项目配置Blender Python开发环境,帮助开发者高效地实现模型处理自动化流程。 ## Blender Python开发环境配置 ### 启用代码提示功能 Blender内置了强大的Python API支持,通过简单的设置即可启用代码提示功能: 1. 打开Blender,进入"编辑"菜单 2.

gitblog_00816的博客 694

【开源游戏引擎Python开发】:揭秘5大主流引擎选型策略与实战技巧

掌握开源游戏引擎Python开发核心策略,详解Pygame、Arcade等5大引擎选型与实战技巧,覆盖2D游戏开发、性能优化与跨平台部署。适用独立开发者与教学场景,提升项目效率,值得收藏。

SimTrans的博客 1189

python开发游戏引擎_Python游戏引擎开发(五):动画研究

接下来让我们来研究下动画,其实这个动画就是一个Sprite+Bitmap的结合体。不知道什么是Sprite和Bitmap就看Python游戏引擎开发系列中的文章。动画的原理一般而言,我们的动画是用的这样一种图片:播放动画的时候,像播放电影一样,这张图就是胶卷。我们可以弄一个放映机,放映机的镜头大小就是每个动作小图的大小。如果我们的胶卷不停地移动,那么就会连成动画,如下图:如何实现这个效果呢?我们在...

weixin_39689870的博客 234

绪论-游戏引擎开发

绪论

weixin_45750442的博客 2046

pyglet游戏开发实战:从简单小游戏到完整游戏引擎的构建

pyglet是一个跨平台的Python窗口和多媒体库,专为开发游戏和其他视觉丰富的应用程序而设计。它提供了强大的图形渲染、音频处理和用户输入支持,让开发者能够快速构建专业级游戏应用。本文将带你从基础开始,逐步掌握pyglet游戏开发的核心技能,最终构建属于自己的游戏引擎。 ## 🚀 为什么选择pyglet进行游戏开发? pyglet作为一款轻量级游戏开发框架,具有以下显著优势: - **无

gitblog_00638的博客 949

python游戏库入门:ursina游戏引擎——复原《我的世界》

Ursina 是一个强大的工具,适合于从小型项目到有趣的游戏开发。它简化了游戏开发的复杂性,同时保留了足够的灵活性来满足更多高级需求。对于希望用 Python 开发 3D 游戏的开发者来说,Ursina 是一个值得尝试的引擎。

baoyushuoBYsuu的博客 5590

如何快速掌握Pyxel Python游戏引擎:从零开始的复古游戏开发完整指南

Pyxel是一款专为Python设计的复古游戏引擎,它让开发者能够轻松创建像素风格的经典游戏。无论是游戏开发新手还是有经验的程序员,都能通过Pyxel的简洁API和丰富功能,快速实现自己的游戏创意。本文将深入解析Pyxel的技术架构,帮助你全面了解这个强大工具的内部工作原理和使用方法。 ## Pyxel引擎核心架构解析 Pyxel采用分层架构设计,主要由五大核心模块组成:系统模块、图形模块、音

gitblog_00985的博客 797
上一篇: Python游戏引擎开发(一):序
下一篇: Python游戏引擎开发(三):显示图片
YhPythonJSCPP
YhPythonJSCPP 领域专家: 前端开发技术领域 领域专家: 前端开发技术领域
博客等级 码龄14年 3146粉丝 67原创
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值