feat
This commit is contained in:
79
assets/scripts/CameraFollow.ts
Normal file
79
assets/scripts/CameraFollow.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { _decorator, Component, Node, Vec3, Camera } from 'cc';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('CameraFollow')
|
||||
export class CameraFollow extends Component {
|
||||
|
||||
@property(Node)
|
||||
target: Node | null = null; // 要跟随的目标(玩家)
|
||||
|
||||
@property({ range: [0.1, 10] })
|
||||
followSpeed: number = 5.0; // 跟随速度
|
||||
|
||||
@property(Vec3)
|
||||
offset: Vec3 = new Vec3(0, 0, 10); // 相机相对目标的偏移
|
||||
|
||||
@property({ range: [0, 1] })
|
||||
smoothness: number = 0.1; // 平滑度,0为瞬间跟随,1为最慢跟随
|
||||
|
||||
private camera: Camera | null = null;
|
||||
|
||||
onLoad() {
|
||||
// 获取相机组件
|
||||
this.camera = this.getComponent(Camera);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime: number) {
|
||||
if (!this.target) return;
|
||||
|
||||
// 计算目标位置
|
||||
const targetPosition = this.target.position.clone();
|
||||
targetPosition.add(this.offset);
|
||||
|
||||
// 使用插值实现平滑跟随
|
||||
const currentPosition = this.node.position;
|
||||
const newPosition = new Vec3();
|
||||
|
||||
// 根据平滑度设置插值速度
|
||||
const lerpFactor = Math.min(1.0, this.followSpeed * deltaTime * (1 - this.smoothness + 0.1));
|
||||
|
||||
Vec3.lerp(newPosition, currentPosition, targetPosition, lerpFactor);
|
||||
this.node.position = newPosition;
|
||||
}
|
||||
|
||||
// 设置跟随目标
|
||||
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);
|
||||
this.node.position = targetPosition;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "424a4011-cdc4-4bf5-a5f5-b1ec4d9c2ba2",
|
||||
"uuid": "5e22957b-e707-4adb-9e53-e039f4e68e03",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { _decorator, Component, Node, Vec3, UITransform, math } from 'cc';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('CameraFollow2D')
|
||||
export class CameraFollow2D extends Component {
|
||||
@property(Node)
|
||||
target: Node = null!; // 要跟随的角色
|
||||
|
||||
@property(Node)
|
||||
map: Node = null!; // 地图节点(需要有 UITransform 才能获取大小)
|
||||
|
||||
@property
|
||||
offset: Vec3 = new Vec3(0, 0, 1000); // 相机与角色的偏移(Z 轴拉远)
|
||||
|
||||
@property
|
||||
smoothSpeed: number = 5; // 平滑跟随速度,越大越快
|
||||
|
||||
private _currentPos: Vec3 = new Vec3();
|
||||
private _mapSize: Vec3 = new Vec3();
|
||||
|
||||
onLoad() {
|
||||
if (this.map) {
|
||||
const ui = this.map.getComponent(UITransform);
|
||||
if (ui) {
|
||||
this._mapSize.set(ui.width, ui.height, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime: number) {
|
||||
if (!this.target) return;
|
||||
|
||||
// 目标位置
|
||||
const targetPos = this.target.getWorldPosition();
|
||||
const desiredPos = new Vec3(
|
||||
targetPos.x + this.offset.x,
|
||||
targetPos.y + this.offset.y,
|
||||
this.offset.z // 相机保持固定 Z
|
||||
);
|
||||
|
||||
// 平滑跟随
|
||||
Vec3.lerp(this._currentPos, this.node.getWorldPosition(), desiredPos, deltaTime * this.smoothSpeed);
|
||||
|
||||
// 限制相机不超出地图
|
||||
if (this.map) {
|
||||
const halfW = this._mapSize.x / 2;
|
||||
const halfH = this._mapSize.y / 2;
|
||||
|
||||
// 相机视口一半宽高(取 UITransform 尺寸的一半)
|
||||
const camUI = this.getComponent(UITransform);
|
||||
let viewW = camUI ? camUI.width / 2 : 0;
|
||||
let viewH = camUI ? camUI.height / 2 : 0;
|
||||
|
||||
this._currentPos.x = math.clamp(this._currentPos.x, -halfW + viewW, halfW - viewW);
|
||||
this._currentPos.y = math.clamp(this._currentPos.y, -halfH + viewH, halfH - viewH);
|
||||
}
|
||||
|
||||
// 更新相机位置
|
||||
this.node.setWorldPosition(this._currentPos);
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
import { _decorator, Component, input, Input, EventTouch, Camera, Node, Vec3, UITransform, math } from 'cc';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
interface NodeCell {
|
||||
x: number;
|
||||
y: number;
|
||||
g: number;
|
||||
h: number;
|
||||
f: number;
|
||||
parent?: NodeCell;
|
||||
}
|
||||
|
||||
@ccclass('GameController')
|
||||
export class GameController extends Component {
|
||||
@property(Camera)
|
||||
camera: Camera = null!;
|
||||
|
||||
@property(Node)
|
||||
player: Node = null!;
|
||||
|
||||
@property(Node)
|
||||
map: Node = null!;
|
||||
|
||||
@property
|
||||
gridSize: number = 32;
|
||||
|
||||
@property
|
||||
moveSpeed: number = 200;
|
||||
|
||||
@property
|
||||
camSmooth: number = 5;
|
||||
|
||||
private mapData: number[][] = [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
];
|
||||
|
||||
private path: Vec3[] = [];
|
||||
private camPos: Vec3 = new Vec3();
|
||||
private mapSize: Vec3 = new Vec3();
|
||||
|
||||
onLoad() {
|
||||
input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
|
||||
|
||||
// 获取地图大小
|
||||
const ui = this.map.getComponent(UITransform);
|
||||
if (ui) {
|
||||
this.mapSize.set(ui.width, ui.height, 0);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
|
||||
}
|
||||
|
||||
// 点击地图 -> 计算寻路路径
|
||||
onTouchEnd(event: EventTouch) {
|
||||
if (!this.camera) return;
|
||||
|
||||
const screenPos = event.getLocation();
|
||||
const worldPos = this.camera.screenToWorld(new Vec3(screenPos.x, screenPos.y, 0));
|
||||
|
||||
const startCell = this.worldToCell(this.player.getWorldPosition());
|
||||
const endCell = this.worldToCell(worldPos);
|
||||
|
||||
const cellPath = this.findPath(startCell, endCell);
|
||||
if (cellPath.length > 0) {
|
||||
this.path = cellPath.map(c => this.cellToWorld(c));
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime: number) {
|
||||
// === 角色移动 ===
|
||||
if (this.path.length > 0) {
|
||||
const pos = this.player.getWorldPosition();
|
||||
const target = this.path[0];
|
||||
const dir = target.subtract(pos);
|
||||
const dist = dir.length();
|
||||
|
||||
if (dist < this.moveSpeed * deltaTime) {
|
||||
this.player.setWorldPosition(target);
|
||||
this.path.shift();
|
||||
} else {
|
||||
dir.normalize();
|
||||
const move = dir.multiplyScalar(this.moveSpeed * deltaTime);
|
||||
this.player.setWorldPosition(pos.add(move));
|
||||
}
|
||||
}
|
||||
|
||||
// === 相机跟随 ===
|
||||
const targetPos = this.player.getWorldPosition();
|
||||
const desiredPos = new Vec3(targetPos.x, targetPos.y, 1000); // Z 拉远
|
||||
Vec3.lerp(this.camPos, this.camera.node.getWorldPosition(), desiredPos, deltaTime * this.camSmooth);
|
||||
|
||||
// 限制相机范围
|
||||
const halfW = this.mapSize.x / 2;
|
||||
const halfH = this.mapSize.y / 2;
|
||||
const viewW = 400; // 你可以用实际相机视口宽高
|
||||
const viewH = 300;
|
||||
|
||||
this.camPos.x = math.clamp(this.camPos.x, -halfW + viewW, halfW - viewW);
|
||||
this.camPos.y = math.clamp(this.camPos.y, -halfH + viewH, halfH - viewH);
|
||||
|
||||
this.camera.node.setWorldPosition(this.camPos);
|
||||
}
|
||||
|
||||
// ============ A* 寻路实现 ============
|
||||
|
||||
worldToCell(worldPos: Vec3): { x: number, y: number } {
|
||||
return {
|
||||
x: Math.floor(worldPos.x / this.gridSize),
|
||||
y: Math.floor(worldPos.y / this.gridSize),
|
||||
};
|
||||
}
|
||||
|
||||
cellToWorld(cell: { x: number, y: number }): Vec3 {
|
||||
return new Vec3(
|
||||
cell.x * this.gridSize + this.gridSize / 2,
|
||||
cell.y * this.gridSize + this.gridSize / 2,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
findPath(start: { x: number, y: number }, end: { x: number, y: number }): { x: number, y: number }[] {
|
||||
const open: NodeCell[] = [];
|
||||
const closed: boolean[][] = [];
|
||||
const rows = this.mapData.length;
|
||||
const cols = this.mapData[0].length;
|
||||
|
||||
function heuristic(a: { x: number, y: number }, b: { x: number, y: number }) {
|
||||
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
||||
}
|
||||
|
||||
open.push({ x: start.x, y: start.y, g: 0, h: heuristic(start, end), f: 0 });
|
||||
|
||||
while (open.length > 0) {
|
||||
open.sort((a, b) => (a.g + a.h) - (b.g + b.h));
|
||||
const current = open.shift()!;
|
||||
closed[current.y] = closed[current.y] || [];
|
||||
closed[current.y][current.x] = true;
|
||||
|
||||
if (current.x === end.x && current.y === end.y) {
|
||||
const path: { x: number, y: number }[] = [];
|
||||
let node: NodeCell | undefined = current;
|
||||
while (node) {
|
||||
path.unshift({ x: node.x, y: node.y });
|
||||
node = node.parent;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
const neighbors = [
|
||||
{ x: current.x + 1, y: current.y },
|
||||
{ x: current.x - 1, y: current.y },
|
||||
{ x: current.x, y: current.y + 1 },
|
||||
{ x: current.x, y: current.y - 1 },
|
||||
];
|
||||
|
||||
for (const n of neighbors) {
|
||||
if (n.x < 0 || n.y < 0 || n.y >= rows || n.x >= cols) continue;
|
||||
if (this.mapData[n.y][n.x] === 1) continue;
|
||||
if (closed[n.y]?.[n.x]) continue;
|
||||
|
||||
const g = current.g + 1;
|
||||
const h = heuristic(n, end);
|
||||
const existing = open.find(o => o.x === n.x && o.y === n.y);
|
||||
if (!existing || g < existing.g) {
|
||||
open.push({ x: n.x, y: n.y, g, h, f: g + h, parent: current });
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
123
assets/scripts/PlayerController.ts
Normal file
123
assets/scripts/PlayerController.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view } from 'cc';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('PlayerController')
|
||||
export class PlayerController extends Component {
|
||||
|
||||
@property(Node)
|
||||
player: Node | null = null; // 玩家节点
|
||||
|
||||
@property(Camera)
|
||||
camera: Camera | null = null; // 主摄像机
|
||||
|
||||
@property({ range: [1, 20] })
|
||||
moveSpeed: number = 5; // 移动速度
|
||||
|
||||
private isMoving: boolean = false;
|
||||
private targetPosition: Vec3 = new Vec3();
|
||||
private originalPosition: Vec3 = new Vec3();
|
||||
|
||||
onLoad() {
|
||||
// 注册触摸事件
|
||||
input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
// 移除触摸事件
|
||||
input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.player) {
|
||||
this.originalPosition.set(this.player.position);
|
||||
}
|
||||
}
|
||||
|
||||
private onTouchStart(event: EventTouch) {
|
||||
if (!this.player || !this.camera) return;
|
||||
|
||||
// 获取触摸点的UI坐标
|
||||
const touchLocation = event.getUILocation();
|
||||
|
||||
// 将UI坐标转换为世界坐标
|
||||
const worldPos = this.screenToWorldPoint(touchLocation);
|
||||
|
||||
console.log(`触摸UI坐标: (${touchLocation.x}, ${touchLocation.y})`);
|
||||
console.log(`转换后世界坐标: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)})`);
|
||||
|
||||
this.moveToPosition(worldPos);
|
||||
}
|
||||
|
||||
private screenToWorldPoint(screenPos: { x: number, y: number }): Vec3 {
|
||||
if (!this.camera) {
|
||||
console.error('Camera未设置,无法进行坐标转换');
|
||||
return new Vec3(screenPos.x, screenPos.y, 0);
|
||||
}
|
||||
|
||||
// 获取可见区域大小
|
||||
const visibleSize = view.getVisibleSize();
|
||||
|
||||
// 计算屏幕中心点
|
||||
const centerX = visibleSize.width * 0.5;
|
||||
const centerY = visibleSize.height * 0.5;
|
||||
|
||||
// 将屏幕坐标转换为以屏幕中心为原点的坐标
|
||||
const normalizedX = screenPos.x - centerX;
|
||||
const normalizedY = screenPos.y - centerY;
|
||||
|
||||
// 考虑相机的位置偏移
|
||||
const cameraPos = this.camera.node.position;
|
||||
|
||||
// 计算世界坐标
|
||||
const worldX = normalizedX + cameraPos.x;
|
||||
const worldY = normalizedY + cameraPos.y;
|
||||
|
||||
return new Vec3(worldX, worldY, 0);
|
||||
}
|
||||
|
||||
|
||||
private moveToPosition(worldPos: Vec3) {
|
||||
if (!this.player) return;
|
||||
|
||||
// 设置目标位置(保持Z轴不变)
|
||||
this.targetPosition.set(worldPos.x, worldPos.y, this.player.position.z);
|
||||
this.isMoving = true;
|
||||
|
||||
console.log(`移动目标: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)})`);
|
||||
}
|
||||
|
||||
update(deltaTime: number) {
|
||||
if (!this.isMoving || !this.player) return;
|
||||
|
||||
const currentPos = this.player.position;
|
||||
const distance = Vec3.distance(currentPos, this.targetPosition);
|
||||
|
||||
// 如果距离很小,直接到达目标位置
|
||||
if (distance < 0.1) {
|
||||
this.player.position = this.targetPosition.clone();
|
||||
this.isMoving = false;
|
||||
console.log('到达目标位置');
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算移动方向
|
||||
const direction = new Vec3();
|
||||
Vec3.subtract(direction, this.targetPosition, currentPos);
|
||||
direction.normalize();
|
||||
|
||||
// 计算这一帧应该移动的距离
|
||||
const moveDistance = this.moveSpeed * deltaTime * 100; // 增加移动速度倍数
|
||||
|
||||
// 如果剩余距离小于这一帧要移动的距离,直接到达目标
|
||||
if (distance <= moveDistance) {
|
||||
this.player.position = this.targetPosition.clone();
|
||||
this.isMoving = false;
|
||||
console.log('到达目标位置');
|
||||
} else {
|
||||
// 正常移动
|
||||
const newPosition = new Vec3();
|
||||
Vec3.scaleAndAdd(newPosition, currentPos, direction, moveDistance);
|
||||
this.player.position = newPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "7d60dc26-9d91-48d4-b48c-e8b6f116e3b0",
|
||||
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
Reference in New Issue
Block a user