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

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

以下是之前的文章:
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

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值