I saw a masturbation game in Donnet’s DEMO before, and then I took down its pictures and audio. . . . I re-wrote it just for fun. Just for entertainment. . . . . . I don't use a framework, I write all the js myself. . . . . . So this is a simple tutorial. It may be helpful to those who are new to canvas. The author has not been playing canvas for a long time and his skills are not very good. Please forgive me.
Without further ado, let’s start with the DEMO: Airplane Game. The original poster wrote this just for fun, and didn’t intend to write it into a serious game.
Let’s get into the topic: The masturbation game file includes the index.html entry file, the logic processing file of allSprite.js sprite, the loading.js loading processing file and data.js (some initialized data).
First of all, normal games basically require a loading. The loading page is used to preload data, including sprite sheet images, audio, etc. Because this is a small game, only some audio and images need to be loaded. The loading code inside is mainly the following. The others are for making loading animations. The one is relatively simple, so I won’t post it. If you are interested, just look at the console in the DEMO:
XML/HTML CodeCopy content to clipboard
- loadImg:function(datas){
-
var _this = this;
-
var dataIndex = 0;
- li();
- function li(){
-
if(datas[dataIndex].indexOf("mp3")>=0){
-
var audio = document.createElement("audio");
- document.body.appendChild(audio);
-
audio.preload = "auto";
-
audio.src = datas[dataIndex];
-
audio.oncanplaythrough = function(){
-
this.oncanplaythrough = null;
- dataIndex ;
-
if(dataIndex===datas.length){
-
_this.percent = 100;
- }else {
-
_this.percent = parseInt(dataIndex/datas.length*100);
- li.call(_this);
- }
- }
- }else {
- preLoadImg(datas[dataIndex] , function(){
- dataIndex ;
-
if(dataIndex===datas.length){
-
_this.percent = 100;
- } else {
-
_this.percent = parseInt(dataIndex/datas.length*100);
- li.call(_this);
- }
- })
- }
- }
- },
-
- //再贴出preLoadImg的方法
- function preLoadImg(src , callback){
-
var img = new Image();
-
img.src = src;
- if(img.complete){
- callback.call(img);
- }else {
-
img.onload = function(){
- callback.call(img);
- }
- }
-
}
I first use an array to save the links to the files in data.js, and then determine whether these links are pictures or audios. If they are pictures, use preLoadImg to load them. The code for preloading pictures is very simple, just create a new picture. Object, then assign the link to it, and call back after loading. Audio is loaded by generating an HTML5 audio dom object and assigning the link to it. Audio has an event "canplaythrough". When the browser expects to be able to continue playing the specified audio/video without stopping for buffering, The canplaythrough event will occur, which means that when canplaythrough is called, the audio has almost been loaded and the next audio can be loaded. Just like this, after everything is loaded, the callback is made and the game starts.
The game started. A game will require many objects, so I unified them into one sprite object. The movement of each frame between different objects can be written separately using behavior.
XML/HTML CodeCopy content to clipboard
- W.Sprite = function(name , painter , behaviors , args){
-
if(name !== undefined) this.name = name;
-
if(painter !== undefined) this.painter = painter;
-
this.top = 0;
-
this.left = 0;
-
this.width = 0;
-
this.height = 0;
-
this.velocityX = 3;
-
this.velocityY = 2;
-
this.visible = true;
-
this.animating = false;
-
this.behaviors = behaviors;
-
this.rotateAngle = 0;
-
this.blood = 50;
-
this.fullBlood = 50;
-
if(name==="plan"){
-
this.rotateSpeed = 0.05;
-
this.rotateLeft = false;
-
this.rotateRight = false;
-
this.fire = false;
-
this.firePerFrame = 10;
-
this.fireLevel = 1;
-
}else if(name==="star"){
-
this.width = Math.random()*2;
-
this.speed = 1*this.width/2;
-
this.lightLength = 5;
-
this.cacheCanvas = document.createElement("canvas");
-
thisthis.cacheCtx = this.cacheCanvas.getContext('2d');
-
thisthis.cacheCanvas.width = this.width this.lightLength*2;
-
thisthis.cacheCanvas.height = this.width this.lightLength*2;
- this.painter.cache(this);
-
}else if(name==="badPlan"){
-
this.badKind = 1;
-
this.speed = 2;
-
this.rotateAngle = Math.PI;
-
}else if(name==="missle"){
-
this.width = missleWidth;
-
}else if(name==="boom"){
-
this.width = boomWidth;
-
}else if(name==="food"){
-
this.width = 40;
-
this.speed = 3;
-
this.kind = "LevelUP"
- }
-
this.toLeft = false;
-
this.toTop = false;
-
this.toRight = false;
-
this.toBottom = false;
-
-
this.outArcRadius = Math.sqrt((this.width/2*this.width/2)*2);
-
- if(args){
- for(var arg in args){
- this[arg] = args[arg];
- }
- }
- }
-
Sprite.prototype = {
- constructor:Sprite,
- paint:function(){
-
if(this.name==="badPlan"){this.update();}
-
- if(this.painter !== undefined && this.visible){
- if(this.name!=="badPlan") {
- this.update();
- }
-
if(this.name==="plan"||this.name==="missle"||this.name==="badPlan"){
- ctx.save();
- ctx.translate(this.left , this.top);
- ctx.rotate(this.rotateAngle);
- this.painter.paint(this);
- ctx.restore();
- }else {
- this.painter.paint(this);
- }
- }
- },
- update:function(time){
- if(this.behaviors){
-
for(var i=0;i<this.behaviors.length;i ){
- this.behaviors[i].execute(this,time);
-
- }
- }
-
After writing the elf class, you can generate different objects by writing each painter and behavior. The next step is to write painters. Painters are divided into two types, one is ordinary painter, and the other is sprite sheet painter. Because explosion animations and airplane shooting animations cannot be done with just one picture, so you need to use It’s time to sprite sheet:
To draw these, you need to customize a sprite sheet painter for them. The following is the simplest sprite sheet painter. According to the complexity of the game, the sprite sheet writing method can be modified accordingly until it is suitable. However, the principles are similar, that is Just minor modifications:
XML/HTML Code
Copy content to clipboard
- var SpriteSheetPainter = function(cells){
- this.cells = cells || [];
- this.cellIndex = 0;
- }
- SpriteSheetPainter.prototype = {
- advance:function(){
- if(this.cellIndex === this.cells.length-1){
- this.cellIndex = 0;
- }
- else this.cellIndex ;
- },
- paint:function(sprite){
- var cell = this.cells[this.cellIndex];
- context.drawImage(spritesheet , cell.x , cell.y , cell.w , cell.h , sprite.left , sprite.top , cell.w , cell.h);
- }
- }
而普通的绘制器就更简单了,直接写一个painter,把要画的什么东西都写进去就行了。
有了精灵类和精灵表绘制器后,我们就可以把星星,飞机,子弹,爆炸对象都写出来了:下面是整个allSprite.js的代码:
JavaScript Code复制内容到剪贴板
- (function(W){
- "use strict"
- var planWidth = 24,
- planHeight = 24,
- missleWidth = 70,
- missleHeight = 70,
- boomWidth = 60;
-
- W.Sprite = function(name , painter , behaviors , args){
- if(name !== undefined) this.name = name;
- if(painter !== undefined) this.painter = painter;
- this.top = 0;
- this.left = 0;
- this.width = 0;
- this.height = 0;
- this.velocityX = 3;
- this.velocityY = 2;
- this.visible = true;
- this.animating = false;
- this.behaviors = behaviors;
- this.rotateAngle = 0;
- this.blood = 50;
- this.fullBlood = 50;
- if(name==="plan"){
- this.rotateSpeed = 0.05;
- this.rotateLeft = false;
- this.rotateRight = false;
- this.fire = false;
- this.firePerFrame = 10;
- this.fireLevel = 1;
- }else if(name==="star"){
- this.width = Math.random()*2;
- this.speed = 1*this.width/2;
- this.lightLength = 5;
- this.cacheCanvas = document.createElement("canvas");
- this.cacheCtx = this.cacheCanvas.getContext('2d');
- this.cacheCanvas.width = this.width this.lightLength*2;
- this.cacheCanvas.height = this.width this.lightLength*2;
- this.painter.cache(this);
- }else if(name==="badPlan"){
- this.badKind = 1;
- this.speed = 2;
- this.rotateAngle = Math.PI;
- }else if(name==="missle"){
- this.width = missleWidth;
- }else if(name==="boom"){
- this.width = boomWidth;
- }else if(name==="food"){
- this.width = 40;
- this.speed = 3;
- this.kind = "LevelUP"
- }
- this.toLeft = false;
- this.toTop = false;
- this.toRight = false;
- this.toBottom = false;
-
- this.outArcRadius = Math.sqrt((this.width/2*this.width/2)*2);
-
- if(args){
- for(var arg in args){
- this[arg] = args[arg];
- }
- }
- }
- Sprite.prototype = {
- constructor:Sprite,
- paint:function(){
- if(this.name==="badPlan"){this.update();}
-
- if(this.painter !== undefined && this.visible){
- if(this.name!=="badPlan") {
- this.update();
- }
- if(this.name==="plan"||this.name==="missle"||this.name==="badPlan"){
- ctx.save();
- ctx.translate(this.left , this.top);
- ctx.rotate(this.rotateAngle);
- this.painter.paint(this);
- ctx.restore();
- }else {
- this.painter.paint(this);
- }
- }
- },
- update:function(time){
- if(this.behaviors){
- for(var i=0;i<this.behaviors.length;i ){
- this.behaviors[i].execute(this,time);
- }
- }
- }
- }
-
-
- W.SpriteSheetPainter = function(cells , isloop , endCallback , spritesheet){
- this.cells = cells || [];
- this.cellIndex = 0;
- this.dateCount = null;
- this.isloop = isloop;
- this.endCallback = endCallback;
- this.spritesheet = spritesheet;
- }
- SpriteSheetPainter.prototype = {
- advance:function(){
- this.cellIndex = this.isloop?(this.cellIndex===this.cells.length-1?0:this.cellIndex 1):(this.cellIndex 1);
- },
- paint:function(sprite){
- if(this.dateCount===null){
- this.dateCount = new Date();
- }else {
- var newd = new Date();
- var tc = newd-this.dateCount;
- if(tc>40){
-
this.advance();
-
this.dateCount = newd;
- }
- }
-
if(this.cellIndex<this.cells.length || this.isloop){
- var cell = this.cells[this.cellIndex];
- ctx.drawImage(this.spritesheet , cell.x , cell.y , cell.w , cell.h , sprite.left-sprite.width/2 , sprite.top-sprite.width/2 , cell.w , cell.h);
- } else if(this.endCallback){
- this.endCallback.call(sprite);
- this.cellIndex = 0;
- }
- }
- }
-
-
- W.controllSpriteSheetPainter = function(cells , spritesheet){
- this.cells = cells || [];
- this.cellIndex = 0;
- this.dateCount = null;
- this.isActive = false;
- this.derection = true;
- this.spritesheet = spritesheet;
- }
- controllSpriteSheetPainter.prototype = {
- advance:function(){
- if(this.isActive){
- this.cellIndex ;
- if(this.cellIndex === this.cells.length){
- this.cellIndex = 0;
- this.isActive = false;
- }
- }
- },
- paint:function(sprite){
- if(this.dateCount===null){
- this.dateCount = new Date();
- }else {
- var newd = new Date();
- var tc = newd-this.dateCount;
- if(tc>sprite.firePerFrame){
-
this.advance();
-
this.dateCount = newd;
- }
- }
-
var cell = this.cells[this.cellIndex];
-
ctx.drawImage(this.spritesheet , cell.x , cell.y , cell.w , cell.h , -planWidth/2 , -planHeight/2 , cell.w , cell.h);
- }
- }
-
- W.planBehavior = [
-
{execute:function(sprite,time){
-
if(sprite.toTop){
- sprite.top = sprite.top
- }
- if(sprite.toLeft){
- sprite.left = sprite.left
- }
- if(sprite.toRight){
- sprite.left = sprite.left>canvas.width-planWidth/2? sprite.left : sprite.left sprite.velocityX;
- }
-
if(sprite.toBottom){
- sprite.top = sprite.top>canvas.height-planHeight/2? sprite.top : sprite.top sprite.velocityY;
- }
-
if(sprite.rotateLeft){
- sprite.rotateAngle -= sprite.rotateSpeed;
- }
-
if(sprite.rotateRight){
- sprite.rotateAngle = sprite.rotateSpeed;