一个canvas小游戏(守护女神)

wocacaca
wocacaca 发布于 2017-03-11 23:29:44 浏览:2945 类型:原创 - 随笔 分类:HTML/CSS - 我的游戏 二维码: 作者原创 版权保护
哈喽,作为一个资深的懒癌患者,我又好久没有写东西了,今天带来一个canvas小游戏。这个需求是要我写一个小游戏,完全模拟 易起玩 的游戏《保卫单身狗》。本来我是想偷懒,直接把他的案例copy过来的,结果发现他们代码压缩后竟然有900多k,感觉提取出来怪麻烦的,就打算自己写了。

    提供一个基本案例  点此体验  
    (由于绑定的是touch事件,如果电脑打开,用chrome打开开发者模式的手机调试测试)

    或者扫描下面二维码:
一个canvas小游戏(守护女神)

这个demo虽然是整体的模仿但是整体的思路其实来源于之前在慕课网看到的一篇教程 《h5游戏爱心鱼》 讲真,真的给我提供了很多的思路。

    下面讲讲代码和踩到的坑,希望能帮到一些人,显示html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>game test</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <!-- <meta name="viewport" content="user-scalable=no"/> -->
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0" /> -->
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <style> 
        *{margin:0; padding:0;}
        body,html{
            width:100%;
            height:100%;
            overflow: hidden;
        }
        canvas{
            position:relative;
        }
        p{
         position:absolute;
         left:10px;
         top:10px;
         z-index: 99;
         font-size:60px;
         color:red;
        }
 
        .ani-shake{
            animation:shake .1s;
            -webkit-animation:shake .1s;
        }
 
        @keyframes shake{
            0% {left:-4px; top:-4px;}
            33% {left:0px; top:0px;}
            66% {left:4px; top:4px; }
            100% { left:0px; top:0px; }
        }
        @-webkit-keyframes shake{
            0% {left:-4px; top:-4px;}
            33% {left:0px; top:0px;}
            66% {left:4px; top:4px; }
            100% { left:0px; top:0px; }
        }
    </style>
</head>
<body>
<p>分数:<span id="score">0</span></p>
    <canvas id="canvas" >纳尼?!!你的浏览器不支持canvas?你该换浏览器啦!</canvas>
    <img id="bg" src="images/bg.jpg" style="display:none" alt="">
    <img id="hit" src="images/hit.png" style="display:none" alt="">
    <img id="role" src="images/role.png" style="display:none" alt="">
    <img id="role2" src="images/role-b.png" style="display:none" alt="">
    <img id="star" src="images/star.png" style="display: none" alt="">
    <img id="blood" src="images/blood.png" style="display: none" alt="">
 
<script src="js/star.js"></script>
<script src="js/me.js" ></script>
<script src="js/people.js"></script>
<script src="js/main.js" ></script>
 
</body>
</html>

可以看到 html 里我直接把图片写入了html里,而不是在js里 new Image(); 这是因为在我测试的时候,发现如果是在js里创建的图片,在画布上画出时,在iphone手机下,图片是不会显示的。因为在js里创建图片,需要在图片onload之后才会显示,但是由于画布游戏实际上是类似帧动画。等于是在极短的时间内不停的重绘画布内容,如果把img创建写在js里,iphone手机是不会缓存的,于是就导致了图片一直无法显示,当然 安卓和pc是不会有这个问题的。

    从上面的html可以看出 这个游戏主要的js有四个 一个是 main.js 用于创建画布和做动画的循环。 star.js 用于画游戏中间我们要保护的妹纸, me.js 用于画游戏里旋转的主人公,people.js用于画给妹纸献花的路人甲乙丙丁~

main.js
//window.onload = function(){
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var win_w = window.innerWidth;
    var win_h = window.innerHeight;
    var timmer;
    var isShaked = false;
    canvas.width = win_w;
    canvas.height = win_h;
    window.requestAnimFrame = window.requestAnimationFrame || 
                              window.webkitRequestAnimationFrame || 
                              window.mozRequestAnimationFrame || 
                              window.oRequestAnimationFrame || 
                              window.msRequestAnimationFrame;
    var isPlaying = true;
    var radio = 1;
 
 
    if(typeof(requestAnimationFrame) == 'undefined'){ // 手机不支持requestAnimationFrame时将画布整体大小缩小一半
        radio = 0.5;
        win_w = window.innerWidth*radio;
        win_h = window.innerHeight*radio;
        canvas.width = win_w;
        canvas.height = win_h;
    }
 
    var star = new star();
    var  Me = new me();
    var people = new people();
 
     
 
    function reset(){
        Me.init();
        star.init();
        people.init();
        document.getElementById('score').innerHTML = '0';
    }
 
    function loop(){   // loop function 
        ctx.clearRect(0,0,win_w,win_h);
        draw_bg();
        Me.draw();
        star.draw();
        people.draw();
         
        if(isPlaying && typeof(requestAnimationFrame) == 'function' ){
            timmer = requestAnimationFrame(loop);
        }
    }
 
    if(typeof(requestAnimationFrame) == 'undefined' && isPlaying){ 
        timmer = setInterval(loop,20);// 手机不支持requestAnimationFrame时用setInterval替代此时为了更好的渲染效果将画布整体大小缩小一半(不然卡到死)
    }
 
     
     
 
    function draw_bg(){
        ctx.drawImage(document.getElementById('bg'),0,0,win_w,win_h);
    }
 
function shake(){
    if(!isShaked){
        isShaked = true;
        canvas.className = 'ani-shake';
        setTimeout(function(){
            canvas.className = '';
        },120)
    }
    isShaked = false;
}
 
document.addEventListener('touchstart',function(e){
    e.preventDefault();
})
canvas.addEventListener('touchmove',function(e){
    var touch = e.changedTouches[0];
    Me.toX = touch.clientX;
    Me.toY = touch.clientY;
    Me.isMove = true;
});
 
canvas.addEventListener('touchstart',function(e){
    var touch = e.changedTouches[0];
    Me.toX = touch.clientX;
    Me.toY = touch.clientY;
    Me.isMove = true;
});
 
window.onload = function(){
    loop();
}
//}

   main.js 主要规定了几个全局变量和一个reset(重新开始游戏)的函数。主要用于控制整个游戏绘制的循环和鼠标控制人物移动。(其实这个鼠标控制人物移动由于是控制me.js绘制的人物,其实放到me.js里会更贴切)。

me.js
var me = function(){
    this.init();
}
 
me.prototype.init = function(){
    this.r = 50*radio;
    this.R = 100*radio;
    this.width = 250*radio;
    this.height = 200*radio;
    this.x = win_w/2;
    this.y = win_h/2 + this.r + this.height;
    this.speed = 20; // 规定移动速度
    this.toX = 0; // 记录到达下一地点x位置
    this.toY = 0; // 记录到达下一地点y位置
    this.isMove = false;
    this.imgIndex = 0;
    this.pre = 2; // 每隔两帧图片切换一次
    //this.draw();
}
 
me.prototype.draw = function(){
    if(this.isMove){
        this.move();
    }
    ctx.drawImage(document.getElementById('hit'),parseInt(this.imgIndex/this.pre)*this.width,0,this.width,this.height,this.x - this.width/2,this.y - this.height/2,this.width,this.height);
    this.imgIndex += 1;
    if(this.imgIndex > 7*this.pre){
        this.imgIndex = 0;
    }
}
 
me.prototype.move = function(){
        if(this.x < this.toX){
            this.x = this.x > this.toX - this.speed ? this.toX : this.x + this.speed;
        }else{
            this.x = this.x < this.toX + this.speed ? this.toX : this.x - this.speed;
        }
        if(this.y < this.toY){
            this.y = this.y > this.toY - this.speed ? this.toY : this.y + this.speed;
        }else{
            this.y = this.y < this.toY + this.speed ? this.toY : this.y - this.speed;
        }
 
        // 边界判断
        if(this.x < this.width/2){
            this.x = this.width/2;
        }
        if(this.x > win_w - this.width/2){
            this.x = win_w - this.width/2;
        }
        if(this.y < this.height/2){
            this.y = this.height/2;
        }
        if(this.y > win_h - this.height/2){
            this.y = win_h - this.height/2;
        }
        // 中心区域判断
 
        if(this.x > win_w/2 - this.R  && this.x < win_w/2 + this.R ){
            var r = Math.sqrt(Math.pow(win_w/2 - this.x,2) + Math.pow(win_h/2 - this.y,2) );
            if(r < this.r + this.R){
                var y = Math.sqrt(Math.pow(this.r+this.R,2) - Math.pow(win_w/2 - this.x,2) );
                this.y = this.y < win_h/2 ? win_h/2 - y : win_h/2 + y;
            }
        }
        if(this.y > win_h/2 - this.R  && this.y < win_h/2 + this.R ){
            var r = Math.sqrt(Math.pow(win_w/2 - this.x,2) + Math.pow(win_h/2 - this.y,2) );
            if(r < this.r + this.R){
                var x = Math.sqrt(Math.pow(this.r+this.R,2) - Math.pow(win_h/2 - this.y,2) );
                this.x = this.x < win_w/2 ? win_w/2 - x : win_w/2 + x;
            }
        }
}

me.js 主要用于画游戏中移动的主人公。此处需要注意的就是不能超出边界的检测和中心区域的碰撞检测,碰撞检测我全部使用的是高中学的《勾股定理》。没办法,我也不会其他高深算法~哈哈。

star.js
var star = function(){
    this.init();
}
 
star.prototype.init = function(){
    this.r = 200*radio;
    this.width = 130*radio;
    this.height = 200*radio;
    this.x = win_w/2;
    this.y = win_h/2;
    this.life = 150*radio;
    this.nowLife = 150*radio;
}
 
star.prototype.draw = function(){
    ctx.save();
    ctx.beginPath();
 
    ctx.strokeStyle = "blue";
 
    ctx.arc(this.x,this.y,this.r, 0, Math.PI*2, false);  
     
    ctx.stroke();
 
    ctx.closePath();
    ctx.restore();
    ctx.drawImage(document.getElementById('star'),this.x - this.width/2,this.y - this.height/2,this.width,this.height);
 
    this.drawLine(this.life,'#666');
    this.drawLine(this.nowLife,'rgba(255,255,255,1)');
    this.lose();
}
 
star.prototype.drawLine = function(width,color){ // 绘制血条
    ctx.save()
    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.lineWidth = 20 * radio;
    if(width != 0){
        ctx.lineCap="round";
    }
    ctx.moveTo(win_w/2 - this.life/2,win_h/2 - this.height/2 - 20);
    ctx.lineTo(win_w/2 - this.life/2 + width,win_h/2 - this.height/2 - 20);
    ctx.fill();
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
} 
 
star.prototype.lose = function(){
    if(this.nowLife == 0){
        alert('YOU LOSE !');
        reset(); // 重新开始
    }
}

star.js 用于画中间需要守护的妹纸,关键操作就是对血量的判断,如果血量为0则游戏结束,重新开始游戏。

people.js
var people = function(){
    this.init();
}
 
people.prototype.init = function(){
    this.width = 85*radio;
    this.height = 65*radio;
    this.R = 200*radio;
    this.r = 50*radio;
    this.num = 3;
    this.all = 30;
    this.x = [];
    this.y = [];
    this.speed = [];
    this.baseSpeed = 400; // 基础速度,值越小,速度越快
    //this.x = []; // 记录鼠标位置x
    //this.y = []; // 记录鼠标位置y
    this.isTouch = []; // 判断是否和人物碰撞
    this.success = []; // 判断是否到达终点
    this.count = [];  // 到达终点后计数
    this.blood = []; // 击中后的血迹
    this.showBlood = [];
    this.attack = 10; // 攻击力
    this.interval = 50; // 设置到达终点切未被打飞的小人每n帧攻击一次
    this.score = 0;
    this.getScort = 10;
    this.pre = 15;
    this.imgIndex = [];
    this.img = [];
 
    for(var i=0;i<this.all; i++){
        this.born(i);
    }
    //this.draw();
}
 
people.prototype.level = function(score){ // 难度设置
    // if(this.score > 100 && this.sc ){ // 难度提升
    //     this.num = 4;
    //     this.pre = 200;
    // }
    if(score > 150){
        this.num = parseInt(score/150) + 3;
        this.baseSpeed =  400 - parseInt(score/200)*20;
    } 
     
 
}
 
people.prototype.draw = function(){
 
    this.level(this.score); 
 
    for(var i=0;i<this.num;i++){
        if(this.showBlood[i]){ // 先画血迹,避免血迹挡住人物
            this.drowBlood(i,this.blood[i].x,this.blood[i].y);
        }
        ctx.drawImage(this.img[i],parseInt(this.imgIndex[i]/this.pre)*this.width,0,this.width,this.height,this.x[i]-this.width/2,this.y[i]-this.height/2,this.width,this.height);
        //ctx.fillRect(this.x[i]-this.width/2,this.y[i]-this.height/2,this.width,this.height);
        this.boom(i);
        this.add(i);
        this.imgIndex[i] += 1;
        if(this.imgIndex[i] > 2*this.pre){
            this.imgIndex[i] = 0;
        }
 
        //console.log(this.x[i],)
    }
     
}
 
people.prototype.born = function(i){
    var x = Math.random() - 0.5;
    if(x > 0){
        this.x[i] = x * 1500 +  win_w ;
    }else{
        this.x[i] = x * 1500
    }
    var y = Math.random() - 0.5;
    if(y>0){
        this.y[i] = y*1500+win_h;
        this.img[i] = document.getElementById('role2');
    }else{
        this.y[i] = y*1500;
        this.img[i] = document.getElementById('role');
    }
    this.speed[i] = {} // 每个人物速度不同 
    var interval = Math.random()*200 + this.baseSpeed; 
    this.speed[i].x = (win_w/2 - this.x[i])/interval;
    this.speed[i].y = (win_h/2 - this.y[i])/interval;
    //this.x[i] = (Math.random() - 0.5) * 2000 + win_w/2; 
    //this.y[i] = (Math.random() - 0.5) * 2000 + win_h/2;
    //this.x[i] = this.x[i];
    //this.y[i] = this.y[i];
    this.isTouch[i] = false; 
    this.success[i] = false;
    this.count[i] = 0;
    this.showBlood[i] = false;
    this.blood[i] = {};
    this.imgIndex[i] = 0;
}
 
people.prototype.boom = function(i){
    var x = (win_w/2 - this.x[i]);
    var y = (win_h/2 - this.y[i]);
    var x1 = (Me.x - this.x[i]);
    var y1 = (Me.y - this.y[i]);
    if(Math.sqrt(x1*x1 + y1*y1) < this.r + Me.r){ // 碰撞检测人物之间
        if(!this.isTouch[i]){  // 获取得分
            this.score += this.getScort;
            document.getElementById('score').innerHTML = this.score;
            shake(); // 震动
            this.blood[i] = { // 记录血迹位置
                x: this.x[i],
                y: this.y[i],
                count: 0
            }
            this.showBlood[i] = true;
        }
        this.isTouch[i] = true;
    }
 
    if(!this.isTouch[i]){ 
        if(Math.sqrt(x*x + y*y) > this.R + this.r ){
            this.x[i] += this.speed[i].x;
            this.y[i] += this.speed[i].y;
        }else{
            if(!this.success[i]){
                star.nowLife = star.nowLife > this.attack ? star.nowLife - this.attack : 0;
                this.success[i] = true;
            }else{
                this.count[i] += 1;
                if(this.count[i] == this.interval){
                    this.count[i] = 0;
                    star.nowLife = star.nowLife > this.attack ? star.nowLife - this.attack : 0;
                }
            }
             
        }
    }else{
        this.x[i] -= this.speed[i].x*5;
        this.y[i] -= this.speed[i].y*5;
    }
}
 
people.prototype.add = function(i){
    if(this.isTouch[i]){
        if(this.x[i] < -this.width - 100 || this.x[i] > win_w+100 || this.y[i] < -this.height-100 || this.y[i] > win_h+100 ){
            this.born(i);
        }
         
    }
}
 
people.prototype.drowBlood = function(i,x,y){ // 绘制血迹
    var r = Math.sqrt(Math.pow(win_w/2-x,2) + Math.pow(win_h/2-y,2));
    var deg = Math.acos( (x - win_w/2)/r);
    deg = y < win_h/2 ? deg : -deg; // 判断血液溅射方向
    if( (y > win_h/2 && x < win_w/2) || (y < win_h/2 && x > win_w/2) ){
        x = x < win_w/2 ? x+this.width/2 : x-this.width/2;
        y = y < win_h/2 ? y-this.height/2 : y+this.height/2;
    }else{
        x = x < win_w/2 ? x-this.width/2 : x+this.width/2;
        y = y < win_h/2 ? y+this.height/2 : y-this.height/2;
    }
     
    ctx.save();
    ctx.translate(x,y);
    ctx.rotate(-deg);
    ctx.drawImage(document.getElementById('blood'),0,0,330,81);
    ctx.translate(0,0);
    ctx.restore();
}

people.js 是游戏最复杂的部分,也是最核心的部分,这个js用于画出向妹纸求爱的小人,关键的碰撞检测比如打飞小人和对妹纸攻击减少血量,攻击后打飞,以及绘制血迹都在这个js里完成。因为小人的数量很多,所以实际上是维护了好几个数组。这里具体不好解释,大家可以去慕课看看之前我推荐的那篇《爱心鱼》的教程,当然我不是打广告的。。。只是觉得她课讲得挺清楚,对这些有帮助。

    总之不要把游戏想的太难,这里用到数学的内容还是高中知识  勾股定理 和 三角函数 –当然也许是有更好的算法我不会,所以用不到。

    然后。请不要吐槽游戏的ui除了背景和中间的妹纸是百度的,其他任务素材我是直接从 易起玩 拔过来的,想想只是做个demo不涉及到商业用途,应该不算侵权吧~

    游戏github地址  https://github.com/mikoshu/games    对了这里提醒大家一下 ios10 以后safari浏览器即使你meta设置了user-scale=0用户依然可以用手机拖动或双击缩放页面(这个坑也是坑到我了)就喜欢苹果公司的尿性啊!
    最后,再推荐下我的博客 http://blog.mikoshu.me/
z
给个赞 22 人点赞
收藏 20 人收藏
评论 已有 15 条评论;以下用户言论只代表其个人观点,不代表 前端网(QDFuns) 的观点或立场。
登录 以后才能发表评论
最新评论
wocacaca
wocacaca2017-03-17 10:20:4815F
互相学习咯 //@sharminKid:棒棒哒。学习
举报 支持 (0) 回复 (0)
sharminKid
sharminKid2017-03-16 19:17:2514F
棒棒哒。学习
举报 支持 (0) 回复 (1)
wocacaca
wocacaca2017-03-15 10:41:2313F
唔,那你华丽丽的错过了~ 我估计回不去了~emoticon //@圆滚滚的小疯子:太可惜了,应该赶在没炸之前看一眼的emoticon //@wocacaca:对呀~~简直炸了呢~emoticon //@圆滚滚的小疯子:让我觉得莫名喜感的游戏
ps:你真的真的帅炸了咩?!!!!
举报 支持 (0) 回复 (0)
圆滚滚的小疯子
圆滚滚的小疯子2017-03-15 08:58:2212F
太可惜了,应该赶在没炸之前看一眼的emoticon //@wocacaca:对呀~~简直炸了呢~emoticon //@圆滚滚的小疯子:让我觉得莫名喜感的游戏
ps:你真的真的帅炸了咩?!!!!
举报 支持 (0) 回复 (1)
皮皮虾
皮皮虾2017-03-14 18:46:3311F
google浏览器,手机模式下 //@13603066193:难道就我一个人不会玩?。。。
举报 支持 (0) 回复 (0)
wocacaca
wocacaca2017-03-14 18:29:5710F
不会玩是指神马? //@13603066193:难道就我一个人不会玩?。。。
举报 支持 (0) 回复 (0)
13603066193
136030661932017-03-14 17:33:049F
难道就我一个人不会玩?。。。
举报 支持 (0) 回复 (2)
wocacaca
wocacaca2017-03-14 10:09:098F
对呀~~简直炸了呢~emoticon //@圆滚滚的小疯子:让我觉得莫名喜感的游戏
ps:你真的真的帅炸了咩?!!!!
举报 支持 (0) 回复 (1)
圆滚滚的小疯子
圆滚滚的小疯子2017-03-13 16:34:267F
让我觉得莫名喜感的游戏
ps:你真的真的帅炸了咩?!!!!
举报 支持 (0) 回复 (1)
wocacaca
wocacaca2017-03-13 16:30:536F
是............. //@Queen_5200:两千也是“you lose”吗? //@wocacaca:我可以玩到两千哦,果然我对女神的爱,一往无前~! //@Queen_5200:守护到1410,爱慕女神的人太多了,守护不过来了emoticon
举报 支持 (0) 回复 (0)
Queen_5200
Queen_52002017-03-13 16:11:025F
两千也是“you lose”吗? //@wocacaca:我可以玩到两千哦,果然我对女神的爱,一往无前~! //@Queen_5200:守护到1410,爱慕女神的人太多了,守护不过来了emoticon
举报 支持 (0) 回复 (1)
wocacaca
wocacaca2017-03-13 15:33:414F
我可以玩到两千哦,果然我对女神的爱,一往无前~! //@Queen_5200:守护到1410,爱慕女神的人太多了,守护不过来了emoticon
举报 支持 (0) 回复 (1)
Queen_5200
Queen_52002017-03-13 14:51:043F
守护到1410,爱慕女神的人太多了,守护不过来了emoticon
举报 支持 (0) 回复 (1)
wocacaca
wocacaca2017-03-13 10:21:572F
morning //@Anymore:good
举报 支持 (0) 回复 (0)
Anymore
Anymore2017-03-13 10:08:241F
good
举报 支持 (0) 回复 (1)
wocacaca wocacaca 作者

快闪开,我要帅炸了!

作者最新