mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-03-04 03:37:07 +08:00
feat: 摄像机动画控制类
This commit is contained in:
parent
fe1579e1dc
commit
19ac59ee1b
@ -75,6 +75,7 @@ import { HeroKeyMover } from './main/action/move';
|
|||||||
import { Camera } from './render/camera';
|
import { Camera } from './render/camera';
|
||||||
import * as Animation from 'mutate-animate';
|
import * as Animation from 'mutate-animate';
|
||||||
import './render/index';
|
import './render/index';
|
||||||
|
import * as RenderUtils from './render/utils';
|
||||||
|
|
||||||
// ----- 类注册
|
// ----- 类注册
|
||||||
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
||||||
@ -163,7 +164,8 @@ Mota.register('module', 'Render', {
|
|||||||
RenderAdapter,
|
RenderAdapter,
|
||||||
Layer,
|
Layer,
|
||||||
LayerGroupFloorBinder,
|
LayerGroupFloorBinder,
|
||||||
Camera
|
Camera,
|
||||||
|
Utils: RenderUtils
|
||||||
});
|
});
|
||||||
Mota.register('module', 'Action', {
|
Mota.register('module', 'Action', {
|
||||||
HeroKeyMover
|
HeroKeyMover
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Animation, Transition } from 'mutate-animate';
|
import { Animation, TimingFn, Transition } from 'mutate-animate';
|
||||||
import { RenderItem } from './item';
|
import { RenderItem } from './item';
|
||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { Transform } from './transform';
|
import { Transform } from './transform';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
interface CameraTranslate {
|
interface CameraTranslate {
|
||||||
readonly type: 'translate';
|
readonly type: 'translate';
|
||||||
@ -26,7 +27,11 @@ interface CameraScale {
|
|||||||
|
|
||||||
type CameraOperation = CameraTranslate | CameraScale | CameraRotate;
|
type CameraOperation = CameraTranslate | CameraScale | CameraRotate;
|
||||||
|
|
||||||
export class Camera {
|
interface CameraEvent {
|
||||||
|
destroy: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Camera extends EventEmitter<CameraEvent> {
|
||||||
/** 当前绑定的渲染元素 */
|
/** 当前绑定的渲染元素 */
|
||||||
readonly binded: RenderItem;
|
readonly binded: RenderItem;
|
||||||
/** 目标变换矩阵,默认与 `this.binded.transform` 同引用 */
|
/** 目标变换矩阵,默认与 `this.binded.transform` 同引用 */
|
||||||
@ -62,6 +67,8 @@ export class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(item: RenderItem) {
|
constructor(item: RenderItem) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.binded = item;
|
this.binded = item;
|
||||||
|
|
||||||
this.delegation = item.delegateTicker(() => this.tick());
|
this.delegation = item.delegateTicker(() => this.tick());
|
||||||
@ -324,6 +331,13 @@ export class Camera {
|
|||||||
this.applyAnimation(time, update);
|
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.binded.removeTicker(this.delegation);
|
||||||
this.animationIds.forEach(v => this.binded.removeTicker(v));
|
this.animationIds.forEach(v => this.binded.removeTicker(v));
|
||||||
Camera.cameraMap.delete(this.binded);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,11 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
*/
|
*/
|
||||||
enable() {
|
enable() {
|
||||||
this.enabled = true;
|
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);
|
item.setPosition(x, y);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
adapter.receiveSync('disable', item => {
|
||||||
|
item.disable();
|
||||||
|
});
|
||||||
|
adapter.receiveSync('enable', item => {
|
||||||
|
item.enable();
|
||||||
|
});
|
||||||
|
|
||||||
const hook = Mota.require('var', 'hook');
|
const hook = Mota.require('var', 'hook');
|
||||||
hook.on('changingFloor', (_, loc) => {
|
hook.on('changingFloor', (_, loc) => {
|
||||||
|
14
src/core/render/utils.ts
Normal file
14
src/core/render/utils.ts
Normal 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');
|
||||||
|
}
|
@ -40,6 +40,7 @@ import type { HeroKeyMover } from '@/core/main/action/move';
|
|||||||
import type { BlockMover, HeroMover, ObjectMoverBase } from './state/move';
|
import type { BlockMover, HeroMover, ObjectMoverBase } from './state/move';
|
||||||
import type { Camera } from '@/core/render/camera';
|
import type { Camera } from '@/core/render/camera';
|
||||||
import type * as Animation from 'mutate-animate';
|
import type * as Animation from 'mutate-animate';
|
||||||
|
import type * as RenderUtils from '@/core/render/utils';
|
||||||
|
|
||||||
interface ClassInterface {
|
interface ClassInterface {
|
||||||
// 渲染进程与游戏进程通用
|
// 渲染进程与游戏进程通用
|
||||||
@ -122,6 +123,7 @@ interface ModuleInterface {
|
|||||||
Layer: typeof Layer;
|
Layer: typeof Layer;
|
||||||
LayerGroupFloorBinder: typeof LayerGroupFloorBinder;
|
LayerGroupFloorBinder: typeof LayerGroupFloorBinder;
|
||||||
Camera: typeof Camera;
|
Camera: typeof Camera;
|
||||||
|
Utils: typeof RenderUtils;
|
||||||
};
|
};
|
||||||
State: {
|
State: {
|
||||||
ItemState: typeof ItemState;
|
ItemState: typeof ItemState;
|
||||||
@ -130,7 +132,7 @@ interface ModuleInterface {
|
|||||||
ObjectMoverBase: typeof ObjectMoverBase;
|
ObjectMoverBase: typeof ObjectMoverBase;
|
||||||
heroMoveCollection: {
|
heroMoveCollection: {
|
||||||
mover: HeroMover;
|
mover: HeroMover;
|
||||||
keyMover: HeroKeyMover;
|
keyMover?: HeroKeyMover;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
Action: {
|
Action: {
|
||||||
@ -168,7 +170,6 @@ interface PluginInterface {
|
|||||||
chase_g: typeof import('../plugin/game/chase');
|
chase_g: typeof import('../plugin/game/chase');
|
||||||
skill_g: typeof import('../plugin/game/skill');
|
skill_g: typeof import('../plugin/game/skill');
|
||||||
towerBoss_g: typeof import('../plugin/game/towerBoss');
|
towerBoss_g: typeof import('../plugin/game/towerBoss');
|
||||||
rewrite_g: typeof import('../plugin/game/fx/rewrite');
|
|
||||||
itemDetail_g: typeof import('../plugin/game/fx/itemDetail');
|
itemDetail_g: typeof import('../plugin/game/fx/itemDetail');
|
||||||
checkBlock_g: typeof import('../plugin/game/enemy/checkblock');
|
checkBlock_g: typeof import('../plugin/game/enemy/checkblock');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user