Python游戏引擎开发:动画的小小研究
Posted Yorhom
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python游戏引擎开发:动画的小小研究相关的知识,希望对你有一定的参考价值。
今天我们来研究动画,其实这个动画就是一个Sprite
+Bitmap
的结合体。不造什么是Sprite
和Bitmap
?=__=#看来你是半路杀进来的,快去看看前几章吧:
Python游戏引擎开发(五):Sprite精灵类和鼠标事件
动画的原理
一般而言,我们的动画是用的这样一种图片:
播放动画的时候,像播放电影一样,这张图就是胶卷。我们可以弄一个放映机,放映机的镜头大小就是每个动作小图的大小。如果我们的胶卷不停地移动,那么就会连成动画,如下图:
如何实现这个效果呢?我们在第三章中学到了如何显示图片,其中提到了BitmapData
类(不懂?良辰劝你去读读前几章),这个类中有个两个方法:setCoordinate
和setProperty
用于设置图片显示的位置和大小:
bmpd.setCoordinate(x, y)
bmpd.setProperty(x, y, width, height)
参数图解如下:
在播放动画时,我们的“胶卷”就是一个Bitmap
图片显示对象,其中包含了一个BitmapData
对象,我们通过调用这个对象的上述两个方法,就能实现动画播放。
不过到此好像还是少了什么?也许你会问,动画是个连续的过程,且每帧动画之间需要间隔一点时间,是不是少了一个计时器?是的,是的,是的,重要的事情说三遍,我们的确少了一个计时器类似物。不过别急。大家还记得第三章中提到的显示对象的_show
方法吗?这个方法是在窗口的paintEvent
中被调用的,paintEvent
又是在一个计时器中被调用的(涉及第二章内容)。等等……计时器……所以我们其实已经有计时器了,差了个进入计时器的接口罢了。
时间轴事件
既然少了个接口,那么加个不就完了嘛。更改DisplayObject
的_show
方法:
def _show(self, c):
if not self.visible:
return
# 加入时间轴事件入口
self._loopFrame()
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()
就加入了时间轴事件入口那一行代码。这个方法在子类Sprite
中具体实现:
def _loopFrame(self):
e = object()
e.currentTarget = self
s.__enterFrameListener(e)
其中__enterFrameListener
为新加入Sprite
的属性,是时间轴事件的监听器。与鼠标事件相同,我们向监听器传入一个参数,用于获取事件信息。
更改Sprite
的addEventListener
使其能加入时间轴事件:
def addEventListener(self, eventType, listener):
if eventType == Event.ENTER_FRAME:
self.__enterFrameListener = listener
else:
self.mouseList.append({
"eventType" : eventType,
"listener" : listener
})
再在Event
类中定义一下时间轴事件ENTER_FRAME
:
class Event(Object):
ENTER_FRAME = "enter_frame"
# more code
...
使用时,这么写就OK:
layer = Sprite()
layer.addEventListener(Event.ENTER_FRAME, onframe)
def onframe(e):
print("enter frame")
简单的动画类
动画类:
class Animation(Sprite):
def __init__(self, bitmapData = BitmapData(), frameList = [[AnimationFrame()]]):
super(Animation, self).__init__()
这个类需要一个BitmapData
对象作为参数,还需要一个list
对象,这个list
是用来装AnimationFrame
对象的帧列表,AnimationFrame
顾名思义是一个保存每帧数据的类。代码实现如下:
class AnimationFrame(object):
def __init__(self, x = 0, y = 0, width = 0, height = 0):
super(AnimationFrame, self).__init__()
self.x = x
self.y = y
self.width = width
self.height = height
其中x
,y
属性储存了每帧位于图片上的位置,width
和height
储存每帧的宽高。
对于一般的动画图片(上文中的示例图片),每帧都是均匀分布在图片上的,所以我们可以加入一个函数进行帧的均匀裁剪,这样一来,我们获取帧列表就会方便很多。为Animation
类添加如下代码:
def divideUniformSizeFrames(width = 0, height = 0, col = 1, row = 1):
result = []
frameWidth = width / col
frameHeight = height / row
for i in range(row):
rowList = []
for j in range(col):
frame = AnimationFrame(j * frameWidth, i * frameHeight, frameWidth, frameHeight)
rowList.append(frame)
result.append(rowList)
return result
接下来,我们调用这个函数,传入相应参数就可以切割出帧列表了。如下:
l = Animation.divideUniformSizeFrames(160, 160, 4, 4)
# 得到如下列表:
[
[AnimationFrame(0, 0, 40, 40), AnimationFrame(40, 0, 40, 40), AnimationFrame(80, 0, 40, 40), AnimationFrame(120, 0, 40, 40)],
[AnimationFrame(0, 40, 40, 40), AnimationFrame(40, 40, 40, 40), AnimationFrame(80, 40, 40, 40), AnimationFrame(120, 40, 40, 40)],
[AnimationFrame(0, 80, 40, 40), AnimationFrame(40, 80, 40, 40), AnimationFrame(80, 80, 40, 40), AnimationFrame(120, 80, 40, 40)],
[AnimationFrame(0, 120, 40, 40), AnimationFrame(40, 120, 40, 40), AnimationFrame(80, 120, 40, 40), AnimationFrame(120, 120, 40, 40)]
]
接下来就是实现播放动画了,修改Animation
类:
class Animation(Sprite):
def __init__(self, bitmapData = BitmapData(), frameList = [[AnimationFrame()]]):
super(Animation, self).__init__()
self.bitmapData = bitmapData
self.frameList = frameList
self.bitmap = Bitmap(bitmapData)
self.currentRow = 0
self.currentColumn = 0
self.addEventListener(Event.ENTER_FRAME, self.__onFrame)
def __onFrame(self, e):
currentFrame = self.frameList[self.currentRow][self.currentColumn]
self.bitmap.bitmapData.setProperty(currentFrame.x, currentFrame.y, currentFrame.width, currentFrame.height)
self.currentColumn += 1
if self.currentColumn >= len(self.frameList[self.currentRow]):
self.currentColumn = 0
由于这个类继承自Sprite
,所以就继承了加入事件的addEventListener
方法。以上代码实现的是播放一排动画,大家可以自行拓展为播放一列动画或者播放整组动画。
这样一来,写入以下代码就能播放动画了:
# 加载图片
loader = Loader()
loader.load("./player.png")
# 动画数据
bmpd = BitmapData(loader.content)
l = Animation.divideUniformSizeFrames(160, 160, 4, 4)
# 加入动画
anim = Animation(bmpd, l)
addChild(anim)
预告:下一篇我们来绘制矢量图形。
欢迎大家继续关注我的博客
转载请注明出处:Yorhom’s Game Box
以上是关于Python游戏引擎开发:动画的小小研究的主要内容,如果未能解决你的问题,请参考以下文章