小游戏敌机爆炸效果 - 雪碧图动画效果
敌机被消灭了怎么可能没有爆炸效果呢?对吧
接下来这章节我们就来做一个爆炸效果
同样,我们也不使用官方的图片,而是使用下面这张雪碧图
- 图片地址为: https://www.twle.cn/static/i/minigame/boom.png
- 图片大小为
300x100
- 每个子弹大小为
100x100
下载这张图片,并保存到 images
目录
爆炸效果出现点不决定,一般出现于被消灭的敌机附近
所以我们随便来一个点
game.js
var c = wx.createCanvas(); var ctx = c.getContext('2d'); var boom = wx.createImage(); function reset() { ctx.save(); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height); ctx.restore(); } function draw_copy() { ctx.save(); ctx.textAlign = "center" // 居中对齐 ctx.textBaseline = "middle" //垂直居中绘制 ctx.fillStyle = "#aaa"; ctx.font = "16px Arial" // 字体大小 16 像素 ctx.fillText("简单教程,简单编程", c.width / 2, (c.height - 108)) ctx.fillText(" © 2015-2018 www.twle.cn", c.width / 2, (c.height - 128)); ctx.restore(); } boom.onload = function () { reset(ctx); ctx.drawImage(boom, 0, 0, 100, 100, c.width / 2 - 25, 100, 50, 50); draw_copy(); } reset(); boom.src = 'images/boom.png';
虽然爆炸图片大小为 100x100
,但我们把它绘制小一点,不然爆炸效果会闪到其它的敌机
运行效果如下
让爆炸动画显示出来
我们每 300ms
更新一次图片,更新图片很简单就是简单的移动 drawImage()
中的截图位置
game.js
var c = wx.createCanvas(); var ctx = c.getContext('2d'); var boom = wx.createImage(); function reset() { ctx.save(); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height); ctx.restore(); } function draw_copy() { ctx.save(); ctx.textAlign = "center" // 居中对齐 ctx.textBaseline = "middle" //垂直居中绘制 ctx.fillStyle = "#aaa"; ctx.font = "16px Arial" // 字体大小 16 像素 ctx.fillText("简单教程,简单编程", c.width / 2, (c.height - 108)) ctx.fillText(" © 2015-2018 www.twle.cn", c.width / 2, (c.height - 128)); ctx.restore(); } var i = 0; function draw_boom() { reset(ctx); ctx.drawImage(boom, i * 100, 0, 100, 100, c.width / 2 - 25, 100, 50, 50); i++; if (i == 3) i = 0 } boom.onload = function () { draw_boom(); setInterval(function () { draw_boom(); }, 200); draw_copy(); } reset(); boom.src = 'images/boom.png'
运行演示如下
对爆炸效果进行封装
首先,爆炸效果的点是不会移动的,当显示完最后一张图片,爆炸效果就要消失
function Boom() { // 位置 this.x = 0; this.y = 0; //宽高 this.w = 50; this.h = 50; // 爆炸程度 this.speed = 0; // 图片 this.img = null; this.ctx = null; this.img_size = 3; // 类型 this.kind = 1; // 原图位置 this.rx = 0; this.ry = 0; this.rw = 100; this.rh = 100; this.change_kind = function () { this.kind++; this.rx = (this.kind - 1) * this.rw; this.ry = 0; } this.move = function () { this.speed++; if (this.speed % 20 == 0) this.change_kind(); }; this.draw = function () { this.ctx.drawImage(this.img, this.rx, this.ry, this.rw, this.rh, this.x - this.w / 2, this.y - this.h / 2, this.w, this.h); } this.hit = function () { return this.kind == this.img_size; } }
对这个封装是不是很熟悉,对的,其实所有的图形动画都是大同小异
我们写一个范例演示下封装后的效果
game.js
function Boom() { // 位置 this.x = 0; this.y = 0; //宽高 this.w = 50; this.h = 50; // 爆炸程度 this.speed = 0; // 图片 this.img = null; this.ctx = null; this.img_size = 3; // 类型 this.kind = 1; // 原图位置 this.rx = 0; this.ry = 0; this.rw = 100; this.rh = 100; this.change_kind = function () { this.kind++; this.rx = (this.kind - 1) * this.rw; this.ry = 0; } this.move = function () { this.speed++; if (this.speed % 20 == 0) this.change_kind(); }; this.draw = function () { this.ctx.drawImage(this.img, this.rx, this.ry, this.rw, this.rh, this.x - this.w / 2, this.y - this.h / 2, this.w, this.h); } this.hit = function () { return this.kind == this.img_size; } } var c = wx.createCanvas(); var ctx = c.getContext('2d'); var buckets = []; function reset() { ctx.save(); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height); ctx.restore(); } function draw_copy() { ctx.save(); ctx.textAlign = "center" // 居中对齐 ctx.textBaseline = "middle" //垂直居中绘制 ctx.fillStyle = "#aaa"; ctx.font = "16px Arial" // 字体大小 16 像素 ctx.fillText("简单教程,简单编程", c.width / 2, (c.height - 108)) ctx.fillText(" © 2015-2018 www.twle.cn", c.width / 2, (c.height - 128)); ctx.restore(); } function animate_it() { reset(ctx); for ( var i in buckets ) { buckets[i].move(); if (buckets[i].hit()) { buckets.splice(i,1); continue; } buckets[i].draw(ctx); } draw_copy(); requestAnimationFrame(animate_it); } function create_boom(img) { var boom = new Boom(); boom.x = Math.random() * c.width; boom.y = Math.random() * c.height; boom.ctx = ctx; boom.img = img; buckets.push(boom); } var boom = wx.createImage(); boom.onload = function () { create_boom(boom); requestAnimationFrame(animate_it); setInterval(function () { create_boom(boom); }, 1000); } reset(); boom.src = 'images/boom.png'
运行演示如下
把爆炸效果加到被命中的飞机上
我们把刚刚爆炸的效果加到被命中的飞机上
这里有一个要点,如果飞机被命中,那么这个飞机就要被销毁,然后用一个新的爆炸效果代替原来的位置
对的,就是这么简单
game.js
var inherits = function (superCtor) { var f = function () { superCtor.call(this); } f.super_ = superCtor; f.prototype = Object.create(superCtor.prototype, { constructor: { value: f, enumerable: false, writable: true, configurable: true } }); return f; }; var Resource = { _res: [], // [][] 资源对象数组, 0 表示资源 1 url 2 类型 3 是否加载完成 _load: function (idx, ops) { var res = this._res[idx]; if (!res[3]) { if (res[2] == "audio") this._load_audio(res[1], idx, ops) if (res[2] == "image") this._load_image(res[1], idx, ops); } }, _load_image: function (src, i, ops) { var img = wx.createImage(); var that = this; that._res[i][0] = img; img.onload = function () { that._res[i][3] = true; } img.src = src; }, _load_audio: function (src, i, ops) { var audio = wx.createInnerAudioContext(); if (ops != null) { for (var k in ops) { audio[k] = ops[k]; } } var that = this; that._res[i][0] = audio; audio.onCanplay(function () { that._res[i][3] = true; }); audio.src = src; }, loaded: function (callback) { for (var i = 0; i < this._res.length; i++) { if (!this._res[i][3]) { var that = this; setTimeout(function () { that.loaded(callback); }, 200); return; } } callback(); }, add: function (src, type, id, ops) { this._res.push([null, src, type, false, id]); this._load(this._res.length - 1, ops); }, item: function (src) { for (var i = 0; i < this._res.length; i++) { if (this._res[i][1] == src) { return this._res[i][0]; } } return null; } } function Sprite() { // 位置 this.x = 0; this.y = 0; //宽高 this.w = 0; this.h = 0; // 飞行速度 this.speed = 0; // 图片 this.img = null; // 类型 this.kind = 0; // 原图位置 this.rx = 0; this.ry = 0; this.rw = 0; this.rh = 0; this.ctx = null; this.change_kind = function (k) { this.rx = (k - 1) * this.rw; this.ry = 0; } this.move = function () { this.y += this.speed; }; this.draw = function () { this.ctx.drawImage(this.img, this.rx, this.ry, this.rw, this.rh, this.x - this.w / 2, this.y - this.h / 2, this.w, this.h); } } var Bucket = { _bucket: {}, move: function () { for (var i in this._bucket) { for (var j in this._bucket[i]) { this._bucket[i][j].move(); if (this._bucket[i][j].hit()) { this._bucket[i].splice(j, 1); } } } }, add: function (obj, bucket) { if (!(bucket in this._bucket)) { this._bucket[bucket] = []; } this._bucket[bucket].push(obj); }, draw: function (ctx) { for (var i in this._bucket) { for (var j in this._bucket[i]) { this._bucket[i][j].draw(); } } }, enemy_hit: function () { var enemys = this._bucket['enemy']; var bullets = this._bucket['bullet']; for (var i in enemys) { var enemy = enemys[i]; for (var j in bullets) { var bullet = bullets[j]; var r = enemy.w / 2; var x = (bullet.x - enemy.x); var y = (bullet.y - enemy.y); // 因为子弹比敌机要小,所以直接判断子弹是否和敌机相交即可 if (x * x + y * y <= r * r) { // 创建爆炸效果 create_boom(enemy.x,enemy.y); // 移除子弹 this._bucket['bullet'].splice(j, 1); // 移除飞机 this._bucket['enemy'].splice(i, 1); } } } } } function Sprite() { // 位置 this.x = 0; this.y = 0; //宽高 this.w = 0; this.h = 0; // 飞行速度 this.speed = 0; // 图片 this.img = null; // 类型 this.kind = 0; // 原图位置 this.rx = 0; this.ry = 0; this.rw = 0; this.rh = 0; this.ctx = null; this.draw = function () { this.ctx.drawImage(this.img, this.rx, this.ry, this.rw, this.rh, this.x - this.w / 2, this.y - this.h / 2, this.w, this.h); } } var Bullet = inherits(Sprite); Bullet.prototype.hit = function () { return this.y <= 0; } Bullet.prototype.move = function () { this.y += this.speed; }; var Enemy = inherits(Sprite); Enemy.prototype.hit = function () { return this.y >= screenHeight; } Enemy.prototype.move = function () { this.y += this.speed; }; var Boom = inherits(Sprite); Boom.prototype.img_size = 3; Boom.prototype.change_kind = function () { this.kind++; this.rx = (this.kind - 1) * this.rw; this.ry = 0; } Boom.prototype.move = function () { this.speed++; if (this.speed % 20 == 0) this.change_kind(); }; Boom.prototype.hit = function () { return this.kind == this.img_size; } var c = wx.createCanvas(); var ctx = c.getContext('2d'); var img = wx.createImage(); var plane = wx.createImage(); var bgCanvas = null; var offset = 0; var w = 0; var h = 0; var screenWidth = c.width; var screenHeight = c.height; // 上一次飞机的位置 var lasted = { x: 0, y: 0 }; // 触摸区域是否在飞机内部 var in_plane = false; // 创建计时器 var t = 0; function init_bg() { bgCanvas = wx.createCanvas(); bgCanvas.width = screenWidth; bgCanvas.height = screenHeight + h; var bgctx = bgCanvas.getContext('2d'); var y = 0; var x = 0; // 底部多绘制一张图片 while (y < screenHeight + h) { x = 0; while (x < screenWidth) { bgctx.drawImage(img, x, y, w, h); x += w; } y += h; } } function reset() { ctx.save(); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height); ctx.restore(); } function draw_copy() { ctx.save(); ctx.textAlign = "center" // 居中对齐 ctx.textBaseline = "middle" //垂直居中绘制 ctx.fillStyle = "#aaa"; ctx.font = "16px Arial" // 字体大小 16 像素 ctx.fillText("简单教程,简单编程", c.width / 2, (c.height - 36)) ctx.fillText(" © 2015-2018 www.twle.cn", c.width / 2, (c.height - 18)); ctx.restore(); } function animate_it() { reset(ctx); ctx.drawImage(bgCanvas, 0, offset, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight); Bucket.move(); Bucket.enemy_hit(); Bucket.draw(ctx); ctx.drawImage(plane, 0, 0, 128, 128, lasted.x - 64, lasted.y - 64, 128, 128); draw_copy(); timeit(); offset; if (offset % h == 0) { offset = 0; } requestAnimationFrame(animate_it); } wx.onShow(function () { Resource.item('audio/bgm.mp3').play(); }) wx.onAudioInterruptionBegin(function () { // 暂停音乐 Resource.item('audio/bgm.mp3').stop(); // 中断动画 //cancelAnimationFrame(animate_it); }) wx.onAudioInterruptionEnd(function () { Resource.item('audio/bgm.mp3').play() // 恢复动画 cancelAnimationFrame(animate_it); }) wx.onTouchStart(function (e) { var x = e.touches[0].clientX; var y = e.touches[0].clientY; if (plane_hit(x, y)) { in_plane = true; } }); wx.onTouchEnd(function (e) { if (in_plane) { lasted.x = e.touches[0].clientX; lasted.y = e.touches[0].clientY; in_plane = false; } }); // 触摸移动事件 wx.onTouchMove(function (e) { if (in_plane) { lasted.x = e.touches[0].clientX; lasted.y = e.touches[0].clientY; } }) function plane_hit(x, y) { return x > (lasted.x - 64) && x < (lasted.x + 64) && y > (lasted.y - 64) && y < (lasted.y + 64); } function start() { w = screenWidth > img.width ? img.width : screenWidth; h = img.height * (w / img.width); lasted.x = (screenWidth - 128) / 2; lasted.y = screenHeight - 128; Resource.item('audio/bgm.mp3').play(); init_bg(); ctx.drawImage(plane, 0, 0, 128, 128, lasted.x, lasted.y, 128, 128); draw_copy(); requestAnimationFrame(animate_it); } function create_bullet(img) { var bullet = new Bullet(); bullet.x = lasted.x; bullet.y = lasted.y; bullet.img = img; bullet.w = 30; bullet.h = 30; bullet.speed = - 2; bullet.kind = 1; bullet.rw = 60; bullet.rh = 60; bullet.ctx = ctx; Bucket.add(bullet, 'bullet'); } function create_enemy(img) { var enemy = new Enemy(); enemy.w = 50; enemy.h = 50; enemy.x = (enemy.w / 2) + Math.random() * (screenWidth - enemy.w / 2); enemy.y = -50; enemy.img = img; enemy.speed = 2; enemy.kind = 1; enemy.rw = 100; enemy.rh = 100; enemy.ctx = ctx; Bucket.add(enemy, 'enemy'); } function create_boom(x, y) { var boom = new Boom(); boom.w = 50; boom.h = 50; boom.x = x; boom.y = y; boom.img = Resource.item('images/boom.png'); boom.speed = 0; boom.kind = 1; boom.rw = 100; boom.rh = 100; boom.ctx = ctx; Bucket.add(boom, 'boom'); } function timeit() { if (t % 30 == 0) { var enemy = Resource.item('images/enemy.png'); create_enemy(enemy); } if (in_plane && t % 20 == 0) { var bullet = Resource.item('images/bullet.png'); create_bullet(bullet); } t++; } reset(); Resource.add('images/bg.jpg', 'image'); Resource.add('audio/bgm.mp3', 'audio', null, { loop: true }); Resource.add('images/plane.png', 'image'); Resource.add('images/bullet.png', 'image'); Resource.add('images/enemy.png', 'image'); Resource.add('images/boom.png', 'image'); Resource.loaded(function () { img = Resource.item('images/bg.jpg'); plane = Resource.item('images/plane.png'); start(); });
运行演示如下