From 0b270ff9f990ba283515eb74dc068f213f79d9ca Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 22 Sep 2025 17:45:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B8=E6=9C=BA=E9=AB=98=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/scenes/main.scene | 226 ++++++++--------------------- assets/scripts/CameraFollow.ts | 184 +++++++++++------------ assets/scripts/PlayerController.ts | 18 ++- 3 files changed, 165 insertions(+), 263 deletions(-) diff --git a/assets/scenes/main.scene b/assets/scenes/main.scene index fa19779..2cf5fcd 100644 --- a/assets/scenes/main.scene +++ b/assets/scenes/main.scene @@ -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 diff --git a/assets/scripts/CameraFollow.ts b/assets/scripts/CameraFollow.ts index b1e47a9..176927f 100644 --- a/assets/scripts/CameraFollow.ts +++ b/assets/scripts/CameraFollow.ts @@ -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; + } } \ No newline at end of file diff --git a/assets/scripts/PlayerController.ts b/assets/scripts/PlayerController.ts index 5612d37..0882fdd 100644 --- a/assets/scripts/PlayerController.ts +++ b/assets/scripts/PlayerController.ts @@ -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); }