• 深入解析canvas画布游戏机制


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

    所以canvas的画图的原理就是:

    清屏 → 重绘 → 清屏 → 重绘  → 清屏 → 重绘 → 清屏 → 重绘  →清屏 → 重绘 → 清屏 → 重绘  →清屏 → 重绘 → 清屏 → 重绘  →清屏 → 重绘 → 清屏 → 重绘  →清屏 → 重绘 → 清屏 → 重绘  →清屏 → 重绘 → 清屏 → 重绘  →……

    下面是我写的flappBird,采用了中介者模式参考了白鹭引擎中的场景管理

    首先一个游戏类负责资源的加载与渲染,以及定时器的添加

    // 中介者类
    class Game {
        // 构造函数
        constructor(dataJson) {
            // 获取画布
            this.canvas = document.getElementById(dataJson.canvasId);
            this.ctx = this.canvas.getContext("2d");
            this.jsonUrl = dataJson.jsonUrl;
            // 数据对象
            this.R = {};
            // 定时器
            this.timer = null;
            // 帧数
            this.fno = 0;
            // 管子数组
            this.pipeArr = [];
            this.init();
            // 游戏logo坐标
            this.logoY = -48;
            // 开始按钮坐标
            this.buttonY = this.canvas.height;
            // 提示板透明度
            this.tutorialOpacity = 1;
            // 提示板闪烁间隔
            this.tutorialNum = 0.1;
            // 作者介绍从上而下
            this.autorY = -330;
            // 坠地动画序列
            this.boomNum = 1;
            // 碰撞闪屏
            this.screenFlash = 1;
            // 闪屏是否开启
            this.flashLook = true;
            // 分数
            this.score = 0;
            // 资源加载完毕
            this.allResourceLoadEnd(() => {
                // 开始按钮
                this.button_play = this.R['button_play'];
                // 游戏logo
                this.logo = this.R['logo'];
                // 游戏提示板
                this.tutorial = this.R['tutorial'];
                // 作者介绍
                this.autor = this.R['autor'];
                // 坠地动画资源
                this.boomImg;
                // 结束标志
                this.text_game_over = this.R["text_game_over"];
                // 绑定场景管理器
                this.sceneManager = new SceneManager();
                // 开始游戏
                this.start();
            });
        };
        // 初始化画布
        init() {
            let windowX = document.documentElement.clientWidth;
            let windowY = document.documentElement.clientHeight;
            // 设置画布最小宽度
            windowX = windowX < 320 ? 320 : windowX;
            windowX = windowX > 414 ? 414 : windowX;
            windowY = windowY < 500 ? 500 : windowY;
            windowY = windowY > 812 ? 812 : windowY;
            this.canvas.width = windowX;
            this.canvas.height = windowY;
    
        };
        // 资源管理器函数
        allResourceLoadEnd(callback) {
            let that = this;
            // 图片加载计数
            let imageLoadNum = 0;
            // 异步加载资源
            let xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                        // 获取数据数组
                        let imageArr = JSON.parse(xhr.responseText).images;
                        // 遍历对象
                        imageArr.forEach(value => {
                            that.R[value.name] = new Image();
                            that.R[value.name].src = value.url;
                            // 所有资源加载完毕开始游戏
                            that.R[value.name].onload = function() {
                                imageLoadNum++
                                // 渲染进度
                                that.ctx.clearRect(0, 0, that.canvas.width, that.canvas.height);
                                that.ctx.font = "20px Microsoft YaHei"
                                that.ctx.textAlign = "center"
                                    // 资源加载提醒
                                that.ctx.fillText("正在加载资源" + imageLoadNum + "/" + imageArr.length + "请稍后...", that.canvas.width / 2, that.canvas.height * (1 - 0.618))
                                if (imageLoadNum == imageArr.length) {
                                    callback && callback()
                                }
                            }
    
                        });
    
                    }
                }
    
            }
    
            xhr.open("get", this.jsonUrl);
            xhr.send(null);
    
        };
        // 分数渲染
        scoreRender() {
            // 渲染分数
            let scoreStr = this.score.toString();
            for (let i = 0; i < scoreStr.length; i++) {
                this.ctx.drawImage(this.R['shuzi' + scoreStr[i]], this.canvas.width / 2 - scoreStr.length / 2 * 28 + i * 28, this.canvas.height * 0.2);
            }
        };
        // 游戏开始函数
        start() {
            this.timer = setInterval(() => {
                // 清屏
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                // 渲染场景
                this.sceneManager.update();
                this.sceneManager.render();
                // 帧数自增
                this.fno++;
                // 打印帧数
                this.ctx.font = "14px Arial";
                this.ctx.fillStyle = "#333"
                this.ctx.textAlign = "left";
                this.ctx.fillText("FNO:" + this.fno, 10, 20);
                // 打印场景编号
                this.ctx.font = "550 14px Microsoft YaHei";
                this.ctx.fontWeight = "700";
                this.ctx.fillText("场景号:" + this.sceneManager.sceneNumber, 10, 40)
    
            }, 10)
        }
    }

    JSON文件

    {
        "images": [
            { "name": "bg_day", "url": "images/bg_day.png" },
            { "name": "bird0_0", "url": "images/bird0_0.png" },
            { "name": "bird0_1", "url": "images/bird0_1.png" },
            { "name": "bird0_2", "url": "images/bird0_2.png" },
            { "name": "bird1_0", "url": "images/bird1_0.png" },
            { "name": "bird1_1", "url": "images/bird1_1.png" },
            { "name": "bird1_2", "url": "images/bird1_2.png" },
            { "name": "bird2_0", "url": "images/bird2_0.png" },
            { "name": "bird2_1", "url": "images/bird2_1.png" },
            { "name": "bird2_2", "url": "images/bird2_2.png" },
            { "name": "land", "url": "images/land.png" },
            { "name": "pipe_down", "url": "images/pipe_down.png" },
            { "name": "pipe_up", "url": "images/pipe_up.png" },
            { "name": "shuzi0", "url": "images/font_048.png" },
            { "name": "shuzi1", "url": "images/font_049.png" },
            { "name": "shuzi2", "url": "images/font_050.png" },
            { "name": "shuzi3", "url": "images/font_051.png" },
            { "name": "shuzi4", "url": "images/font_052.png" },
            { "name": "shuzi5", "url": "images/font_053.png" },
            { "name": "shuzi6", "url": "images/font_054.png" },
            { "name": "shuzi7", "url": "images/font_055.png" },
            { "name": "shuzi8", "url": "images/font_056.png" },
            { "name": "shuzi9", "url": "images/font_057.png" },
            { "name": "logo", "url": "images/title.png" },
            { "name": "button_play", "url": "images/button_play.png" },
            { "name": "tutorial", "url": "images/tutorial.png" },
            { "name": "b1", "url": "images/b1.png" },
            { "name": "b2", "url": "images/b2.png" },
            { "name": "b3", "url": "images/b3.png" },
            { "name": "b4", "url": "images/b4.png" },
            { "name": "b5", "url": "images/b5.png" },
            { "name": "b6", "url": "images/b6.png" },
            { "name": "b7", "url": "images/b7.png" },
            { "name": "b8", "url": "images/b8.png" },
            { "name": "b9", "url": "images/b9.png" },
            { "name": "b10", "url": "images/b10.png" },
            { "name": "b11", "url": "images/b11.png" },
            { "name": "b12", "url": "images/b12.png" },
            { "name": "b13", "url": "images/b13.png" },
            { "name": "b14", "url": "images/b14.png" },
            { "name": "b15", "url": "images/b15.png" },
            { "name": "b16", "url": "images/b16.png" },
            { "name": "text_game_over", "url": "images/text_game_over.png" },
            { "name": "autor", "url": "images/autor.png" }
        ]
    
    }

    场景管理类负责各个场景下的渲染以及更新指向

    // 场景管理器类game
    class SceneManager {
        // 构造函数
        constructor() {
            // 绑定天空个背景
            game.backGround = new BackGround();
            // 绑定大地背景
            game.land = new Land();
            // 绑定小鸟
            game.bird = new Bird();
            // 场景标识符
            this.sceneNumber = 1;
            this.bindEvenet();
        };
        // 更新函数
        update() {
            switch (this.sceneNumber) {
                case 1:
                    // 更新logo和开始按钮坐标
                    game.logoY += 10;
                    game.buttonY -= 10;
                    // logo距离天空200
                    if (game.logoY > 120) {
                        game.logoY = 120;
                    }
                    // 按钮距离大地140
                    if (game.buttonY < game.land.y - 150) {
                        game.buttonY = game.land.y - 150
                    }
                    break;
                case 2:
                    // 煽动翅膀
                    game.bird.wingUpdate();
                    // 背景板闪烁
                    if (game.fno % 3 == 0) {
                        if (game.tutorialOpacity < 0.1 || game.tutorialOpacity > 0.9) {
                            game.tutorialNum *= -1;
                        }
                        game.tutorialOpacity += game.tutorialNum;
                    }
                    break;
                case 3:
                    // 煽动翅膀
                    game.bird.wingUpdate();
                    // 作者介绍
                    game.autorY += 10;
                    if (game.autorY > 118) {
                        game.autorY = 118
                    }
                    break;
                case 4:
                    // 更新小鸟
                    game.bird.update();
                    game.backGround.update();
                    game.land.update();
                    // 每150帧创建管子
                    game.fno % 150 == 0 && new Pipe();
                    // 遍历管子数组更新
                    game.pipeArr.forEach(ele => {
                        ele.update();
                    });
                    break;
                case 5:
                    if (game.bird.y > game.land.y) {
                        game.bird.isDropFloor = true;
                    }
                    // 碰撞后小鸟失去能量
                    game.bird.hasEnergy = false;
                    // 闪屏更新
                    if (game.flashLook) {
                        game.screenFlash -= 0.1;
                    }
                    if (game.screenFlash < 0) {
                        game.flashLook = false;
                        game.screenFlash = 1;
                    }
                    // 没落地更新小鸟
                    if (!game.bird.isDropFloor) {
                        // 更新小鸟
                        game.fno % 3 == 0 && (game.bird.birdFon++);
                        game.bird.y += game.bird.downSpeed * game.bird.birdFon;
                    } else {
                        game.fno % 3 == 0 && (game.boomNum++);
                        // 坠地动画结束后进入场景6
                        if (game.boomNum >= 16) {
                            this.enter(6);
                        };
                        game.boomImg = game.R["b" + game.boomNum];
                    }
                    break;
                case 6:
            }
    
        }
        render() {
            switch (this.sceneNumber) {
                case 1:
                    // 场景一静态天空大地
                    game.backGround.render();
                    game.land.render();
                    // 渲染小鸟
                    game.bird.render();
                    // 渲染logo和button
                    game.ctx.drawImage(game.logo, game.canvas.width / 2 - 89, game.logoY);
                    game.ctx.drawImage(game.button_play, game.canvas.width / 2 - 58, game.buttonY);
                    break
    
                case 2:
                    // 场景二静态天空大地
                    game.backGround.render();
                    game.land.render();
                    // 渲染小鸟
                    game.bird.render();
                    // 渲染提示板
                    game.ctx.save();
                    game.ctx.globalAlpha = game.tutorialOpacity
                    game.ctx.drawImage(game.tutorial, game.canvas.width / 2 - 57, game.land.y - 180);
                    game.ctx.restore();
                    break;
    
                case 3:
                    // 场景三静态天空大地
                    game.backGround.render();
                    game.land.render();
                    // 渲染小鸟
                    game.bird.render();
                    game.ctx.drawImage(game.autor, game.canvas.width / 2 - 175, game.autorY);
                    break;
                case 4:
                    // 场景四静态天空大地
                    game.backGround.render();
                    game.land.render();
                    // 遍历管子数组渲染
                    game.pipeArr.forEach(ele => {
                        ele.render();
                    });
                    // 渲染小鸟
                    game.bird.render();
                    // 渲染分数
                    game.scoreRender();
                    break;
                case 5:
                    game.ctx.globalAlpha = game.screenFlash;
                    // 场景五静态天空大地
                    game.backGround.render();
                    game.land.render();
                    // 遍历管子数组渲染
                    game.pipeArr.forEach(ele => {
                        ele.render();
                    });
    
                    // 坠地动画
                    if (!game.bird.isDropFloor) {
                        // 渲染小鸟
                        game.bird.y <= game.land.y && game.bird.render();
                    } else {
                        game.boomImg && game.ctx.drawImage(game.boomImg, game.bird.x - 93, game.land.y - 350)
                    }
                    // 渲染分数
                    game.scoreRender();
                    break;
                case 6:
                    // 场景五静态天空大地
                    game.backGround.render();
                    game.land.render();
                    game.ctx.drawImage(game.text_game_over, game.canvas.width / 2 - 102, game.canvas.height * (1 - 0.618))
                        // 渲染分数
                    game.scoreRender();
                    break;
    
    
            }
    
        }
        enter(num) {
            this.sceneNumber = num;
            switch (this.sceneNumber) {
                case 1:
                    // 每次进入场景一瞬间还原位置
                    game.fno = 0;
                    game.logoY = -48;
                    game.buttonY = game.canvas.height;
                    game.bird = new Bird();
                    game.score = 0;
                    break;
                case 2:
                    // 场景二位置重设
                    game.bird.y = 150;
                    break;
                case 3:
                    // 场景三位置重设
                    game.autorY = -330;
                    break;
                case 4:
                    game.pipeArr = [];
                    game.bird.birdFon = 0;
                    break;
                case 5:
                    game.boomNum = 0;
                    game.screenFlash = 1;
                    game.flashLook = true;
                    game.bird.isDropFloor = false;
            }
    
        }
        bindEvenet() {
            // document.onclick = (event) => {
            //     clickHandler.call(this, event.clientX, event.clientY);
            // };
            document.addEventListener("touchstart", (event) => {
                clickHandler.call(this, event.touches[0].clientX, event.touches[0].clientY);
            })
    
            function clickHandler(mousex, mousey) {
                switch (this.sceneNumber) {
                    case 1:
                        if (mousey > game.buttonY && mousey < game.buttonY + 70 && mousex > game.canvas.width / 2 - 58 && mousex < game.canvas.width / 2 + 58) {
                            this.enter(2)
                        }
                        break;
                    case 2:
                        this.enter(3);
                        break;
                    case 3:
                        this.enter(4)
                        break;
                    case 4:
                        game.bird.fly();
                        break;
                    case 6:
                        this.enter(1)
                        break;
    
                }
            }
    
    
    
        }
    }

    接着就是各个演员类,例:小鸟,管子,天空,大地

    // 天空背景类
    class BackGround {
        constructor() {
            this.bg_day = game.R['bg_day'];
            this.x = 0;
            this.y = game.canvas.height * 0.73 - 412;
            this.h = 512;
            this.w = 288;
            this.speed = 1.8;
    
    
        };
        // 更新函数
        update() {
            this.x -= this.speed;
            if (this.x < -this.w) {
                this.x = 0;
            }
        };
        // 渲染函数
        render() {
            game.ctx.fillStyle = "#4ec0ca";
            game.ctx.fillRect(0, 0, game.canvas.width, this.y);
            game.ctx.fillStyle = "#5ee270";
            game.ctx.fillRect(0, this.y + this.h - 1, game.canvas.width, game.canvas.height - this.y - this.h);
            game.ctx.drawImage(this.bg_day, this.x, this.y);
            game.ctx.drawImage(this.bg_day, this.x + this.w, this.y);
            game.ctx.drawImage(this.bg_day, this.x + this.w * 2 - this.speed, this.y);
        }
    }
    class Bird {
        constructor() {
            // 鸟颜色随机
            this.color = parseInt(Math.random() * 3);
            // 小鸟翅膀状态
            this.birdWing = 0;
            // 小鸟资源
            this.birdArr = [
                game.R["bird" + this.color + "_0"],
                game.R["bird" + this.color + "_1"],
                game.R["bird" + this.color + "_2"],
            ];
    
            // 小鸟坐标
            this.x = game.canvas.width / 2;
            this.y = game.canvas.height * (1 - 0.618);
            // 小鸟的旋转角度
            this.d = 0;
            // 小鸟每帧旋转角度
            this.framD = 0.023;
            // 小鸟帧
            this.birdFon = 0;
            // 下落速度
            this.downSpeed = 0.53;
            // 上升速度
            this.upSpeed = 0.18;
            // 是否有能量
            this.hasEnergy = false;
            // 能量数量
            this.energyNum = 16;
            // 是否落地
            this.isDropFloor = false;
        };
        // 更新
        update() {
            // 小鸟翅膀切换
            this.wingUpdate();
            // 小鸟帧数自增
            game.fno % 4 == 0 && this.birdFon++;
            // 没有能量下落加速度
            if (!this.hasEnergy) {
                this.y += this.downSpeed * this.birdFon;
                // 旋转
                this.d += this.framD;
            } else {
                // 有能量上升加速度
                this.y += this.upSpeed * parseInt((this.birdFon - this.energyNum));
                // 上升时旋转放缓
                game.fno % 4 == 0 && (this.d += this.framD);
                // 当能量耗尽关掉能量开关
                if (this.birdFon > this.energyNum) {
                    this.hasEnergy = false;
                    this.birdFon = 0;
                }
            }
    
            // 触碰大地结束
            if (this.y > game.land.y) {
                this.isDropFloor = true;
                game.sceneManager.enter(5);
            }
            // 不能飞过天空
            if (this.y < 0) {
                this.y = 0;
            }
            // 计算小鸟上右下左位置
            this.T = this.y - 12;
            this.R = this.x + 17;
            this.B = this.y + 12;
            this.L = this.x - 17;
    
        };
        // 渲染
        render() {
            game.ctx.save();
            game.ctx.translate(this.x, this.y);
            game.ctx.rotate(this.d);
            game.ctx.drawImage(this.birdArr[this.birdWing], -24, -24)
            game.ctx.restore();
        };
        // 翅膀更新
        wingUpdate() {
            game.fno % 8 == 0 && this.birdWing++;
            if (this.birdWing > 2) {
                this.birdWing = 0;
            }
        };
        // 小鸟飞翔
        fly() {
            // 点击屏幕时赋予小鸟能量
            // 赋予能量
            this.hasEnergy = true;
            // 重新计算帧数
            this.birdFon = 0;
            this.d = -0.6
        };
    }
    class Land {
        constructor() {
            this.land = game.R['land'];
            this.x = 0;
            this.y = game.canvas.height * 0.73;
            this.w = 336;
            this.h = 112;
            this.speed = 3;
        };
        // 更新函数
        update() {
            this.x -= this.speed;
            if (this.x < -this.w) {
                this.x = 0;
            }
    
        };
        // 渲染函数
        render() {
            game.ctx.fillStyle = "#ded895";
            game.ctx.fillRect(0, this.y + this.h - 1, game.canvas.width, game.canvas.height - this.y - 112);
            game.ctx.drawImage(this.land, this.x, this.y);
            game.ctx.drawImage(this.land, this.x + this.w, this.y);
            game.ctx.drawImage(this.land, this.x + this.w * 2, this.y);
        }
    }
    class Pipe {
        constructor() {
            this.pipe_down = game.R["pipe_down"];
            this.pipe_up = game.R["pipe_up"];
            this.w = 52;
            this.allH = 320;
            this.x = game.canvas.width;
            this.speed = 3;
            // 管子间隔
            this.spacing = 160;
            // 上管子高度随机
            this.height1 = parseInt(Math.random() * 121) + 100;
            console.log(this.height1)
                // 下管子高度自适应
            this.height2 = game.land.y - this.height1 - this.spacing;
            game.pipeArr.push(this);
            // 小鸟是否越过当前管子
            this.leapOver = false;
        };
        // 更新
        update() {
            this.x -= this.speed;
            if (this.x <= -52) {
                this.removeSelf();
            }
            // 碰撞检测
            if (game.bird.R > this.x && game.bird.L < this.x + 52) {
                if (game.bird.T < this.height1 || game.bird.B > this.height1 + this.spacing) {
                    // 死亡进入场景5
                    game.sceneManager.enter(5)
                }
            }
            // 小鸟是否跃过当前管子
    
            if (!this.leapOver) {
                if (game.bird.L > this.x + 52) {
                    game.score++;
                    this.leapOver = true;
    
                }
            }
        };
        // 渲染
        render() {
            game.ctx.drawImage(this.pipe_down, 0, this.allH - this.height1, this.w, this.height1, this.x, 0, this.w, this.height1);
            game.ctx.drawImage(this.pipe_up, 0, 0, this.w, this.height2, this.x, this.height1 + this.spacing, this.w, this.height2)
    
        };
        // 删除自己
        removeSelf() {
            game.pipeArr.forEach((ele, i) => {
                if (ele === this) {
                    game.pipeArr.splice(i, 1)
                }
            });
        }
    }

    最后调用中介者类

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0,user-scalable=no" id="viewport" />
        <meta autor="xiaBin">
        <title>xiaBin_flappBird</title>
        <style>
            * {
                padding: 0;
                margin: 0;
            }
            
            body {
                overflow: hidden;
            }
        </style>
    </head>
    
    <body>
        <canvas id="xBinCanvas">
    
        </canvas>
        <script src="js/game.js"></script>
        <script src="js/sceneManager.js"></script>
        <script src="js/backGround.js"></script>
        <script src=js/land.js></script>
        <script src=js/pipe.js></script>
        <script src=js/bird.js></script>
        <script>
            let game = new Game({
                "canvasId": "xBinCanvas",
                "jsonUrl": "R.json"
            })
        </script>
        <script>
        </script>
    </body>
    
    </html>
  • 相关阅读:
    struts.custom.i18n.resources 如何配置多个资源文件?
    axis : java.lang.NoSuchMethodError
    org/apache/commons/discovery/tools/DiscoverSingleton
    java.lang.NoClassDefFoundError: javax/wsdl/OperationType
    .propertie文件注释
    数据库的名称尽量要以英文开头,如果全部输数字的话可能会出错的
    单例模式
    java异常 之 异常的层次结构
    php抓取网页
    &#39;hibernate.dialect&#39; must be set when no Connection available
  • 原文地址:https://www.cnblogs.com/tengx/p/12501947.html
Copyright © 2020-2023  润新知