开维游戏引擎实例:扫雷

0 阅读8分钟

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

运行效果:

image.png

代码如下:


// 扫雷小游戏

// 玩儿法:
// 左键单击:翻开格子
// 右键单击或左键长按:标记地雷,网页版无法右键控制,用长按标记雷
// 左键双击:在已翻开的数字格上,如果周围已标记的红旗数等于数字,则自动翻开周围未翻开的格子

// 初始化游戏
game.initSize(900,900)
game.setFPS(10); // 设置帧率

// 设置窗口log和标题
var texture = game.getResource().getTexture("logo.png"); // 另一种获取纹理数据对象
var window = game.getWindow(); //获取游戏窗口
window.setIcon(texture); // 设置窗口图标
window.setTitle("扫雷小游戏");

// 设置声音类
var audio = new Audio();
audio.setMusicVolume(1); // 设置背景音乐音量大小
audio.setSoundVolume(0.5); // 设置音效音量大小
//audio.playSound("dj.mp3"); // 播放音效
//audio.playSound("baoza.mp3"); // 播放音效

new LevelScene();
game.setKeyCallBack((key,action)=>{
    if(action==0){
    }
});

// 游戏运行
game.run();
class Card {
	// 雷
	isbomb = false;
	// UI元素
	sprite_ = null;
	// 点击时间
	sTime =0;
	size = 0;
	// 0-8雷数  9雷
	category = 0;
	flip = false;
	row = 0;
	col = 0;
	flag=false;

	getSprite = function()
	{
		return this.sprite_;
	}

	constructor(scene,row,col,size)
	{
		this._scene = scene
		this.row = row;
		this.col = col;
		this.flip = false;
		this.flag = false;
		let interval = 50;
		this.sprite_ = new Sprite();
		this.size = size;
		scene.addNode(this.sprite_);

		const cache_ = game.getResource();
		let bg = cache_.getTexture("leibg.png");
		this.sprite_.setTexture(bg);
		this.sprite_.setSize(size, size);		
		this.sprite_.setPosition((col+1)*interval, (row+1)*interval+10);
		// 0是左键  1是右键
		this.sprite_.click((type,x,y)=>{
			let time = new Date().getTime();
			const etime = time;
			const st = etime - this.sTime;

			if(type ===0){
				if(st < 600)
				{
					// 双击
					log("双击");
					MineSweep.click(2,this.row,this.col);
				}else {
					log("单击");
					//click
					MineSweep.click(0,this.row,this.col);
				}
			}else if (type ===1){
				// 标记
				MineSweep.click(1,this.row,this.col);
			}

			this.sTime = time;
			this.changeStatus()
		})

		this.sprite_.longClick((type,x,y)=>{
			// 浏览器环境适配
			if(window){
				// 标记
				MineSweep.click(1,this.row,this.col);
			}
		})

	}



	// 显示雷数量
	showLeiNum()
	{
		let num = this.category;
		let cache_ = game.getResource();
		let bg = cache_.getTexture("lei0.png");
		this.sprite_.setTexture(bg);

		let position = this.sprite_.getPosition();
		// log("position:"+JSON.stringify(position)+" num:"+num);

                    	let x = position.x;
		let y = position.y;

		if(num!==9 && num!==0)
		{
			let number = this.size/3;
			let lab = new Label();
			lab.setPosition(x+number, y+number-10);
			lab.setSize(30, 30);
			lab.setFont("st.ttf", 20);
			lab.setText(""+num);
			lab.setTextColor(1.0,1.0,1.0,1.0);
			lab.setColor(1.0,1.0,1.0,0);
			this.sprite_.addNode(lab);
		}
	}

	// 改变状态
	changeStatus()
	{
		const cache_ = game.getResource();
		let bg = cache_.getTexture("leibg.png");
		if(this.flip && !this.flag)
		{
			if(this.category==9)
			{
				bg = cache_.getTexture("btlei.png");
				this.sprite_.setTexture(bg);
				//Play.gameEndLogic();
			}else {
				// bg = cache_.getTexture("lei0.png");
				// this.sprite_.setTexture(bg);
				this.showLeiNum();
			}
		}else {
			bg = cache_.getTexture("leibg.png");
			this.sprite_.setTexture(bg);
		}

		if(this.flag)
		{
			bg = cache_.getTexture("hongqi.png");
			this.sprite_.setTexture(bg);
		}

	}


	get flip() {
		return this.flip;
	}

	set flip(value) {
		log("flip(value):"+value);
		this.flip = value;
	}
}
class LevelScene{

    constructor(){
        let scene = Util.bj();
        let cache_ = game.getResource();
        let w = game.getWindow().getWidth();
        let h = game.getWindow().getHeight();
        let left = w*0.3;
        let top = 60;
        let width=332;
        let height=106;
        let chujichangBg = cache_.getTexture("chujichang.png");
        let chujichang = new Sprite();
        chujichang.setTexture(chujichangBg);
        chujichang.setSize(width,height);
        chujichang.setPosition(left,top);
        chujichang.setColor(1,1,1,1);
        chujichang.setName("chujichang");
        scene.addNode(chujichang);
        chujichang.click((type,x,y)=>{
            let play = new MineSweep(9,9,10);
        });


        let gaojichangPg = cache_.getTexture("gaojichang.png");
        const gaojichang = new Sprite();
        gaojichang.setTexture(gaojichangPg);
        gaojichang.setSize(width,height);
        gaojichang.setPosition(left,top*2+height);
        gaojichang.setColor(1,1,1,1);
        scene.addNode(gaojichang);
        gaojichang.click((type,x,y)=>{
            let play = new MineSweep(16,9,30);
        });

        let dashichangPg = cache_.getTexture("dashichang.png");
        const dashichang = new Sprite();
        dashichang.setTexture(dashichangPg);
        dashichang.setSize(width,height);
        dashichang.setPosition(left,top*3+height*2);
        dashichang.setColor(1,1,1, 1);
        scene.addNode(dashichang);
        dashichang.click((type,x,y)=>{
            let play = new MineSweep(16,16,50);
        });
    }
}
class Util{
    static scene;
    static bj=()=>{
        let scene = new Scene();
        this.scene = scene;
        game.pushScene(scene);

        const cache_ = game.getResource();

        let w = game.getWindow().getWidth();
        let h = game.getWindow().getHeight();

        let bg = cache_.getTexture("mainbg.png");
        const node = new Sprite();
        node.setTexture(bg);
        node.setSize(w,h);
        node.setPosition(0,0);
        node.setColor(1,1,1,1);
        scene.addNode(node);

        return scene;
    }

    static newSprite(options={}){
        let config = {
            x: 0,
            y: 0,
            width: 50,
            height: 30,
            clickCb: undefined,
            texture: "",
            ...options
        };
        if(!this.scene){
            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);
        this.scene.addNode(sprite);

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


    static newText(x,y,text,width=50,height=30){
        if(!this.scene){
            return;
        }

        const lab = new Label();
        lab.setPosition(x, y);
        lab.setSize(width, height);
        lab.setFont("st.ttf", 20);
        lab.setText(text);
        lab.setTextColor(1.0,0.5,0.2,1.0);
        lab.setColor(1.0,0.5,0.2,0);
        this.scene.addNode(lab);
        return lab;
    }
}
class MineSweep {
    /**
     * 行数
     */
    static row;
    /**
     * 列数
     */
    static col;

    /**
     * 地雷总数
     */
    static leiNum = 10;

    /**
     * 剩余地雷数
     */
     static remain = 10;

    /**
     * 格子
     */
    static blocks = [];

    /**
     * 游戏是否结束
     */
    static over = false;

    /**
     * 游戏是否开始
     */
    static begin = false;
    /**
     * 玩家是否胜利
     */
    static win = false;
    static firstClickNormal = false;
    static startTime;
    static scene;

    constructor(row, col, sum) {
        MineSweep.initRowColSum(row, col, sum);
    }

    /**
     * 开始新游戏
     */
    static restart() {
        this.initRowColSum(this.row, this.col, this.leiNum);
    }

    /**
     * 初始化 行列 地雷数量
     *
     * @param row
     * @param col
     * @return
     */
    static initRowColSum(row, col, sum) {
        this.scene = Util.bj();
        this.row = row;
        this.col = col;
        this.leiNum = sum;
        this.remain = this.leiNum;
        this.firstClickNormal = false;
        this.over = false;
        this.begin = false;
        this.win = false;
        this.initBlocks();

        let w = game.getWindow().getWidth();

        // 返回主页
        Util.newSprite({
            x: w-100,
            y: 5,
            width: 90,
            height: 50,
            texture: 'zaicitiaozhan.png',
            clickCb: ()=>{
                MineSweep.restart();
            }
        })

        // 返回按钮
        Util.newSprite({
            x: 10,
            y: 10,
            width: 60,
            height: 40,
            texture: 'btback.png',
            clickCb: ()=>{
                new LevelScene();
            }
        })

        return this;
    }

    /**
     * 初始化方块,随机产生地雷
     */
    static initBlocks() {
        // 已有地雷数
        let count = 0;

        let row = this.row;
        let col = this.col;
        let sum = this.leiNum;

        this.blocks = [];
        let blocks = this.blocks;
        // 剩余地雷数
        this.remain = this.leiNum;

        for (let i = 0; i < row; i++) {
            let arr = [];
            for (let j = 0; j < col; j++) {
                arr.push(new Card(this.scene,i,j,40));
            }
            blocks.push(arr);
        }

        // 随机指定地雷
        while (count < sum) {
            let i = parseInt(Math.random() * row);
            let j =parseInt (Math.random() * col);
            if (blocks[i][j].category != 9) {
                blocks[i][j].category = 9;
                count++;
            }
        }

        // 计算非地雷方块的数值
        for (let i = 0; i < row; i++) {
            for (let j = 0; j < col; j++) {
                if (blocks[i][j].category != 9) {
                    // 左上
                    if (i - 1 >= 0 && j - 1 >= 0 && blocks[i - 1][j - 1].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 上
                    if (i - 1 >= 0 && blocks[i - 1][j].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 右上
                    if (i - 1 >= 0 && j + 1 < col && blocks[i - 1][j + 1].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 左
                    if (j - 1 >= 0 && blocks[i][j - 1].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 右
                    if (j + 1 < col && blocks[i][j + 1].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 左下
                    if (i + 1 < row && j - 1 >= 0 && blocks[i + 1][j - 1].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 下
                    if (i + 1 < row && blocks[i + 1][j].category == 9) {
                        blocks[i][j].category++;
                    }
                    // 右下
                    if (i + 1 < row && j + 1 < col && blocks[i + 1][j + 1].category == 9) {
                        blocks[i][j].category++;
                    }
                }
            }
        }

        return this;
    }

    /**
     * 翻开相邻的方块
     * 如果方块为空(category为0),则递归地翻开与空相邻的方块
     * fanNumber  翻开相邻的方块的数量
     */
     static flipAround(i, j,fanNumber) {
        let blocks = this.blocks;
        let row = this.row;
        let col = this.col;
        let block = blocks[i][j];
        block.changeStatus()

        // 左上
        if (i - 1 >= 0 && j - 1 >= 0 && !blocks[i - 1][j - 1].flip && !blocks[i - 1][j - 1].flag) {
            blocks[i - 1][j - 1].flip = true;
            blocks[i - 1][j - 1].changeStatus();
            fanNumber++;
            this.clickOver(blocks[i - 1][j - 1]);
            if (blocks[i - 1][j - 1].category == 0) {
                this.flipAround(i - 1, j - 1, fanNumber);
            }
        }
        // 上
        if (i - 1 >= 0 && !blocks[i - 1][j].flip && !blocks[i - 1][j].flag) {
            blocks[i - 1][j].flip = true;
            blocks[i - 1][j].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i - 1][j]);
            if (blocks[i - 1][j].category == 0) {
                this.flipAround(i - 1, j, fanNumber);
            }
        }
        // 右上
        if (i - 1 >= 0 && j + 1 < col && !blocks[i - 1][j + 1].flip && !blocks[i - 1][j + 1].flag) {
            blocks[i - 1][j + 1].flip = true;
            blocks[i - 1][j + 1].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i - 1][j + 1]);
            if (blocks[i - 1][j + 1].category == 0) {
                this.flipAround(i - 1, j + 1, fanNumber);
            }
        }
        // 左
        if (j - 1 >= 0 && !blocks[i][j - 1].flip && !blocks[i][j - 1].flag) {
            blocks[i][j - 1].flip = true;
            blocks[i][j - 1].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i][j - 1]);
            if (blocks[i][j - 1].category == 0) {
                this.flipAround(i, j - 1, fanNumber);
            }
        }
        // 右
        if (j + 1 < col && !blocks[i][j + 1].flip && !blocks[i][j + 1].flag)
        {
            blocks[i][j + 1].flip = true;
            blocks[i][j + 1].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i][j + 1]);
            if (blocks[i][j + 1].category == 0) {
                this.flipAround(i, j + 1, fanNumber);
            }
        }
        // 左下
        if (i + 1 < row && j - 1 >= 0 && !blocks[i + 1][j - 1].flip && !blocks[i + 1][j - 1].flag) {
            blocks[i + 1][j - 1].flip = true;
            blocks[i + 1][j - 1].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i + 1][j - 1]);
            if (blocks[i + 1][j - 1].category == 0) {
                this.flipAround(i + 1, j - 1, fanNumber);
            }
        }
        // 下
        if (i + 1 < row && !blocks[i + 1][j].flip && !blocks[i + 1][j].flag) {
            blocks[i + 1][j].flip = true;
            blocks[i + 1][j].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i + 1][j]);
            if (blocks[i + 1][j].category == 0) {
                this.flipAround(i + 1, j, fanNumber);
            }
        }
        // 右下
        if (i + 1 < row && j + 1 < col && !blocks[i + 1][j + 1].flip && !blocks[i + 1][j + 1].flag) {
            blocks[i + 1][j + 1].flip = true;
            blocks[i + 1][j + 1].changeStatus()

            fanNumber++;
            this.clickOver(blocks[i + 1][j + 1]);
            if (blocks[i + 1][j + 1].category == 0) {
                this.flipAround(i + 1, j + 1, fanNumber);
            }
        }
        return fanNumber;
     }



    /**
     * 当双击方块周围已标记雷数等于该位置数字时,相当于对该方块周围未打开的方块均进行一次左键单击操作
     * 地雷未标记完全时使用双击无效。若数字周围有标错的地雷,则游戏结束
     */
    static doubleClickFlip(i, j) {
        let blocks = this.blocks;
        let number = 0;
        let row = this.row;
        let col = this.col;
        let wrong = false;
        // 左上
        if (i - 1 >= 0 && j - 1 >= 0) {
            let block = blocks[i - 1][j - 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 上
        if (i - 1 >= 0 ) {
            let block = blocks[i - 1][j];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 右上
        if (i - 1 >= 0 && j + 1 < col) {
            let block = blocks[i - 1][j + 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 左
        if (j - 1 >= 0) {
            let block = blocks[i][j - 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 右
        if (j + 1 < col) {
            let block = blocks[i][j + 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 左下
        if (i + 1 < row && j - 1 >= 0) {
            let block = blocks[i + 1][j - 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 下
        if (i + 1 < row) {
            let block = blocks[i + 1][j];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }
        // 右下
        if (i + 1 < row && j + 1 < col) {
            let block = blocks[i + 1][j + 1];
            if (!block.flip) {
                number+=block.flag?1:0;
            }
        }

        // 数字和标旗数量相同
        if (number == blocks[i][j].category) {
            let flipNumber = this.flipAround(i, j,0);
        }else{
            // 数字和标旗数量不相同
            // blocks[i][j].cha = true;
        }
        this.isOver();
    }


    /**
     * 翻开所有方块
     */
    static flipAll() {
        let row = this.row;
        let col = this.col;
        for (let i = 0; i < row; i++) {
            for (let j = 0; j < col; j++) {
                let blockElement = this.blocks[i][j];
                blockElement.flip = true;
                blockElement.changeStatus()
            }
        }
    }

    /**
     * 游戏操作
     * @param type 0:单击 ,1:标记 2 双击
     * @param i
     * @param j
     * @return 0 单击;1 标记;2 双击成功 ;3 双击成功无反应  4 双击失败 5 单击标记无反应
     */
    static click(type, i , j) {

        let blocks = this.blocks;
        let row = this.row;
        let col = this.col;
        let win = this.win;
        let block = blocks[i][j];

        // 单击 未翻开 是雷
        if(type == 0 && !block.flip && block.category == 9 && !this.firstClickNormal){
            this.restart();
            return this.click(type,i,j);
        }

        // 对局没结束 坐标没越界 方块没翻开
        if ((!this.over || !win) && i >= 0 && i < row && j >= 0 && j < col && !block.flip) {
            if (type == 0 && !block.flag){
                log("click 单击")
                // 第一次单击时开始记时
                if (!this.begin) {
                    this.begin = new Date();
                    this.startTime = new Date();
                }
                log(JSON.stringify(block))
                // 左键点击翻开方块
                block.flip = true;
                block.changeStatus();

                if (block.category == 0) {
                    // 空白 ,翻开相邻的
                    this.flipAround(i, j,0);
                } else{
                    this.clickOver(block);
                }
                this.firstClickNormal = true;
                if(block.category !== 9){
                    log("dj.mp3")
                    let audio = new Audio();
                    audio.playSound("dj.mp3"); // 播放音效
                }
            }
            else if (type == 1) {
                log("click 标记")
                block.flag = !block.flag;
                block.changeStatus();
            }else if (type == 2) {
                // 双击(未点开的) 同单击
                log("click 双击(未点开的) 同单击")
                return this.click(0,i,j);
            }
            this.isOver();
        } else if ((!this.over || !win) && i >= 0 && i < row && j >= 0 && j < col && type == 2) {
            // 方块已经被翻开 且不是地雷
            if (block.flip && block.category < 9) {
                log("click 双击")
                // 双击标旗 或 空白  不做任何处理
                if( block.category==0){

                }else{
                    audio.playSound("dj.mp3"); // 播放音效
                    // 双击成功
                    return this.doubleClickFlip(i, j);
                }
            }
        }
    }

    /**
     * 点到雷
     * @param block
     */
    static clickOver(block){
        if(block.category == 9) {
                this.over = true;
                this.begin = false;
                this.flipAll();
                this.gameEnd(false);
                audio.playSound("baoza.mp3"); // 播放音效
        }
    }

    /**
     * 游戏是否结束( 非雷格子全部翻开)
     */
    static isOver(){
        for (let block of this.blocks) {
            for (let block1 of block) {
                // 非雷 未翻开
                if(!block1.flip && block1.category<9){
                    return false;
                }
                // 雷 未标记
                if(!block1.flag && block1.category==9 && !block1.flip){
                    return false;
                }
            }
        }

        this.over = true;
        this.gameEnd();
        return true;
    }

    static gameEnd(win){
        let w = game.getWindow().getWidth();
        let h = game.getWindow().getHeight();
        if(win === undefined){
            win = this.isWin();
        }
        let time = new Date();
        log("游戏结束:"+win)
        if(win){
            let number = time.getTime() - this.startTime.getTime();
            Util.newText(150,10,"游戏胜利,耗时:"+parseInt(number/1000)+"秒",220,30)
        }else {
            Util.newText(100,10,"游戏失败",80,30)
        }
    }

    /**
     * 游戏是否胜利:标记数量和雷格子数量是否相等
     */
    static isWin(){
        let flagNum = 0;
        for (let block of this.blocks) {
            for (let block1 of block) {
                // 非雷 未翻开
                if(block1.flag && block1.category===9){
                    flagNum++
                }
            }
        }

        if(flagNum === this.leiNum){
            this.win = true;
        }else{
            this.win = false;
        }
        return this.win;
    }
}

代码功能:

  1. main.js:游戏初始化,设置背景音乐等
  2. util.js:公共类,设置文字等
  3. Card.js:地雷数据类
  4. LevelScene.js:初级、中级、高级菜单
  5. MineSweep.js:扫雷操作类

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

在这里插入图片描述

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

扫雷页面演示: www.ikaiwei.com/gamejs/exam…

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

gamejs.ikaiwei.com/#/Market

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