【逐帧动画】

其实就canvas而言,和flash有太多相似的地方。最直接的一点:比如把一个object从a点移动到b点。并不是直接去改变object的位置,而是把a点的object擦掉,在b点重新画一个。这其实和我们儿时喜爱的动画原理一致的。电视机里播放的好看的动画,早期都是由我们的动画制作者一帧一帧画出来的。一秒30帧或者其他数。快速的转变欺骗我们的眼睛。

如今的web动画,尤其是web Game一块,因为大量复杂绚丽的原因,简单依靠底层矢量的绘制基本已经不能满足需求。更多的,或者说更实际可用的是怎么把一张张图片资源连接到一起变成动画的方法。

dom里面background 本身支持background-position,canvas就更不用说了,drawImage本身也就支持针对图片区域绘制。如此以来,一张张图片的切换就可以换成一张图片不同位置的切换。

假如我们预先设定好一个数据格式,那么动画帧的制作就会慢慢开始变得简单起来了,意味着只需要按照固定的格式填写每一帧的具体信息即可以完成一个动画过程。

/*** Animation* {Class}* 动画类*/Laro.register('.action', function (La) {var Class = La.Class || La.base.Class,extend = La.extend;/*** @param anim {Object} 从json配置里面获取的anim 配置{"nbrOfFrames": 73,"name": "TimeTrap","atlas": "atlas/game/timetrap","type": "animation","image": "anims/timetrap.png","pivoty": 128,"framerate": 30,"pivotx": 256,"events": []}* @param frames {Array} 从json配置文件里面获得的每帧的位置信息*/var Animation = Class(function (anim, frames) {extend(this, anim);this.frames = frames;if (anim.framerate == undefined) anim.framerate = 20;// 这个动画执行需要的时间this.animationLength = frames.length / anim.framerate;}).methods({// 获取动画时间内指定时间段[from, to]中插入的事件getEvents: function (from, to) {var events = [];for (var e = 0; e < this.events.length; e ++) {var evt = this.events[e];if (evt.time >= from && evt.time < to) {events.push(evt.name);}}return events;},// 获取下一个动画内(指定时间段内)插入事件的触发具体时间getTimeForNextEvent: function (from, to) {var first = -1;for (var e = 0; e < this.events.length; e ++) {var evt = this.events[e];if (evt.time > from && evt.time < to) {if (first != -1) return first;first = evt.time;}}return first;},// 给定两个时间区间,如果有交集,交集中的事件push两次getEventsSlow: function (from, to, start, end, dt) {var events = [],e,evt;for (e = 0; e < this.events.length; e++) {evt = this.events[e];if (evt.time >= from && evt.time < end) {events.push(evt.name);}}for (e = 0; e < this.events.length; e ++) {evt = this.events[e];if (evt.time >= start && evt.time < to) {events.push(evt.name);}}return events;}});this.Animation = Animation;Laro.extend(this);})

/*** AnimationHandle* {Class}* 管理驱动动画*/Laro.register('.action', function (La) {var Class = La.Class || La.base.Class;var AnimationHandle = Class(function (animation, callback, mirrored) {if (animation instanceof AnimationHandle) {animation = animation.animation;}if (mirrored == undefined) {mirrored = false;}this.animation = animation;this.callback = callback == undefined ? null : callback;this.currentFrame = 0;this.time = 0;this.renderMirrored = mirrored;this.speed = 1;this.start = 0;this.end = 1;this.playTo = -1;this.loop = true;this.playing = false;}).methods({clone: function () {var clone = new AnimationHandle(this.animation, this.callback, this.renderMirrored);clone.start = this.start;clone.end = this.end;clone.time = this.time;return clone;},update: function (dt) {if (!this.playing) {this.currentFrame = Math.floor(this.time * this.animation.framerate) % this.animation.nbrOfFrames;return;}var oldTime = this.time;this.time += this.speed * dt;var halfFrame = 0.5/this.animation.framerate;var animationLength = this.animation.animationLength;// 循环if (this.loop) {if (this.speed > 0) {this.time = this.time >= animationLength * this.end ? this.start * animationLength : this.time;} else {this.time = this.time <= animationLength * this.start ? this.end * animationLength - halfFrame : this.time;}} else {var tempPlayTo; if (this.speed > 0) {tempPlayTo = this.playTo >= 0 ? this.playTo : this.end;if (this.time >= animationLength * tempPlayTo) { this.time = animationLength * tempPlayTo - halfFrame;this.playing = false;}} else {tempPlayTo = this.playTo >= 0 ? this.playTo : this.start;if (this.time <= animationLength * tempPlayTo) {this.time = animationLength * tempPlayTo + halfFrame;this.playing = false;}}}this.time = Math.max(this.time, 0);this.currentFrame = Math.floor(this.time * this.animation.framerate) % this.animation.nbrOfFrames;if (this.callback != null) {var timeToCheck = this.playing ? this.time : (this.speed > 0 ? animationLength * this.end : this.animation.animationLength * this.start);var evts;if (oldTime < timeToCheck) {evts = this.animation.getEvents(oldTime, timeToCheck);} else {evts = this.animation.getEventsSlow(oldTime, timeToCheck, animationLength * this.start, animationLength * this.end, dt);}if (evts.length >= 2) {this.time = this.animation.getTimeForNextEvent(oldTime, timeToCheck);evts = [evts[0]];}for (var e = 0; e < evts.length; e++) {this.callback(evts[e], this);}if (!this.playing) {this.callback('stopped', this);}}},draw : function (render, x, y, angle, alpha, tint) {var image = this.animation.frames[this.currentFrame];var baseX = this.renderMirrored ? x - (image.textureWidth - this.animation.pivotx) : x - this.animation.pivotx;render.drawImage(image, baseX, y - this.animation.pivoty, angle, false, alpha, tint, this.renderMirrored);},mirror: function () {this.renderMirrored = !this.renderMirrored;       },// play animation play: function (loop) {this.playTo = -1;if (loop == undefined) {loop = true;}if (this.time >= this.end * this.animation.animationLength - 0.5 / this.animation.framerate) {this.time = this.start * this.animation.animationLength;}this.loop = loop;this.playing = true;},// play 到指定时间点playToTime: function (t) {this.playTo = t;if (this.time >= this.playTo * this.animation.animationLength - 0.5 / this.animation.framerate) {this.time = this.start * this.animation.animationLength;}this.playing = true;},// play 到指定 event位置playToEvent: function (name) {for (var i = 0; i < this.animation.events.length; i ++) {var e = this.animation.events[i];if (e.name == name) {this.playToTime(e.time / this.animation.animationLength);break;}}          },// stopstop: function () {this.playing = false;   },// 返回动画初始位置rewind: function () {this.time = this.start * this.animation.animationLength;       },// 跳到指定位置gotoTime: function (time) {this.time = time * this.animation.animationLength;         },// 跳到指定事件位置gotoEvent: function (evt) {for (var i = 0; i < this.animation.events.length; i ++) {var e = this.animation.events[i];if (e.name == evt) {this.time = e.time;break;}}          },//跳到动画结束位置gotoEnd: function () {var halfFrame = 0.5 / this.animation.framerate;this.time = (this.end - halfFrame) * this.animation.animationLength;},// 指定动画播放的区间setRange: function (s, e) {this.start = s;this.end = e;var length = this.animation.animationLength;if (this.time < s * length) this.time = s * length;if (this.time > e * length) this.time = e * length;},// 播放速度setSpeed: function (s) {this.speed = s;         },// 获取用于播放的动画长度getLength: function () {return this.animation.animationLength * (this.end - this.start);         },// 获取当前位置getCurrentPosition: function () {return this.time;                    },// 是否停止isStopped: function () {return !this.playing;         },setCallback: function (cb) {this.callback = cb;            }});this.AnimationHandle = AnimationHandle;Laro.extend(this);})

按照既定的格式填写我们每一帧动画数据,动画跑起来就变得容易。 至于resource图片的拼接,和具体坐标的计算,不妨写个脚本自动生成。那么一切就变得简单了。

animation demo 1 animation demo 2 (demo 运行于现代浏览器) 

【有限状态机 FSM】

事件驱动的代码组织。以状态为标志进行进程或场景间的转换。简单的说,可以看成 switch case的进阶版。以状态为单位对每个场景进行模块化,以状态顺序为时间轴辅以事件相应进行不同状态间的切换。软件技术里很古老的编码模式,却又很经典。适用于强烈依赖事件驱动的程序或者 需按照既定顺序转换的多个场景切换。比如开场动画,或者游戏。

/*** fsm* 有限状态机*/Laro.register('.game', function (La) {var Class = La.Class || La.base.Class,SimpleState = La.SimpleState || La.game.SimpleState,BaseState = La.BaseState || La.game.BaseState;var FSM = Class(function (host, states, onStateChange){if (host == undefined) return;this.host = host;this.onStateChange = onStateChange;this.stateArray = [];// states list 中,俩个元素为一组,分别是state标识{Int,标识状态顺序},和对应的state 类for (var i = 0; i < states.length; i += 2) {var stateId = states[i],stateClass = states[i + 1];if (stateClass instanceof SimpleState) {this.stateArray[stateId] = stateClass;} else {this.stateArray[stateId] = new stateClass(host, this, stateId);}}this.currentState = FSM.kNoState;this.numSuspended = 0;this.suspendedArray = [];this.numPreloaded = 0;this.preloadedArray = [];this.numStates = this.stateArray.length;}).methods({// 进入某一个stateenter: function (startState, message) {this.setState(startState, message);},// 退出state,回到初始化状态leave: function () {this.setState(FSM.kNoState);},// 每帧状态机更新update: function (dt) {for (var i = 0; i < this.numSuspended; i ++) {this.stateArray[this.suspendedArray[i]].suspended(dt);}if (this.currentState != FSM.kNoState) {this.stateArray[this.currentState].update(dt);// update 之后再判断transitionif (this.currentState != FSM.kNoState) {this.stateArray[this.currentState].transition();}}},// 发出消息message: function (msg) {this.currentState != FSM.kNoState && this.stateArray[this.currentState].message(msg);     },// 消息挂起messageSuspended: function (msg) {for (var i = 0; i < this.numSuspended; i ++) {this.stateArray[this.suspendedArray[i]].message(msg);}                },// 改变状态,根据一个判断来决定是否改变状态进入下一个状态// 返回boolean值表示尝试改变是否成功tryChangeState: function (condition, toState, msg, reEnter, suspendedCurrent) {if (reEnter == undefined) { reEnter = true } //重进当前状态if (suspendedCurrent == undefined) { suspendedCurrent = true }if (toState == FSM.kNextState) { toState = this.currentState + 1 }if (condition && (toState != this.currentState || reEnter)) { console.log(toState)this.setState(toState, msg, suspendedCurrent);return true;}return false;},// 设置状态setState: function (state, msg, suspendedCurrent) {if (state = FSM.kNextState) {state = this.currentState + 1;}if (state == FSM.kNoState) {// 当前挂起的状态全部推出for ( ; this.numSuspended > 0; this.numSuspended --) {this.stateArray[this.suspendedArray[this.numSuspended - 1]].leave();this.stateArray[this.suspendedArray[this.numSuspended - 1]].isSuspended = false;}// 等待中的状态也全部终止for ( ; this.numPreloaded > 0; this.numPreloaded --) {this.stateArray[this.preloadedArray[this.numPreloaded - 1]].cancelPreload();}} else {if (suspendedCurrent) { // 需要挂起当前的状态this.stateArray[this.currentState].suspended();this.stateArray[this.currentState].isSuspended = true;this.suspendedArray[this.numSuspended ++] = this.currentState;} else {// 推出当前状态,进入指定状态if (this.currentState != FSM.kNoState) {this.stateArray[this.currentState].leave();}// 如果指定状态并没有挂起的话,需要把所有挂起的状态退出if (!this.stateArray[state].isSuspended) {for ( ; this.numSuspended > 0; this.numSuspended --) {this.stateArray[this.suspendedArray[this.numSuspended - 1]].leave();this.stateArray[this.suspendedArray[this.numSuspended - 1]].isSuspended = false;}}}}// 处理等待中的状态,如果不是指定状态,都取消for (var p = 0; p < this.numPreloaded; p++) {this.preloadedArray[p] != state && this.stateArray[this.preloadedArray[p]].cancelPreload();}this.numPreloaded = 0;// 从当前状态到指定状态this.onStateChange != undefined && this.onStateChange(this.currentState, state, msg);var lastState = this.currentState;this.currentState = state;if (this.currentState != FSM.kNoState) {if (this.stateArray[this.currentState].isSuspended) {// 状态转变后,如果状态是挂起的,不能是最后一个this.stateArray[this.currentState].resume(msg, lastState);this.stateArray[this.currentState].isSuspended = false;-- this.numSuspended;} else {// 进入指定状态this.stateArray[this.currentState].enter(msg, lastState);}}},// 获取当前状态getCurrentState: function () {if (this.currentState == FSM.kNoState) return null;return this.stateArray[this.currentState];},preload: function (state) {this.preloadedArray[this.numPreloaded ++] = state;      },isSuspended: function (state) {return this.stateArray[state].isSuspended;             }}).statics({kNoState : -1, // 默认初始化状态码kNextState: -2 // 默认进入下一个状态的状态码});/*** app 的 FSM* 添加一些简单的交互处理*/var AppFSM = FSM.extend().methods({draw: function (render) {// 如果状态类支持 draw,调用draw来绘制相关内容for (var i = 0; i < this.numSuspended; i ++) {this.stateArray[this.suspendedArray[i]].draw(render);}var s = this.getCurrentState();!!s && s.draw(render);},onMouse: function (x, y, left, leftPressed) {// 鼠标事件var s = this.getCurrentState();!!s && s.onMouse(x, y, left, leftPressed);}})this.FSM = FSM;this.AppFSM = AppFSM;Laro.extend(this);})

 FSM animation demo (需现代浏览器)

=============================
这一阵事情稍微有点多,写blog的时间都快没有了

转载于:https://www.cnblogs.com/hongru/archive/2011/12/04/2275682.html

逐帧动画 and 有限状态机(fsm)相关推荐

  1. Android 逐帧动画(Frame)

    Android 逐帧动画(Frame)  很好理解就是将多张图片放到一个容器里面通过控制这些图片一帧一张图片从而形成动画 使用的使用通过AnimationDrawable 加载放好的图片 然后通过调用 ...

  2. [练习]利用CSS steps 实现逐帧动画

    网页逐帧动画的实现方式 网页中的逐帧动画,大致可分为两大类的实现方式, 分别是使用JS控制,和单纯使用CSS实现,两者的优劣总体概括来说就是: JS动画可控性更强,但开销大,实现复杂. CSS动画实现 ...

  3. 逐帧动画和补间动画的使用场景(二)

    2019独角兽企业重金招聘Python工程师标准>>> 逐帧动画和补间动画的使用场景(二) 上一节我们详细的介绍了补间动画和逐帧动画的基本使用,如果你对这还不熟悉的请看这篇文章: h ...

  4. Android自定义控件:动画类---逐帧动画AnimationDrawable

    1:概述 Android动画包括View Animation(视图动画)和Property Animator(属性动画),而View Animation包括Tween Animation(补间动画)和 ...

  5. Android中实现一个简单的逐帧动画(附代码下载)

    场景 Android中的逐帧动画,就是由连续的一张张照片组成的动画. 效果 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 ...

  6. Android 高级编程【6个实战案例(附源码):刮刮卡、补间动画、逐帧动画、Fragment、RecyclerView、下拉刷新】

    目   录 刮刮卡案例[ScratchCard] 结构图 activity_main.xml MainActivity.java 运行效果图 补间动画(Tween Animation) 逐帧动画(Fr ...

  7. Android 动画(四)---逐帧动画

    1创建逐帧动画资源---文件res/drawable/animated_rocket.xml <?xml version="1.0" encoding="utf-8 ...

  8. objective-c 逐帧动画

    2019独角兽企业重金招聘Python工程师标准>>> // //  ViewController.m //  逐帧动画 // //  Created by DC017 on 15/ ...

  9. cocos2d-x之逐帧动画

    cocos2d-x之逐帧动画 在bool HelloWorld::init()中添加如下内容 //缓存,帧动画的帧的缓存 auto cache = SpriteFrameCache::getInsta ...

  10. 1.逐帧动画shader

    最近项目压力不大,抽时间看了些关于shader和游戏引擎的书籍,准备开始shader的学习. 在网上看到这位前辈(http://blog.sina.com.cn/s/articlelist_23127 ...

最新文章

  1. notepad如何新建php,notepad新手怎么使用教程
  2. matlab 极坐标作图polar
  3. 1 Strut2 Mapping to MVC
  4. opencv查找表值直方图均衡化
  5. Proj.4 升级新版本5.x和6.x
  6. 全局性事务控制如何在springboot中配置
  7. 大厂面试又崩了?这份CV资料请收好!
  8. java web 截图_如何以Java实现网页截图技术
  9. 项目:飞凌单片机boa服务器遇到问题总结
  10. 飞鸽传书从天齐庙南门出来
  11. 联合国为何 Pick 腾讯?
  12. Python入门--顺序结构,选择结构,对象的布尔值
  13. Net framework3.5本地镜像离线装
  14. 苹果手机远程服务器桌面,如何进行远程管理?如何实现苹果手机远程管理电脑?...
  15. eclipse 2020版 安装与配置完美教程
  16. 认知盈余时代,知乎是如何运营的
  17. PowerDesigner 将 CDM 转为 Oracle SQL建表语句
  18. 成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘
  19. 一个简单的圆形图片实现
  20. 白杨SEO:微信视频号怎么玩?推广、涨粉、上热门及赚钱技巧大全

热门文章

  1. 项目开发版本控制----Git
  2. 5-8 第五天 微信 JS-SDK
  3. mac系统下为emacs设置中文字体,解决乱码问题
  4. setInterval和setTImeout中的this指向问题
  5. 判断文件格式并输出文件流
  6. iOS ViewController利用代理页面传值
  7. Eclipse的一些常用的快捷键
  8. android:AIDL
  9. Newtonsoft.Json报错:未能加载文件或程序集...或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配...
  10. hadoop 入门学习系列十一----hue安装