This article mainly introduces the use of HTML5 Canvas to create a simple masturbation game. The author also provides the relevant Javascript code. Friends who need it can refer to
which was previously posted in Dang Knight’s DEMO. I saw a masturbation game and pulled 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 purely for entertainment and did not intend to write it into a formal game.
Let’s get into the topic: The masturbation game files include 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, the only things that need to be loaded are some audio and picture. 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 the 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 in data.js to save the links to the files, and then determine whether these links are pictures or audios. If they are pictures Just use preLoadImg to load. The code to preload the image is very simple, just create a new image 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 the content to the 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 Copy after login
After writing the elf class, you can write each painter and behavior to generate different objects. 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 for the 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, you can modify the sprite sheet writing method until it is suitable, but the principles are similar, just minor modifications:
XML/HTML CodeCopy the 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); } }
The ordinary painter is even simpler. Just write a painter and write everything you want to draw.
With the sprite class and sprite sheet renderer, we can write stars, planes, bullets, and explosion objects: The following is the code of the entire allSprite.js:
JavaScript CodeCopy content to the clipboard
(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;i40){ this.advance(); this.dateCount = newd; } } if(this.cellIndex 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 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; } if(sprite.fire&&!sprite.painter.isActive){ sprite.painter.isActive = true; this.shot(sprite); } }, shot:function(sprite){ this.addMissle(sprite , sprite.rotateAngle); var missleAngle = 0.1 for(var i=1;i =0&&audio[i].paused){ audio[i].play(); break; } } }, addMissle:function(sprite , angle){ for(var j=0;j canvas.height){ sprite.left = Math.random()*canvas.width; sprite.top = Math.random()*canvas.height - canvas.height; } sprite.top += sprite.speed; }} ] W.starPainter = { paint:function(sprite){ ctx.drawImage(sprite.cacheCanvas , sprite.left-sprite.width/2-sprite.lightLength , sprite.top-sprite.width/2-sprite.lightLength) }, cache:function(sprite){ sprite.cacheCtx.save(); var opacity = 0.5,addopa = 1/sprite.lightLength; sprite.cacheCtx.fillStyle = "rgba(255,255,255,0.8)"; sprite.cacheCtx.beginPath(); sprite.cacheCtx.arc(sprite.width/2+sprite.lightLength , sprite.width/2+sprite.lightLength , sprite.width/2 , 0 , 2*Math.PI); sprite.cacheCtx.fill(); for(var i=1;i<=sprite.lightLength;i+=2){ opacity-=addopa; sprite.cacheCtx.fillStyle = "rgba(255,255,255,"+opacity+")"; sprite.cacheCtx.beginPath(); sprite.cacheCtx.arc(sprite.width/2+sprite.lightLength , sprite.width/2+sprite.lightLength , sprite.width/2+i , 0 , 2*Math.PI); sprite.cacheCtx.fill(); } } } W.foodBehavior = [ {execute:function(sprite,time){ sprite.top += sprite.speed; if(sprite.top > canvas.height+sprite.width){ sprite.visible = false; } }} ] W.foodPainter = { paint:function(sprite){ ctx.fillStyle = "rgba("+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+","+parseInt(Math.random()*255)+",1)" ctx.font="15px 微软雅黑" ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(sprite.kind , sprite.left , sprite.top); } } W.missleBehavior = [{ execute:function(sprite,time){ sprite.left -= sprite.velocityX; sprite.top -= sprite.velocityY; if(sprite.left<-missleWidth/2||sprite.top<-missleHeight/2||sprite.left>canvas.width+missleWidth/2||sprite.top<-missleHeight/2){ sprite.visible = false; } } }]; W.misslePainter = { paint:function(sprite){ var img = new Image(); img.src="../planGame/image/plasma.png" ctx.drawImage(img , -missleWidth/2+1 , -missleHeight/2+1 , missleWidth , missleHeight); } } W.badPlanBehavior = [{ execute:function(sprite,time){ if(sprite.top > canvas.height || !sprite.visible){ var random = Math.random(); if(point>=200&&point<400){ sprite.fullBlood = 150; if(random<0.1){ sprite.badKind = 2; sprite.fullBlood = 250; } }else if(point>=400&&point<600){ sprite.fullBlood = 250; if(random<0.2){ sprite.badKind = 2; sprite.fullBlood = 400; } if(random<0.1){ sprite.badKind = 3; sprite.fullBlood = 600; } }else if(point>=600){ sprite.fullBlood = 500; if(random<0.4){ sprite.badKind = 2; sprite.fullBlood = 700; } if(random<0.2){ sprite.badKind = 3; sprite.fullBlood = 1000; } } sprite.visible = true; sprite.blood = sprite.fullBlood; sprite.left = Math.random()*(canvas.width-2*planWidth)+planWidth; sprite.top = Math.random()*canvas.height - canvas.height; } sprite.top += sprite.speed; }, shot:function(sprite){ this.addMissle(sprite , sprite.rotateAngle); var missleAngle = 0.1 for(var i=1;i Copy after login
These drawing methods are relatively simple.
Mainly talk about the movement of the aircraft and the control of the number of objects. How does the aircraft move? There is no doubt that by controlling its movement through the keyboard, many people may think of making the aircraft continue to move by judging the keyCode when pressing the keydown method. But there is a problem. The keydown event does not support multiple key presses. That is to say, when you press the X key, the keyCode is 88. At the same time, when you press the direction key, the keyCode will instantly become 37, that is , if you simply want to control the movement of the aircraft with keydown, the aircraft can only do one thing, either it can only move in a certain direction, or it can only shoot.
所以,我们要通过keydown和keyup来实现飞机的运动,原理很容易理解:当我们按下往左的方向键时,我们给飞机一个往左的状态,也就是让飞机的toLeft属性为true,而在动画循环中,判断飞机的状态,如果toLeft为true则飞机的x值不停地减少,飞机也就会不停地往左移动,然后当我们抬起手指时触发keyup事件,我们就再keyup事件中解除飞机往左的状态。飞机也就停止往左移动了。其他状态也一样的原理,这样写的话,就能够让飞机多种状态于一生了。可以同时开枪同时到处跑了。
实现的代码如下:
XML/HTML Code复制内容到剪贴板
//keydown/keyup事件的绑定 window.onkeydown = function(event){ switch(event.keyCode){ case 88:myplan.fire = true; break; case 90:myplan.rotateLeft=true; break; case 67:myplan.rotateRight=true; break; case 37:myplan.toLeft = true; break; case 38:myplan.toTop = true; break; case 39:myplan.toRight = true; break; case 40:myplan.toBottom = true; break; } } window.onkeyup = function(event){ switch(event.keyCode){ case 88:myplan.fire = false; break; case 90:myplan.rotateLeft=false; break; case 67:myplan.rotateRight=false; break; case 37:myplan.toLeft = false; break; case 38:myplan.toTop = false; break; case 39:myplan.toRight = false; break; case 40:myplan.toBottom = false; break; } } //飞机每一帧的状态更新处理代码 execute:function(sprite,time){ if(sprite.toTop){ spritesprite.top = sprite.topcanvas.width-planWidth/2? sprite.left : sprite.left+sprite.velocityX; } if(sprite.toBottom){ spritesprite.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; } if(sprite.fire&&!sprite.painter.isActive){ sprite.painter.isActive = true; this.shot(sprite); }
就是如此简单。
然后说下对象控制,打飞机游戏,会发射大量子弹,产生大量对象,包括爆炸啊,飞机啊,子弹等,如果不停地进行对象的生成和销毁,会让浏览器的负荷变得很大,运行了一段时间后就会卡出翔了。所以,我们要用可以循环利用的对象来解决这个问题,不进行对象的销毁,对所有对象进行保存,循环利用。
我的做法就是,在游戏初始化的时候,直接生成一定数量的对象,存放在数组里面。当我们需要一个对象的时候,就从里面取,当用完后,再放回数组里面。数组里的所有对象都有一个属性,visible,代表对象当前是否可用。
举个例子,当我的飞机发射一发炮弹,我需要一发炮弹,所以我就到炮弹数组里遍历,如果遍历到的炮弹visible为true,也就说明该对象正在使用着,不能拿来用,所以继续遍历,直到遍历到visible为false的炮弹对象,说明这个对象暂时没人用。然后就可以拿过来重新设置属性,投入使用了。当炮弹击中敌人或者打出画布外的时候,把炮弹的visible设成false,又成了一个没人用的炮弹在数组里存放起来等待下一次调用。
所以,我们要预算算好页面大概要用到多少个对象,然后就预先准备好对象,这样,在游戏进行中,不会有对象进行生成和销毁,对游戏性能方面就有了提升了。
最后再说下音频,游戏里面要用到多个同样的audio才能保证音效的不间断性:
复制代码
XML/HTML Code复制内容到剪贴板
var audio = document.getElementsByTagName("audio"); for(var i=0;i=0&&audio[i].paused){ audio[i].play(); break; } }
好吧,基本上就这样了。技术或许还不够好,纯碎做个记录,如果代码有不当正处,欢迎指出,共同学习。
相关推荐:
The above is the detailed content of Use HTML5 Canvas to create a simple masturbation game. For more information, please follow other related articles on the PHP Chinese website!