开维游戏引擎(Kaiwei Engine)是基于js设计的跨平台游戏引擎。内核c++编写,v8引擎封装游戏函数,Assembly实现htm跨平台高效运行。下面以“扫雷”为例演示。
运行效果:
代码如下:
// 扫雷小游戏
// 玩儿法:
// 左键单击:翻开格子
// 右键单击或左键长按:标记地雷,网页版无法右键控制,用长按标记雷
// 左键双击:在已翻开的数字格上,如果周围已标记的红旗数等于数字,则自动翻开周围未翻开的格子
// 初始化游戏
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;
}
}
代码功能:
- main.js:游戏初始化,设置背景音乐等
- util.js:公共类,设置文字等
- Card.js:地雷数据类
- LevelScene.js:初级、中级、高级菜单
- MineSweep.js:扫雷操作类
开维游戏引擎代码简单,函数精简,尽量用极少的js脚本实现游戏功能。 代码跨平台通用,一次编写,多端运行。 支持导出生成exe或html目录直接运行,导出的网页版游戏调试页面:
开维游戏引擎下载: www.ikaiwei.com/download/ga…
扫雷页面演示: www.ikaiwei.com/gamejs/exam…
源码下载: github.com/ctrljshaha/…