开维游戏引擎(Kaiwei Engine)是基于js设计的跨平台游戏引擎。内核c++编写,v8引擎封装游戏函数,Assembly实现htm跨平台高效运行。下面以“生存者”小游戏为例,演示实现过程。
代码如下:
// 生存者小游戏
game.init()
game.setFPS(10);
var window = game.getWindow(); // 获取资源对象
var texture = game.getResource().getTexture("img/logo.png"); // 获取纹理数据对象
window.setIcon(texture); // 设置主游戏窗口图标
window.setTitle("开维游戏引擎-生存者小游戏"); // 设置主游戏窗口标题
// Audio音乐类,设置背景音乐
var audio = new Audio();
audio.playMusic("sound/bg.ogg"); // 播放背景音乐,循环播放
//audio.stopMusic(); // 停止当前背景音乐
//audio.playSound("sound/1.wav"); // 循环音效,例如按钮点击声、脚步声、爆炸声、技能音效
new Survivor();
game.run();
class Character {
// 角色对象
sprite;
// 用户的角色对象
hero;
// 子弹集合
bullets = [];
// 是否是敌方
enemy;
// 血量
heart = 10;
heartList= [];
// 移动时间
moveTime;
// 移动间隔时间毫秒
moveInterval = 300;
// 创建子弹间隔
createBulletInterval = 1000;
// 创建子弹时间
createBulletTime = new Date().getTime();
// 更新子弹时间
updateBulletTime = new Date().getTime();
// 能否发射子弹
canFire = true;
// 构造函数
constructor(x, y, enemy,hero) {
this.enemy = enemy;
this.moveTime = new Date().getTime();
this.hero = hero;
let img;
if (enemy == 0) {
img = "hero.png";
this.heartUpdate()
} else if (enemy == 1) {
img = "monster.png";
this.canFire = false;
} else if (enemy == 2) {
img = "monster2.png";
}
// 创建角色
this.sprite = Util.newSprite({
x: x,
y: y,
width: 100,
height: 100,
texture: img,
});
// 更新事件
this.sprite.upDate((time) => {
this.handleUpdate(time);
})
}
// 函数功能:移除子弹
removeBullet() {
let bullets = this.bullets;
if (bullets && bullets.length > 0) {
for (let j = 0; j < bullets.length; j++) {
let enemyBullet = bullets[j].bullet;
enemyBullet.setHide(true);
}
}
this.bullets = [];
}
// 函数功能:更新事件
handleUpdate() {
if (GlobalVariable.gameOver) {
return;
}
let createBulletTime = this.createBulletTime;
let moveTime = this.moveTime;
let createBulletInterval = this.createBulletInterval;
let sprite_ = this.sprite;
let heroSprite;
if(this.hero){
heroSprite = this.hero.sprite;
}
let bullets_ = this.bullets;
let updateBulletTime = this.updateBulletTime;
let w = game.getWindow().getWidth();
let h = game.getWindow().getHeight();
let position;
let x;
let y;
if (sprite_) {
position = Util.getPosition(sprite_);
x = position.x;
y = position.y;
}
let now = new Date().getTime();
if (this.enemy) {
// 创建子弹
if (now - createBulletTime > createBulletInterval * 5 && y <= h && sprite_ ) {
this.createBullets(x, y, Util.getPosition(heroSprite));
}
// 敌方移动
if (sprite_ && now - moveTime > this.moveInterval) {
this.move(sprite_, heroSprite.getPosition(), 8);
this.moveTime = now;
// 碰撞检测
if (this.crash(sprite_, heroSprite)) {
this.hero.heart = this.hero.heart - 1;
this.hero.bloodChange();
this.resetPos();
}
}
} else {
// 创建子弹
if (now - createBulletTime > createBulletInterval && sprite_) {
this.createBullets(x, y, GlobalVariable.cursorPosition);
}
// 判断子弹是否击中敌人
this.defeatEnemy();
}
// 子弹移动
if (bullets_ && bullets_.length > 0) {
for (let i = 0; i < bullets_.length; i++) {
let bullet = bullets_[i];
let bulletSprite = bullet.bullet;
let targetPosition = bullet.targetPosition;
let bulletPosition = Util.getPosition(bulletSprite);
let bulletX = bulletPosition.x;
let bulletY = bulletPosition.y;
// 超出边界移除子弹
if (this.beyondBorder(bulletX, bulletY)) {
bulletSprite.setHide(true);
bullets_.splice(i, 1);
i--;
continue;
}
let x = bulletPosition.x + targetPosition.x;
let y = bulletY + targetPosition.y;
if (bullet.enemy) {
// 碰撞检测
let b = this.crash(bulletSprite, heroSprite);
if (b) {
this.hero.heart = this.hero.heart - 1;
bulletSprite.setHide(true);
this.hero.bloodChange();
bullets_.splice(i, 1);
i--;
continue;
}
if (targetPosition) {
bulletSprite.setPosition(x, y);
}
// 移动后碰撞检测
b = this.crash(bulletSprite, heroSprite);
if (b) {
this.hero.heart = this.hero.heart - 1;
bulletSprite.setHide(true);
this.hero.bloodChange();
bullets_.splice(i, 1);
i--;
}
} else {
if (targetPosition) {
bulletSprite.setPosition(x, y);
}
}
}
}
this.updateBulletTime = now;
if (this.heart <= 0) {
GlobalVariable.gameOver = true;
}
if (GlobalVariable.gameOver) {
return;
}
}
/**
* 函数功能:创建子弹
* @param x x坐标
* @param y y坐标
* @param targetPosition 目标位置
*/
createBullets(x, y, targetPosition) {
if(!this.canFire){
return;
}
const cache_ = game.getResource();
let bulletPng;
if (this.enemy) {
bulletPng = cache_.getTexture("bullet1.png");
} else {
bulletPng = cache_.getTexture("bullet2.png");
}
let bullet = new Sprite();
x = x + 35;
y = y + 30;
bullet.setPosition(x, y);
bullet.setTexture(bulletPng);
bullet.setSize(21, 21);
GlobalVariable.scene.addNode(bullet);
this.createBulletTime = new Date().getTime();
if (targetPosition) {
let initialPosition = {x, y};
let directionPosition = {x: targetPosition.x - initialPosition.x, y: targetPosition.y - initialPosition.y};
let normalization1 = this.normalization(directionPosition);
this.bullets.push({bullet: bullet, targetPosition: normalization1, enemy: this.enemy});
} else {
this.bullets.push({bullet: bullet, enemy: this.enemy});
}
}
// 函数功能:碰撞检测
crash(o1, o2) {
if (o1 && o2) {
let position1 = Util.getPosition(o1);
let position2 = Util.getPosition(o2);
if (Physics.rectRect(position1, position2)) {
audio.playSound("sound/1.wav"); // 循环音效,例如按钮点击声、脚步声、爆炸声、技能音效
return true;
}
}
return false;
}
/**
* 函数功能:移动到目标位置
* @param sprite 移动对象
* @param targetPosition 目标位置
* @param speed 速度
* @returns {boolean}
*/
move(sprite, targetPosition, speed) {
if (sprite && targetPosition) {
let position = Util.getPosition(sprite);
let spriteX = position.x;
let spriteY = position.y;
let targetX = targetPosition.x;
let targetY = targetPosition.y;
spriteX = spriteX + (spriteX > targetX ? -speed : speed);
spriteY = spriteY + (spriteY > targetY ? -speed : speed);
sprite.setPosition(spriteX, spriteY);
}
return false;
}
// 函数功能:向量化: 保持方向不变,将长度缩小
normalization(pos) {
// 处理无效输入
if (!pos || (pos.x === 0 && pos.y === 0)) {
return {x: 0, y: 0}; // 零向量无法向量化,返回零向量
}
let x = pos.x;
let y = pos.y;
// 倍数
let m = 8;
// 计算向量的模(长度)
let number = Math.sqrt(x * x + y * y);
x = Math.ceil((x / number) * m);
y = Math.ceil((y / number) * m);
return {x, y};
}
// 函数功能:检测是否击中敌人
defeatEnemy() {
if (!this.enemy) {
let bullets = this.bullets;
if (bullets && bullets.length > 0) {
let crashEnemy = false;
for (let i = 0; i < bullets.length; i++) {
let bullet = bullets[i].bullet;
let enemyList = GlobalVariable.enemyList;
if (enemyList.length > 0) {
for (let j = 0; j < enemyList.length; j++) {
let childNode = enemyList[j];
let sprite = childNode.sprite;
if (this.crash(bullet, sprite)) {
crashEnemy = true;
GlobalVariable.score += 1;
GlobalVariable.scoreText.setText("分数:"+GlobalVariable.score);
bullet.setHide(true);
childNode.resetPos();
bullets.splice(i, 1);
break;
}
}
}
}
}
}
}
// 函数功能:判断是否超出界限
beyondBorder(x, y) {
let w = game.getWindow().getWidth();
let h = game.getWindow().getHeight();
if (x < 0 || x > w || y < 0 || y > h) {
return true;
}
return false;
}
// 函数功能:创建血量
createHeart(x, y,full){
const cache_ = game.getResource();
let img;
if (full){
img = cache_.getTexture("full-heart.png");
}else {
img = cache_.getTexture("empty-heart.png");
}
const heart = new Sprite();
heart.setTexture(img);
heart.setPosition(x, y);
heart.setSize(24, 22);
GlobalVariable.scene.addNode(heart);
return heart;
}
// 函数功能:血量
heartUpdate(){
let list = this.heartList;
if(list && list.length === 0){
for (let i = 0; i < this.heart; i++) {
list.push(this.createHeart(10 + i * 32, 5,true));
}
}else {
const cache_ = game.getResource();
for (let i = 0; i < list.length; i++) {
const h = list[i];
let img;
if (i < this.heart){
img = cache_.getTexture("full-heart.png");
}else {
img = cache_.getTexture("empty-heart.png");
}
h.setTexture(img);
}
}
}
// 函数功能:血量变化
bloodChange(){
if (this.enemy){
return;
}
if (this.heart <= 0){
GlobalVariable.gameOver = true;
}
this.heartUpdate()
if (GlobalVariable.gameOver) {
let w = game.getWindow().getWidth();
let h = game.getWindow().getHeight();
Util.newText({
text: "游戏结束",
x: w/3 + 70,
y: 10,
width:90,
});
// 重新开始按钮
Util.newSprite({
x: w/2-73,
y: h/2-26,
width: 147,
height: 53,
texture: 'restart.png',
clickCb: ()=>{
new Survivor()
}
})
}
}
// 函数功能:重置位置
resetPos() {
if(this.canFire){
this.removeBullet();
}
let w = game.getWindow().getWidth();
let h = game.getWindow().getHeight();
// x小于0 或大于w
// y小于0 或大于h
let x;
let y;
if(Math.random() > 0.5){
x = Math.floor(10 * Math.random() ) + w;
}else {
x = Math.floor(-10 * Math.random() );
}
if(Math.random() > 0.5){
y = Math.floor(10 * Math.random() ) + h;
}else {
y = Math.floor(-10 * Math.random());
}
if (this.sprite) {
this.sprite.setHide(false);
this.sprite.setPosition(x, y);
}
};
}
// 工具类:生成对象
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("font/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);
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 Physics {
// 碰撞检测算法: 矩形 vs 矩形
static rectRect(rect1, rect2,rate1 = 1,rate2=1) {
// log("rect1:"+JSON.stringify(rect1)+"; rect2:"+JSON.stringify(rect2));
if(rate1!==1){
rect1.x += (rate1-1)*rect1.width;
rect1.y += (rate1-1)*rect1.height;
rect1.width *= rate1;
rect1.height *= rate1;
}
if(rate2!==1){
rect2.x += (rate2-1)*rect2.width;
rect2.y += (rate2-1)*rect2.height;
rect2.width *= rate2;
rect2.height *= rate2;
}
return rect1.x < rect2.x + rect2.width
&& rect1.x + rect1.width > rect2.x
&& rect1.y < rect2.y + rect2.height
&& rect1.y + rect1.height > rect2.y;
}
// 碰撞检测算法: 圆形 vs 圆形
static circleCircle(circle1, circle2) {
const dx = circle1.x - circle2.x;
const dy = circle1.y - circle2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < circle1.radius + circle2.radius;
}
// 碰撞检测算法: 矩形 vs 圆形
static rectCircle(rect, circle) {
let testX = circle.x;
let testY = circle.y;
if (circle.x < rect.x) testX = rect.x; else if (circle.x > rect.x + rect.width) testX = rect.x + rect.width;
if (circle.y < rect.y) testY = rect.y; else if (circle.y > rect.y + rect.height) testY = rect.y + rect.height;
const distX = circle.x - testX;
const distY = circle.y - testY;
const distance = Math.sqrt(distX * distX + distY * distY);
return distance <= circle.radius;
}
}
// 全局类,全局变量和参数
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 scene;
// 游戏结束
static gameOver = false;
// 分数
static score = 0;
// 分数文本对象
static scoreText;
// 敌方
static enemyList = [];
// 鼠标点击坐标
static cursorPosition = {
x: 800,
y: 0
}
}
class Survivor{
hero;
constructor() {
this.init()
}
// 函数功能:初始化
init(){
GlobalVariable.gameOver = false;
let {backgroundNode} = Util.bj({picture: "mainbg.png"});
// 鼠标点击事件
backgroundNode.click((type,x,y) => {
log("mouse click,type:"+type+" x: "+x+" y: "+y);
GlobalVariable.cursorPosition = {x,y};
});
// 创建英雄
this.hero = new Character(300,300,0);
// 创建敌人
this.createEnemy();
let w = game.getWindow().getWidth();
// 分数
GlobalVariable.score = 0;
GlobalVariable.scoreText = Util.newText({
x: w - 120,
y: 10,
text: "分数:"+GlobalVariable.score+"",
width:300
})
// 更新事件
GlobalVariable.scoreText.upDate(()=> {
if (GlobalVariable.gameOver) {
return;
}
// 难度升级
let score = GlobalVariable.score;
if(GlobalVariable.enemyList.length - 1 < score/5){
this.createEnemy();
}
})
// 键盘事件
game.setKeyCallBack((key,action)=>{
let type = "";
if (key == GlobalVariable.KEY_W || key == GlobalVariable.KEY_UP){
type = "up";
}
if (key == GlobalVariable.KEY_S || key == GlobalVariable.KEY_BOTTOM){
type = "down";
}
if (key == GlobalVariable.KEY_A || key == GlobalVariable.KEY_LEFT){
type = "left";
}
if (key == GlobalVariable.KEY_D || key == GlobalVariable.KEY_RIGHT){
type = "right";
}
// log("key "+key+" action "+action+" type "+type);
this.changePos(type);
});
}
// 函数功能:改变位置
changePos(type){
let position = Util.getPosition(this.hero.sprite);
switch (type){
case "up":
position.y -= 10;
break;
case "down":
position.y += 10;
break;
case "left":
position.x -= 10;
break;
case "right":
position.x += 10;
break;
}
if (GlobalVariable.gameOver) {
return;
}
this.hero.sprite.setPosition(position.x,position.y);
}
// 函数功能:创建敌人
createEnemy(){
let w = game.getWindow().getWidth();
let h = game.getWindow().getHeight();
// x小于0 或大于w
// y小于0 或大于h
let x;
let y;
if(Math.random() > 0.5){
x = Math.floor(10 * Math.random() ) + w;
}else {
x = Math.floor(-10 * Math.random() );
}
if(Math.random() > 0.5){
y = Math.floor(10 * Math.random() ) + h;
}else {
y = Math.floor(-10 * Math.random());
}
let type = Math.random() > 0.5 ? 2 : 1;
let monster = new Character(x,y,type,this.hero);
GlobalVariable.enemyList.push(monster);
}
}
代码功能:
- main.js:游戏初始化,设置背景音乐等
- util.js:公共类,设置文字等
- Character.js:子弹跟踪碰撞
- survivor.js:创建英雄和敌人
- GlobalVariable.js:键盘码值
- physics.js:碰撞算法
开维游戏引擎代码简单,函数精简,尽量用极少的js脚本实现游戏功能。代码跨平台通用,一次编写,多端运行,可以直接导出生成exe或者html目录直接运行:
开维游戏引擎下载:
生存者小游戏页面演示:
源码下载:
开发文档: