mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +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 * 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
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										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 { 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');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user