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

0 阅读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/…