zoukankan      html  css  js  c++  java
  • canvas入门级小游戏《开关灯》思路讲解

    游戏很简单,10行10列布局,每行每列各10盏灯,游戏初始化时随机点亮其中一些灯,点击某盏灯,其上下左右的灯及本身状态反转,如果点击前是灭着的,点击后即点亮,将所有灯全部点亮才算过关。游戏试玩:

     下面说说大概思路:

    生成画布

    创建canvas画布,先是生成10*10阵列的100盏灯,每盏灯之间的间隔为margin = 5px,第一盏灯圆心坐标为它的半径"R, R",第一行第二盏灯坐标为3R+margin,以此类推得出灯坐标计算公式:第一行第i盏灯横坐标(2*i + 1)*R + i*margin,同理第一列第j行纵坐标为(2*j + 1)*R + j*margin 

    为其中灯开关的状态是随机的,很自然的想到利用随机数Math.random()去和0.5比较大小,随机数大于0.5则灯熄灭,否则点亮。为了便于游戏扩展和维护,游戏中灯的半径、所有灯的坐标、颜色、数量、是否点亮等数据用变量存起来。 

    <canvas id="canvas" width="445" height="445"> 
        您的浏览器不支持canvas!
    </canvas>
    <script>
    var lightOff = {
            gameData : [], // 灯开关状态
            r : 20, // 灯半径
            margin: 5, // 灯间距
            padding: 20, // canvas内边距
            onColor: "#0077AA", // 灯亮的颜色
            offColor: "#E0E0E0", // 灯灭的颜色
            row : 10, // 10行
            col : 10, // 10列
            canvas : document.getElementById("canvas"),
            ctx : this.canvas.getContext("2d"),
            reDraw: function(p){
                for (var i = 0; i < this.row; i++) {
                    for (var j = 0; j < this.col; j++) {
                        this.ctx.beginPath();                    
                        this.ctx.fillStyle = this.gameData[i][j] ? this.onColor : this.offColor;
                        this.ctx.arc( i*this.margin+(2*i+1)*this.r, j*this.margin+(2*j+1)*this.r, this.r, 0, 2*Math.PI );                    
                        this.ctx.fill();
                    }
                }            
                return this;
            },
            // 随机生成100盏灯的状态
            createMap: function(){
                for (var i = 0; i < this.row; i++) {
                    this.gameData[i] = [];
                    for (var j = 0; j < this.col; j++) {
                        this.gameData[i].push( Math.random() > 0.5 ? false : true );
                    }
                }
                return this;
            }
        }
    </script>

    添加点击事件

     众所周知,canvas是一个整体,如何给其中某一盏灯添加点击事件?事实上,可以给整个canvas对象添加点击事件,将event对象下的event.layerX, event.layerY传入isPointInPath(x, y)方法判断点击的坐标event.layerX, event.layerY是否在灯上从而触发回调函数。

    示例:

    需要注意的是:isPointInPath(x, y)方法只能判断坐标x, y是否在最近的上下文所绘制的图形内,如果canvas一次性绘制了100盏灯,isPointInPath(x, y)只能判断x, y是否在第100盏灯内,要想判断x, y是否在所点击的某盏灯内,就应该在每次点击的时候重绘这100盏灯(调用reDraw方法),每绘制一盏灯触发一次isPointInPath(x, y)方法,如果在灯上,将gameData对应灯的数据状态反转。如当前绘制第i行第j盏灯,调用isPointInPath(x, y)返回true,则反转该灯状态game[i][j] = !game[i][j]之后再次调用reDraw方法重绘。每次点击需要重绘两次。

    综上所述,代码修改为:

    var lightOff = {
            gameData : [],
            r : 20,
            margin: 5,
            padding: 20,
            onColor: "#0077AA",
            offColor: "#E0E0E0",
            row : 10,
            col : 10,
            canvas : document.getElementById("canvas"),
            ctx : this.canvas.getContext("2d"),
            init: function(){
                var This = this;
                // 判断是否点击了该圆点
                this.canvas.addEventListener("click", function(ev){
                    This.reDraw(ev);
                    This.reDraw();
                })
                this.createMap().reDraw();
                return this;
            },
            reDraw: function(p){
                this.count = 0;
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                for (var i = 0; i < this.row; i++) {
                    for (var j = 0; j < this.col; j++) {
                        this.ctx.beginPath();
                        
                        this.ctx.fillStyle = this.gameData[i][j] ? this.onColor : this.offColor;
                        this.ctx.arc( i*this.margin+(2*i+1)*this.r, j*this.margin+(2*j+1)*this.r, this.r, 0, 2*Math.PI );                    
                        this.ctx.fill();
                        if( p && this.ctx.isPointInPath(p.layerX-this.padding, p.layerY-this.padding) ){
                            this.gameData[i][j] = !this.gameData[i][j];
                            i > 0 && (this.gameData[i-1][j] = !this.gameData[i-1][j]);
                            i < 9 && (this.gameData[i+1][j] = !this.gameData[i+1][j]);
                            j > 0 && (this.gameData[i][j-1] = !this.gameData[i][j-1]);
                            j < 9 && (this.gameData[i][j+1] = !this.gameData[i][j+1]);
                        }
                        this.gameData[i][j] && this.count++;
                    }
                }
    
                if(this.count === 100 ){
                    alert("成功了")
                }
                
                return this;
            },
            createMap: function(){
                for (var i = 0; i < this.row; i++) {
                    this.gameData[i] = [];
                    for (var j = 0; j < this.col; j++) {
                        this.gameData[i].push( Math.random() > 0.5 ? false : true );
                    }
                }
                return this;
            }
        }
    
        lightOff.init();

     

    作者:古德God
    出处:http://www.cnblogs.com/wangmeijian
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!

  • 相关阅读:
    连接池的实现 redis例子
    XSS的防御
    element-UI使用中:el-input type为textarea时@change无法触发?
    textarea高度自适应(转载)
    友盟统计单页面应用vue
    axios formData提交数据 && axios设置charset无效???
    解锁技能:sass + node-sass多页面应用编译(转载)
    css3新单位vw、vh、vmin、vmax的使用详解(转载)
    移动端bug集合
    Python3之Memcache使用
  • 原文地址:https://www.cnblogs.com/wangmeijian/p/4738184.html
Copyright © 2011-2022 走看看