This commit is contained in:
richarjiang
2025-09-19 17:21:53 +08:00
commit 98f28ec5bf
32 changed files with 1839 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
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);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "424a4011-cdc4-4bf5-a5f5-b1ec4d9c2ba2",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,177 @@
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 [];
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7d60dc26-9d91-48d4-b48c-e8b6f116e3b0",
"files": [],
"subMetas": {},
"userData": {}
}