闪屏还可以这样玩

splash-screen-prototyping-a-splash-screen-ux-design-splash-screen-best-practices_02

前言

对于多数应用来说,在进入APP的时候使用短暂的闪屏广告来吸引用户是很常见的一个场景。但随着这种模式的频繁应用,越来越多的用户会感到审美疲劳,甚至不看就跳过闪屏了。那么,是否有形式比较新颖的闪屏,来改变这个现状呢?下面开始来介绍可互动闪屏。

一.什么是可互动闪屏

可互动闪屏对于传统广告闪屏的区别就是,在之前的基础上,补充了可交互的内容形式,增加了互动性和趣味性,可充分唤起用户的好奇心,从而提升整个广告或者某个模块的点击率。
举个例子:
在手Q游戏中心中,针对FIFA足球世界新游上线之际,我们尝试设计了一个可踢球互动的广告闪屏,引导用户下载游戏,具体如下:

 

这个闪屏上线之后,数据非常可观,点击率是以往传统营销闪屏的3到4倍,进一步提升了游戏的下载转化。

 

二.关键技术点

这种可互动闪屏的形式,功能上跟目前市面上的H5小游戏很相似,但从技术实现的角度来看,在内容繁多、逻辑复杂的H5页面上增加一个小游戏框架来实现这种闪屏,是不可取的。一方面增加了文件资源大小,另一方面给页面渲染带来了更多的压力。那是不是就没有办法解决了?其实办法是有的,可以借鉴游戏框架的实现方式并进行简化。下面会围栏这个案例开始讲解。

1.设计总体互动框架

通过对多个游戏框架进行对比分析,以及接口文档研究,可以总结出以下处理模块

* 精灵图管理
* 预加载
* 物理引擎
* 动画
* 粒子效果
* 事件输入
* 声音管理
* 设备插件管理
* 基本图形绘制
* 网络模块
* ……

对于互动闪屏来说,并不需要太多的模块,经过对视频中的玩法分析,可以精简为以下模块进行开发,减少工作量。

* 精灵图
* 资源预加载
* 动画
* 事件输入
* 特效处理
* 生命周期

除了分析模块组成,还得设计一下总体流程框架图:

 游戏流程图

 

根据框架图,我们对整个互动闪屏的逻辑一目了然,可以开始编码整个互动闪屏的控制逻辑:

 

2

 

经过设计后的代码框架,大小有10kb,初步符合我们对框架简化的要求。

 

2.游戏元素设计

在这个互动闪屏中,有足球场,守门员,门框,足球,发射按钮,准心等元素。可以先设计一下这些元素的通用类属性和方法,并对其进行派生。

3
除此之外,闪屏中比较复杂的逻辑就是对足球的状态控制,涉及射击轨迹,守门员的状态变化等。

A.射击轨迹

一条射击轨迹一般会经过两个点,一个发射的起点和结束的终点。起点是固定的,关键点在于结束的终点,其实也就是准心的位置,可以获取准心的位置来确定:


Aim.prototype = {
......
        // 获取准心的位置,以及获胜的概率
        getInfo: function() {
            return {
                x: this.drawX + this.moveX + this.width / 2,
                y: this.drawY + this.height / 2
            }
        },
......
}

由于轨迹有可能是曲线的,可以通过二次贝赛尔曲线公式来计算得出。其中需要知道中间控制点的位置,把参数代入公式中就可以获得每个时间段的位置,把这些位置连起来就是这条曲线了。


//发射了,并还在运动时间内
if (this.isFired && this.t < this.duration) {
    //移动的时候根据时间缩小宽高
    this.width -= this.t * 15;
    this.height -= this.t * 15;
    //最终位置
    this.endpos[0] = this.finalpos[0] - this.width / 2;
    this.endpos[1] = this.finalpos[1] - this.height / 2;
    //发射位置
    this.startpos = [this.drawX, this.drawY];
    //计算控制点位置
    this.cp = [
        (this.startpos[0] + this.endpos[0]) / 2 - (this.startpos[1] - this.endpos[1]) * this.curveness,
        (this.startpos[1] + this.endpos[1]) / 2 - (this.endpos[0] - this.startpos[0]) * this.curveness
    ];
    //通过贝塞尔公式获得每个时间t的位置
    var x = quadraticBezier(this.startpos[0], this.cp[0], this.endpos[0], this.t);
    var y = quadraticBezier(this.startpos[1], this.cp[1], this.endpos[1], this.t);
    //时间t
    this.t += 0.2;
 
    this.drawX = Math.floor(x);
    this.drawY = Math.floor(y);
}

B.守门员状态

守门员有准备状态,守到球,守不到球的情况,可以通过雪碧图的方式,设定守门员的不同状态,并通过控制当前帧来确定守门员的状态。

4

利用canvas画图的API,恰好可以指定绘制原图片的某个区域大小,满足绘制控制精灵图不同状态的需求
相关代码如下:


Goalkeeper.prototype = {
    update: function() {
        // 预备阶段
        if (this.isReady) {
            this.srcY = this.height * this.curFrame;
            //控制准备状态的帧数
            if (this.curFrame < this.frameNum - 1) {
                this.curFrame++;
            } else {
                this.curFrame = 0;
            }
        } else {
            // 准备扑球,可进球可不进
            if (this.isKeepBall) {
                this.curFrame = 12
            } else {
                this.curFrame = 13
            }
            this.srcY = this.height * this.curFrame;
        }
    },
    draw: function(gamectx) {
        if (this.visible) {
            //绘制守门员
            gamectx.drawImage(SpriteTool.goalkeeperSprite, this.srcX, this.srcY, this.width, this.height, this.drawX, this.drawY, this.width, this.height);
        }
    },
}

C.进球逻辑判断

在用户点击发射按钮后,根据准心的位置可以判定是否进球。若是在球框外,那是肯定进不了的;若是在球框内,那有一定的进球概率。可以设定进球概率为60%,通过设置随机数的方式进行判断,代码如下:


var getRandomPercent = function(probability) {
    var probability = probability * 100 || 1;
    var odds = Math.floor(Math.random() * 100);
    if (probability === 1) { return true };
    if (odds < probability) {
        return true;
    } else {
        return false;
    }
}

确定了进球概率后,接下来就是设定守门员的状态变化了,可以根据球的位置移动,以及终点的位置后确定守门员的状态


//判断是否进球
var info = this.aim.getInfo();
// 球停止了,且被扑中了
if (this.ball.isStop && info.result == false) {

    this.keeper.catchball();
    this.keeper.keepBall(info.x);
    this.ball.hide();
    //球停止了,且进球了
} else if (this.ball.isStop && info.result) {
    this.keeper.catchball();
    this.ball.hide();
    // 进球的时候,球在门框的位置
    if (this.aim.moveX > 0) {
        this.ballstage.drawBallInRight();
    } else {
        this.ballstage.drawBallInLeft();
    }
}

3.特效应用

在互动结束后,可以看到整个闪屏以螺旋扭曲的形式缩小到新游运营位,这种炫酷的形式,其核心是应用了WebGL来动态改变图片的展现形式。
可以想象扭曲一张纸,通过确定了扭曲的中心点,扭曲的角度和扭曲的半径,就可以实现。在WebGL中,是通过这3个变量以及扭曲算法来改变图片的顶点着色器,控制螺旋特效的展现情况。


//控制变量
uniform float radius; //螺旋半径
uniform float angle;  //螺旋角度
uniform vec2 center;  //螺旋中心点
uniform vec2 texSize; //螺旋画布大小
uniform sampler2D texture; //纹理素材
varying vec2 texCoord; //坐标轴

void main() {
   //更新坐标轴xy
    vec2 coord = texCoord * texSize;
    coord -= center;
    float distance = length(coord);
    if (distance < radius) {
        float percent = (radius - distance) / radius;
        float theta = percent * percent * angle;
        float s = sin(theta);
        float c = cos(theta);
        
        coord = vec2(
            coord.x * c - coord.y * s,
            coord.x * s + coord.y * c
        );
    }
    coord += center;
    //改变顶点
    gl_FragColor = texture2D(texture, coord / texSize);
    vec2 clampedCoord = clamp(coord, vec2(0.0), texSize);
    if (coord != clampedCoord) {
        /* fade to transparent if we are outside the image */
        gl_FragColor.a *= max(0.0, 1.0 - length(coord - clampedCoord));
    }
}

这里涉及GLSL的着色器代码,不懂的同学可以阅读其他资料来了解,这里就不赘述了。

三,结尾

整体来说,借鉴其他游戏框架并输出一个简洁有力的微互动框架,一方面可以满足产品方面对互动闪屏的需求,另一方面也会后续的互动闪屏开发奠定了基础,以后面对这样的需求开发就更加省心省力了。文章如有不正确的地方,欢迎指正。

 

参考文章

http://phaser.io/docs/2.6.2/index
https://github.com/hujiulong/blog/issues/1
https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/
http://evanw.github.io/glfx.js/

发表评论