版本: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
}
浙公网安备 33010602011771号