• HTML5 2D平台游戏开发#7Camera


      在庞大的游戏世界中,玩家不能一览地图全貌,而是只能看到其中一部分,并一步步探索,这时就要用到一种技术来显示局部的地图,游戏术语称为摄像机(Camera)。下面两张图中的白色矩形框表示了Camera的作用,玩家控制的角色总是在该矩形内。

    可以想像成一个200X100宽高的相框固定在坐标(0,0)处,然后移动下面的蓝纸,蓝纸的不同位置就会显示在相框中。

    同时,也只需绘制出现在相框中的地图即可,这样可以提升一部分程序的性能。为实现Camera功能,需要添加一些辅助方法。

    改造一下原来的AABB函数:

    class AABB {
        /**
         * 碰撞盒子
         * @param x    {number} 盒子x坐标
         * @param y    {number} 盒子y坐标
         * @param w    {number} 盒子宽度
         * @param h    {number} 盒子高度
         */
        constructor(x,y,w,h) {
            this.pos = new Vector(x,y);
            this.size = new Vector(w,h);
            this.center = new Vector(this.pos.x + w / 2,this.pos.y + h / 2);
            this.halfSize = new Vector(this.size.x / 2,this.size.y / 2);
    
            this.init();
        }
    
        set(x, y, /*optional*/w, /*optional*/h) {
            this.pos = new Vector(x, y);
            this.size = new Vector(w || this.width, h || this.height);
    
            this.init();
        }
    
        init() {
            this.left = this.pos.x;
            this.top = this.pos.y;
            this.width = this.size.x;
            this.height = this.size.y;
            this.right = this.left + this.width;
            this.bottom = this.top + this.height;
        }
    
        within(r) {
            return r.left <= this.left &&
                r.right >= this.right &&
                r.top <= this.top &&
                r.bottom >= this.bottom;
        }
    }

    同时新增Camera构造函数:

    let AXIS = {};
    Object.defineProperties(AXIS,{
        'NONE':{
            value:"none"
        },
        'HORIZONTAL':{
            value:"horizontal"
        },
        'VERTICAL':{
            value:"vertical"
        },
        'BOTH':{
            value:"both"
        }
    });
    
    class Camera {
        /**
         * 摄像机构造函数
         * @param level {map} 地图
         * @param x {Number} camera的x坐标
         * @param y {Number} camera的y坐标
         * @param canvasWidth {Number} camera视口宽度
         * @param canvasHeight {Number} camera视口高度
         * @param maxX {Number} camera的最大x坐标
         * @param maxY {Number} camera的最大y坐标
         */
        constructor(level,x,y,canvasWidth,canvasHeight,maxX,maxY) {
            //摄像机左上角的x,y坐标
            this.x = x;
            this.y = y;
            //摄像机的大小
            this.w = canvasWidth;
            this.h = canvasHeight;
    
            //摄像机开始移动的临界点
            //跟踪对象到摄像机边界的距离
            this.xDeadZone = 0; //距离水平边界的距离
            this.yDeadZone = 0; //距离垂直边界的距离
    
            //摄像机能够移动的最大范围
            this.maxX = maxX || level.cols - this.w;
            this.maxY = maxY || level.rows - this.h;
            //摄像机移动的方向
            this.axis = AXIS.BOTH;
            //镜头跟随的对象
            this.followed = null;
    
            //表示camera视口
            this.viewportRect = new AABB(this.x,this.y,this.w,this.h);
    
            //表示整个地图范围
            this.worldRect = new AABB(0,0,level.cols,level.rows);
        }
    
        follow(gameObject,xDeadZone,yDeadZone) {
            this.followed = gameObject;
            this.xDeadZone = xDeadZone;
            this.yDeadZone = yDeadZone;
        }
    
        update() {
            //仅在有跟随对象时更新摄像机位置
            if(this.followed !== null) {
                if(this.axis === AXIS.HORIZONTAL || this.axis === AXIS.BOTH) {
                    //根据跟随对象位置更新摄像机的x坐标
                    if(this.followed.pos.x - this.x + this.xDeadZone > this.w) {
                        this.x = this.followed.pos.x - (this.w - this.xDeadZone);
                    } else if(this.followed.pos.x - this.xDeadZone < this.x) {
                        this.x = this.followed.pos.x - this.xDeadZone;
                    }
                }
    
                if(this.axis === AXIS.VERTICAL || this.axis === AXIS.BOTH) {
                    //根据跟随对象位置更新摄像机的y坐标
                    if(this.followed.pos.y - this.y + this.yDeadZone > this.h) {
                        this.y = this.followed.pos.y - (this.h - this.yDeadZone);
                    } else if(this.followed.pos.y - this.yDeadZone < this.y) {
                        this.y = this.followed.pos.y - this.yDeadZone;
                    }
                }
            }
    
            //重新设置camera视口的x坐标和y坐标
            this.viewportRect.set(this.x,this.y);
    
    
            //保证camera不会超出地图范围
            if(!this.viewportRect.within(this.worldRect)) {
                if(this.viewportRect.left < this.worldRect.left) this.x = this.worldRect.left;
                if(this.viewportRect.top < this.worldRect.top) this.y = this.worldRect.top;
                if(this.viewportRect.right > this.worldRect.right) this.x = this.worldRect.right - this.w;
                if(this.viewportRect.bottom > this.worldRect.bottom) this.y = this.worldRect.bottom - this.h;
            }
        }
    }

    在游戏开始时初始化Camera:

    camera = new Camera(levels,0,0,c.width / MAPCONFIG.TILESIZE,c.height / MAPCONFIG.TILESIZE);
    camera.follow(player,c.width / 2 / MAPCONFIG.TILESIZE,c.height / 2 / MAPCONFIG.TILESIZE);

    渲染地图时只绘制Camera部分:

    _drawLayer(layerIndex) {
            let tileSize = MAPCONFIG.TILESIZE,
                startCol = camera.x >> 0,   //起始列
                endCol = Math.floor(startCol + camera.w) + 1,    //结束列
                startRow = camera.y >> 0,   //开始行
                endRow = Math.floor(startRow + camera.h) + 1,    //结束行
                offsetX = -camera.x + startCol,
                offsetY = -camera.y + startRow;
    
            for (let r = startRow; r < endRow; r++) {
                for (let c = startCol; c < endCol; c++) {
                    let tile = this.getTile(layerIndex, c, r),
                        x = (c - startCol + offsetX) * tileSize,  //瓦片的x坐标
                        y = (r - startRow + offsetY) * tileSize;  //瓦片的y坐标
    
                    if (tile !== -1) {
                        this.ctx.drawImage(
                            this.spriteSheet,
                            tile * tileSize % this.dimensions.w,    //瓦片精灵图上的x坐标
                            Math.floor(tile * tileSize / this.dimensions.w) * tileSize, //瓦片精灵图上的y坐标
                            tileSize,
                            tileSize,
                            Math.round(x),
                            Math.round(y),
                            tileSize,
                            tileSize
                        );
                    }
                }
            }
        }

    以下是演示效果:

     

    更新日志

      2017/04/09  更新角色跳跃

      2017/04/21  更新角色冲刺

      2017/05/01  更新角色状态机

      2017/05/16  更新角色攻击动画

      2017/05/22  更新角色移动攻击动画

      2017/05/24  更新角色跳跃攻击动画

      2017/06/04  更新地图绘制

      2017/06/22  更新摄像机、长距离冲刺

  • 相关阅读:
    MyString
    Django疑难问题
    mysql 疑难问题-django
    python时间转换 ticks-FYI
    django建议入门-FYI
    Python风格规范-FYI
    scrum敏捷开发☞
    git基本命令
    centos下的安装mysql,jdk
    memcached for .net on windows
  • 原文地址:https://www.cnblogs.com/undefined000/p/how-to-implement-camera-in-2d-platform-game.html
Copyright © 2020-2023  润新知