开维游戏引擎实例:生存者小游戏

0 阅读7分钟

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

image.png

代码如下:

// 生存者小游戏

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);
    }
}

代码功能:

  1. main.js:游戏初始化,设置背景音乐等
  2. util.js:公共类,设置文字等
  3. Character.js:子弹跟踪碰撞
  4. survivor.js:创建英雄和敌人
  5. GlobalVariable.js:键盘码值
  6. physics.js:碰撞算法

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

image.png

开维游戏引擎下载:

www.ikaiwei.com/download/ga…

生存者小游戏页面演示:

www.ikaiwei.com/gamejs/exam…  

源码下载:

github.com/ctrljshaha/…

gamejs.ikaiwei.com/#/Market

  开发文档:

www.ikaiwei.com/gamejs/api/…