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.
137 lines
4.5 KiB
137 lines
4.5 KiB
|
3 weeks ago
|
import { Node, Vec3 } from 'cc';
|
||
|
|
import { computeAimAngleDeg } from './SkillTransformDriver';
|
||
|
|
|
||
|
|
/** 悟/棍 skillParent 预制体默认朝向(euler.z) */
|
||
|
|
export const SKILL_PARENT_REST_ANGLE = 90;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 箭发射相对目标连线的偏角(度)。
|
||
|
|
* 默认朝上=90°;按象限决定 ±:
|
||
|
|
* - 第1、4象限(目标在右侧):目标角 + offset(抛物线向上拱)
|
||
|
|
* - 第2、3象限(目标在左侧):目标角 − offset
|
||
|
|
* 例:150→120,25→55,225→195,320→350
|
||
|
|
*/
|
||
|
|
export const ARROW_BOW_OFFSET_DEG = 30;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 计算箭发射/拉弓朝向(度)。
|
||
|
|
* 不写节点角度;由表现层插值到该角,再沿此角出弓并做抛物线飞向真实目标。
|
||
|
|
*/
|
||
|
|
export function computeArrowBowAimDeg(
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
offsetDeg = ARROW_BOW_OFFSET_DEG,
|
||
|
|
): number | null {
|
||
|
|
const raw = computeAimAngleDeg(pivotWorld, targetWorld);
|
||
|
|
if (raw == null) return null;
|
||
|
|
const absOff = Math.abs(offsetDeg);
|
||
|
|
// atan2:右0°、上90°、左±180°、下-90°。dx>=0 为第1/4象限,dx<0 为第2/3象限
|
||
|
|
const dx = targetWorld.x - pivotWorld.x;
|
||
|
|
return dx >= 0 ? raw + absOff : raw - absOff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 立刻把 skillParent 拧到偏转后的瞄准角(无插值时用)。
|
||
|
|
* @returns 实际拉弓角(度);无有效朝向时返回 null
|
||
|
|
*/
|
||
|
|
export function applyArrowBowAim(
|
||
|
|
skillParent: Node | null | undefined,
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
offsetDeg = ARROW_BOW_OFFSET_DEG,
|
||
|
|
): number | null {
|
||
|
|
const aimDeg = computeArrowBowAimDeg(pivotWorld, targetWorld, offsetDeg);
|
||
|
|
if (aimDeg != null && skillParent?.isValid) {
|
||
|
|
skillParent.angle = aimDeg;
|
||
|
|
}
|
||
|
|
return aimDeg;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 将 skillParent 旋转到指向目标,并限制在 [minDeg, maxDeg]。
|
||
|
|
* @returns 实际使用的瞄准角(度)
|
||
|
|
*/
|
||
|
|
export function aimSkillParentClamped(
|
||
|
|
skillParent: Node | null | undefined,
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
minDeg: number,
|
||
|
|
maxDeg: number,
|
||
|
|
): number {
|
||
|
|
const angle = computeClampedAimAngleDeg(pivotWorld, targetWorld, minDeg, maxDeg);
|
||
|
|
if (skillParent?.isValid && angle != null) {
|
||
|
|
skillParent.angle = angle;
|
||
|
|
}
|
||
|
|
return angle ?? (minDeg + maxDeg) * 0.5;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** pivot→target 瞄准角,限制在 [minDeg, maxDeg] */
|
||
|
|
export function computeClampedAimAngleDeg(
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
minDeg: number,
|
||
|
|
maxDeg: number,
|
||
|
|
): number | null {
|
||
|
|
const raw = computeAimAngleDeg(pivotWorld, targetWorld);
|
||
|
|
if (raw == null) return null;
|
||
|
|
return Math.max(minDeg, Math.min(maxDeg, raw));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 沿瞄准角与距离计算落点世界坐标 */
|
||
|
|
export function worldPointAtAimAngle(
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
angleDeg: number,
|
||
|
|
distancePx: number,
|
||
|
|
): Vec3 {
|
||
|
|
const rad = angleDeg * Math.PI / 180;
|
||
|
|
return new Vec3(
|
||
|
|
pivotWorld.x + Math.cos(rad) * distancePx,
|
||
|
|
pivotWorld.y + Math.sin(rad) * distancePx,
|
||
|
|
pivotWorld.z,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 将 skillParent 旋转到指向目标(与悟一致:以 pivot 为原点,atan2 得 Cocos angle)。
|
||
|
|
* 右=0°,上=90°,左=±180°。
|
||
|
|
*/
|
||
|
|
export function aimSkillParentAtTarget(
|
||
|
|
skillParent: Node | null | undefined,
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
): void {
|
||
|
|
if (!skillParent?.isValid) return;
|
||
|
|
const dx = targetWorld.x - pivotWorld.x;
|
||
|
|
const dy = targetWorld.y - pivotWorld.y;
|
||
|
|
if (Math.abs(dx) < 1e-4 && Math.abs(dy) < 1e-4) return;
|
||
|
|
skillParent.angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resetSkillParentAngle(
|
||
|
|
skillParent: Node | null | undefined,
|
||
|
|
restAngle = SKILL_PARENT_REST_ANGLE,
|
||
|
|
): void {
|
||
|
|
if (skillParent?.isValid) {
|
||
|
|
skillParent.angle = restAngle;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 按 pivot→目标距离拉伸 skillParent.localScale.x(金箍棒/棍等沿父节点 X 轴延伸)。
|
||
|
|
* @param baseReach 预制体 scale=1 时 skill 子节点相对 pivot 的基准长度
|
||
|
|
*/
|
||
|
|
export function scaleSkillParentReach(
|
||
|
|
skillParent: Node | null | undefined,
|
||
|
|
pivotWorld: Readonly<Vec3>,
|
||
|
|
targetWorld: Readonly<Vec3>,
|
||
|
|
baseReach: number,
|
||
|
|
initScale: Readonly<Vec3>,
|
||
|
|
): void {
|
||
|
|
if (!skillParent?.isValid || baseReach <= 1e-4) return;
|
||
|
|
const dx = targetWorld.x - pivotWorld.x;
|
||
|
|
const dy = targetWorld.y - pivotWorld.y;
|
||
|
|
const dist = Math.hypot(dx, dy);
|
||
|
|
const factor = Math.max(0.1, dist / baseReach);
|
||
|
|
skillParent.setScale(initScale.x * factor, initScale.y, initScale.z);
|
||
|
|
}
|