Last active
May 2, 2020 16:06
-
-
Save mitallast/76dd1476c20514a5f86807952b573381 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Curve} from "./curves"; | |
export class Animation { | |
private readonly clips: AnimationClip[] = []; | |
private _playing: boolean = false; | |
get isPlaying(): boolean { | |
return this._playing; | |
} | |
add(clip: AnimationClip): void { | |
this.clips.push(clip); | |
} | |
clear(): void { | |
this.stop(); | |
this.clips.splice(0, this.clips.length); | |
} | |
start(): void { | |
this._playing = true; | |
for (let clip of this.clips) { | |
clip.start(); | |
} | |
} | |
stop(): void { | |
for (let clip of this.clips) { | |
clip.stop(); | |
} | |
} | |
update(deltaTime: number): void { | |
let hasPlaying = false; | |
for (let clip of this.clips) { | |
clip.update(deltaTime); | |
if (clip.isPlaying) { | |
hasPlaying = true; | |
} | |
} | |
if (!hasPlaying) this.stop(); | |
} | |
} | |
export abstract class AnimationClip { | |
protected readonly animationSpeed: number; | |
protected _time: number = 0; | |
protected _playing: boolean = false; | |
get isPlaying(): boolean { | |
return this._playing; | |
} | |
protected constructor(animationSpeed: number) { | |
this.animationSpeed = animationSpeed; | |
} | |
start(): void { | |
this._time = 0; | |
this._playing = true; | |
this.play(); | |
} | |
stop(): void { | |
this._playing = false; | |
} | |
update(deltaTime: number): void { | |
this._time += this.animationSpeed * deltaTime; | |
if (this._playing) { | |
this.play(); | |
} | |
} | |
protected abstract play(): void; | |
} | |
export class SpriteAnimationClip extends AnimationClip { | |
private readonly _sprite: PIXI.AnimatedSprite; | |
constructor(sprite: PIXI.AnimatedSprite) { | |
super(sprite.animationSpeed); | |
this._sprite = sprite; | |
} | |
protected play(): void { | |
const sprite = this._sprite; | |
let currentFrame = Math.floor(this._time) % sprite.totalFrames; | |
if (currentFrame < 0) { | |
currentFrame += sprite.totalFrames; | |
} | |
const previousFrame = sprite.currentFrame; | |
if (this._time < 0) { | |
this.stop(); | |
} else if (this._time >= sprite.totalFrames) { | |
this.stop(); | |
} else if (previousFrame !== currentFrame) { | |
sprite.gotoAndStop(currentFrame); | |
} | |
} | |
} | |
export class AnimationCurveClip<Args extends number[]> extends AnimationClip { | |
private readonly _duration: number; | |
private readonly _start: Args; | |
private readonly _finish: Args; | |
private readonly _curve: Curve<Args>; | |
private readonly _method: (...args: any[]) => void; | |
constructor( | |
start: Args, | |
finish: Args, | |
curve: Curve<Args>, | |
duration: number, | |
animationSpeed: number, | |
method: (...args: any[]) => void | |
) { | |
super(animationSpeed); | |
this._start = start; | |
this._finish = finish; | |
this._curve = curve; | |
this._duration = duration; | |
this._method = method; | |
} | |
protected play(): void { | |
let t = this._time / this._duration; | |
if (t === 0) { | |
this._method(...this._start); | |
} else if (t >= 1) { | |
t = 1; | |
this._playing = false; | |
this._method(...this._finish); | |
} else { | |
const delta = this._curve(t); | |
const args = [] as number[] as Args; | |
for (let i = 0; i < delta.length; i++) { | |
args[i] = this._start[i] + (this._finish[i] - this._start[i]) * delta[i]; | |
} | |
this._method(...args); | |
} | |
} | |
} | |
export class AnimationEventClip<Args extends []> extends AnimationClip { | |
private readonly _method: (...args: Args) => void; | |
private readonly _events: AnimationEvent<Args>[] = []; | |
private _event: number | null = null; | |
constructor(animationSpeed: number, method: (...args: Args) => void) { | |
super(animationSpeed); | |
this._method = method; | |
} | |
protected play(): void { | |
while (this._playing) { | |
let next = this._event === null ? 0 : this._event + 1; | |
if (next <= this._events.length) { | |
if (this._events[next].time <= this._time) { | |
this._event = next; | |
this._method(...this._events[next].args); | |
} else { | |
this._playing = true; | |
} | |
} else { | |
this._playing = false; | |
} | |
} | |
} | |
add(time: number, ...args: Args): void { | |
this._events.push(new AnimationEvent(time, args)); | |
this._events.sort(this.compare); | |
} | |
private compare(a: AnimationEvent<Args>, b: AnimationEvent<Args>): number { | |
return a.time - b.time; | |
} | |
} | |
export class AnimationEvent<Args extends []> { | |
readonly time: number; | |
readonly args: Args; | |
constructor(time: number, args: Args) { | |
this.time = time; | |
this.args = args; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface AnimationController { | |
readonly isPlaying: boolean; | |
start(): void; | |
update(deltaTime: number): void; | |
cancel(): void; | |
finish(): void; | |
} | |
export class IdleAnimationController implements AnimationController { | |
private readonly ai: CharacterAI; | |
private readonly view: CharacterView; | |
private readonly animation: Animation | |
constructor(character: CharacterAI) { | |
this.ai = character; | |
this.view = character.view; | |
this.animation = new Animation(); | |
} | |
get isPlaying(): boolean { | |
return this.animation.isPlaying; | |
} | |
start(): void { | |
this.animation.clear(); | |
this.animation.add(this.view.setSprite(this.ai.character.name + '_idle', this.ai.character.speed)); | |
this.animation.start(); | |
} | |
update(deltaTime: number): void { | |
this.animation.update(deltaTime); | |
if (!this.animation.isPlaying) { | |
this.finish(); | |
} | |
} | |
cancel(): void { | |
this.animation.stop(); | |
} | |
finish(): void { | |
this.animation.stop(); | |
} | |
} | |
export class RunAnimationController implements AnimationController { | |
private readonly ai: CharacterAI; | |
private readonly view: CharacterView; | |
private readonly spriteName: string | |
private readonly animation: Animation | |
private readonly x: number; | |
private readonly y: number; | |
private readonly new_x: number; | |
private readonly new_y: number; | |
get isPlaying(): boolean { | |
return this.animation.isPlaying; | |
} | |
constructor(character: CharacterAI, spriteName: string, new_x: number, new_y: number) { | |
this.ai = character; | |
this.view = character.view; | |
this.spriteName = spriteName; | |
this.x = this.ai.x; | |
this.y = this.ai.y; | |
this.new_x = new_x; | |
this.new_y = new_y; | |
this.animation = new Animation(); | |
} | |
start(): void { | |
this.ai.dungeon.set(this.new_x, this.new_y, this.ai); | |
this.animation.clear(); | |
this.animation.add(this.view.setSprite(this.spriteName, this.ai.character.speed)); | |
this.animation.add(new AnimationCurveClip( | |
[this.x, this.y], | |
[this.new_x, this.new_y], | |
LinearCurve.matrix(2), | |
1, | |
this.ai.character.speed, | |
this.view.setPosition | |
)); | |
this.animation.start(); | |
} | |
update(deltaTime: number): void { | |
this.animation.update(deltaTime); | |
if (!this.animation.isPlaying) { | |
this.finish(); | |
} | |
} | |
cancel(): void { | |
this.animation.stop(); | |
this.ai.dungeon.remove(this.x, this.y, this.ai); | |
this.ai.dungeon.remove(this.new_x, this.new_y, this.ai); | |
this.ai.setPosition(this.ai.x, this.ai.y); | |
} | |
finish(): void { | |
this.animation.stop(); | |
this.ai.dungeon.remove(this.x, this.y, this.ai); | |
this.ai.dungeon.remove(this.new_x, this.new_y, this.ai); | |
this.ai.setPosition(this.new_x, this.new_y); | |
} | |
} | |
export class HitAnimationController implements AnimationController { | |
private readonly ai: CharacterAI; | |
private readonly view: CharacterView; | |
private readonly spriteName: string; | |
private readonly animation: Animation | |
get isPlaying(): boolean { | |
return this.animation.isPlaying; | |
} | |
constructor(character: CharacterAI, spriteName: string) { | |
this.ai = character; | |
this.view = character.view; | |
this.spriteName = spriteName; | |
this.animation = new Animation(); | |
} | |
start(): void { | |
const weapon = this.ai.character.weapon; | |
const speed = weapon ? weapon.speed : this.ai.character.speed; | |
this.animation.clear(); | |
this.animation.add(this.view.setSprite(this.spriteName, speed)); | |
if (weapon) { | |
const weaponSprite = this.view.weaponSprite!; | |
this.animation.add(new AnimationCurveClip( | |
[0], [this.view.isLeft ? -90 : 90], | |
t => [weapon.curve(t)], | |
1, | |
speed, | |
(angle) => weaponSprite.angle = angle | |
)); | |
} | |
this.animation.start(); | |
} | |
update(deltaTime: number): void { | |
this.animation.update(deltaTime); | |
if (!this.animation.isPlaying) { | |
this.finish(); | |
} | |
} | |
cancel(): void { | |
this.animation.stop(); | |
} | |
finish(): void { | |
this.animation.stop(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment