开维游戏引擎(Kaiwei Engine)是基于js设计的跨平台游戏引擎。内核c++编写,v8引擎封装游戏函数,Assembly技术实现htm跨平台高效运行。下面以“2048小游戏”为实例,演示实现过程。
下载源码,打开gmp工程后,运行即可出现界面:
代码如下:
// 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,
});
}
}
}
代码分及部分:
- main.js:游戏初始化,设置背景音乐等
- util.js:创建场景,设置文字等公用类
- Cell.js:2048每个小格子的类
- GlobalVariable.js:键盘码值类
- game2048.js:2048游戏中合并方格子算法类
开维游戏引擎代码简单,函数精简,尽量用极少的js脚本实现游戏功能。代码跨平台通用,一次编写,多端运行,可以直接导出生成exe或者html目录直接运行:
开维游戏引擎下载: www.ikaiwei.com/download/ga…
2048小游戏html版页面演示: www.ikaiwei.com/gamejs/exam…
源码下载: github.com/ctrljshaha/…