当前位置: 移动技术网 > IT编程>网页制作>Html5 > 前端笔记之Canvas

前端笔记之Canvas

2019年04月19日  | 移动技术网IT编程  | 我要评论

一、canvas基本使用

canvashtml5的画布,canvas算是“不务正业”的面向对象大总结,将面向对象玩极致。

算法为王!就是说canvas你不会,但是算法好,不怕写业务,不怕代码量,只要稍微学学api就能出活。

canvas这里是html5新标签,直接要了flash的命。

 

1.1 canvas简介

mdncanvas在线手册:

https://developer.mozilla.org/zh-cn/docs/web/api/canvas_api

了解:

<canvas>是一个可以使用脚本(通常为javascript)来绘制图形的 html 元素.它可以用于绘制图表、制作图片构图或者制作简单的(以及不那么简单的)动画. 右边的图片展示了一些 <canvas> 的实现示例

历史:

<canvas> 最早由apple引入webkit,用于mac os x dashboard,随后被各个浏览器实现。如今,所有主流的浏览器都支持它。

mozilla 程序从 gecko 1.8 (firefox 1.5) 开始支持 <canvas>。它首先是由 apple 引入的,用于 os x  safariinternet explorer ie9开始支持<canvas> ,更旧版本的ie可以引入 google  explorer canvas 项目中的脚本来获得<canvas>支持。chromeopera 9+ 也支持 <canvas>

canvas兼容到ie9


1.2 canvas入门

canvashtml5中比较特殊的双标签,可以在body中放:

<html>
<head>
    <meta charset="utf-8" />
    <title>document</title>
    <style type="text/css">
        canvas{border:1px solid #000;}
    </style>
</head>
<body>
    <canvas width="600" height="400"></canvas>
</body>
</html>

不能将widthheightcss中设置,否则画布的像素的会被缩放,画面质量粗糙了。

 

<canvas>元素可以像任何一个普通的图像一样(有marginborderbackground等等属性)被设计。然而,这些样式不会影响在canvas中的实际图像。

 

画布没什么用,所有操作都要在“上下文”中进行,这里的上下文是环境的意思,不是面向对象中的this

<script type="text/javascript">
     //得到画布标签
     var canvas = document.queryselector("canvas");
     //使用上下文,得到一个2d的画布
     var ctx = canvas.getcontext("2d");
     //画画
     ctx.fillrect(100, 100, 300, 100);
</script>

canvas的本质就是用js来画画,所有的绘画函数,都是ctx的方法。

 

 

canvas马上开始面对一堆api

<script type="text/javascript">
     //得到画布标签
     var canvas = document.queryselector("canvas");
     //使用上下文,得到一个2d的画布
     var ctx = canvas.getcontext("2d");
     //绘制矩形
     ctx.fillstyle = "orange"; //先提供一个颜色的笔
     ctx.fillrect(100, 100, 300, 100); //在根据以上颜色填充

     ctx.fillstyle = "green"; //先提供一个颜色的笔
     ctx.fillrect(100, 200, 300, 200); //在根据以上颜色填充
</script>

 

canvas的坐标系和绝对定位的坐标系是一样的。

 


二、canvas绘制形状

2.1绘制形状路径

canvas中有两种东西:

l stroke路径【笔触】,也叫描边,就是形状的轮廓

l fill填充,就是里面的颜色

//得到画布标签
var canvas = document.queryselector('canvas');
//使用上下文,得到一个2d画布
var ctx = canvas.getcontext("2d");
//画画
ctx.beginpath();     //声明要开始绘制路径
ctx.moveto(100,100); //移动到绘制点,将“画笔”移动到100,100的位置
ctx.lineto(250,250); //划线
ctx.lineto(500,250); //划线
ctx.linewidth = 10;  //线的粗细
ctx.strokestyle = "red"; //线的颜色
ctx.fillstyle = "blue"; //准备填充的颜色
ctx.closepath();     //闭合路径(自动补全)
ctx.stroke();        //显示线(绘制线),可以绘制的路径显示出来
ctx.fill();          //填充颜色

只有矩形有快捷方法,比如想绘制多边形,都要用以上这些组合。

 


2.2绘制矩形

ctx.fillrect(x,y,w,h);    //绘制填充矩形
ctx.strokerect(x,y,w,h);   //绘制路径矩形

 

绘制调色板:

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");
for (var i = 0;i < 6;i++){
   for (var j = 0;j < 6;j++){
       ctx.fillstyle = 'rgba('+ math.floor(255-42.5 * i) +','+ math.floor(255-42.5 * j) +', 200)';
       ctx.fillrect(i * 25, j * 25, 25, 25);
   }
}

记住一句话:canvas是不可逆,绘制的元素一旦上了屏幕,是无法针对它再次操作。


2.3绘制弧度

ctx.arc(圆心x, 圆心y, 半径, 开始的弧度, 结束的弧度, 是否逆时针);

ctx.beginpath();  //开始绘制路径
// ctx.arc(100, 100, 60, 0, 6.28, false);
ctx.arc(100, 100, 60, 0, math.pi * 2, false);
ctx.stroke(); //显示路径线

切一个圆,让切下来的弧边长等于圆的半径,此时弧对应的角度是57.3度左右,此时角度是固定的。

 

正方向是正右方

canvas中所有涉及角度的坐标系有两点注意的:

l 0弧度的方向是正右方向。

 

弧度的顺时针和逆时针:

ctx.arc(100,100,60, 0, 3, false); //绘制圆弧

ctx.arc(100,100,60, 0, 1, true); //绘制圆弧

 

绘制圆形:

ctx.arc(100,100,60, 0, math.pi * 2, false);
ctx.arc(100,100,60, 0, 6.28, false);
ctx.arc(100, 100, 60, 0, -6.28, true);

注意:xy坐标是到圆心的位置,而且圆的大小是半径,后面绘制的形状会覆盖前面的形状。

 

绘制笑脸

<script type="text/javascript">
     var canvas = document.queryselector("canvas");
     var ctx = canvas.getcontext("2d");

     //绘制大脸
     ctx.beginpath(); //开始绘制路径
     ctx.arc(300,200, 160, 0, 6.28, false); //绘制圆弧
     ctx.stroke(); //显示路径线
     ctx.fillstyle = "brown";
     ctx.fill();

     //绘制左眼睛
     ctx.beginpath();
     ctx.arc(230,150, 30, 0, 6.28, false);
     ctx.stroke();
     ctx.fillstyle = "orange";
     ctx.fill();

     //绘制右眼睛
     ctx.beginpath();
     ctx.arc(370,150, 30, 0, 6.28, false);
     ctx.stroke();
     ctx.fillstyle = "blue";
     ctx.fill();

     //绘制嘴巴
     ctx.beginpath();
     ctx.arc(300,160, 120, 0.5, 2.6, false);
     ctx.linewidth = 10;
     ctx.stroke();
</script>


三、使用图片

3.1图片基本使用

canvas中不可能所有形状都自己画,一定是设计师给我们素材,然后使用。

canvas中使用图片的方法:注意,必须等img完全加载后才能呈递图片。

ctx.drawimage();

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");
//创建一个img标签
var image = new image()
//设置图片的路径
image.src = "images/baby1.jpg";
//当图片成功加载,就画图(上屏幕)
image.onload = function(){
   //显示图片的api
   ctx.drawimage(image, 100, 100); //表示x和y坐标
}

3.2使用切片

如果2个数字参数,此时表示左上角位置的xy坐标:

ctx.drawimage(img,100,100);
ctx.drawimage(img图片对象,画布x,画布y);
如果4个数字参数,此时表示x、y、w、h:
ctx.drawimage(img图片对象, 画布x,画布y,图片w,图片h);
如果8个数字参数,此时表示:
ctx.drawimage(img,切片x,切片y,切片w,切片h,画布x,画布y,图片w,图片h);
//创建一个img标签
var image = new image()
//设置图片的路径
image.src = "images/baby1.jpg";
//当图片成功加载,就画图(上屏幕)
image.onload = function(){
   //显示图片的api
   // ctx.drawimage(image, 100, 100); //表示x和y坐标
   // ctx.drawimage(image, 100, 100, 150, 150); //表示x和y坐标
   // ctx.drawimage(img,切片x,切片y,切片w,切片h,画布x,画布y,图片w,图片h);
   ctx.drawimage(image, 108, 200, 145, 120, 100, 100, 145, 120);
}

图片apihttps://developer.mozilla.org/zh-cn/docs/web/api/canvas_api/tutorial/using_images

 


3.3简易的图片加载器

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");
var r = {
   "0":"images/d1.jpg",
   "1":"images/d2.jpg",
   "2":"images/d3.jpg"
}
var arr = [];
for(var k in r){
   arr[k] = new image(); //创建img对象
   arr[k].src = r[k];    //设置图片地址
   // 当图片成功加载,就画图(上屏幕)
   arr[k].onload = function(){
       ctx.drawimage(arr[k], 50, 50)
   }
}

3.4显示gif动态图

 html代码:

<img id="testimg" src="xxx.gif" width="224" height="126">
<p><input type="button" id="testbtn" value="停止"></p>
if ('getcontext' in document.createelement('canvas')) {
    htmlimageelement.prototype.play = function() {
        if (this.storecanvas) {
            // 移除存储的canvas
            this.storecanvas.parentelement.removechild(this.storecanvas);
            this.storecanvas = null;
            // 透明度还原
            image.style.opacity = '';
        }
        if (this.storeurl) {
            this.src = this.storeurl;    
        }
    };
    htmlimageelement.prototype.stop = function() {
        var canvas = document.createelement('canvas');
        // 尺寸
        var width = this.width, height = this.height;
        if (width && height) {
            // 存储之前的地址
            if (!this.storeurl) {
                this.storeurl = this.src;
            }
            // canvas大小
            canvas.width = width;
            canvas.height = height;
            // 绘制图片帧(第一帧)
            canvas.getcontext('2d').drawimage(this, 0, 0, width, height);
            // 重置当前图片
            try {
                this.src = canvas.todataurl("image/gif");
            } catch(e) {
                // 跨域
                this.removeattribute('src');
                // 载入canvas元素
                canvas.style.position = 'absolute';
                // 前面插入图片
                this.parentelement.insertbefore(canvas, this);
                // 隐藏原图
                this.style.opacity = '0';
                // 存储canvas
                this.storecanvas = canvas;
            }
        }
    };
}

var image = document.getelementbyid("testimg"), 
    button = document.getelementbyid("testbtn");
    
if (image && button) {
    button.onclick = function() {
        if (this.value == '停止') {
            image.stop();
            this.value = '播放';
        } else {
            image.play();
            this.value = '停止';
        }
    };
}

3.5游戏图片资源加载器

//得到画布
var canvas = document.queryselector("canvas");
// 使用上下文,得到一个2d的画布
var ctx = canvas.getcontext("2d");
//资源文件
var r = {
   "d1" : "images/d1.jpg",
   "d2" : "images/d2.jpg",
   "d3" : "images/d3.jpg"
}
//遍历这个对象,将他们的地址变为真实图片地址
var count = 0; //已成功加载的图片个数
var length = object.keys(r).length; //所有图片的总数
for(var k in r){
   //创建image对象
   var image = new image();
   //设置src图片路径
   image.src = r[k];
   //将r里面的资源文件,变为真正的图片对象
   r[k] = image;
   //当image加载成功后,显示图片在画布上
   image.onload = function(){
       count++; //当某张图片加载成功,给计数器+1
       ctx.clearrect(0,0,600,600)
       //绘制文本,提升用户体验,提示加载的进度
       //填充文字api
       ctx.textalign = "center";
       ctx.font = "30px 微软雅黑";
       ctx.filltext("正在加载图片:" + count + "/" + length, canvas.width / 2,50)
       //当加载完毕,开始游戏
       if(count == length){
           //开始游戏的回调函数
           ctx.clearrect(0,0,600,600)
           start();
       }
   }
}
// 开始游戏的函数
function start(){
   ctx.drawimage(r["d1"],100,100);
   ctx.drawimage(r["d2"],0,100);
   ctx.drawimage(r["d3"],300,200);
}

四、画布的变形

4.1 translate移动变形

translate()移动画布,rotate()旋转画布。

canvas中不能只移动某一个对象,移动的都是整个画布。

canvas中不能只旋转某一个对象,旋转的都是整个画布。

但是可以用save()restore()来巧妙设置,实现让某一个元素进行移动和旋转。

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");
ctx.translate(100, 100); //将画布移动,坐标系就发生变化了
ctx.fillrect(100, 100, 100, 100); //相对于移动后的坐标系开始画画

 

移动变形、移动的是整个画布、而不是某个元素,在ctx.translate()之后绘制的语句都将被影响。

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");
ctx.translate(100, 100); //将画布移动,坐标系就发生变化了
ctx.fillrect(100, 100, 100, 100); //相对于移动后的坐标系开始画画
ctx.beginpath();
ctx.arc(100,100, 100, 0, 6.28, false);
ctx.fillstyle = 'skyblue';
ctx.fill();


4.2 save()保存和restore()恢复

ctx.save()表示保存上下文的物理性质,ctx.restore()表示恢复最近一次的保存。

save表示保存sava函数之前的状态,restore表示获取save保存的状态。

移动了的元素,会影响不需要移动圆点坐标的元素,所以可以使用以上两个方法保存起来,可以解决让某一个元素移动变形不受影响。

var canvas = document.queryselector('canvas');
var ctx = canvas.getcontext("2d");

ctx.save();
ctx.translate(100, 100); //将画布移动,坐标系就发生变化了
ctx.fillrect(100, 100, 100, 100); //相对于移动后的坐标系开始画画
ctx.restore();

ctx.beginpath();
ctx.arc(100,100, 100, 0, 6.28, false);
ctx.fillstyle = 'skyblue';
ctx.fill();


4.3 rotate()旋转变形

旋转的是整个坐标系,坐标系以0,0点为中心点进行旋转。

rotate(1)的参数,是弧度,旋转的也不是矩形,而是画布。

var canvas = document.queryselector("canvas");
var ctx = canvas.getcontext("2d");
ctx.rotate(1); //1表示57.3度(1弧度)
ctx.fillrect(100, 100, 100, 100); //相对于旋转后的坐标系开始画画

 

如果想旋转某一个元素,必须将坐标轴原点,放到要旋转的元素身上,然后再旋转。

ctx.save();
ctx.translate(150,150)
ctx.rotate(1); //1表示57.3度(1弧度)
ctx.fillrect(-50, -50, 100, 100); //相对于旋转后的坐标系开始画画
ctx.restore();

坐标系移动到物体的中心点,物体以负半宽、半高、为xy绘制。

function box(){
    this.x = 150;
    this.y = 150;
    this.w = 100;
    this.h = 100;
    this.deg = 0;
}
box.prototype.render = function(){
    ctx.save()
    ctx.translate(this.x,this.y)
    ctx.rotate(this.deg); 
    ctx.fillrect(-this.w / 2,-this.h / 2,this.w,this.h);
    ctx.restore()
}
box.prototype.update = function(){
    this.deg += 0.2;
}
var b = new box();
b.render();
setinterval(function(){
    ctx.clearrect(0,0,600,400)
    b.update();
    b.render();
},20);

globalcompositeoperation

用来设置新图像和老图形如何“融合”、“裁剪”。

值有以下这些:

新图形是:source,老图形是destination

ctx.globalcompositeoperation="destination-over";


五、flappybird游戏

5.1游戏结构

游戏采用中介者模式开发,game类统领全局,负责读取资源、设置定时器、维护各种演员的实例,也就是说所有的演员都是gamenew出来,当做一个子属性。

也就是,游戏项目外部就一条语句:

var game = new game();

其他的所有语句都写在game类里面。

 

需要的类:

game类:         中介者,读取资源、设置定时器、维护各种演员的实例
bird类:         小鸟类,这个类是单例的,实例化一次
pipe类:         管子类
land类:         大地类
background类:   背景类
<body>
    <canvas width="414" height="650"></canvas>
</body>
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
<script type="text/javascript" src="js/game.js"></script>
<script type="text/javascript" src="js/bird.js"></script>
<script type="text/javascript" src="js/land.js"></script>
<script type="text/javascript" src="js/pipe.js"></script>
<script type="text/javascript" src="js/background.js"></script>

5.2创建game类:开始界面、加载资源

(function(){
    window.game = function() {
        this.f = 0; //帧编号
        this.init();//初始化dom
    }

    game.prototype.init = function() {
        this.canvas = document.getelementbyid("canvas");
        this.ctx = this.canvas.getcontext("2d");
        //r对象表示资源文件,图片总数
        this.r = {
            "bg_day": "images/bg_day.png",
            "land": "images/land.png",
            "pipe_down": "images/pipe_down.png",
            "pipe_up": "images/pipe_up.png",
            "bird0_0": "images/bird0_0.png",
            "bird0_1": "images/bird0_1.png",
            "bird0_2": "images/bird0_2.png",
        }

        var self = this;
        //遍历对象用for in语句
        //遍历这个对象,将它们变为真的图片地址
        var count = 0; //计算加载好的图片总数(成功加载一张就+1)
        var length = object.keys(this.r).length; //得到图片的总数
        for (var k in this.r) {
            //创建一个img标签,发出图片的请求,目前img对象是孤儿节点
            var img = new image();
            //将这个r[k]对象赋值给src设置图片的路径
            img.src = this.r[k];
            //将r里面的资源文件,改为img真的图片对象
            this.r[k] = img;
            //当图片加载完毕,就画图上画布(图片必须load才能上画布)
            img.onload = function () {
                count++; //当某张图片加载完毕,给计数器+1
                //清屏
                self.clear()
                //绘制文本,提升用户加载到什么程度了

                //save和restore方法配合使用,防止污染其他样式
                self.ctx.save(); //保存状态
                self.ctx.textalign = "center";
                self.ctx.font = "18px 微软雅黑";
                self.ctx.fillstyle = "blue";
                //填充文字
                self.ctx.filltext(`加载中 ${count} / ${length}`, self.canvas.width / 2, 100);
                self.ctx.restore(); //恢复保存的状态
                //当加载完毕的图片总数==图片总数时,此时就开始加载图片并开始游戏
                if (count == length) {
                    self.start(); //开始游戏的回调函数
                }
            }
        }
    }
    //清屏
    game.prototype.clear = function() {
        this.ctx.clearrect(0, 0, this.canvas.width, this.canvas.height)
    }
    // 游戏主循环
    game.prototype.start = function () {
        var self = this;
        this.timer = setinterval(function(){
            self.f++;
            // 清屏
            self.clear();
            //显示帧率
            self.ctx.font = "16px 微软雅黑";
            self.ctx.filltext(self.f,10,20);
        },20);
    }
})();

5.3创建background.js背景类

(function () {
    window.background = function () {
       this.image = game.r["bg_day"]; //图片
       this.x = 0; 
    }
    background.prototype.render = function () {
        // 画一个矩形,补充一下天空的颜色
        game.ctx.save()
        game.ctx.fillstyle = "#4ec0ca";
        game.ctx.fillrect(0,0,game.canvas.width,game.canvas.height - 512);
        //第一步:为了不穿帮绘制背景连续放3张图片让背景无缝滚动             game.ctx.drawimage(this.image,this.x,game.canvas.height - 512);
        game.ctx.drawimage(this.image,this.x + 288 ,game.canvas.height - 512);
        game.ctx.drawimage(this.image,this.x + 288 * 2 ,game.canvas.height - 512);
        game.ctx.restore();
    }
    background.prototype.update = function () {
        this.x--;
        if(this.x < -288){
            this.x = 0;
        }
    }
})();
game.prototype.start = function() {
    // 游戏开始主 循环
    var self = this;
    this.background = new background();// new 背景类
    this.land = new land(); //new实例化大地类
    this.timer = setinterval(function(){
        self.f++;
        self.clear();
        // 渲染 和 更新 背景类
        self.background.render();
        self.background.update();
        // 每隔100帧,实例化一根管子类
        self.f % 100 == 0 && new pipe();
        // 渲染 和 更新 大地类
        self.land.render();
        self.land.update();
        // 渲染 和 更新所有管子类
        for (var i = 0; i < self.pipearr.length; i++) {
            self.pipearr[i].render()
            self.pipearr[i].update()
        }
        self.ctx.font = "16px 微软雅黑";
        self.ctx.filltext(self.f,10,20);
    }, 20)
}

5.4创建land.js大地类

(function () {
    window.land = function () {
        this.image = game.r["land"]; //图片
        this.x = 0;
    }
    land.prototype.render = function () {
        game.ctx.drawimage(this.image, this.x, game.canvas.height - 112);
        game.ctx.drawimage(this.image, this.x + 336, game.canvas.height - 112);
        game.ctx.drawimage(this.image, this.x + 336 * 2, game.canvas.height - 112);
    }
    land.prototype.update = function () {
        this.x--;
        if (this.x < -336) {
            this.x = 0;
        }
    }
})();

5.5创建pipe.js管子类:

(function () {
    window.pipe = function () {
        this.pipedown = game.r["pipe_down"]; //上管子
        this.pipeup = game.r["pipe_up"]; //下管子

        this.pipedownh = _.random(50,300); //随机一个上面管子的高度(因)
        this.space = 120; //上下管子之间的空隙(因)
        //下面管子的高度随之而定了(果),高度-大地高-上管子高-空隙
        this.pipeuph = game.canvas.height - 112 - this.pipedownh - this.space; 
        this.x = game.canvas.width; //让管子在屏幕右侧外面就绪
        game.pipearr.push(this); //将自己存进数组

    }
    pipe.prototype.render = function () {
        //两根管子在画布的位置(image对象, 切片x, 切片y, 切片w,切片h,画布x,画布y,图片w,图片h)
        //渲染上面的管子
        game.ctx.drawimage(this.pipedown, 0, 400 - this.pipedownh, 52, this.pipedownh, this.x, 0, 52, this.pipedownh);
        //下面的管子
        game.ctx.drawimage(this.pipeup, 0, 0, 52, this.pipeuph, this.x, this.pipedownh + this.space, 52, this.pipeuph);
    }
    pipe.prototype.update = function () {
       this.x -= 2;//更新管子(让管子移动)
       if(this.x < -52){
           this.godie(); //超过屏幕左侧-52的位置(删除管子)
       }
    }
    pipe.prototype.godie = function () {
        for(var i = game.pipearr.length - 1; i >= 0; i--){
             if (game.pipearr[i] == this){
                 game.pipearr.splice(i,1);
             }
        }
    }
})();

5.6创建bird.js小鸟类

(function () {
    window.bird = function () {
        this.img = [game.r["bird0_0"], game.r["bird0_1"], game.r["bird0_2"]]; //小鸟
        this.x = 100;
        this.y = 100;
        //位置,这里的x,y不是小鸟的左上角位置,而是小鸟的中心点
        // this.x = game.width / 2 * 0.618;
        this.dy = 0.2; //下降的增量,每帧的恒定变
        this.deg = 0; //旋转
        this.wing = 0; //拍打翅膀的编号
    }

    //渲染小鸟
    bird.prototype.render = function() {
        game.ctx.save();
        game.ctx.translate(this.x,this.y);
        game.ctx.rotate(this.deg);
        //减去24是因为x、y是中心点位置(减去宽度和高度的一半)
        game.ctx.drawimage(this.img[this.wing],-24,-24);
        game.ctx.restore()
    }
    //更新小鸟
    bird.prototype.update = function () {
        // 下降的增量0.88,变化的量也在变,这就是自由落体
        this.dy += 0.88;
        //旋转角度的增量
        this.deg += 0.08;
        this.y += this.dy;
        //每2帧拍打一次翅膀
        game.f % 2 && this.wing++;
        if(this.wing > 2){
            this.wing = 0;
        }
    }
    //小鸟飞
    bird.prototype.fly = function() {
        //小鸟只要有一个负的dy此时就会向上飞,因为this.y += 一个数
        this.dy = -10;
        this.deg = -1;
    }
})();

5.7碰撞检测

小鸟的碰撞检测,使用aabb盒方法,就是把小鸟看作是一个矩形,去判断有没有碰撞。

碰撞公式:
鸟的x2 >管子的x1 && (鸟的y1 < 管子的y1 || 鸟的y2 > 管子的y2) && 鸟的x1 < 管子的x2

aabb盒,轴对齐包围盒也称为矩形盒,要自己会调试,将关键的数据filltext()渲染到界面上。

碰撞检测写在管子身上,因为管子很多,只需要检测有没有碰撞到那唯一的小鸟即可,没有for循环。

如果写在鸟身上,要用for循环遍历所有管子,一一检测。


六、场景管理器

游戏是有各种场景的:

① 欢迎界面

② 教学界面

③ 游戏界面

④ gameover界面

 

场景管理器(scenemanager)的好处就是可以管理零碎的东西。

l game类现在不再直接管理birdbackgroundpipeland了。而是只管理场景管理器。

l 场景管理器负责管理其他类的实例化、更新渲染。

 

删除game类所有的实例化、更新、渲染,然后创建场景管理器,并实例化场景管理器

继续引入图片资源:

this.r = {
   ...
   "title" : "images/title.png",
   "button_play" : "images/button_play.png",
   "text_ready" : "images/text_ready.png",
   "tutorial" : "images/tutorial.png",
   "gameoverbg": "images/gameoverbg.png",
   "b0" : "images/b0.png",
   "b1" : "images/b1.png",
   "b2" : "images/b2.png",
   "b3" : "images/b3.png",
   "b4" : "images/b4.png",
   "b5" : "images/b5.png",
   "b6" : "images/b6.png",
   "b7" : "images/b7.png",
   "b8" : "images/b8.png",
   "b9" : "images/b9.png",
   "b10" : "images/b10.png",
   "b11" : "images/b11.png"
}

 

3号场景:

window.scenemanager = function () {
    //当前场景的编号
    this.smnumber = 1;
    // 初始化场景编号的方法
    this.init(1);
    this.bindevent();
}


(function () {    
    //场景初始化方法
    scenemanager.prototype.init = function(number) {
        switch(number){
            case 1:
                // 1号场景
                break;
            case 2:
                // 2号场景
                break;
            case 3:
                // 3号场景:游戏的主场景
                this.background = new background(); //实例化背景类
                this.land = new land(); //实例化大地类
                this.bird = new bird(); //实例化小鸟类
                break;
        }
    }
    //场景渲染方法
    scenemanager.prototype.render = function () {
        //这里才是真正的渲染方法,可以写动画,因为game类里面render此方法了
        switch (this.smnumber) {
            case 1:
                break;
            case 2:
                break;
            case 3:
                // 渲染 和 更新背景
                this.background.render();
                this.background.update();

                // 每间隔100帧,实例化一根管子
                game.f % 100 == 0 && new pipe(); //实例化管子类
                // 循环遍历管子数组,更新和渲染管子类
                for(var i = 0; i < game.pipearr.length;i++){
                    game.pipearr[i].render();
                    game.pipearr[i].update();
                }
                // 渲染 和 更新大地类
                this.land.render();
                this.land.update();

                // 渲染 和 更新小鸟类
                this.bird.render();
                this.bird.update();
                break;
        }
    }
    
})();
修改碰撞检测:
if(game.sm.bird.x2 > this.x1 && ( game.sm.bird.y1 < this.y1 || game.sm.bird.y2 > this.y2 ) && game.sm.bird.x1 < this.x2) { }

 

3号场景事件:

scenemanager.prototype.bindevent = function(){
    var self = this;
    game.canvas.onmousedown = function(e){
        //添加事件监听,要根据当前场景是几号,触发对应的场景事件
        switch(self.smnumber){
            case 1:
                break;
            case 2:
                break;
            case 3:
                self.bird.fly();
                break;
            case 4:
                break;
        }
    }
}

以上是全是3号场景业务,都已经完成。


1号场景:

//场景初始化方法
//不管什么时候来到这个场景,此时都有一个默认就位状态
//我们动画是可以重复的,但是这个函数不是每帧执行。
scenemanager.prototype.init = function(number) {
    //init中只有一个初始化参数,不要涉及到运动
    switch(number){
        case 1:
            // 1号场景:游戏封面和开始按钮场景的初始化
            this.background = new background(); //实例化背景类
            this.land = new land();     //实例化大地类
            this.titley = -48;         //初始化title位置
            this.titleytarget = 120; //title停留的位置
            this.buttony = game.canvas.height; //初始化按钮的位置
            this.buttonytarget = 360;          //按钮停留的位置
            
            this.birdy = 180;         //初始化小鸟的位置
            this.birdd = "down";     //小鸟的运动方向
            break;
        case 2:
            // 2号场景
            break;
        case 3:
            // 3号场景:游戏的主场景
            this.background = new background(); //实例化背景类
            this.land = new land(); //实例化大地类
            this.bird = new bird(); //实例化小鸟类
            break;
    }
}

//场景渲染方法
scenemanager.prototype.render = function () {
    //这里才是真正的渲染方法,可以写动画,因为game类里面render此方法了
    switch (this.smnumber) {
        case 1:
            // 1号场景:游戏封面和开始按钮场景的更新和渲染
            // 渲染 和 更新背景
            this.background.render();
            this.background.update();
            // 渲染 和 更新大地类
            this.land.render();
            this.land.update();
            //渲染title图片
           game.ctx.drawimage(game.r["title"],(game.canvas.width-178)/2,this.titley);
               
game.ctx.drawimage(game.r["button_play"],(game.canvas.width-116)/2,this.buttony);
            game.ctx.drawimage(game.r["bird1_2"], (game.canvas.width - 48) / 2, this.birdy);
            //title下降运动到目标位置
            this.titley += 2;
            if(this.titley > this.titleytarget){
                this.titley = this.titleytarget;
            }
            //按钮上升运动到目标位置
            this.buttony -= 5;
            if (this.buttony < this.buttonytarget) {
                this.buttony = this.buttonytarget;
            }
            //小鸟不停的上下运动
            if(this.birdd == "down") {
                this.birdy += 2;
                if (this.birdy > 260){
                    this.birdd = "up";
                }
            } else if (this.birdd == "up"){
                this.birdy -= 2;
                if (this.birdy < 170) {
                    this.birdd = "down";
                }
            }
            break;
        case 2:
            break;
        case 3:
            ...
            break;
    }
}

//事件监听方法
scenemanager.prototype.bindevent = function () {
    //根据当前场景触发事件
    var self = this;
    game.canvas.onmousedown = function(e) {
        //鼠标点击的坐标位置
        var x = e.offsetx;
        var y = e.offsety;
        switch (self.smnumber) {
            case 1:
                //1号场景:游戏封面和开始按钮场景的初始化
                //得到按钮的上下左右包围盒
                var left  = (game.canvas.width - 116) / 2
                var right = (game.canvas.width - 116) / 2 + 116;
                var up    = self.buttonytarget;
                var down  = self.buttonytarget + 60;
                if(x >= left && x <= right && y <= down && y >= up){
                    //点击进去2号场景
                    self.smnumber = 2;
                    self.init(2);
                }
                break;
            case 2:
                break;
            case 3:
                break;
        }
    }
}

以上,1号场景完成。


2号场景:

2号场景init初始化:

scenemanganer.prototype.init = function(number) {
    switch (number) {
        case 1:
            ...
            break;
        case 2:
            // 教学 场景
            this.background = new background();
            this.land = new land();
            this.readyy = -62; //2号场景的ready图片
            // 修改 tutorial 的透明度
            this.tutorial = 1
            this.tutoriald = "a"
            break;
        case 3:
            ...
            break;
        case 4:
            ...
            break;
    }
};

2号场景render渲染方法:
//场景渲染方法
scenemanager.prototype.render = function () {
    //这里才是真正的渲染方法,可以写动画,因为game类里面render此方法了
    switch (this.smnumber) {
        case 1:
            ...
            break;
        case 2:
            // 2号场景:教学场景
            this.background.render();
            this.background.update();
            // 渲染 和 更新大地类
            this.land.render();
            this.land.update();
            //渲染title图片
           game.ctx.drawimage(game.r["text_ready"],(game.canvas.width-196)/2,this.readyy)
           game.ctx.drawimage(game.r["bird0_1"],100,180);
            
            this.readyy += 2;
            if (this.readyy > this.readyytarget) {
                this.readyy = this.readyytarget;
            }
            game.ctx.save();
            //让一个物体闪烁
            if(this.tutoriald == "a"){
                this.tutorial -= 0.04;
                if(this.tutorial < 0.1){
                    this.tutoriald = "b"
                }
             }else if(this.tutoriald == "b"){
                this.tutorial += 0.04;
                if(this.tutorial > 1){
                    this.tutoriald = "a"
                }
            }
            // ctx.globalalpha改变透明度的api
            game.ctx.globalalpha = this.tutorialopacity;
            game.ctx.drawimage(game.r["tutorial"], (game.canvas.width - 114) / 2, 250);
            game.ctx.restore();
            break;
        case 3:
           ...
            break;
        case 4:
           ...
            break;
    }
}

2号场景bindevent监听事件方法:
//事件监听方法
scenemanager.prototype.bindevent = function () {
    //根据当前场景触发事件
    var self = this;
    game.canvas.onmousedown = function(e) {
        //鼠标点击的坐标位置
        var x = e.offsetx;
        var y = e.offsety;
        switch (self.smnumber) {
            case 1:
          
                break;
            case 2:
                //2号场景
                var left = (game.canvas.width - 114) / 2
                var right = (game.canvas.width - 114) / 2 + 114;
                var up = 250;
                var down = 350;
                if (x >= left && x <= right && y <= down && y >= up) {
                    //点击进去2号场景
                    self.smnumber = 3;
                    self.init(3);
                }
                break;
            case 3:
                //3号场景:游戏的主场景
                self.bird.fly(); 
                break;
        }
}
}

4号场景:

4号场景init方法

scenemanager.prototype.init = function(number) {
    //init中只有一个初始化参数,不要涉及到运动
    switch(number){
        case 1:
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            //4号场景:死亡场景
            //红色边框图片的透明度
            this.gameoverbg = 1;
            //小鸟落地死亡的爆炸动画初始化图片编号
            this.boom = 0;
            break;
    }
}
碰撞检测死亡,进入4号场景:
//碰撞检测
if (game.sm.bird.x1 < this.x2 && game.sm.bird.x2 > this.x1 && (game.sm.bird.y1 < this.y1 || game.sm.bird.y2 > this.y2)){
    //死亡之后,进入4号场景(小鸟下坠)
    document.getelementbyid("die").play();
    game.sm.smnumber = 4;
    game.sm.init(4);
    return;
}else if(!this.isscore && game.sm.bird.x1 > this.x2){
    // 这里是记分,条件就是把是否加分的true或false给管子身上
    this.isscore = true;
    game.sm.score++;
    document.getelementbyid("score").play();
}
4号场景render渲染方法:
//场景渲染方法
scenemanager.prototype.render = function () {
    switch (this.smnumber) {
        case 1:
            ...
            break;
        case 2:
            break;
        case 3:
           ...
            break;
        case 4:
        // 让所有的物体静止,只渲染,不更新(update不用调用了)
        this.background.render();
        this.land.render();
        for (var i = 0; i < game.pipearr.length; i++) {
            game.pipearr[i].render();
        }
        this.bird.render();
        // 让鸟急速下降
        this.bird.y += 10;
        //播放声音
        document.getelementbyid("down").play();
        // 保证鸟头朝下掉
        this.bird.deg += 0.5;
        if (this.bird.deg > 1.57){
            this.bird.deg = 1.57;
        }
        
        //撞击地面产生爆炸动画,并且小鸟飞升
        if(this.bird.y > game.canvas.height - 112 - 17){
            //小鸟撞地停留在原位
            this.bird.y = game.canvas.height - 112 - 17;
            game.f % 2 == 0 && this.boom++;
            if (this.boom >= 11) {
                //清空管子数组和分数,为下一回合准备
                game.pipearr = [];
                this.score = 0;
                //回到1号场景
                this.smnumber = 1;
                this.init(1);
                this.boom = 5;
                // clearinterval(game.timer); //停止游戏主循环
            }
            //渲染爆炸动画
            game.ctx.drawimage(game.r["b" + this.boom], this.bird.x - 50, this.bird.y - 100);
            
        }
        //渲染红色边框
        this.gameoverbg -= 0.03;
        if(this.gameoverbg < 0){
            this.gameoverbg = 0;
        }
        game.ctx.save()
        game.ctx.globalalpha = this.gameoverbg;
        game.ctx.drawimage(game.r["gameoverbg"],0,0,game.canvas.width,game.canvas.height)
        game.ctx.restore();
        break;
      }    
}
bird.prototype.update = function(){
    this.y += this.dy; //下降的速度
    this.deg += 0.08;
    if(this.deg > 1.57){
        this.deg = 1.57;
}
//掉地死亡
    if(this.y > game.canvas.height - 112){
        document.getelementbyid('die').play();
        game.sm.smnumber = 4;
        game.sm.init(4);
}
}

添加键盘事件和声音、分数:

<audio src="music/die.ogg" id="die"></audio>   
<audio src="music/down.ogg" id="down"></audio>   
<audio src="music/fly.ogg" id="fly"></audio>   
<audio src="music/score.ogg" id="score"></audio>   
<canvas id="canvas" width="414" height="650" tabindex="1"></canvas>

写在bindevent里:

ame.canvas.onkeydown = function(e){
    switch(self.smnumber){
        case 3:
            if(e.keycode == 32){
                self.bird.fly();
            }
            break;
    }
}
game.canvas.focus();

七、canvas动画

让元素在canvas上运动,需要使用定时器。

canvas使用了一个特殊的模式,上画布的元素,立刻被像素化。也就是说,上画布的元素,你将得不到这个“对象”的引用。比如,一个圆形画到了画布上面,此时就是一堆的像素点,不是一个整体的对象了,你没有任何变量能够得到这个对象,改变这个对象的属性。也就是说,这种改变的思路在canvas中是行不通的。

<

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网