记一次游戏H5开发经验

快到年终的时候作了一个以游戏形式展现的h5活动页,第一次尝试使用js写小游戏,颇有趣的过程,很宝贵的经验。css

效果图

直接上个效果的gif图,游戏的一小部分效果,录出来有点卡
图片描述canvas

结果页:
图片描述微信

原由

产品妹子忽然给我拉进来一个群,跟咱们讲作了这么久的制做平台(用户制做手机主题的平台),咱们是否是应该反馈给用户点什么东西,就像以前特别火的微信年终总结那样。总之就是要打动用户,要特别酷。说特别酷的时候她回头朝我微微一笑,微笑中带着一点点,嗯,杀意。
活动形式,展示方式,什么数据反正就是通通都没想好,整个过程当中你们讨论的热火朝天。当时不知道我为啥脑子一热,跟她说了一句:“没事儿,搞吧,你能想出来我就给你作出来。”而我也由于这句话把本身置身于水深火热之中。。
讨论的结果就是你们的idea感受都不是特别酷,又很差玩儿,干脆就作个游戏形式的吧!全部人都转头看向我,我想了想以前说的话,只吐出来一个字,“搞”。而心里中五味杂陈,“游戏?有意思啊?搞!没搞过啊?能搞定吗?搞!”。最终敲定,两周时间,游戏方式,展示用户在魔秀的点点滴滴。架构

准备工做

游戏的形式大概相似一个滑雪大冒险和赛车的结合,以赛车的形式进行伪3d效果的展示,滑雪大冒险的样式做为咱们的主题,同时你们还给咱们的游戏起了个酷炫的名字----魔秀时光道。app

游戏引擎

游戏的展示形式肯定后,直觉告诉我,想要将游戏快速稳定的呈现,免去图片加载控制,动画控制之类的复杂处理,我须要一个JS游戏引擎。最终在EgretPhaserPixiJS中选定了PixiJS,虽然不像Egret同样有完善的中文文档,可是它提供了清晰易懂的examples可快速上手,没有复杂的生态,简单的几行代码就能够用js实现我想要如下几点功能:ide

容器渲染及背景描绘

我须要定制整个画布的大小和背景,我须要使用不一样的容器来承接不一样的内容,而且灵活控制每一个容器的属性:函数

//画布
var app = new PIXI.Application(800, 600, {backgroundColor : 0x1099bb});
document.body.appendChild(app.view);

//定制container
var container = new PIXI.Container();

container.x = (app.renderer.width - container.width) / 2;
container.y = (app.renderer.height - container.height) / 2;

app.stage.addChild(container);

图片加载及动画处理

你们都知道,使用canvas进行图片绘制的时候,须要肯定图片已经成功加载,而游戏中有着大量的图片资源须要去维护,PixiJS已经为咱们提供此项服务:工具

var bunny = PIXI.Sprite.fromImage('required/assets/basics/bunny.png’);

bunny.x = app.renderer.width / 2;
bunny.y = app.renderer.height / 2;

app.stage.addChild(bunny);

同时,咱们须要一个动画控制器,来控制各Sprite的运动和重绘,而不是生硬的对各项属性进行从新修改:学习

app.ticker.add(function(delta) {
  bunny.rotation += 0.1 * delta;
});

须要注意的是,咱们会发现,此处的Sprite动画控制,至关于添加了运动的动画队列,而且实现了相似transformjs的效果,可直接对实例的属性进行操做。而我在写项目的时候官方的例子是经过统一animate函数进行操做,经过requestAnimationFrame进行帧动画控制,更推荐新的方式而不是以下:动画

function animate () {
  requestAnimationFrame(animate);
  
  bunny.rotation += 1;
  renderer.render(stage);
}

事件处理

游戏最重要的部分至关于用户的交互了,也就是所谓的事件处理,为Sprite添加事件监听,很简单,以下所示:

//元素可点击
sprite.interactive = true;

//鼠标移入cursor
sprite.buttonMode = true;

sprite.on('click', onClick); // mouse-only
prite.on('tap', onClick); // touch-only

function onClick () {
  sprite.scale.x *= 1.25;
  sprite.scale.y *= 1.25;
}

设计图

设计图固然也是很重要的,决定了咱们如何去实现这个游戏,当我拿到设计图的时候,他是长成这样的,个人心里是崩溃的。我能怎么样,我也很无奈呀~ 开搞吧!
图片描述

实现思路

根据以上,PixiJS已经基本知足咱们的需求,也就是说,工具准备和素材准备已经都完成了。在动手书写以前,咱们须要把实现思路想好,才能保证书写过程的清晰,避免没必要要的麻烦。

背景滑动效果实现

就像咱们平时玩儿赛车游戏同样,咱们感受赛车在跑道上进行比赛,实际上赛车只进行左右移动而已,而运动的则是背景,如何规划好路线,让背景按照既定的场景去运动,并展示不一样的视角,特地向央美的同胞咨询了下,他们是用一个叫“摄像机”的东西实现的。对于咱们来讲,不须要那么复杂的场景,只需让背景像前规律的“平移”,形成“树动我不动”的视觉效果,同时咱们利用“透视”的原理,让背景以“近大远小”的方式进行变化,就会产生一种low low的立体效果。

关键词:透视 近大远小(偏移,大小,速度)

偏移路线

对于背景及物体的运动,大概路线规划以下:
图片描述

肯定视觉焦点后,咱们只需随机生成物体出现的位置,计算出a,b相对固定,使其y进行相应速度的增长,x根据运动轨迹进行对应偏移,则可实现往近跑的效果。针对运动轨迹, 假设物体向下偏移距离为N,则对应水平针对中轴线的偏移为:
图片描述

大小

同时,咱们还需对物体进行近大远小的显示,这个比较简单,以焦点为0,页面最底端为1,进行对应比例放大便可:

scale = (curY - startY) / ( endY - startY);

运动速度

针对物体的运动速度,也应在远近有不一样的体现。

背景树与碰撞物体的区别

针对背景树,咱们需在最初对全部的树进行展示,铺满两边背景。每列树对应的运动路线一致,可直接让其进行循环展现,当树运动到最底时,让其出如今最顶点。所以只需肯定一共有几行几列树,并设定其边界,根据行列肯定初始惟一并对其进行运动。同时,可让树进行小范围的随机偏移,使树错落有致。以下所示:

export default function Tree (row, col, direction) {
  this.cfg = {
    direction: direction, //方向
    col: col, //第几列
    row: row, //第几行
    MaxX: 440,
    minY: 210,
    maxY: 500,
    range: 10 //坐标浮动范围
  }
};

而针对物体,则须要随机生成它的初始x坐标,并计算出其对于的路线进行运动,在运动过程当中,进行碰撞检测,检测是否与人物进行相撞。

人物滑动实现

人物滑动的操做,用了最简单的实现方式:按钮。当用户点击不一样方向时,让人物向对应方向进行偏移。同时,为了让人物滑动不僵硬,在左右滑动过程当中,人也应该随着运动有对应角度的倾斜,就像咱们平时玩儿滑雪拐弯时,会改变中心同样。思路以下:

  1. 点击按钮时,改变方向

  2. 运动时检测方向,若向左,则x减少,向右,则增长

  3. 向右(左)运动时,人物对应rotation也进行增长(减少)

  4. 松开手时,人物对应rotation慢慢恢复成0;

碰撞检测

因为人物有吃东西的环节(否则这还叫什么游戏呢),所以碰撞检测确定是必须的啦。咱们能够经过两种方式进行碰撞检测

  1. 人物检测碰撞物体,需实时遍历物体坐标列表,进行检测

  2. 每一个物体自身进行碰撞检测,检测自身与人物位置的对应差

我很机智的选择了第二个,毕竟每一个物体的位置都是实时变更的,而每次碰撞检测都进行一次循环的方法,太笨重啦。在这里咱们设置碰撞检测的区域(宽高),在物体运动时,针对人物的x,y坐标,与自身的x,y坐标加减造成的四条边界进行比较便可,若进行碰撞,则进行对应的操做便可,如播放音频,得分+1等。

架构设计

思路理清楚以后,后面的路就很明朗啦。接下来咱们就能够着手设计下如何实现这个东西了,很显然,游戏中咱们拥有许许多多的“角色”,使用“面向对象”的方式再好不过了。大概的划分以下

  • stage //舞台,进行基本场景渲染,游戏总体控制(开始,中止)等

  • player //玩儿家,也就是对应的人物

  • sprite //出现的物体,如蛋糕等,提供玩儿家吃。 包含碰撞检测等,会本身运动

  • tree //由于tree自身会运动,因此每一个tree为一个类

  • score //进行分数控制及显示

  • cfg.js //包含总体游戏配置

对象内部划分

每一个对象包含如下几点属性及功能:

1. 对象配置

每一个对象包含其内部自身基本配置,包括位置,边界,图片等。直观,便于调试

export default function People (stage) {
  this.cfg = {
    img: require('./img/people.png'),
    anchor: {
      x: 0.5,
      y: 0.5,
    },
    position: {
      x: cfg.width / 2,
      y: 500
    },
    speed: 5,
  }
}

2. 其余方法

每一个对象都包含其自身方法,以下所示:

  • render //进行图片等渲染

  • animate //动画function

  • init //一些初始化配置

实现

经过以上思路的设计和结构的设计,我很快的将这个游戏实现了。。。没错,理清思路和结构的重要性就是这样。固然,在实现过程当中,也有一些小的点能够记录下:

资源加载器(图片)

为了游戏的进行效果,仍是决定在加载完全部资源(尤为是图片资源)后,才中止loading页面。如何判断全部内容都加载完毕了呢?写了个小loader

var pics = [
  require('./img/bg-start.png'),
  require('./img/btn-start.png'),
  ...
];

function loadImages (pics, callback) {
  if(pics.length) {
    var img = new Image(),
      pic = pics.shift();

    img.onload = callback;
    img.src = pic;
    loadImages(pics, callback);
  } else{
    return;
  }
}

$(function() {
  loadImages(pics, function () {
    if (!pics.length) {
      $('.loading').hide();
    };
  })
});

强制横屏

游戏是横屏展现的,那就强制横屏好啦。这个当时还纠结挺久,仍是本身功底不扎实脑子走私了,还在想是监听resize事件仍是旋转屏幕事件,都没有这些事儿啊好吗!直接让它旋转就好。

if(window.orientation==180||window.orientation==0) {
  $('#main').height(winW);
  $('#main').width(winH);

  $(‘#main’).css({
    'transform': 'rotate(90deg)',
  });
} else{
  $(‘#main’).css('transform', 'rotate(0)');
}

timer控制

理清思路后,最乱的仍是各类定时器啦。 为了实现物体随机出现的效果,让每一个物体随机多少秒后开始出现;最后一个物体出现完,多少秒后出现结束画面等等,须要理清楚各个定时器的关系,并对其添加语义化良好的标记,及时对未完结的定时器进行清除,防止定时器带来的意想不到的问题。

写在最后

最终游戏的效果基本让你们满意啦,也是第一次尝试这方面的开发,周围也彻底没有作过这东西的人。从开始的忐忑和一无所措,到过程当中理清思路和结构,到书写中的各类未知的坑,本身在这两周感受经历了很充实的一件事情。同时也对后续进行一些未知事物的探索和学习有了更丰富的经验,找对路子才是王道呀!

相关文章
相关标签/搜索