Python游戏引擎开发(三):显示图片

在上一章中我们讲了如何创建窗口以及对界面进行重绘。可能有朋友不理解为什么要进行全窗口的重绘呢?我在这里可以大致讲一下原因:
由于我们的游戏是动态的,所以我们每次更改数据后(例如播放动画时切换图片),要让界面显示更改后的结果,一般的想法是:首先进行擦除原先要改的地方,然后再把变更的内容画出来。不过这个看似简单,如果遇到了重叠放置的对象就麻烦了,比如说A在B的下面,我们要更改A,那么把A擦掉后,B也会被擦掉,原因在于我们的画布是2D的,无法控制Z方向的擦除。这样一来,我们除了重画A还要再把B画上去。这是个比较复杂的问题,所以为了简化操作,我们直接使用全窗口的重绘,也就是定期的进行擦除,然后重绘。

在阅读本章正文前,请先阅读前两章:

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

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

显示对象

在前面的章节中,我们屡次提到了显示对象这个东西,那显示对象到底是什么呢?顾名思义,它是一个可视的物体,比如说游戏中的人物,地图等。例如listtuple等,这些对象是不可以显示的,它们只用于内部的数据存储,所以不是显示对象。同理,游戏中的资源加载器也不是显示对象。

程序开发可以看作一个归类的过程(所以class成为了一种主要的程序语句)。如果我们以对象的尺寸,或者颜色来分类显示对象,那么可能会出现这些类:BigTreeGreenTree……这样的分类存在明显的问题:分类不够细化,而且无法实现所有效果。于是flash给了我们很好的示例:通过负责显示的内容来分类。也就是说,图片显示为一类,文本显示为一类,矢量图形显示为一类……这样一来,细化层度不仅高,而且界面上的一切都可以用这几个类来组合完成。

今天就先来实现显示图片。由于上述的类都和显示对象有关,所以我们先创造一个所有显示对象的父类DisplayObject

class DisplayObject(object):
    def __init__(self):
        super(DisplayObject, self).__init__()

        self.parent = None
        self.x = 0
        self.y = 0
        self.alpha = 1
        self.rotation = 0
        self.scaleX = 1
        self.scaleY = 1
        self.visible = True

    @property
    def width(self):
        return self._getOriginalWidth() * abs(self.scaleX)

    @property
    def height(self):
        return self._getOriginalHeight() * abs(self.scaleY)

    def _show(self, c):
        if not self.visible:
            return

        c.save()

        c.translate(self.x, self.y)
        c.setOpacity(self.alpha * c.opacity())
        c.rotate(self.rotation)
        c.scale(self.scaleX, self.scaleY)

        self._loopDraw(c)

        c.restore()

    def _loopDraw(self, c):
        pass

    def _getOriginalWidth(self):
        return 0

    def _getOriginalHeight(self):
        return 0

    def remove(self):
        self.parent.removeChild(self)

这个类中的所有属性,就是所有显示对象的公共属性。比如说xy分别表示平面直角坐标系中横纵坐标(原点为屏幕最左上角);rotation表示对象绕其左上角旋转的角度。
前面提到了重复渲染,所以我们要提供一个方法来实现自我重绘。在重绘的途中,不同的显示对象显示的内容不同,比如说图片类就该显示图片,文本类显示文本。但是这些类又有统一之处,比如说都可以设置横纵坐标,旋转度数等。所以我们创建_show方法,其中对旋转,缩放,移动进行统一处理,然后调用_loopDraw来进行绘制不同的内容。
由于显示对象还有获取宽高的功能,所以我们再加入_getOriginalWidth_getOriginalHeight进行获取widthheight属性时计算宽高。
还有个remove方法用于将自身从显示列表中移除。

以上在代码安排进行了说明,接下来来解释代码。首先追忆一下上一章的代码:

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

这是Stage类中的一个方法,在这个方法中,我们遍历了显示对象并调用显示对象的_show方法,所以这里是绘制显示对象的入口。我们可以看到,在调用这个函数时,我们传入了Stage类的canvas属性,这是个啥玩意呢?在上一章中我们介绍过,这是一个QPainter对象,“只是当时已惘然”【1】的同学还当看看本文前一章才是

【1】出自李义山《锦瑟》一诗,原诗的意思一说是:“只是当年早已惘然”,我这里借代使用,翻译为字面意思:“现在感到茫然”

_show方法中,我们首先接受这个参数,这个QPainter中有很多方法,可以用来设置整个画笔的一些属性,比如说画笔的透明度,绘画的位置等。QPaintersave方法用于记录当前坐标状态,方便实现相对定位。translatescalerotatesetOpacity分别用于设置画笔起始位置,拉升/压缩画笔,旋转画笔,设置画笔透明度。随后调用_loopDraw绘制不同显示对象的特殊内容。最后是调用restore恢复画笔状态到记录状态(save调用时的)。

显示图片

有了以上的显示对象类作为基础,我们就可以来实现显示图片了。

加载图片

首先是加载图片。写个Loader类:

class Loader(DisplayObject):
    def __init__(self):
        super(Loader, self).__init__()

        self.content = None

    def load(self, url):
        image = QtGui.QImage()
        image.load(url)

        self.content = image

用到了QImage这个Qt的类,这个类有个load方法,通过向这个方法传入一个图片地址来加载图片。另外提一下,我们前面说过资源加载器不属于显示对象,但是据我所知,flash中的Loader就是个显示对象,继承自DisplayObject,还可以被加入到显示列表中进行显示,当然这是在加载.swf等文件的条件下,我们这里就暂且模仿flash,以后有别的运用再拓展拓展也不迟。

储存图片数据

在flash中,储存图片使用BitmapData类,这小蹄子就不是显示对象了:

class BitmapData(object):
    def __init__(self, image = QtGui.QImage(), x = 0, y = 0, width = 0, height = 0):
        super(BitmapData, self).__init__()

        self.image = image
        self.x = x
        self.y = y
        self.width = width
        self.height = height

        if image is not None:
            if width == 0:
                self.width = image.width()

            if height == 0:
                self.height = image.height()

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        if value > self.image.width():
            value = self.image.width()

        self.__x = value

    @property
    def y(self):
        return self.__y

    @y.setter
    def y(self, value):
        if value > self.image.height():
            value = self.image.height()

        self.__y = value

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, value):
        if (value + self.x) > self.image.width():
            value = self.image.width() - self.x

        self.__width = value

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, value):
        if (value + self.y) > self.image.height():
            value = self.image.height() - self.y

        self.__height = value

    def setCoordinate(self, x = 0, y = 0):
        self.x = x
        self.y = y

    def setProperties(self, x = 0, y = 0, width = 0, height = 0):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

这个类就只是对图片数据进行一些储存,比如说显示范围的宽高,和显示范围的起始坐标。当然这个类还有其他的用途,如像素处理,以后会逐步拓展。值得注意的是,这个类中各个属性代表的含义如下:

图一

图片显示类

图片既加载了又储存了,那么接下来就显示图片了。显示图片的类号Bitmap,是DisplayObject子类:

class Bitmap(DisplayObject):
    def __init__(self, bitmapData = BitmapData()):
        super(Bitmap, self).__init__()

        self.bitmapData = bitmapData

    def _getOriginalWidth(self):
        return self.bitmapData.width

    def _getOriginalHeight(self):
        return self.bitmapData.height

    def _loopDraw(self, c):
        bmpd = self.bitmapData

        c.drawImage(0, 0, bmpd.image, bmpd.x, bmpd.y, bmpd.width, bmpd.height)

这里使用了对_loopDraw的重写来完成绘制图片这一特殊内容。在这个方法中,值得注意的是QPainterdrawImage方法,这个方法接受的参数分别是:[起始点x,起始点y,QImage对象,图片显示内容的x属性,图片显示内容的y属性,图片显示内容的宽,图片显示内容的高]
顺便重写了_getOriginalWidth_getOriginalHeight来获取图片的宽高。

完成后,结合前面的代码,进行测试:

from pylash import init, addChild, Bitmap, Loader, BitmapData

def main():
    loader = Loader()
    loader.load("./face.png")

    bmpd = BitmapData(loader.content)
    bmp = Bitmap(bmpd)
    addChild(bmp)

    bmp.x = 80
    bmp.y = 100
    bmp.rotation = -20
    bmp.alpha = 0.8

init(30, "Display An Image", 800, 600, main)

效果图:

图二

本次封装的所有代码:

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

        self.parent = None
        self.x = 0
        self.y = 0
        self.alpha = 1
        self.rotation = 0
        self.scaleX = 1
        self.scaleY = 1
        self.visible = True

    @property
    def width(self):
        return self._getOriginalWidth() * abs(self.scaleX)

    @property
    def height(self):
        return self._getOriginalHeight() * abs(self.scaleY)

    def _show(self, c):
        if not self.visible:
            return

        c.save()

        c.translate(self.x, self.y)
        c.setOpacity(self.alpha * c.opacity())
        c.rotate(self.rotation)
        c.scale(self.scaleX, self.scaleY)

        self._loopDraw(c)

        c.restore()

    def _loopDraw(self, c):
        pass

    def _getOriginalWidth(self):
        return 0

    def _getOriginalHeight(self):
        return 0

    def remove(self):
        self.parent.removeChild(self)

class Loader(DisplayObject):
    def __init__(self):
        super(Loader, self).__init__()

        self.content = None

    def load(self, url):
        image = QtGui.QImage()
        image.load(url)

        self.content = image

class BitmapData(object):
    def __init__(self, image = QtGui.QImage(), x = 0, y = 0, width = 0, height = 0):
        super(BitmapData, self).__init__()

        self.image = image
        self.x = x
        self.y = y
        self.width = width
        self.height = height

        if image is not None:
            if width == 0:
                self.width = image.width()

            if height == 0:
                self.height = image.height()

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        if value > self.image.width():
            value = self.image.width()

        self.__x = value

    @property
    def y(self):
        return self.__y

    @y.setter
    def y(self, value):
        if value > self.image.height():
            value = self.image.height()

        self.__y = value

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, value):
        if (value + self.x) > self.image.width():
            value = self.image.width() - self.x

        self.__width = value

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, value):
        if (value + self.y) > self.image.height():
            value = self.image.height() - self.y

        self.__height = value

    def setCoordinate(self, x = 0, y = 0):
        self.x = x
        self.y = y

    def setProperties(self, x = 0, y = 0, width = 0, height = 0):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

class Bitmap(DisplayObject):
    def __init__(self, bitmapData = BitmapData()):
        super(Bitmap, self).__init__()

        self.bitmapData = bitmapData

    def _getOriginalWidth(self):
        return self.bitmapData.width

    def _getOriginalHeight(self):
        return self.bitmapData.height

    def _loopDraw(self, c):
        bmpd = self.bitmapData

        c.drawImage(0, 0, bmpd.image, bmpd.x, bmpd.y, bmpd.width, bmpd.height)

预告:下一篇我们实现文本显示。


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

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

http://blog.csdn.net/yorhomwang

发布了67 篇原创文章 · 获赞 45 · 访问量 93万+
展开阅读全文

Python tkinter图形界面BUTTON事件控制和编辑框返回值出现异常

06-02

使用win7的Python3.73的IDLE界面编写图形界面程序,在编码过程中发现,如果定义一个主窗口后再定义一个局部窗口会导致后面的窗口的编辑框设置和取值异常。具体问题用一个测试程序来说明如下: ``` import tkinter as tk from tkinter import messagebox def newwin(title='test',geometry='300x300'): def ShowMessage(): tk.messagebox.showinfo(input.get()) window = tk.Tk() window.title(title) window.geometry(geometry) input = tk.StringVar() input.set('请输入任意内容!') tk.Label(window, text='请输入任意内容: ').place(x=10, y= 10) entry_new_name = tk.Entry(window, textvariable=input) entry_new_name.place(x=150, y=10) btn_show = tk.Button(window, text='显示输入', command=ShowMessage) btn_show.place(x=80, y=90) btn_newwin = tk.Button(window, text='新弹窗', command=newwin) btn_newwin.place(x=150, y=90) btn_exit = tk.Button(window, text='退出', command=exit) btn_exit.place(x=220, y=90) return window root = newwin() root.mainloop() ``` 上述代码执行后,显示一个窗口,内有:一个输入框,三个按钮,一个是显示输入框内容,一个是再次新建一个同样的窗口,一个是系统退出。执行后第一次打开窗口时相关功能正常,但当选择“新弹窗”弹出一个新的窗口后,编辑框的初始内容没有显示,且输入内容后,在新窗口显示编辑框的内容取得的值为变量初始化的值,没有取得新的输入。 关于以上代码,有如下几个问题: 1、为什么新弹窗中编辑框的显示和变化没有和变量关联起来? 2、上述代码中btn_newwin按钮定义如果改为:“btn_newwin = tk.Button(window, text='新弹窗', command=newwin)”中,如果newwin使用带实参格式,则会导致一启动就会不停新建窗口,导致递归调用过深异常出现才终止?这是为什么 ? 3、该代码存储到文件后,使用dos命令行方式执行文件无任何反应。是图形界面的程序无法在非图形界面解释器下执行吗?该怎么解决? 非常抱歉,本人没有C币,不知可有哪位大拿免费指教?谢谢! 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览