标签:
本次来实现Sprite
类和鼠标事件。
说起这个Sprite
啊,涉及过2D游戏研究领域的看官应该都听说过它。它中文原意是“精灵”,不过在不同人的眼中,它所表示的意义不同。比如说在cocos2d中,它可以是一张图片。不过在flash中,Sprite
是一个类似于“层”的家伙。当然你把它定义为层并不是很准确,实际上它是一个含显示列表的显示对象。什么意思呢?各位看官如果阅读了前面的章节,那对显示列表并不陌生。它说白了就是一个包含其他显示对象的容器。
那也许你会想,为什么要有这个类呢?举个例子大家就明白了。在一款RPG游戏中(如:口袋妖怪),我们的地图上有树林、小河等一系列地图元件。玩过此类游戏的同学都知道,如果我们的人物走到了地图中央继续前进的话,地图会进行卷轴移动,显示出下部分地图。这个时候我们如果要把每个地图元件进行移动,操作起来会相当麻烦。因此flash为我们提供的Sprite
就是为了统一处理一系列显示对象而生的。
经过上面的介绍,大家可能仍然无法理解这么抽象的一个类。那姑且把它视作一个层吧,我们可以通过Sprite
的addChild
来向这个层添加显示对象。添加进去的对象所进行的操作都是相对的,比如说移动,旋转。
以下是前面章节目录:
以下是实现代码:
class Sprite(DisplayObject):
def __init__(self):
super(Sprite, self).__init__()
self.childList = []
def addChild(self, child):
self.childList.append(child)
def removeChild(self, child):
self.childList.remove(child)
child.parent = None
def _loopDraw(self, c):
stage._showDisplayList(self.childList)
可以看到,这个类的实现代码很简单,就是添加了显示列表属性和添加/删除对象的方法。实际上在flash中,这个类还有很多功能,比如说以后会提及的矢量绘图。看过第二章的同学应该会注意到stage._showDisplayList
这个方法,他负责遍历显示列表并显示遍历得到的对象(及子对象)。由于这个方法是在QPainter
变换(平移,旋转,拉伸)之后,QPainter.restore()
之前被调用的,所以再次调用到这个子对象显示方法时,显示方法中对QPainter
的变换就是相对于先前QPainter
变换而言的。因此,我们就实现了子对象相对父对象变换的效果。
我们要来实现鼠标事件方面的功能了。首先需要了解的是,由于我们无法直接对我们写的显示对象添加事件,所以只能先对QWidget
添加鼠标事件然后在进一步进行计算来判断是否触发到我们的事件。为此我们需要改动CanvasWidget
类:
class CanvasWidget(QtGui.QWidget):
def __init__(self):
super(CanvasWidget, self).__init__()
self.setMouseTracking(True)
def paintEvent(self, event):
stage._onShow()
def mousePressEvent(self, event):
self.__enterMouseEvent(event, "mouse_down")
def mouseMoveEvent(self, event):
self.__enterMouseEvent(event, "mouse_move")
def mouseReleaseEvent(self, event):
self.__enterMouseEvent(event, "mouse_up")
def __enterMouseEvent(self, event, eventType):
e = {"offsetX" : event.x(), "offsetY" : event.y(), "eventType" : eventType, "target" : None}
stage._enterMouseEvent(e, {"x" : 0, "y" : 0, "scaleX" : 1, "scaleY" : 1})
主要是重写了QWidget
中的几个事件回调(mouseReleaseEvent
,mouseMoveEvent
,mousePressEvent
)以及添加事件进入显示对象的入口__enterMouseEvent
。
值得注意的是,setMouseTracking
方法是用于不停地触发移动事件。
Stage._enterMouseEvent
的代码如下:
def _enterMouseEvent(self, event, cd):
childList = self.childList[:: -1]
currentCd = {"x" : cd["x"], "y" : cd["y"], "scaleX" : cd["scaleX"], "scaleY" : cd["scaleY"]}
for o in childList:
if hasattr(o, "_enterMouseEvent") and hasattr(o._enterMouseEvent, "__call__") and o._enterMouseEvent(event, currentCd):
break
在其中,我们遍历了所有底层子对象并且判断是否可以进入鼠标事件向下级子对象循环,如果可以(及判断有无_enterMouseEvent
方法),则进行。除此之外,为了实现鼠标事件遮挡效果,我们特地的反着遍历显示列表,也就是说先遍历得到显示在上层的对象,调用这些对象的_enterMouseEvent
方法,该方法返回值若为True
则代表鼠标在该显示对象上面,通过break
中断遍历。
一般情况下,有_enterMouseEvent
的,大半是Sprite
对象。所以我们为Sprite
添加这个方法:
def _enterMouseEvent(self, e, cd):
if not self.visible:
return
currentCd = self.__getVisualCoordinate(cd, self)
isOn = self._isMouseOn(e, currentCd)
if isOn:
for o in self.childList[::-1]:
if (hasattr(o, "_enterMouseEvent") and hasattr(o._enterMouseEvent, "__call__") and o._enterMouseEvent(e, currentCd)):
break
self.__dispatchMouseEvent(e, currentCd)
return False
和Stage中的_enterMouseEvent
非常类似。其中用了子对象的_isMouseOn
方法,用于判断是否点击到该对象上。而__dispatchMouseEvent
用于触发鼠标事件。__getVisualCoordinate
用于得到一个显示坐标,这个好比我们看三维图形直观图,实际的大小和看到的不是一样的。所以我们通过这个方法来实现得到看到的大小和位置。__getVisualCoordinate
代码:
def __getVisualCoordinate(self, origin, obj):
return {
"x" : origin["x"] + obj.x * origin["scaleX"],
"y" : origin["y"] + obj.y * origin["scaleY"],
"scaleX" : origin["scaleX"] * obj.scaleX,
"scaleY" : origin["scaleY"] * obj.scaleY
}
由于_isMouseOn
和__dispatchMouseEvent
两个方法实现得不怎么好,还带优化,所以就先不给代码了。等库件发布的时候会给出相应的完善方案。
最后添加加入事件函数addEventListener
和removeEventListener
即可。顾名思义,它们分别用于添加事件和移除事件,主要用到list
和dict
来完成事件存储。
至此,我们就把Sprite
和鼠标事件大致实现了。由于此处较为复杂,而且没有得到相应完善,所以我讲得很粗糙。等库件发布的时候会给出完整代码。
预告:下一篇我们实现动画类。
欢迎大家继续关注我的博客
转载请注明出处:Yorhom’s Game Box
Python游戏引擎开发(五):Sprite精灵类和鼠标事件
标签:
原文地址:http://blog.csdn.net/yorhomwang/article/details/49366455