开维游戏引擎实例:2048小游戏

48 阅读9分钟

开维游戏引擎(Kaiwei Engine)是基于js设计的跨平台游戏引擎。内核c++编写,v8引擎封装游戏函数,Assembly技术实现htm跨平台高效运行。下面以“2048小游戏”为实例,演示实现过程。

下载源码,打开gmp工程后,运行即可出现界面:

image.png

代码如下:

    // 2048小游戏

    // 初始化游戏引擎
    game.init(); // 默认窗口大小为800*600
    //game.initSize(1024, 768); // 设置游戏主窗口大小,1024*768
    game.setFPS(10);
     
    //  获取游戏主窗口,设置图标和标题
    let window = game.getWindow();
    let texture = game.getResource().getTexture("logo.png");
    window.setIcon(texture);
    window.setTitle("2048小游戏");

    // 设置声音类
    var audio = new Audio();
    audio.setMusicVolume(1); // 设置背景音乐音量大小
    audio.setSoundVolume(0.5); // 设置音效音量大小
    audio.playSound("dj.mp3"); // 播放音效
     
    // 创建游戏类,初始化在其构造函数中
    new Game2048();
     
    // 设置键盘回调函数,定义上下左右健
    game.setKeyCallBack((key,action)=>{
        let type = "";
        if (key == GlobalVariable.KEY_W || key == GlobalVariable.KEY_UP){
            type = "up";
        }
        else if (key == GlobalVariable.KEY_S || key == GlobalVariable.KEY_BOTTOM){
            type =  "down";
        }
        else if (key == GlobalVariable.KEY_A || key == GlobalVariable.KEY_LEFT){
            type =  "left";
        }
       else if (key == GlobalVariable.KEY_D || key == GlobalVariable.KEY_RIGHT){
            type =  "right";
        }
        
        //log("key "+key+" action "+action+" type "+type);

        Game2048.logic(type)
    });

    // 运行游戏
    game.run();


    // 工具类:生成对象

    class Util{
        
        // 函数功能:创建背景场景,并返回场景和背景节点
        static bj=(options={})=>{

            let w = game.getWindow().getWidth();
            let h = game.getWindow().getHeight();
            let config = {
                x: 0,
                y: 0,
                width: w,
                height: h,
                picture: "bg.jpg",
                ...options
            };

            // 创建场景
            let scene = new Scene();
            GlobalVariable.scene = scene;
            game.pushScene(scene);

             // 添加游戏背景图   
            const cache_res = game.getResource();
            let bg = cache_res.getTexture(config.picture);
            const node = new Sprite();
            node.setTexture(bg);
            node.setSize(config.width,config.height);
            node.setPosition(config.x,config.y);
            node.setColor(1,1,1,1);
            scene.addNode(node);

            // 返回场景对象
            return {scene:scene,backgroundNode:node};
        }

        // 函数功能:创建精灵节点,并返回精灵
        static newSprite(options={}){
            let config = {
                x: 0,
                y: 0,
                width: 50,
                height: 30,
                clickCb: undefined,
                texture: "",
                ...options
            };
            if(!GlobalVariable.scene){
             //   log("scene is not exist");
                return;
            }
            const cache_ = game.getResource();
            let bg = cache_.getTexture(config.texture);
            let sprite = new Sprite();
            sprite.setTexture(bg);
            sprite.setSize(config.width, config.height);
            sprite.setPosition(config.x, config.y);
            GlobalVariable.scene.addNode(sprite);

            sprite.click(()=>{
                if (config.clickCb !== undefined && config.clickCb instanceof Function){
                    config.clickCb();
                }
            });
            return sprite;
        }


        // 函数功能:创建文本节点,并返回文本对象
        static newText(options){
            
            // 如果场景不存在,返回
            if(!GlobalVariable.scene){
                return;
            }
            
            // 文本节点参数
            let config = {
                x: 0,
                y: 0,
                width: 50,
                height: 30,
                text: "",
                fontSize: 20,
                textColor: [1,0,0],
                ...options
            };

            // 打印日志,调试用
            log(JSON.stringify(config));

            // 设置lab标签
            const lab = new Label();
            lab.setPosition(config.x, config.y);
            lab.setSize(config.width, config.height);
            lab.setFont("st.ttf", config.fontSize);
            lab.setText(config.text);

            // 设置颜色
            if(config.textColor !== undefined && config.textColor.length === 3){
                let configColor = config.textColor;
                lab.setTextColor(configColor[0],configColor[1],configColor[2],1);
            }else {
                lab.setTextColor(1,0,0,1);
            }
            lab.setColor(1,1,1,0);
            // lab.setbackgroundColor(0,0,0,0);
            GlobalVariable.scene.addNode(lab);
            return lab;
        }

       // 函数功能:获取节点位置和大小
        static getPosition(node){
            if (!node){
                return;
            }
            let x = node.getPosition().x;
            let y = node.getPosition().y;
            
            let width = node.getSize().x;
            let height = node.getSize().y;

            return {x:x, y:y, width:width, height:height};
        }

    }

    // 方格子类

    class Cell{
        // 行
        row;
        // 列
        col;
        // 数字
        number;
        // 游戏对象:格子
        sprite;
        // 游戏对象:格子里的数字文本对象
        textSprite;
        // 行坐标位置
        posX;
        // 列坐标位置
        posY;

        // 构造函数
        constructor(number,x,y,row,col) {
            this.number = number; 
            this.row = row; 
            this.col = col;
            // 创建格子对象
            this.sprite = Util.newSprite({
                x: x,
                y: y,
                width: 50,
                height: 50,
                texture: "0.png"
            });

            let text = number>0?number+"":"";
            this.posX = x;
            this.posY = y;
            // 创建格子里的数字文本对象
            this.textSprite = Util.newText({
                x: this.posX,
                y: this.posY,
                text: text,
                width: 70,
                height: 50,
                fontSize:30
            });
            this.updateNumber();
        }

        // 函数功能:更新格子里的数字文本对象
        updateNumber(){
            let lab = this.textSprite;
            let x = Number(this.posX);
            let y = Number(this.posY);

            // 数字不同位数,设置不同起始位置
            let s = this.number+"";
            let length = s.length;
            if(length == 1){
                x = x + 18;
                lab.setPosition(x, y);
                lab.setFont("st.ttf", 20);
            }else if(length == 2){
                x = x + 15;
                lab.setPosition(x, y);
                lab.setFont("st.ttf", 20);
            }else if(length == 3){
                x = x + 10;
                lab.setPosition(x, y);
                lab.setFont("st.ttf", 20);
            }else if(length == 4){
                lab.setFont("st.ttf", 18);
            }

            lab.setTextColor(0,0,0,1);
            let n = this.number;
            if(n != 0) {
                lab.setText(this.number + "");
            }else {
                lab.setText("");
            }
        }

        // 函数功能:更改图片
        changePicture(){
            if(this.sprite){
                let cache_ = game.getResource();
                let img = cache_.getTexture(this.number + ".png");
                this.sprite.setTexture(img);
                this.updateNumber();
            }
        }
        
        
    }


    // 全局类,全局变量和参数

    class GlobalVariable{

        // 键盘键值
        static KEY_W = 87;
        static KEY_S = 83;
        static KEY_A = 65;
        static KEY_D = 68;
        static KEY_BOTTOM = 40;
        static KEY_UP = 38;
        static KEY_LEFT = 37;
        static KEY_RIGHT = 39;

        static gameOver;

        // 场景
        static scene;

    }


    // 游戏类

    class Game2048 {
        static cells = [];
        static score = 0;
        static scoreText;

        constructor() {
            Game2048.init();
        }

        // 函数功能:初始化游戏
        static init(){
            this.cells = []; // 格子数组
            GlobalVariable.gameOver = false;
            let cells = this.cells;
            let w = game.getWindow().getWidth();
            let h = game.getWindow().getHeight();
            let {scene} = Util.bj();

            // 显示游戏介绍
            Util.newSprite({
                x: w/10+20,
                y: 0,
                width: 552,
                height: 231,
                texture: 'header.png',
                clickCb: ()=>{
                }
            })

            //介绍文字
            Util.newText({
                x: w/5 - 30,
                y: 140,
                text: "玩法: 使用WASD键,或上下左右键,移动数字方块。相邻的两个方块\n                 数字相同,可以合并成一个。",
                width:600,
                height: 70,
                fontSize: 18,
                textColor:[0,0,0]
            })
            /*Util.newText({
                x: w/3,
                y: 180,
                text: "数字相同,可以合并成一个。",
                width:300,
                fontSize: 18,
                textColor:[0,0,0]
            })*/

            // 重新开始按钮
            Util.newSprite({
                x: w/3+25,
                y: h-65,
                width: 147,
                height: 53,
                texture: 'restart.png',
                clickCb: ()=>{
                    Game2048.init(); // 点击回调函数,重新开始游戏
                }
            })

            // 分数
            this.score = 0;
            this.scoreText = Util.newText({
                x: w/3 + 60,
                y: h/2 - 45,
                text: "分数:"+this.score+"",
                width:300
            })

            // 格子背景框
            Util.newSprite({
                x: w/3 - 15,
                y: h/2 - 15,
                width: 240,
                height: 240,
                texture: 'cellbg.png',
                clickCb: ()=>{
                }
            })


            // 生成格子
            let cellX = w/3;
            let cellY = h/2;
            for (let i = 0; i < 4; i++) {
                for (let j = 0; j < 4; j++) {
                    let cell = new Cell(0,cellX+j*53,cellY+i*53,i+1,j+1); // 创建方格
                    cells.push(cell);
                }
            }

            // 设置格子初始值
            cells[0].number = 2;
            cells[4].number = 2;
            cells[0].changePicture();
            cells[4].changePicture();
        }

        // 函数功能:游戏主逻辑
        // 函数参数:direction,方向 up down left right
        static logic(direction){
            if(GlobalVariable.gameOver){
                return;
            }
            
            // 处理玩家输入的方向,移动所有格子并合并相同数字
            if(direction == "up") {
                // 向上移动:从第一列到第四列逐列处理
                for (let col = 1; col < 5; col++) {
                    // 从上到下遍历每一行(从第一行到第四行)
                    // 每行的格子都会被尝试与下面的格子合并
                    for (let row = 1; row < 5; row++) {
                        // 核心合并逻辑:对于当前位置(row, col)的格子
                        // 检查它下面(row+1到第4行)的所有格子
                        for (let i = row + 1; i < 5; i++) {
                            // 将当前格子与其下方第i行的格子尝试合并
                            // getCell(row, col): 当前要移动/合并的目标格子(基准格子)
                            // getCell(i, col): 当前格子下方待检查的格子
                            // addCell()方法会判断两个格子是否能合并(值相同且未合并过)
                            // 如果能合并,则值相加;如果不能,则可能只是移动位置
                            this.addCell(this.getCell(row, col), this.getCell(i, col));
                        }
                    }
                }
            } else if (direction == "down") {
                // 向下移动:从第一列到第四列逐列处理
                for (let col = 1; col < 5; col++) {
                    // 从下到上遍历每一行(从第四行到第一行)
                    // 这样确保底部的格子优先作为合并基准
                    for (let row = 4; row > 0; row--) {
                        // 对于当前位置(row, col)的格子
                        // 检查它上面(row-1到第1行)的所有格子
                        for (let i = row - 1; i > 0; i--) {
                            // 将当前格子与其上方第i行的格子尝试合并
                            // 向下移动时,底部的格子是基准,上方的格子向其靠拢
                            this.addCell(this.getCell(row, col), this.getCell(i, col));
                        }
                    }
                }
            } else if (direction == "left") {
                // 向左移动:从第一行到第四行逐行处理
                for (let row = 1; row < 5; row++) {
                    // 从左到右遍历每一列(从第一列到第四列)
                    for (let col = 1; col < 5; col++) {
                        // 对于当前位置(row, col)的格子
                        // 检查它右边(col+1到第4列)的所有格子
                        for (let i = col + 1; i < 5; i++) {
                            // 将当前格子与其右侧第i列的格子尝试合并
                            // 向左移动时,左边的格子是基准,右边的格子向其靠拢
                            this.addCell(this.getCell(row, col), this.getCell(row, i));
                        }
                    }
                }
            } else if (direction == "right") {
                // 向右移动:从第一行到第四行逐行处理
                for (let row = 1; row < 5; row++) {
                    // 从右到左遍历每一列(从第四列到第一列)
                    // 这样确保右边的格子优先作为合并基准
                    for (let col = 4; col > 0; col--) {
                        // 对于当前位置(row, col)的格子
                        // 检查它左边(col-1到第1列)的所有格子
                        for (let i = col - 1; i > 0; i--) {
                            // 将当前格子与其左侧第i列的格子尝试合并
                            // 向右移动时,右边的格子是基准,左边的格子向其靠拢
                            this.addCell(this.getCell(row, col), this.getCell(row, i));
                        }
                    }
                }
            }


            // 生成新的格子
            this.createNumberCell();
            // 更新分数
            this.scoreText.setText("分数:"+this.score+"");
            // 判断游戏是否结束
            this.gameEnd();
            
           audio.playSound("dj.wav"); // 播放音效

        }

        // 函数功能:随机生成新的格子
        static createNumberCell(){
            let cells = this.cells;

            // 获取所有值为0的格子
            const zeroCells = [];
            if(cells && cells.length > 0){
                for (let i = 0; i < cells.length; i++) {
                    if(cells[i].number == 0){
                        zeroCells.push(cells[i]);
                    }
                }
            }
            if (zeroCells && zeroCells.length > 0){
                // 随机选择一个格子生成数字
                const number = Math.floor(Math.random(0, zeroCells.length));
                const neroCell = zeroCells[number];
                if(neroCell){
                    let b = Math.random() > 0.5;
                    if (b){
                        neroCell.number = 4
                    }else {
                        neroCell.number = 2;
                    }
                    neroCell.changePicture();
                }
            }
        }

         // 函数功能:获取指定行,列的格子
         // 函数参数:row-行号;col-列号
         // 函数返回:方格对象
        static getCell(row,col){
            let cells = this.cells;
            if(cells && cells.length > 0){
                for (let i = 0; i < cells.length; i++) {
                    if(cells[i].row == row && cells[i].col == col ){
                        return cells[i];
                    }
                }
            }
        }

        // 函数功能:根据距离判断是否可以相加:两个格子相邻 或则 中间格子为0
        // 函数返回:true 相邻 false 不相邻
        static canAdd(cell1, cell2){
            let cell;
            let i;
            if(cell1 && cell2) {
                const row = cell1.row;
                const col = cell1.col;
                const row2 = cell2.row;
                const col2 = cell2.col;

                // 同行
                if(row == row2){
                    // 相邻:列距离为1
                    const b = Math.abs(col - col2) == 1;
                    if(!b){
                        // 判断中间是否有格子不为0
                        if(col > col2){
                            for (i = col2+1; i < col; i++) {
                                cell = this.getCell(row, i);
                                if(cell.number > 0){
                                    return false;
                                }
                            }
                            return true;
                        }else {
                            for (i = col+1; i < col2; i++) {
                                cell = this.getCell(row,i);
                                if(cell.number > 0){
                                    return false;
                                }
                            }
                            return true;
                        }
                    }
                    return b;
                }

                // 同列
                if(col == col2){
                    // 相邻:行距离为1
                    const b1 = Math.abs(row - row2) == 1;
                    if(!b1){
                        // 判断中间是否有格子不为0
                        if(row > row2){
                            for (i = row2+1; i < row; i++) {
                                cell = this.getCell(i,col);
                                if(cell.number > 0){
                                    return false;
                                }
                            }
                            return true;
                        }else {
                            for (i = row+1; i < row2; i++) {
                                cell = this.getCell(i,col);
                                if(cell.number > 0){
                                    return false;
                                }
                            }
                            return true;
                        }
                    }
                    return b1;
                }
            }
            return false;
        }

         // 函数功能:格子相加:
         //                 相邻/中间为空 且数字相同,则相加。
         //                 否则交换位置。
         // 函数参数:cell1 被加格子
         //                 cell2 格子
        static addCell(cell1, cell2){
            if(cell1 && cell2){
                const num1 = cell1.number;
                const num2 = cell2.number;
                if(num1 != 0 || num2 != 0){
                    // 两个格子相同且相邻位置
                    if(num1 == num2 && this.canAdd(cell1,cell2)){
                        cell1.number = num1 + num2;
                        cell2.number = 0;
                        this.score = this.score + cell1.number;
                    }
                    // 被加格子为0,交换位置
                    if (num1 == 0){
                        cell1.number = num2;
                        cell2.number = 0;
                    }
                    cell1.changePicture();
                    cell2.changePicture();
                }
            }
        }

        // 游戏结束判断
        static gameEnd(){
            let cells = this.cells;

            // 判断相邻位置是否有相同的格子
            let same = false;
            for (let i = 0; i < cells.length; i++) {
                const cell = cells[i];
                if(cell.number == 0){
                    return;
                }
                const row = cell.row;
                const col = cell.col;
                const right = this.getCell(row, col + 1);
                const down = this.getCell(row + 1, col);
                if(right && right.number == cell.number){
                    same = true;
                    break;
                }
                if(down && down.number == cell.number){
                    same = true;
                    break;
                }
            }
            if(!same){
                GlobalVariable.gameOver = true;
            }
          //  log("same: "+same)

            if (GlobalVariable.gameOver){
              //  log("game over");
                let w = game.getWindow().getWidth();
                let h = game.getWindow().getHeight();
                Util.newText({
                    text: "游戏结束",
                    x: w/3 + 60,
                    y: h/2 - 70,
                    width:90,
                });
            }
        }
    }

代码分及部分:

  1. main.js:游戏初始化,设置背景音乐等
  2. util.js:创建场景,设置文字等公用类
  3. Cell.js:2048每个小格子的类
  4. GlobalVariable.js:键盘码值类
  5. game2048.js:2048游戏中合并方格子算法类

开维游戏引擎代码简单,函数精简,尽量用极少的js脚本实现游戏功能。代码跨平台通用,一次编写,多端运行,可以直接导出生成exe或者html目录直接运行:

image.png

开维游戏引擎下载: www.ikaiwei.com/download/ga…

2048小游戏html版页面演示: www.ikaiwei.com/gamejs/exam…

源码下载: github.com/ctrljshaha/…

开发文档: www.ikaiwei.com/gamejs/api/…