Python游戏引擎开发(六):动画的小小研究
今天我們來(lái)研究動(dòng)畫(huà),其實(shí)這個(gè)動(dòng)畫(huà)就是一個(gè)Sprite+Bitmap的結(jié)合體。不造什么是Sprite和Bitmap?=__=#看來(lái)你是半路殺進(jìn)來(lái)的,快去看看前幾章吧:
Python游戲引擎開(kāi)發(fā)(一):序
Python游戲引擎開(kāi)發(fā)(二):創(chuàng)建窗口以及重繪界面
Python游戲引擎開(kāi)發(fā)(三):顯示圖片
Python游戲引擎開(kāi)發(fā)(四):TextField文本類
Python游戲引擎開(kāi)發(fā)(五):Sprite精靈類和鼠標(biāo)事件
動(dòng)畫(huà)的原理
一般而言,我們的動(dòng)畫(huà)是用的這樣一種圖片:
播放動(dòng)畫(huà)的時(shí)候,像播放電影一樣,這張圖就是膠卷。我們可以弄一個(gè)放映機(jī),放映機(jī)的鏡頭大小就是每個(gè)動(dòng)作小圖的大小。如果我們的膠卷不停地移動(dòng),那么就會(huì)連成動(dòng)畫(huà),如下圖:
如何實(shí)現(xiàn)這個(gè)效果呢?我們?cè)诘谌轮袑W(xué)到了如何顯示圖片,其中提到了BitmapData類(不懂?良辰勸你去讀讀前幾章),這個(gè)類中有個(gè)兩個(gè)方法:setCoordinate和setProperty用于設(shè)置圖片顯示的位置和大小:
bmpd.setCoordinate(x, y) bmpd.setProperty(x, y, width, height)參數(shù)圖解如下:
在播放動(dòng)畫(huà)時(shí),我們的“膠卷”就是一個(gè)Bitmap圖片顯示對(duì)象,其中包含了一個(gè)BitmapData對(duì)象,我們通過(guò)調(diào)用這個(gè)對(duì)象的上述兩個(gè)方法,就能實(shí)現(xiàn)動(dòng)畫(huà)播放。
不過(guò)到此好像還是少了什么?也許你會(huì)問(wèn),動(dòng)畫(huà)是個(gè)連續(xù)的過(guò)程,且每幀動(dòng)畫(huà)之間需要間隔一點(diǎn)時(shí)間,是不是少了一個(gè)計(jì)時(shí)器?是的,是的,是的,重要的事情說(shuō)三遍,我們的確少了一個(gè)計(jì)時(shí)器類似物。不過(guò)別急。大家還記得第三章中提到的顯示對(duì)象的_show方法嗎?這個(gè)方法是在窗口的paintEvent中被調(diào)用的,paintEvent又是在一個(gè)計(jì)時(shí)器中被調(diào)用的(涉及第二章內(nèi)容)。等等……計(jì)時(shí)器……所以我們其實(shí)已經(jīng)有計(jì)時(shí)器了,差了個(gè)進(jìn)入計(jì)時(shí)器的接口罷了。
時(shí)間軸事件
既然少了個(gè)接口,那么加個(gè)不就完了嘛。更改DisplayObject的_show方法:
def _show(self, c):if not self.visible:return# 加入時(shí)間軸事件入口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()就加入了時(shí)間軸事件入口那一行代碼。這個(gè)方法在子類Sprite中具體實(shí)現(xiàn):
def _loopFrame(self):e = object()e.currentTarget = selfs.__enterFrameListener(e)其中__enterFrameListener為新加入Sprite的屬性,是時(shí)間軸事件的監(jiān)聽(tīng)器。與鼠標(biāo)事件相同,我們向監(jiān)聽(tīng)器傳入一個(gè)參數(shù),用于獲取事件信息。
更改Sprite的addEventListener使其能加入時(shí)間軸事件:
def addEventListener(self, eventType, listener):if eventType == Event.ENTER_FRAME:self.__enterFrameListener = listenerelse:self.mouseList.append({"eventType" : eventType,"listener" : listener})再在Event類中定義一下時(shí)間軸事件ENTER_FRAME:
class Event(Object):ENTER_FRAME = "enter_frame"# more code...使用時(shí),這么寫(xiě)就OK:
layer = Sprite() layer.addEventListener(Event.ENTER_FRAME, onframe)def onframe(e):print("enter frame")簡(jiǎn)單的動(dòng)畫(huà)類
動(dòng)畫(huà)類:
class Animation(Sprite):def __init__(self, bitmapData = BitmapData(), frameList = [[AnimationFrame()]]):super(Animation, self).__init__()這個(gè)類需要一個(gè)BitmapData對(duì)象作為參數(shù),還需要一個(gè)list對(duì)象,這個(gè)list是用來(lái)裝AnimationFrame對(duì)象的幀列表,AnimationFrame顧名思義是一個(gè)保存每幀數(shù)據(jù)的類。代碼實(shí)現(xiàn)如下:
class AnimationFrame(object):def __init__(self, x = 0, y = 0, width = 0, height = 0):super(AnimationFrame, self).__init__()self.x = xself.y = yself.width = widthself.height = height其中x,y屬性儲(chǔ)存了每幀位于圖片上的位置,width和height儲(chǔ)存每幀的寬高。
對(duì)于一般的動(dòng)畫(huà)圖片(上文中的示例圖片),每幀都是均勻分布在圖片上的,所以我們可以加入一個(gè)函數(shù)進(jìn)行幀的均勻裁剪,這樣一來(lái),我們獲取幀列表就會(huì)方便很多。為Animation類添加如下代碼:
def divideUniformSizeFrames(width = 0, height = 0, col = 1, row = 1):result = []frameWidth = width / colframeHeight = height / rowfor 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ái),我們調(diào)用這個(gè)函數(shù),傳入相應(yīng)參數(shù)就可以切割出幀列表了。如下:
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)] ]接下來(lái)就是實(shí)現(xiàn)播放動(dòng)畫(huà)了,修改Animation類:
class Animation(Sprite):def __init__(self, bitmapData = BitmapData(), frameList = [[AnimationFrame()]]):super(Animation, self).__init__()self.bitmapData = bitmapDataself.frameList = frameListself.bitmap = Bitmap(bitmapData)self.currentRow = 0self.currentColumn = 0self.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 += 1if self.currentColumn >= len(self.frameList[self.currentRow]):self.currentColumn = 0由于這個(gè)類繼承自Sprite,所以就繼承了加入事件的addEventListener方法。以上代碼實(shí)現(xiàn)的是播放一排動(dòng)畫(huà),大家可以自行拓展為播放一列動(dòng)畫(huà)或者播放整組動(dòng)畫(huà)。
這樣一來(lái),寫(xiě)入以下代碼就能播放動(dòng)畫(huà)了:
# 加載圖片 loader = Loader() loader.load("./player.png")# 動(dòng)畫(huà)數(shù)據(jù) bmpd = BitmapData(loader.content) l = Animation.divideUniformSizeFrames(160, 160, 4, 4)# 加入動(dòng)畫(huà) anim = Animation(bmpd, l) addChild(anim)預(yù)告:下一篇我們來(lái)繪制矢量圖形。
歡迎大家繼續(xù)關(guān)注我的博客
轉(zhuǎn)載請(qǐng)注明出處:Yorhom’s Game Box
http://blog.csdn.net/yorhomwang
總結(jié)
以上是生活随笔為你收集整理的Python游戏引擎开发(六):动画的小小研究的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 18 freertos消息队列-任务通信
- 下一篇: Headroom.js – 快速响应用户