You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
8.4 KiB
255 lines
8.4 KiB
import { _decorator, Component, Material, Vec4, Sprite, Node, UITransform, Vec3, Vec2, tween } from 'cc';
|
|
const { ccclass, property } = _decorator;
|
|
|
|
interface HoleData {
|
|
node: Node;
|
|
radius: number;
|
|
softness: number;
|
|
id: string; // 这里存 uuid
|
|
isActive: boolean;
|
|
}
|
|
|
|
@ccclass('SdfHoleController')
|
|
export class SdfHoleController extends Component {
|
|
|
|
@property({ tooltip: "默认边缘柔化度 (像素)0-0.5" })
|
|
defaultSoftness: number = 0.1;
|
|
|
|
@property({ tooltip: "默认洞的半径 (像素)" })
|
|
defaultRadius: number = 60.0;
|
|
|
|
@property({ tooltip: "最大支持的洞数量" })
|
|
maxHoles: number = 20;
|
|
|
|
private holes: HoleData[] = [];
|
|
private material: Material | null = null;
|
|
private uiTransform: UITransform | null = null;
|
|
private sprite: Sprite | null = null;
|
|
|
|
// 👇 修改:使用 Set<string> 存储已激活节点的 UUID
|
|
private activatedUuids: Set<string> = new Set();
|
|
|
|
// 缓存材质参数数据,避免 updateHoles 里反复 new 数组/Vec
|
|
private _leftBottom: Vec3 = new Vec3();
|
|
private _textureSize: Vec2 = new Vec2(0, 0);
|
|
private _circlesData: Vec4[] = [];
|
|
|
|
isCanMove: boolean=false;
|
|
stratMove: number=0;
|
|
moveEndPosiY: number=0;
|
|
speed = 2
|
|
start() {
|
|
this.sprite = this.getComponent(Sprite);
|
|
this.uiTransform = this.getComponent(UITransform);
|
|
|
|
if (!this.sprite || !this.sprite.material || !this.uiTransform) {
|
|
console.error("❌ 组件缺失!");
|
|
return;
|
|
}
|
|
|
|
this.material = this.sprite.material;
|
|
|
|
// 预分配 Vec4,循环复用,降低 GC
|
|
this._circlesData.length = 0;
|
|
for (let i = 0; i < this.maxHoles; i++) {
|
|
this._circlesData.push(new Vec4(0, 0, 0, 0));
|
|
}
|
|
|
|
this.updateHoles();
|
|
}
|
|
|
|
startMove(fogDownDistance, speed = 2){
|
|
this.speed = speed
|
|
this.isCanMove = true
|
|
this.stratMove = 0
|
|
this.moveEndPosiY = fogDownDistance
|
|
}
|
|
update(dt: number) {
|
|
if (!gg.game.CurentBattle.canUpdateFrame()) return;
|
|
|
|
if(this.isCanMove){
|
|
// 原逻辑按「每帧像素」移动;用 dt 与倍速对齐不同帧率,并与战斗倍速一致
|
|
const step = this.speed * 60 * dt * gg.game.CurentBattle.TimeScale;
|
|
const pos = this.node.getPosition();
|
|
const newY = pos.y - step;
|
|
this.node.setPosition(pos.x, newY, pos.z);
|
|
this.stratMove += step
|
|
if(this.stratMove > this.moveEndPosiY){
|
|
this.isCanMove = false
|
|
this.stratMove = 0
|
|
}
|
|
|
|
}
|
|
if (this.holes.length > 0 && this.isCanMove) {
|
|
this.updateHoles();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 【核心功能】检测固定节点列表,若与雾气相交且未激活,则生成洞
|
|
* @param fixedNodes 需要检测的固定节点数组 (直接传节点!)
|
|
* @param radiusOverride 可选:覆盖默认半径
|
|
* @param softnessOverride 可选:覆盖默认柔化度
|
|
*/
|
|
checkAndActivateHoles(fixedNodes: Node[], radiusOverride?: number, softnessOverride?: number) {
|
|
if (!this.uiTransform) return;
|
|
|
|
// 1. 获取雾气当前的世界边界
|
|
const fogWorldPos = this.node.getWorldPosition();
|
|
const contentSize = this.uiTransform.contentSize;
|
|
const anchor = this.uiTransform.anchorPoint;
|
|
|
|
// 计算雾气的四至边界 (世界坐标)
|
|
const fogBottomY = fogWorldPos.y - contentSize.height * anchor.y;
|
|
const fogTopY = fogBottomY + contentSize.height;
|
|
const fogLeftX = fogWorldPos.x - contentSize.width * anchor.x;
|
|
const fogRightX = fogLeftX + contentSize.width;
|
|
|
|
let hasNewHole = false;
|
|
|
|
// 2. 遍历所有传入的固定节点
|
|
for (const node of fixedNodes) {
|
|
// 跳过无效节点
|
|
if (!node.isValid) continue;
|
|
|
|
const point = node.getWorldPosition();
|
|
|
|
// --- 相交判断逻辑 (AABB 包围盒检测) ---
|
|
// 判断点是否在雾气矩形内部
|
|
const inVertical = point.y <= fogTopY && point.y >= fogBottomY;
|
|
const inHorizontal = point.x >= fogLeftX && point.x <= fogRightX;
|
|
|
|
if (inVertical && inHorizontal) {
|
|
// ✅ 相交了!
|
|
|
|
// 👇 关键:使用 node.uuid 作为唯一标识
|
|
const uuid = node.uuid;
|
|
|
|
// 检查是否已经激活过
|
|
if (!this.activatedUuids.has(uuid)) {
|
|
// 添加到洞列表
|
|
// 注意:这里直接复用传入的 node,不需要创建虚拟节点了!
|
|
// 这样如果节点本身在动,洞也会跟着动(如果需要的话)
|
|
this.addHole(node, radiusOverride || this.defaultRadius, softnessOverride || this.defaultSoftness);
|
|
|
|
// 标记该 UUID 为已激活
|
|
this.activatedUuids.add(uuid);
|
|
hasNewHole = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// addHole 内部会调用 updateHoles,所以这里不需要额外调用
|
|
}
|
|
|
|
/**
|
|
* 【重置功能】清除所有洞,并清空 UUID 记录
|
|
* 调用后,之前的节点再次进入雾气范围时,会重新触发挖洞
|
|
*/
|
|
resetHoles() {
|
|
this.holes = [];
|
|
this.activatedUuids.clear(); // 👈 清空 UUID 记录
|
|
this.updateHoles();
|
|
}
|
|
|
|
// --- 辅助方法 ---
|
|
|
|
addHole(node: Node, radius: number = this.defaultRadius, softness: number = this.defaultSoftness) {
|
|
if (this.holes.length >= this.maxHoles) {
|
|
return;
|
|
}
|
|
// 防止重复添加同一个节点到 holes 数组
|
|
if (this.holes.find(h => h.node === node)) return;
|
|
|
|
this.holes.push({
|
|
node,
|
|
radius,
|
|
softness,
|
|
id: node.uuid, // 存储 uuid
|
|
isActive: true
|
|
});
|
|
this.updateHoles();
|
|
}
|
|
|
|
|
|
|
|
updateHoles() {
|
|
if (!this.material || !this.uiTransform) return;
|
|
|
|
const fogWorldPos = this.node.getWorldPosition();
|
|
const contentSize = this.uiTransform.contentSize;
|
|
const anchor = this.uiTransform.anchorPoint;
|
|
|
|
// 左下角世界坐标(复用,避免每帧 new Vec3)
|
|
this._leftBottom.x = fogWorldPos.x - contentSize.width * anchor.x;
|
|
this._leftBottom.y = fogWorldPos.y - contentSize.height * anchor.y;
|
|
this._leftBottom.z = 0;
|
|
|
|
const width = contentSize.width;
|
|
const height = contentSize.height;
|
|
|
|
const count = this.holes.length;
|
|
|
|
// 填充穴点数据到 Vec4 数组(循环复用对象,避免 new/GC)
|
|
for (let i = 0; i < count; i++) {
|
|
const hole = this.holes[i];
|
|
const pos = hole.node.getWorldPosition();
|
|
const u = (pos.x - this._leftBottom.x) / width;
|
|
const v = 1.0 - ((pos.y - this._leftBottom.y) / height);
|
|
const circle = this._circlesData[i];
|
|
circle.x = u;
|
|
circle.y = v;
|
|
circle.z = hole.radius;
|
|
circle.w = hole.softness;
|
|
}
|
|
|
|
// 清理剩余空槽位,避免 shader 因实现细节读到旧值
|
|
for (let i = count; i < this.maxHoles; i++) {
|
|
const circle = this._circlesData[i];
|
|
circle.x = 0;
|
|
circle.y = 0;
|
|
circle.z = 0;
|
|
circle.w = 0;
|
|
}
|
|
|
|
this.material.setProperty('holeCount', count);
|
|
this._textureSize.x = width;
|
|
this._textureSize.y = height;
|
|
this.material.setProperty('textureSize', this._textureSize);
|
|
this.material.setProperty('circles', this._circlesData);
|
|
}
|
|
|
|
recoverySpr(){
|
|
if (!this.material || !this.uiTransform) return;
|
|
|
|
|
|
const contentSize = this.uiTransform.contentSize;
|
|
|
|
|
|
|
|
const width = contentSize.width;
|
|
const height = contentSize.height;
|
|
|
|
|
|
|
|
this.material.setProperty('holeCount', 0);
|
|
this._textureSize.x = width;
|
|
this._textureSize.y = height;
|
|
this.material.setProperty('textureSize', this._textureSize);
|
|
this.material.setProperty('circles', this._circlesData);
|
|
}
|
|
//返回到原来的位置
|
|
returnToOriginalPosition(moveY = 600){
|
|
// let moveY = 1000
|
|
this.isCanMove = false
|
|
this.stratMove = 0
|
|
//this.resetHoles()
|
|
this.recoverySpr()
|
|
tween(this.node)
|
|
.to(1,{y:moveY})
|
|
.start()
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|