动态条漫实战

2,649 阅读5分钟

前言

不知道你有没有见过这种

或者

以上两案例来自于网易哒哒团队开发的爆款H5

这种条漫形式的H5是不是带给大家眼前一亮的感觉,这篇文章主要带大家揭秘如何制作这种动态条漫

大家可以扫码体验条漫:

框架选型

在进入内容介绍前,先来介绍一下框架选型,为什么选择了Egret引擎来做动态条漫

why Egret

从上面的一张主流游戏框架引擎对照表,可以很明显的找到Egret相对于其他游戏动画引擎的优点:

  • 渲染模式上更加广泛
  • 支持中英文档,案例充足
  • 周边产品丰富,提供一套完整的开发工具
  • 社区活跃

工具流

动画基础知识回顾

在Egret中动画主要分为三类:

  • 逐帧动画
    • 在时间帧上逐帧绘制帧内容,每一帧带有不同的图
    • 适用于表演很细腻的动画
    • 有非常大的灵活性,表现任何想表现的内容
    • 逐帧动画输出文件较大
  • 补间动画
    • 只需第一个关键帧和最后一个关键帧创建内容,两个关键帧之间帧的内容自动生成
    • 过渡更为自然连贯
    • 补间动画输出文件较小
  • 龙骨动画
    • 模拟骨骼运动的机制而制作的动画
    • 实现原理与补间动画一样,添加初始关键帧,结束关键帧
    • 增加了骨骼约束

在Egret中创建逐帧动画和补间

在Egret周边工具中通过Egret DragonBones生成动画

制作逐帧动画

步骤:

  • 打开DragonBones工具,点击创建项目
  • 选择逐帧动画,点击创建
  • 资源面板,导入逐帧资源,将图片拖入画布中
  • 调整图片顺序,帧频

制作补间动画

步骤:

  • 打开DragonBones工具,点击创建项目
  • 选择补间动画,点击创建
  • 资源面板,导入逐帧资源,将图片拖入画布中
  • 在指定帧,选择图片位置,结束帧,图片到结束位置

动态条漫实现思路

核心思路

一图胜千文

动画原理

  • 大部分动画都是,补间动画+逐帧动画的混合
  • 所有动画都放在时间轴上按照先按照滑动的顺序移动变化。
  • 只需要通过用户滑动,改变动画播放的帧数即可
  • 连续不断的滑动即可,实现条漫形式的动画

看完上面的动画分析,下面我们注重要解决的便是:

  • 如何控制动画按照我们预期的形式播放
  • 在动画播放到指定帧时,播放音效
  • 如何实现惯性滚动

滑动控制

原理

  • 在动画场景上盖上蒙层
  • 监听蒙层滚动事件,蒙层滚动的距离来切换动画的帧数

对应Api

核心步骤:

  • 获取龙骨资源、并创建dragonBones实例对象。将龙骨场景对象添加至画布
  • 构建一个滚动容器,容器的高度为视窗大小,设置宽度设置为视窗宽度
  • 在滚动容器中内增加蒙层,蒙层的高度=总的帧数 * 滚动一帧的距离 * 时间缩放 + 视窗高度
  • 创建eui.scroller对象,将其设置为视窗大小,设置滚动容器对象为eui.scroller的视域组件组件
  • 使用eui.scroller监听滚动高度,计算当前滚动条占总可滚动高度的百分比=已滚动高度/(蒙层高度-视窗高度)
  • 获取dragonBones场景一共拥有多少帧,通过总帧数*当前百分比,得到用户滑动到当前所在帧数

具体实现代码:

private timeScale: number = 2;  // 时间缩放倍数,为2时表明帧数切换放慢两倍
private frameFactor: number = 26; // 滚动一帧需要耗费的距离
private totalFrames: number = 1672; // 总得帧数
private totalPrgress: number = this.timeScale * this.frameFactor * this.totalFrames; // 总的滚动长度
private dragonBones: Common.DragonParse; // 获取动画
private music: Common.Music;
private audioKeys: audioKey[] = [{
    start: 98,
    end: 138,
    key: 'audio_sewing_mp3', // 缝纫机
    isPlayed: false,
    shouldStop: true,
    isLoop: false,
    volume: 1
}];

constructor () {
    // 龙骨脚手架中已添加COMMON公共方法,用来解析龙骨资源
    this.dragonBones = Common.DragonParse.getDragonParseInstance();
    this.egretFactory = this.dragonBones.getEgretFactory();
    
    // 获取龙骨资源
    this.armatureDisplay = this.egretFactory.buildArmatureDisplay('listen_mother');
    this.addView();
}

private addView () :void {
   // 获取视窗宽、高
   const { stageWidth, stageHeight } = this.stage;
   
   // 创建滚动容器和填充块
   const group = new eui.Group();
   const placeHolder = new eui.Group();

   placeHolder.width = stageWidth;
   placeHolder.height = this.totalPrgress + stageHeight;
   group.addChild(placeHolder);

   //创建一个Scroller
   this.scroller = new eui.Scroller();
   this.scroller.bounces = false;
   this.scroller.width = stageWidth;
   this.scroller.height = stageHeight;
   // 将group作为滚动的视域组件
   this.scroller.viewport = group;
   this.addChild(this.scroller);
   
   // 监听滚动变化
    this.scroller.addEventListener(egret.Event.CHANGE, this.onScroll, this);
    
}

private onScroll () :void {
   // 获取滚动距离,并计算滚动百分比
   const scrollV: number = this.scroller.viewport.scrollV;
   const progress: number = scrollV / this.totalPrgress;
   let curRateValue = ~~(this.totalFrames * progress);

   this.setSwipeAndButton(curRateValue);
   this.prevFrames = curRateValue;
   
   // 将设置龙骨动画播放到指定帧数
   this.setProgress(progress);
   // 播放指定音效
   this.setMusic(progress)
}

private setProgress (progress: number) :void {
   this.armatureDisplay.animation.gotoAndStopByProgress(this.animationName, progress)
}

private setMusic(curRateValue: number): void {
    this.audioKeys.forEach((item) => {
        if (curRateValue >= item.start && curRateValue <= item.end) {
            if (!item.isPlayed && !this.musicIsMuted) {
                this.music = Common.Music.getMusic(item.key);
                this.music.play(0, item.isLoop ? 0 : 1);
                this.music.setVolumn(item.volume);
                item.isPlayed = true;
            }
        } else {
            if (item.isPlayed) {
                item.shouldStop && this.music.stop();
                item.isPlayed = false;
            }
        }
    });
}

一屏动画设计原则

  • 构建一个基本场景骨架,后续骨架放置基本骨架上,方便整体移动
  • 物件退场需要与背景反方向运动,造成错落感。或顺方向退场,避免导致认为物品或人与背景不分离
  • 龙骨动画设计时以最小十帧为单位,场景转换预留30~50帧,后续方便调整
  • 帧动画设计图片以至少三帧不同动画,可以造成连续感
  • 开始龙骨动画制作前,先确定好场景高度和宽度不会有大的变化,否则后续修改会造成整体调整
  • 文字动画放置物品后
  • 动画效果放置980像素内,否则在小屏幕手机上无法展示全面
  • 帧动画切换中间过度衔接透明度变化

如果觉得本文还不错,不要吝啬您的赞哦~