feat: 摄像机动画控制类

This commit is contained in:
unanmed 2024-10-05 19:36:02 +08:00
parent fe1579e1dc
commit 19ac59ee1b
5 changed files with 299 additions and 5 deletions

View File

@ -75,6 +75,7 @@ import { HeroKeyMover } from './main/action/move';
import { Camera } from './render/camera';
import * as Animation from 'mutate-animate';
import './render/index';
import * as RenderUtils from './render/utils';
// ----- 类注册
Mota.register('class', 'AudioPlayer', AudioPlayer);
@ -163,7 +164,8 @@ Mota.register('module', 'Render', {
RenderAdapter,
Layer,
LayerGroupFloorBinder,
Camera
Camera,
Utils: RenderUtils
});
Mota.register('module', 'Action', {
HeroKeyMover

View File

@ -1,7 +1,8 @@
import { Animation, Transition } from 'mutate-animate';
import { Animation, TimingFn, Transition } from 'mutate-animate';
import { RenderItem } from './item';
import { logger } from '../common/logger';
import { Transform } from './transform';
import EventEmitter from 'eventemitter3';
interface CameraTranslate {
readonly type: 'translate';
@ -26,7 +27,11 @@ interface CameraScale {
type CameraOperation = CameraTranslate | CameraScale | CameraRotate;
export class Camera {
interface CameraEvent {
destroy: [];
}
export class Camera extends EventEmitter<CameraEvent> {
/** 当前绑定的渲染元素 */
readonly binded: RenderItem;
/** 目标变换矩阵,默认与 `this.binded.transform` 同引用 */
@ -62,6 +67,8 @@ export class Camera {
}
constructor(item: RenderItem) {
super();
this.binded = item;
this.delegation = item.delegateTicker(() => this.tick());
@ -324,6 +331,13 @@ export class Camera {
this.applyAnimation(time, update);
}
/**
*
*/
stopAllAnimates() {
this.animationIds.forEach(v => this.binded.removeTicker(v));
}
/**
* 使
*/
@ -331,5 +345,257 @@ export class Camera {
this.binded.removeTicker(this.delegation);
this.animationIds.forEach(v => this.binded.removeTicker(v));
Camera.cameraMap.delete(this.binded);
this.emit('destroy');
}
}
interface CameraAnimationBase {
type: string;
time: number;
start: number;
}
interface TranslateAnimation extends CameraAnimationBase {
type: 'translate';
timing: TimingFn;
x: number;
y: number;
}
interface TranslateAsAnimation extends CameraAnimationBase {
type: 'translateAs';
timing: TimingFn<2>;
time: number;
}
interface RotateAnimation extends CameraAnimationBase {
type: 'rotate';
timing: TimingFn;
angle: number;
time: number;
}
interface ScaleAnimation extends CameraAnimationBase {
type: 'scale';
timing: TimingFn;
scale: number;
time: number;
}
type CameraAnimationData =
| TranslateAnimation
| TranslateAsAnimation
| RotateAnimation
| ScaleAnimation;
interface CameraAnimationExecution {
data: CameraAnimationData[];
animation: Animation;
}
interface CameraAnimationEvent {
animate: [
operation: CameraOperation,
execution: CameraAnimationExecution,
item: CameraAnimationData
];
}
export class CameraAnimation extends EventEmitter<CameraAnimationEvent> {
camera: Camera;
/** 动画开始时刻 */
private startTime: number = 0;
/** 动画结束时刻 */
private endTime: number = 0;
/** 委托ticker的id */
private delegation: number;
/** 动画是否开始 */
private started: boolean = false;
/** 每个摄像机操作的动画映射 */
private animateMap: Map<CameraOperation, CameraAnimationExecution> =
new Map();
constructor(camera: Camera) {
super();
this.camera = camera;
this.delegation = camera.binded.delegateTicker(this.tick);
}
private tick = () => {
if (!this.started) return;
const now = Date.now();
const time = now - this.startTime;
if (now - this.startTime > this.endTime + 50) {
this.destroy();
return;
}
this.animateMap.forEach((exe, ope) => {
const data = exe.data;
if (data.length === 0) return;
const item = data[0];
if (item.time < time) {
this.executeAnimate(exe, item);
data.shift();
this.emit('animate', ope, exe, item);
}
});
};
private executeAnimate(
execution: CameraAnimationExecution,
animate: CameraAnimationData
) {
if (animate.type === 'translateAs') {
const ani = this.ensureAnimate(execution);
ani.time(animate.time).moveAs(animate.timing);
} else if (animate.type === 'translate') {
const ani = this.ensureAnimate(execution);
const { x, y, time, timing } = animate;
ani.mode(timing).time(time).move(x, y);
} else if (animate.type === 'rotate') {
const ani = this.ensureAnimate(execution);
const { angle, time, timing } = animate;
ani.mode(timing).time(time).rotate(angle);
} else {
const ani = this.ensureAnimate(execution);
const { scale, time, timing } = animate;
ani.mode(timing).time(time).scale(scale);
}
}
private ensureAnimate(execution: CameraAnimationExecution) {
if (execution.animation) return execution.animation;
const ani = new Animation();
execution.animation = ani;
return ani;
}
private ensureOperation(operation: CameraOperation) {
if (!this.animateMap.has(operation)) {
const data: CameraAnimationExecution = {
data: [],
animation: new Animation()
};
this.animateMap.set(operation, data);
return data;
} else {
return this.animateMap.get(operation)!;
}
}
/**
*
* @param operation
* @param x
* @param y
* @param time
* @param start
* @param timing
*/
translate(
operation: CameraTranslate,
x: number,
y: number,
time: number,
start: number,
timing: TimingFn
) {
const exe = this.ensureOperation(operation);
const data: TranslateAnimation = {
type: 'translate',
timing,
x,
y,
time,
start
};
exe.data.push(data);
}
/**
*
* @param operation
* @param angle
* @param time
* @param start
* @param timing
*/
rotate(
operation: CameraRotate,
angle: number,
time: number,
start: number,
timing: TimingFn
) {
const exe = this.ensureOperation(operation);
const data: RotateAnimation = {
type: 'rotate',
timing,
angle,
time,
start
};
exe.data.push(data);
}
/**
*
* @param operation
* @param scale
* @param time
* @param start
* @param timing
*/
scale(
operation: CameraScale,
scale: number,
time: number,
start: number,
timing: TimingFn
) {
const exe = this.ensureOperation(operation);
const data: ScaleAnimation = {
type: 'scale',
timing,
scale,
time,
start
};
exe.data.push(data);
}
/**
*
*/
start() {
if (this.started) return;
this.startTime = Date.now();
this.started = true;
let endTime = 0;
this.animateMap.forEach((exe, ope) => {
const data = exe.data;
data.sort((a, b) => a.start - b.start);
const end = data.at(-1);
if (!end) return;
const t = end.start + end.time;
if (t > endTime) endTime = t;
if (ope.type === 'translate') {
this.camera.applyTranslateAnimation(ope, exe.animation, t + 50);
} else if (ope.type === 'rotate') {
this.camera.applyRotateAnimation(ope, exe.animation, t + 50);
} else {
this.camera.applyScaleAnimation(ope, exe.animation, t + 50);
}
});
this.endTime = endTime + this.startTime;
}
destroy() {
this.camera.binded.removeTicker(this.delegation);
this.camera.stopAllAnimates();
}
}

View File

@ -102,6 +102,11 @@ export class FloorViewport implements ILayerGroupRenderExtends {
*/
enable() {
this.enabled = true;
const { x, y } = core.status.hero.loc;
const { x: nx, y: ny } = this.transform;
this.nx = nx;
this.ny = ny;
this.mutateTo(x, y);
}
/**
@ -381,6 +386,12 @@ adapter.receive('setPosition', (item, x, y) => {
item.setPosition(x, y);
return Promise.resolve();
});
adapter.receiveSync('disable', item => {
item.disable();
});
adapter.receiveSync('enable', item => {
item.enable();
});
const hook = Mota.require('var', 'hook');
hook.on('changingFloor', (_, loc) => {

14
src/core/render/utils.ts Normal file
View File

@ -0,0 +1,14 @@
import { RenderAdapter } from './adapter';
import { FloorViewport } from './preset/viewport';
export function disableViewport() {
const adapter = RenderAdapter.get<FloorViewport>('viewport');
if (!adapter) return;
adapter.sync('disable');
}
export function enableViewport() {
const adapter = RenderAdapter.get<FloorViewport>('viewport');
if (!adapter) return;
adapter.sync('disable');
}

View File

@ -40,6 +40,7 @@ import type { HeroKeyMover } from '@/core/main/action/move';
import type { BlockMover, HeroMover, ObjectMoverBase } from './state/move';
import type { Camera } from '@/core/render/camera';
import type * as Animation from 'mutate-animate';
import type * as RenderUtils from '@/core/render/utils';
interface ClassInterface {
// 渲染进程与游戏进程通用
@ -122,6 +123,7 @@ interface ModuleInterface {
Layer: typeof Layer;
LayerGroupFloorBinder: typeof LayerGroupFloorBinder;
Camera: typeof Camera;
Utils: typeof RenderUtils;
};
State: {
ItemState: typeof ItemState;
@ -130,7 +132,7 @@ interface ModuleInterface {
ObjectMoverBase: typeof ObjectMoverBase;
heroMoveCollection: {
mover: HeroMover;
keyMover: HeroKeyMover;
keyMover?: HeroKeyMover;
};
};
Action: {
@ -168,7 +170,6 @@ interface PluginInterface {
chase_g: typeof import('../plugin/game/chase');
skill_g: typeof import('../plugin/game/skill');
towerBoss_g: typeof import('../plugin/game/towerBoss');
rewrite_g: typeof import('../plugin/game/fx/rewrite');
itemDetail_g: typeof import('../plugin/game/fx/itemDetail');
checkBlock_g: typeof import('../plugin/game/enemy/checkblock');
}