feat: 支持更新相机高度

This commit is contained in:
richarjiang
2025-09-22 17:45:20 +08:00
parent 98742745eb
commit 0b270ff9f9
3 changed files with 165 additions and 263 deletions

View File

@@ -52,7 +52,7 @@
},
"autoReleaseAssets": false,
"_globals": {
"__id__": 46
"__id__": 43
},
"_id": "58132e64-0171-4c7f-89be-a2984ca7de6b"
},
@@ -80,9 +80,6 @@
{
"__id__": 31
},
{
"__id__": 34
},
{
"__id__": 6
}
@@ -90,13 +87,13 @@
"_active": true,
"_components": [
{
"__id__": 43
"__id__": 40
},
{
"__id__": 44
"__id__": 41
},
{
"__id__": 45
"__id__": 42
}
],
"_prefab": null,
@@ -282,7 +279,7 @@
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 122.796,
"x": 123.77,
"y": -26.144,
"z": 0
},
@@ -587,13 +584,13 @@
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": -2.9,
"y": -7.3
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 31.5,
"height": 70.3
"width": 39.3,
"height": 55.7
},
"_id": "634GiEUhBB8Z1aGzkT6Zyd"
},
@@ -915,14 +912,14 @@
},
{
"__type__": "cc.Node",
"_name": "Bg",
"_name": "PlayerController",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": false,
"_active": true,
"_components": [
{
"__id__": 26
@@ -959,7 +956,7 @@
"y": 0,
"z": 0
},
"_id": "9fWIW0nlZOAKYYrvMjuWpy"
"_id": "2dBJLKIX9BV4ynG/qUL0Kd"
},
{
"__type__": "cc.UITransform",
@@ -973,18 +970,18 @@
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1080,
"height": 2560
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "45vczKBs5CfZpW98yME17u"
"_id": "6bjIx8+UNMz47XvRycFFNU"
},
{
"__type__": "cc.Sprite",
"__type__": "a1b2cPU5fZ4kKvN7xI0VniQ",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
@@ -993,38 +990,23 @@
},
"_enabled": true,
"__prefab": null,
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
"player": {
"__id__": 6
},
"_spriteFrame": {
"__uuid__": "361c5873-d797-420e-be65-81c5a8f91215@f9941",
"__expectedType__": "cc.SpriteFrame"
"camera": {
"__id__": 4
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
"pathfinder": {
"__id__": 24
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": "f9zWrrB85EzKBNoudMPMro"
"moveSpeed": 300,
"mapWidth": 1080,
"mapHeight": 2560,
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
},
{
"__type__": "cc.Node",
"_name": "PlayerController",
"_name": "Manager",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -1068,100 +1050,6 @@
"y": 0,
"z": 0
},
"_id": "2dBJLKIX9BV4ynG/qUL0Kd"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 28
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "6bjIx8+UNMz47XvRycFFNU"
},
{
"__type__": "a1b2cPU5fZ4kKvN7xI0VniQ",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 28
},
"_enabled": true,
"__prefab": null,
"player": {
"__id__": 6
},
"camera": {
"__id__": 4
},
"pathfinder": {
"__id__": 24
},
"moveSpeed": 300,
"mapWidth": 1080,
"mapHeight": 2560,
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
},
{
"__type__": "cc.Node",
"_name": "Manager",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 32
},
{
"__id__": 33
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "faEV0Z9e5HPJZltMJt2Zn9"
},
{
@@ -1170,7 +1058,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 28
},
"_enabled": true,
"__prefab": null,
@@ -1192,7 +1080,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 28
},
"_enabled": true,
"__prefab": null,
@@ -1214,25 +1102,25 @@
},
"_children": [
{
"__id__": 35
"__id__": 32
}
],
"_active": true,
"_components": [
{
"__id__": 35
},
{
"__id__": 36
},
{
"__id__": 37
},
{
"__id__": 38
},
{
"__id__": 39
},
{
"__id__": 40
},
{
"__id__": 41
},
{
"__id__": 42
}
],
"_prefab": null,
@@ -1271,16 +1159,16 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 34
"__id__": 31
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 36
"__id__": 33
},
{
"__id__": 37
"__id__": 34
}
],
"_prefab": null,
@@ -1319,7 +1207,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 35
"__id__": 32
},
"_enabled": true,
"__prefab": null,
@@ -1341,7 +1229,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 35
"__id__": 32
},
"_enabled": true,
"__prefab": null,
@@ -1403,7 +1291,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 34
"__id__": 31
},
"_enabled": true,
"__prefab": null,
@@ -1425,7 +1313,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 34
"__id__": 31
},
"_enabled": true,
"__prefab": null,
@@ -1464,7 +1352,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 34
"__id__": 31
},
"_enabled": true,
"__prefab": null,
@@ -1495,7 +1383,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 34
"__id__": 31
},
"_enabled": true,
"__prefab": null,
@@ -1523,7 +1411,7 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 34
"__id__": 31
},
"_enabled": true,
"__prefab": null,
@@ -1616,28 +1504,28 @@
{
"__type__": "cc.SceneGlobals",
"ambient": {
"__id__": 47
"__id__": 44
},
"shadows": {
"__id__": 48
"__id__": 45
},
"_skybox": {
"__id__": 49
"__id__": 46
},
"fog": {
"__id__": 50
"__id__": 47
},
"octree": {
"__id__": 51
"__id__": 48
},
"skin": {
"__id__": 52
"__id__": 49
},
"lightProbeInfo": {
"__id__": 53
"__id__": 50
},
"postSettings": {
"__id__": 54
"__id__": 51
},
"bakedWithStationaryMainLight": false,
"bakedWithHighpLightmap": false

View File

@@ -4,115 +4,117 @@ const { ccclass, property } = _decorator;
@ccclass('CameraFollow')
export class CameraFollow extends Component {
@property(Node)
target: Node | null = null; // 要跟随的目标(玩家)
@property(Node)
target: Node | null = null; // 要跟随的目标(玩家)
@property({ range: [0.1, 10] })
followSpeed: number = 5.0; // 跟随速度
@property({ range: [0.1, 10] })
followSpeed: number = 5.0; // 跟随速度
@property(Vec3)
offset: Vec3 = new Vec3(0, 0, 10); // 相机相对目标的偏移
@property(Vec3)
offset: Vec3 = new Vec3(0, 0, 10); // 相机相对目标的偏移
@property({ range: [0, 1] })
smoothness: number = 0.1; // 平滑度0为瞬间跟随1为最慢跟随
@property({ range: [0, 1] })
smoothness: number = 0.1; // 平滑度0为瞬间跟随1为最慢跟随
@property
mapWidth: number = 1080; // 地图宽度
@property
mapWidth: number = 1080; // 地图宽度
@property
mapHeight: number = 2560; // 地图高度
@property
mapHeight: number = 2560; // 地图高度
private camera: Camera | null = null;
private camera: Camera | null = null;
onLoad() {
// 获取相机组件
this.camera = this.getComponent(Camera);
if (!this.camera) {
console.error('CameraFollow: 未找到Camera组件');
}
onLoad() {
// 获取相机组件
this.camera = this.getComponent(Camera);
this.camera.orthoHeight = 500
if (!this.camera) {
console.error('CameraFollow: 未找到Camera组件');
}
}
start() {
if (this.target) {
// 初始化相机位置
const initialPos = this.target.position.clone();
initialPos.add(this.offset);
this.node.position = initialPos;
}
start() {
if (this.target) {
// 初始化相机位置
const initialPos = this.target.position.clone();
initialPos.add(this.offset);
this.node.position = initialPos;
}
}
update(deltaTime: number) {
if (!this.target) return;
update(deltaTime: number) {
if (!this.target) return;
// 计算目标位置
const targetPosition = this.target.position.clone();
targetPosition.add(this.offset);
// 计算目标位置
const targetPosition = this.target.position.clone();
targetPosition.add(this.offset);
// 应用地图边界限制
const clampedPosition = this.clampCameraPosition(targetPosition);
// 应用地图边界限制
const clampedPosition = this.clampCameraPosition(targetPosition);
// 使用插值实现平滑跟随
const currentPosition = this.node.position;
const newPosition = new Vec3();
// 使用插值实现平滑跟随
const currentPosition = this.node.position;
const newPosition = new Vec3();
// 根据平滑度设置插值速度
const lerpFactor = Math.min(1.0, this.followSpeed * deltaTime * (1 - this.smoothness + 0.1));
// 根据平滑度设置插值速度
const lerpFactor = Math.min(1.0, this.followSpeed * deltaTime * (1 - this.smoothness + 0.1));
Vec3.lerp(newPosition, currentPosition, clampedPosition, lerpFactor);
this.node.position = newPosition;
Vec3.lerp(newPosition, currentPosition, clampedPosition, lerpFactor);
this.node.position = newPosition;
}
// 限制相机位置在地图边界内
private clampCameraPosition(position: Vec3): Vec3 {
if (!this.camera) return position;
// 获取屏幕可见区域大小
const visibleSize = view.getVisibleSize();
// 计算相机能看到的世界区域的一半
const halfCameraWidth = visibleSize.width * 0.5;
const halfCameraHeight = visibleSize.height * 0.5;
// 计算地图边界地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2
const mapHalfWidth = this.mapWidth * 0.5;
const mapHalfHeight = this.mapHeight * 0.5;
// 计算相机位置的边界(确保相机边缘不超出地图边界)
const minX = -mapHalfWidth + halfCameraWidth;
const maxX = mapHalfWidth - halfCameraWidth;
const minY = -mapHalfHeight + halfCameraHeight;
const maxY = mapHalfHeight - halfCameraHeight;
// 限制相机位置
const clampedPosition = position.clone();
clampedPosition.x = Math.max(minX, Math.min(maxX, position.x));
clampedPosition.y = Math.max(minY, Math.min(maxY, position.y));
return clampedPosition;
}
// 设置跟随目标
setTarget(target: Node) {
this.target = target;
if (target) {
const initialPos = target.position.clone();
initialPos.add(this.offset);
this.node.position = initialPos;
}
}
// 限制相机位置在地图边界内
private clampCameraPosition(position: Vec3): Vec3 {
if (!this.camera) return position;
// 设置偏移量
setOffset(offset: Vec3) {
this.offset = offset;
}
// 获取屏幕可见区域大小
const visibleSize = view.getVisibleSize();
// 瞬间移动到目标位置
snapToTarget() {
if (!this.target) return;
// 计算相机能看到的世界区域的一半
const halfCameraWidth = visibleSize.width * 0.5;
const halfCameraHeight = visibleSize.height * 0.5;
// 计算地图边界地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2
const mapHalfWidth = this.mapWidth * 0.5;
const mapHalfHeight = this.mapHeight * 0.5;
// 计算相机位置的边界(确保相机边缘不超出地图边界)
const minX = -mapHalfWidth + halfCameraWidth;
const maxX = mapHalfWidth - halfCameraWidth;
const minY = -mapHalfHeight + halfCameraHeight;
const maxY = mapHalfHeight - halfCameraHeight;
// 限制相机位置
const clampedPosition = position.clone();
clampedPosition.x = Math.max(minX, Math.min(maxX, position.x));
clampedPosition.y = Math.max(minY, Math.min(maxY, position.y));
return clampedPosition;
}
// 设置跟随目标
setTarget(target: Node) {
this.target = target;
if (target) {
const initialPos = target.position.clone();
initialPos.add(this.offset);
this.node.position = initialPos;
}
}
// 设置偏移量
setOffset(offset: Vec3) {
this.offset = offset;
}
// 瞬间移动到目标位置
snapToTarget() {
if (!this.target) return;
const targetPosition = this.target.position.clone();
targetPosition.add(this.offset);
const clampedPosition = this.clampCameraPosition(targetPosition);
this.node.position = clampedPosition;
}
const targetPosition = this.target.position.clone();
targetPosition.add(this.offset);
const clampedPosition = this.clampCameraPosition(targetPosition);
this.node.position = clampedPosition;
}
}

View File

@@ -85,12 +85,24 @@ export class PlayerController extends Component {
const normalizedX = screenPos.x - centerX;
const normalizedY = screenPos.y - centerY;
// 获取相机的正交高度
const orthoHeight = this.camera.orthoHeight;
// 计算屏幕坐标到世界坐标的缩放比例
// 屏幕高度的一半对应 orthoHeight 的世界单位
const scaleY = orthoHeight / (visibleSize.height * 0.5);
const scaleX = scaleY; // 保持宽高比一致
// 将屏幕坐标转换为世界坐标(相对于相机中心)
const worldOffsetX = normalizedX * scaleX;
const worldOffsetY = normalizedY * scaleY;
// 考虑相机的位置偏移
const cameraPos = this.camera.node.position;
// 计算世界坐标
const worldX = normalizedX + cameraPos.x;
const worldY = normalizedY + cameraPos.y;
// 计算最终的世界坐标
const worldX = worldOffsetX + cameraPos.x;
const worldY = worldOffsetY + cameraPos.y;
return new Vec3(worldX, worldY, 0);
}