Skip to content

Instantly share code, notes, and snippets.

@mitallast
Last active May 2, 2020 16:06
Show Gist options
  • Save mitallast/76dd1476c20514a5f86807952b573381 to your computer and use it in GitHub Desktop.
Save mitallast/76dd1476c20514a5f86807952b573381 to your computer and use it in GitHub Desktop.
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;
}
}
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