cb/assets/Script/module/Tool/GenieEffect.ts

375 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Genie Effect 动画工具
* 使用 RenderTexture 捕获整个节点,然后应用 Mac 风格最小化效果
* Cocos Creator 2.4.15
*/
const { ccclass, property } = cc._decorator;
@ccclass
export default class GenieEffect extends cc.Component {
@property(cc.Material)
genieMaterial: cc.Material = null;
@property
duration: number = 0.4;
@property
bendStrength: number = 0.5;
private _renderTexture: cc.RenderTexture = null;
private _captureSprite: cc.Sprite = null;
private _isPlaying: boolean = false;
onLoad() {
// 材质可以通过编辑器设置或动态传入
}
/**
* 捕获整个节点为 RenderTexture
*/
captureNode(): cc.SpriteFrame {
const node = this.node;
const size = node.getContentSize();
// 检查是否包含滚动视图,临时禁用裁剪
const scrollView = node.getComponent(cc.ScrollView);
const maskComponents = node.getComponentsInChildren(cc.Mask);
const originalMaskEnabled = maskComponents.map(m => m.enabled);
// 临时禁用所有 Mask 组件
maskComponents.forEach(mask => {
mask.enabled = false;
});
// 创建 RenderTexture
const renderTexture = new cc.RenderTexture();
renderTexture.initWithSize(size.width, size.height);
// 创建相机
const cameraNode = new cc.Node('CaptureCamera');
cameraNode.parent = node;
const camera = cameraNode.addComponent(cc.Camera);
// 设置相机
camera.targetTexture = renderTexture;
camera.clearFlags = cc.Camera.ClearFlags.COLOR | cc.Camera.ClearFlags.DEPTH;
camera.backgroundColor = cc.color(0, 0, 0, 0);
camera.zoomRatio = 1;
camera.cullingMask = 0xffffffff;
// 特别处理:如果节点包含滚动视图,调整渲染设置
if (scrollView) {
// 对于滚动视图,强制渲染所有内容
camera.cullingMask = 0xffffffff;
// 设置更清晰的渲染质量
camera.zoomRatio = 1.5;
}
// 渲染一帧
camera.render();
// 销毁相机节点
cameraNode.destroy();
// 恢复 Mask 组件状态
maskComponents.forEach((mask, index) => {
mask.enabled = originalMaskEnabled[index];
});
// 创建 SpriteFrame
const spriteFrame = new cc.SpriteFrame(renderTexture);
this._renderTexture = renderTexture;
return spriteFrame;
}
/**
* 创建捕获用的 Sprite 节点
*/
createCaptureSprite(): cc.Node {
// 捕获节点画面
const spriteFrame = this.captureNode();
// 创建 Sprite 节点
const spriteNode = new cc.Node('GenieCaptureSprite');
spriteNode.parent = this.node.parent;
spriteNode.setContentSize(this.node.getContentSize());
spriteNode.position = this.node.position;
spriteNode.anchorX = this.node.anchorX;
spriteNode.anchorY = this.node.anchorY;
// 添加 Sprite 组件
const sprite = spriteNode.addComponent(cc.Sprite);
sprite.spriteFrame = spriteFrame;
// 设置混合模式为透明
sprite.srcBlendFactor = cc.macro.BlendFactor.SRC_ALPHA;
sprite.dstBlendFactor = cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA;
// 隐藏原节点
this.node.active = false;
this._captureSprite = sprite;
return spriteNode;
}
/**
* 播放关闭动画(吸入到目标点)
* @param targetNode 目标节点(吸入位置)
* @param callback 动画完成回调
*/
playClose(targetNode: cc.Node, callback?: Function) {
if (this._isPlaying) {
console.warn('GenieEffect: 动画正在播放中');
return;
}
if (!this.genieMaterial) {
console.warn('GenieEffect: 材质未加载,使用默认动画');
this.playDefaultClose(targetNode, callback);
return;
}
// 创建捕获的 Sprite 节点
const captureNode = this.createCaptureSprite();
if (!captureNode) {
console.warn('GenieEffect: 无法创建捕获节点');
this.playDefaultClose(targetNode, callback);
return;
}
this._isPlaying = true;
// 获取 Sprite 组件
const sprite = captureNode.getComponent(cc.Sprite);
// 获取起始位置和目标位置(世界坐标)
const startWorldPos = this.node.convertToWorldSpaceAR(cc.v2(0, 0));
const targetWorldPos = targetNode.convertToWorldSpaceAR(cc.v2(0, 0));
// 设置材质参数
const material = this.genieMaterial;
material.setProperty('u_targetPos', [targetWorldPos.x, targetWorldPos.y]);
material.setProperty('u_progress', 0);
material.setProperty('u_bendStrength', this.bendStrength);
material.setProperty('u_shrinkWidth', 0.2);
// 应用材质
sprite.setMaterial(0, material);
// 播放动画 - 同时移动节点和更新 Shader
const animData = { progress: 0 };
const startPos = captureNode.position;
const targetPos = captureNode.parent.convertToNodeSpaceAR(targetWorldPos);
cc.tween(animData)
.to(this.duration, { progress: 1 }, {
easing: 'quadInOut',
onUpdate: (target, ratio) => {
if (material && sprite) {
material.setProperty('u_progress', animData.progress);
}
// 同时移动捕获节点向目标点靠近
if (captureNode) {
const t = animData.progress;
// 使用缓动函数让移动更自然
const easeT = t * t * (3 - 2 * t); // smoothstep
captureNode.x = startPos.x + (targetPos.x - startPos.x) * easeT;
captureNode.y = startPos.y + (targetPos.y - startPos.y) * easeT;
}
}
})
.call(() => {
this._isPlaying = false;
// 销毁捕获节点
if (captureNode) {
captureNode.destroy();
}
if (callback) callback();
})
.start();
}
/**
* 播放打开动画(从目标点展开)
* @param fromNode 起始节点(展开位置)
* @param callback 动画完成回调
*/
playOpen(fromNode: cc.Node, callback?: Function) {
if (this._isPlaying) {
console.warn('GenieEffect: 动画正在播放中');
return;
}
if (!this.genieMaterial) {
console.warn('GenieEffect: 材质未加载,使用默认动画');
this.playDefaultOpen(fromNode, callback);
return;
}
// 创建捕获的 Sprite 节点
const captureNode = this.createCaptureSprite();
if (!captureNode) {
console.warn('GenieEffect: 无法创建捕获节点');
this.playDefaultOpen(fromNode, callback);
return;
}
this._isPlaying = true;
// 获取 Sprite 组件
const sprite = captureNode.getComponent(cc.Sprite);
// 获取起始位置和目标位置(世界坐标)
const fromWorldPos = fromNode.convertToWorldSpaceAR(cc.v2(0, 0));
const targetWorldPos = this.node.convertToWorldSpaceAR(cc.v2(0, 0));
// 设置材质参数
const material = this.genieMaterial;
material.setProperty('u_targetPos', [fromWorldPos.x, fromWorldPos.y]);
material.setProperty('u_progress', 1);
material.setProperty('u_bendStrength', this.bendStrength);
material.setProperty('u_shrinkWidth', 0.2);
// 应用材质
sprite.setMaterial(0, material);
// 播放动画 - 同时移动节点和更新 Shader
const animData = { progress: 1 };
const startPos = captureNode.position;
const targetPos = captureNode.parent.convertToNodeSpaceAR(targetWorldPos);
cc.tween(animData)
.to(this.duration, { progress: 0 }, {
easing: 'quadInOut',
onUpdate: (target, ratio) => {
if (material && sprite) {
material.setProperty('u_progress', animData.progress);
}
// 同时移动捕获节点向目标点靠近
if (captureNode) {
const t = animData.progress;
// 使用缓动函数让移动更自然
const easeT = t * t * (3 - 2 * t); // smoothstep
captureNode.x = startPos.x + (targetPos.x - startPos.x) * easeT;
captureNode.y = startPos.y + (targetPos.y - startPos.y) * easeT;
}
}
})
.call(() => {
this._isPlaying = false;
// 销毁捕获节点
if (captureNode) {
captureNode.destroy();
}
// 显示原节点
this.node.active = true;
if (callback) callback();
})
.start();
}
/**
* 默认关闭动画(不使用 Shader
*/
playDefaultClose(targetNode: cc.Node, callback?: Function) {
if (this._isPlaying) {
console.warn('GenieEffect: 动画正在播放中');
return;
}
this._isPlaying = true;
// 获取起始位置和目标位置(世界坐标)
const startWorldPos = this.node.convertToWorldSpaceAR(cc.v2(0, 0));
const targetWorldPos = targetNode.convertToWorldSpaceAR(cc.v2(0, 0));
// 播放缩放和移动动画
cc.tween(this.node)
.to(this.duration, {
scale: 0,
position: new cc.Vec3(this.node.parent.convertToNodeSpaceAR(targetWorldPos).x, this.node.parent.convertToNodeSpaceAR(targetWorldPos).y, 0)
}, {
easing: 'quadInOut'
})
.call(() => {
this._isPlaying = false;
this.node.active = false;
if (callback) callback();
})
.start();
}
/**
* 默认打开动画(不使用 Shader
*/
playDefaultOpen(fromNode: cc.Node, callback?: Function) {
if (this._isPlaying) {
console.warn('GenieEffect: 动画正在播放中');
return;
}
this._isPlaying = true;
// 获取起始位置和目标位置(世界坐标)
const fromWorldPos = fromNode.convertToWorldSpaceAR(cc.v2(0, 0));
const targetWorldPos = this.node.convertToWorldSpaceAR(cc.v2(0, 0));
// 设置初始状态
this.node.scale = 0;
const fromPos = this.node.parent.convertToNodeSpaceAR(fromWorldPos);
this.node.position = new cc.Vec3(fromPos.x, fromPos.y, 0);
this.node.active = true;
// 播放缩放和移动动画
cc.tween(this.node)
.to(this.duration, {
scale: 1,
position: new cc.Vec3(this.node.parent.convertToNodeSpaceAR(targetWorldPos).x, this.node.parent.convertToNodeSpaceAR(targetWorldPos).y, 0)
}, {
easing: 'quadInOut'
})
.call(() => {
this._isPlaying = false;
if (callback) callback();
})
.start();
}
}
/**
* GenieEffect 工具类
* 提供静态方法方便调用
*/
export class GenieEffectUtil {
/**
* 播放关闭动画
* @param node 要播放动画的节点
* @param targetNode 目标节点(吸入位置)
* @param callback 动画完成回调
* @param material 可选的材质
*/
static playClose(node: cc.Node, targetNode: cc.Node, callback?: Function, material?: cc.Material) {
const genieEffect = node.addComponent(GenieEffect);
genieEffect.genieMaterial = material;
genieEffect.playClose(targetNode, callback);
}
/**
* 播放打开动画
* @param node 要播放动画的节点
* @param fromNode 起始节点(展开位置)
* @param callback 动画完成回调
* @param material 可选的材质
*/
static playOpen(node: cc.Node, fromNode: cc.Node, callback?: Function, material?: cc.Material) {
const genieEffect = node.addComponent(GenieEffect);
genieEffect.genieMaterial = material;
genieEffect.playOpen(fromNode, callback);
}
}