Files
pixelheros/assets/script/game/common/CameraFX.ts
panFD 5f511ddcb4 feat(skill): add ultimate skill camera and shake effects
add camera focus and screen shake effects for ultimate skills that start with "max" prefix,
add CameraFX utility class for targeted camera zoom and reset functionality
2026-09-13 15:55:33 +08:00

86 lines
3.9 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.
/**
* @file CameraFX.ts
* @description 大招镜头聚焦工具(表现层)
*
* 项目内场景 Camera 从未启用(MapViewScene.camera 可能为 null),
* 与 ScreenShake 同理改为操作地图根节点 smc.map.MapView.node:
* 以施法者位置为锚点做 scale + 位移 tween,实现"画面瞬间拉近再回弹"的镜头突进感,
* UI 层(oops.gui 挂在独立根节点)天然不受影响。
*
* 与 ScreenShake 的区别:本工具是"有明确锚点的缩放聚焦",
* ScreenShake 是"无锚点的随机抖动",两者可叠加(大招命中瞬间聚焦回弹 + 震屏)。
*
* 用法:
* CameraFX.focusOn(heroView.node, 1.2, 0.5);
*/
import { Node, tween, Tween, v3, Vec3, view } from "cc";
import { smc } from "./SingletonModuleComp";
export class CameraFX {
/** 根节点基准状态(首次聚焦时快照:位置 + 缩放,用于回弹复位,防多次触发漂移) */
private static basePos: Vec3 | null = null;
private static baseScale: Vec3 | null = null;
/**
* 镜头聚焦:画面瞬间向锚点拉近,随后回弹复位
* @param anchor 聚焦锚点节点(通常是施法者节点),以其世界坐标为画面中心
* @param zoom 拉近倍率(建议 1.15~1.25,过大容易出戏)
* @param holdTime 聚焦保持到回弹完成的总时长(秒,建议 0.4~0.6)
*/
static focusOn(anchor: Node | null | undefined, zoom: number = 1.2, holdTime: number = 0.5) {
const root = smc.map?.MapView?.node;
if (!root || !root.isValid || !anchor || !anchor.isValid) return;
if (!this.basePos) {
this.basePos = root.position.clone();
this.baseScale = root.scale.clone();
}
// 打断进行中的聚焦(新大招覆盖旧大招,避免多英雄连续放大时 tween 叠加失控)
Tween.stopAllByTarget(root);
// 锚点世界坐标 -> 根节点本地坐标,作为缩放后的画面中心
const worldPos = anchor.worldPosition;
const localPos = v3();
root.inverseTransformPoint(localPos, worldPos);
const designSize = view.getDesignResolutionSize();
const focusScale = this.baseScale.x * zoom;
// 缩放会围绕根节点原点(0,0)放大画面,需反向平移让锚点保持在屏幕中心附近:
// 目标位置 = 基准位置 - 锚点本地坐标 * (zoom - 1) * 基准缩放
// 同时按设计分辨率的一半作为屏幕中心基准,避免锚点在屏幕边缘时拉出黑边
const offsetX = localPos.x * (zoom - 1) * this.baseScale.x;
const offsetY = localPos.y * (zoom - 1) * this.baseScale.y;
const clampX = designSize.width * 0.25; // 平移量限幅:最多偏移 1/4 屏,防边缘英雄把画面拉飞
const clampY = designSize.height * 0.25;
const targetPos = v3(
this.basePos.x - Math.max(-clampX, Math.min(clampX, offsetX)),
this.basePos.y - Math.max(-clampY, Math.min(clampY, offsetY)),
this.basePos.z
);
const focusScaleV3 = v3(focusScale, focusScale, this.baseScale.z);
// 三段式节奏:快速突进(0.12s) -> 短暂停留 -> 慢速回弹(holdTime)
tween(root)
.to(0.12, { position: targetPos, scale: focusScaleV3 }, { easing: "sineOut" })
.delay(Math.max(0.05, holdTime * 0.3))
.to(holdTime, { position: this.basePos, scale: this.baseScale }, { easing: "sineInOut" })
.start();
}
/**
* 复位镜头状态(关卡切换/战斗结束时调用,防止状态残留)
*/
static reset() {
const root = smc.map?.MapView?.node;
if (root && root.isValid && this.basePos && this.baseScale) {
Tween.stopAllByTarget(root);
root.setPosition(this.basePos);
root.setScale(this.baseScale);
}
this.basePos = null;
this.baseScale = null;
}
}