Python游戏引擎开发:创建窗口以及重绘界面
Posted YhPythonJSCPP
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python游戏引擎开发:创建窗口以及重绘界面相关的知识,希望对你有一定的参考价值。
开发本地应用之前,我们得先有个窗口,用于显示界面。其次我们还得实现重绘机制,使游戏不停地刷新,达到动态化的目的。所以我们的引擎开发的第一个阶段就是创建窗口和重绘界面。
以下是之前的文章:
Python游戏引擎开发(一):序
Qt的渲染机制
在上一章《序》中我们讲到本次开发用到了PyQt,也就是Qt的Python版。在开始实现引擎功能之前我们要先了解一下Qt,这里先了解渲染机制。
在Qt中,绘画用到的类叫做QPainter
,顾名思义,就是个画家类吧。在这个类中,提供了非常多的方法用于操控这个“画家”,比如说叫他去画个圆,叫他去测量文字的尺寸,画家先生当然是不会推辞的。
在使用QPainter
时,需要给其一个绘画设备,也就类似于画家的画板。这个绘画设备就是一个QWidget
,部件类。这个类又提供了很多方法,比如说设置部件尺寸等。在Qt中,如果只有一个QWidget
,那么这个QWidget
就会成为窗口对象。
Qt中的事件以及信号和槽
Qt中的事件和javascript dom事件有点不同,不是通过注册一个事件类型和所对应的监听器来进行事件回调的,而是Qt直接在类中写好了监听器,通过重写这个监听器方法来执行自己的代码,也就是是说Js的事件是加了才会调用的,而Qt是自动加上了事件,通过修改监听器来执行自定义的代码。不过Qt对Js添加事件这一功能换了种说法——信号和槽。这个就好比《基督山伯爵》中菲尔兰出卖阿里国王那段一样——送来的是匕首则引爆炸弹炸掉所有的财产和家人,送来的是戒指则平安无事。这里的“送来匕首”就是信号,“引爆炸弹”就是槽;“送来戒指”是信号,“平安无事”为槽。你还可以把信号理解为事件名,槽理解为回调函数。
上文所提到的绘画需要在QWidget
的paintEvent
中进行。
QApplication
QApplication是整个应用程序的入口。这个类有点特别,具体特别在哪里呢?请看如下的代码:
app = QtGui.QApplication(sys.argv)
# ...
app.exec_()
也就是说,如果你要使用Qt里一系列的功能,请在实例化QApplication
后,调用exec_
方法前使用这些功能,也就是程序的入口是在代码中# ...
的位置。另外,QApplication
要接受一个参数,也就是代码中的sys.argv
,因此你需要在使用前引入sys模组。
这里就顺便提一下Qt的一些模组。Qt作为一款强大的引擎,难免有很多模组,不过我们大致要用的就只有QtGui
,QtCore
,QtWebKit
这些模组。开发游戏的话,QtGui
,QtCore
这两个模组就已经够用了。
库件基础类
基础部分大致讲完了,开始封装吧,首先封装的就是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
可以看到,这个类中包含了许多全局属性。比如说width
和height
就是用来获取游戏界面宽高的,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
,实例化后,就是窗口对象。
上面用到的setWindowTitle
和setFixedSize
方法分别是用来设置窗口标题,窗口固定大小的。然后使用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()
在这个函数中,我们首先用QPainter
的begin
方法绑定绘画设备。然后进行擦除界面,再通过_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
类是用来作为最底层显示列表的,所以加入addChild
,removeChild
方法用来完成添加和删除显示对象:
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
以上是关于Python游戏引擎开发:创建窗口以及重绘界面的主要内容,如果未能解决你的问题,请参考以下文章