版本:2.4.4

 

之前好几个游戏有用过镜头跟随,现在再总结一下。

实现效果:

 

一 实现需求

目的就是在人物移动的时候,一直让人物处于视口屏幕中央显示。

例如人物在地图上向右移动200像素,那么人物就会显示在屏幕右边,这时为了让人物居中,就得将地图向左移动200像素,因为人物节点在地图节点下,

右移200和左移200相互抵消,所以人物相当于没动,仍然会保持屏幕居中显示了。

 

 

二 视口、地图、人物

视口就是游戏中显示地图的窗口,如果是满屏幕显示,那么视口就是Canvas屏幕节点,例如Canvas节点高宽1280x720。

地图节点就是放地图map的节点,例如地图高宽是2000x2000。

人物节点就是控制的小人,一般是个骨骼动画。

 

 

三 摄像机代码

摄像机GameCamera在初始化时传入视口、地图、人物。在人物移动时调用updatePosition刷新摄像机位置。

/**
 * 游戏摄像机
 * @author chenkai 2022.8.29
 */
export class GameCamera {
    /**地图节点 */
    private mapNode: cc.Node;
    /**人物节点 */
    private roleNode: cc.Node;

    /**地图x轴最大移动距离 */
    private xRange: number;
    /**地图y轴最大移动距离 */
    private yRange: number;
    /**上一次人物X位置 */
    private lastRoleX: number;
    /**上一次人物Y位置 */
    private lastRoleY: number;

    /**
     * 构造函数
     * @param viewPortNode 视口节点  例如屏幕大小720x1280
     * @param mapNode      地图节点  例如2000x2000
     * @param roleNode     人物节点  
     */
    public constructor(viewPortNode: cc.Node, mapNode: cc.Node, roleNode: cc.Node) {
        //保存节点
        this.mapNode = mapNode;
        this.roleNode = roleNode;
        //计算x,y轴最大移动距离
        if (this.mapNode.width > viewPortNode.width) {
            this.xRange = (this.mapNode.width - viewPortNode.width) / 2;
        } else {
            this.xRange = 0;
        }
        if (this.mapNode.height > viewPortNode.height) {
            this.yRange = (this.mapNode.height - viewPortNode.height) / 2;
        } else {
            this.yRange = 0;
        }
        //保存人物位置
        this.lastRoleX = roleNode.x;
        this.lastRoleY = roleNode.y;

        console.log("摄像头最大移动距离:", this.xRange, this.yRange);
    }

    /**刷新位置 */
    public updatePosition() {
        //人物未移动,则不需要更新位置
        if (this.lastRoleX == this.roleNode.x && this.lastRoleY == this.roleNode.y) {
            return;
        }
        this.lastRoleX = this.roleNode.x;
        this.lastRoleY = this.roleNode.y
        //人物和地图中点距离
        let distX = this.roleNode.x;
        let distY = this.roleNode.y;
        //地图根据距离反向移动,这样人物就能一直处于视口中间
        this.mapNode.x = -distX;
        this.mapNode.y = -distY;
        //地图边缘检测 
        if (this.mapNode.x > this.xRange) {
            this.mapNode.x = this.xRange;
            console.log("摄像头超过右边界");
        } else if (this.mapNode.x < -this.xRange) {
            this.mapNode.x = -this.xRange;
            console.log("摄像头超过左边界");
        }
        if (this.mapNode.y > this.yRange) {
            this.mapNode.y = this.yRange;
            console.log("摄像头超过上边界");
        } else if (this.mapNode.y < -this.yRange) {
            this.mapNode.y = -this.yRange;
            console.log("摄像头超过下边界");
        }
    }
}

  

 四  主场景代码

用cc.SystemEvent监听键盘上下左右按键,用于移动人物,人物移动后刷新摄像机位置updatePosition让人物居中。

const { ccclass, property } = cc._decorator;

/**
 * 摄像机跟随Demo
 * @author chenkai 2022.8.29
 */
@ccclass
export default class MainScene extends cc.Component {

    @property({ type: cc.Node, tooltip: "地图节点" })
    mapNode: cc.Node = null;
    @property({ type: cc.Node, tooltip: "人物节点" })
    roleNode: cc.Node = null;
    @property({ type: cc.Node, tooltip: "视口" })
    viewPort: cc.Node = null;

    /**摄像机 */
    private gameCamera: GameCamera;
    /**按键缓存 */
    private keyCache = {};
    /**人物移动速度 */
    private roleSpeed: number = 10;

    onLoad() {
        this.gameCamera = new GameCamera(this.viewPort, this.mapNode, this.roleNode);

        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    }

    update(dt) {
        this.updateRoleMove();
        this.updateCamera();
    }

    /**刷新人物移动 */
    private updateRoleMove() {
        //根据按键移动
        if (this.keyCache[cc.macro.KEY.up]) {
            this.roleNode.y += this.roleSpeed;
        }
        if (this.keyCache[cc.macro.KEY.down]) {
            this.roleNode.y -= this.roleSpeed;
        }
        if (this.keyCache[cc.macro.KEY.left]) {
            this.roleNode.x -= this.roleSpeed;
        }
        if (this.keyCache[cc.macro.KEY.right]) {
            this.roleNode.x += this.roleSpeed;
        }
        //边缘检测
        if (this.roleNode.x + this.roleNode.width / 2 > this.mapNode.width / 2) {
            this.roleNode.x = this.mapNode.width / 2 - this.roleNode.width / 2;
            console.log("人物超过地图右边缘");
        } else if (this.roleNode.x - this.roleNode.width / 2 < -this.mapNode.width / 2) {
            this.roleNode.x = -this.mapNode.width / 2 + this.roleNode.width / 2;
            console.log("人物超过地图左边缘");
        }
        if (this.roleNode.y + this.roleNode.height / 2 > this.mapNode.height / 2) {
            this.roleNode.y = this.mapNode.height / 2 - this.roleNode.height / 2;
            console.log("人物超过地图上边缘");
        } else if (this.roleNode.y - this.roleNode.height / 2 < -this.mapNode.height / 2) {
            this.roleNode.y = -this.mapNode.height / 2 + this.roleNode.height / 2;
            console.log("人物超过地图下边缘");
        }
    }

    private updateCamera() {
        this.gameCamera.updatePosition();
    }

    /**按下键*/
    public onKeyDown(event) {
        switch (event.keyCode) {
            case cc.macro.KEY.up:
                this.keyCache[cc.macro.KEY.up] = true;
                console.log("按下Up");
                break;
            case cc.macro.KEY.down:
                this.keyCache[cc.macro.KEY.down] = true;
                console.log("按下Down");
                break;
            case cc.macro.KEY.left:
                this.keyCache[cc.macro.KEY.left] = true;
                console.log("按下Left");
                break;
            case cc.macro.KEY.right:
                this.keyCache[cc.macro.KEY.right] = true;
                console.log("按下Right");
                break;
        }
    }

    /**松开按键*/
    public onKeyUp(event) {
        switch (event.keyCode) {
            case cc.macro.KEY.up:
                this.keyCache[cc.macro.KEY.up] = false;
                console.log("松开Up");
                break;
            case cc.macro.KEY.down:
                this.keyCache[cc.macro.KEY.down] = false;
                console.log("松开Down");
                break;
            case cc.macro.KEY.left:
                this.keyCache[cc.macro.KEY.left] = false;
                console.log("松开Left");
                break;
            case cc.macro.KEY.right:
                this.keyCache[cc.macro.KEY.right] = false;
                console.log("松开Right");
                break;
        }
    }
}

/**方向 */
export enum Dir {
    /**上 */
    Up = 0,
    /**下 */
    Down = 1,
    /**左 */
    Left = 2,
    /**右 */
    Right = 3
}

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2022-08-29 17:59  gamedaybyday  阅读(2061)  评论(0编辑  收藏  举报