Python游戏引擎开发(六):动画的小小研究

今天我们来研究动画,其实这个动画就是一个Sprite+Bitmap的结合体。不造什么是SpriteBitmap?=__=#看来你是半路杀进来的,快去看看前几章吧:

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

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

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

Python游戏引擎开发(四):TextField文本类

Python游戏引擎开发(五):Sprite精灵类和鼠标事件

动画的原理

一般而言,我们的动画是用的这样一种图片:

播放动画的时候,像播放电影一样,这张图就是胶卷。我们可以弄一个放映机,放映机的镜头大小就是每个动作小图的大小。如果我们的胶卷不停地移动,那么就会连成动画,如下图:

如何实现这个效果呢?我们在第三章中学到了如何显示图片,其中提到了BitmapData类(不懂?良辰劝你去读读前几章),这个类中有个两个方法:setCoordinatesetProperty用于设置图片显示的位置和大小:

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的属性,是时间轴事件的监听器。与鼠标事件相同,我们向监听器传入一个参数,用于获取事件信息。

更改SpriteaddEventListener使其能加入时间轴事件:

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

其中xy属性储存了每帧位于图片上的位置,widthheight储存每帧的宽高。

对于一般的动画图片(上文中的示例图片),每帧都是均匀分布在图片上的,所以我们可以加入一个函数进行帧的均匀裁剪,这样一来,我们获取帧列表就会方便很多。为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

http://blog.csdn.net/yorhomwang

时间: 2024-08-06 16:01:29

Python游戏引擎开发(六):动画的小小研究的相关文章

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

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

Python游戏引擎开发(五):Sprite精灵类和鼠标事件

本次来实现Sprite类和鼠标事件. 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它.它中文原意是"精灵",不过在不同人的眼中,它所表示的意义不同.比如说在cocos2d中,它可以是一张图片.不过在flash中,Sprite是一个类似于"层"的家伙.当然你把它定义为层并不是很准确,实际上它是一个含显示列表的显示对象.什么意思呢?各位看官如果阅读了前面的章节,那对显示列表并不陌生.它说白了就是一个包含其他显示对象的容器. 那也许你会想,为什么要有这

Python游戏引擎开发(四):TextField文本类

上一章我们主要介绍了显示对象和如何显示图片.本章来讲述显示文本. 以下是本系列教程前几章地址,在阅读本篇正文之前,请务必阅读前几章内容. Python游戏引擎开发(一):序 Python游戏引擎开发(二):创建窗口以及重绘界面 Python游戏引擎开发(三):显示图片 文本类TextField 使用过flash的朋友都知道,这个类其实不光是显示文本用的,还可以用于显示输入框.我这里就只实现一些基础实用的功能,以后有时间了慢慢拓展.和上一章一样,TextField是一个显示对象,继承自Displa

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

开发本地应用之前,我们得先有个窗口,用于显示界面.其次我们还得实现重绘机制,使游戏不停地刷新,达到动态化的目的.所以我们的引擎开发的第一个阶段就是创建窗口和重绘界面. 以下是之前的文章: Python游戏引擎开发(一):序 Qt的渲染机制 在上一章<序>中我们讲到本次开发用到了PyQt,也就是Qt的Python版.在开始实现引擎功能之前我们要先了解一下Qt,这里先了解渲染机制. 在Qt中,绘画用到的类叫做QPainter,顾名思义,就是个画家类吧.在这个类中,提供了非常多的方法用于操控这个&q

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

邂逅Python 写了这么久的html5,感觉html5学得差不多了,是时候去接触更多的语言来扩充自己的能力了.我先后看了Swift,Java等语言.首先开发Swift需要一台mac,对于我这个寒士而言,过于奢华了一些:Java吧,又感觉太胖了,不够苗条,身材不好,也看不上.最后遇到了Miss Python,先前也和她打过交道,不过感觉语法怪怪的,总是出现>>>这类符号(当时没有深入接触,不晓得是命令输入提示),实在是太高冷了.幸好遇见了廖雪峰大侠,在他的引荐下,我开始初步了解Pytho

25 个超棒的 HTML5 &amp; JavaScript 游戏引擎开发库

25 个超棒的 HTML5 & JavaScript 游戏引擎开发库 就像在汽车中,引擎完成主要的工作,使汽车看起来不可思议.游戏引擎同理,游戏开发者完成细节的工作,使游戏看起来真实.吸引人眼球.游戏引擎负责其余的事情.早期,游戏开发者通常从草图做起,花费高昂,且不容易获利.为了让游戏开发更加简单容易,主要的开发者开始授权他们的基本游戏引擎,如 Unreal.而且,随着手机和平板游戏的出现,所需预算比以前更少,对 JAVASCRIPT 和HTML5 游戏引擎的需求大增. 如果你是一个游戏开发者,

libgdx游戏引擎开发笔记(四)文字显示BitmapFont

http://www.tuicool.com/articles/iAJbIj 由于Libgdx底层是用OpenGL实现的,所以Libgdx是可以支持中文的,在libgdx中的汉字都是通过贴图的方式显示,使用 BitmapFont和SpriteBatch 组合来完成文字的绘制,构造BitmapFont时需要一个描述文字构成的fnt文件,和一个提供文字图片的png文件.因此显示中文,归根结底就是读取一个包含中文信息的 .fnt文件 和相应的 .png文件 并展示出来的问题. 1.如何生成这两个文件那

python运维开发(六)----模块续

内容目录 反射 模块 反射 利用字符串的形式去对象(模块)中操作(寻找/检查/删除/设置)成员,我们称之为反射. 方法: getattr delattr setattr hasattr 模块 补充: 特殊变量 __doc__ 注释 __file__ 当前py文件的路径 __packaged__ __cached__ __name__

使用Laya引擎开发微信小游戏(上)

本文由云+社区发表 使用一个简单的游戏开发示例,由浅入深,介绍了如何用Laya引擎开发微信小游戏. 作者:马晓东,腾讯前端高级工程师. 微信小游戏的推出也快一年时间了,在IEG的游戏运营活动中,也出现了越来越多的以小游戏作为载体运营的活动类型,比如游戏预约,抢先试完等等,都收到了非常良好的效果. 在支持微信小游戏的游戏引擎中,Cocos,Egret,Laya都对小游戏的开发提供了很多强大的支持.前段时间正好抽空研究了一下这块的内容,现做一个总结,针对如何使用Laya引擎开发微信小游戏给大家做一下