消消方块阵换皮表情
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

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()
}
}