Skip to content

Commit

Permalink
Merge 0924c25 into 8edbd24
Browse files Browse the repository at this point in the history
  • Loading branch information
straker committed Dec 4, 2022
2 parents 8edbd24 + 0924c25 commit 5b7486a
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 87 deletions.
171 changes: 117 additions & 54 deletions src/animation.js
Expand Up @@ -34,63 +34,97 @@ import { getContext } from './core.js';
* @param {Number[]} properties.frames - List of frames of the animation.
* @param {Number} properties.frameRate - Number of frames to display in one second.
* @param {Boolean} [properties.loop=true] - If the animation should loop.
* @param {String} [properties.name] - The name of the animation.
*/
class Animation {
constructor({ spriteSheet, frames, frameRate, loop = true }) {
/**
* The sprite sheet to use for the animation.
* @memberof Animation
* @property {SpriteSheet} spriteSheet
*/
this.spriteSheet = spriteSheet;

/**
* Sequence of frames to use from the sprite sheet.
* @memberof Animation
* @property {Number[]} frames
*/
this.frames = frames;

/**
* Number of frames to display per second. Adjusting this value will change the speed of the animation.
* @memberof Animation
* @property {Number} frameRate
*/
this.frameRate = frameRate;

/**
* If the animation should loop back to the beginning once completed.
* @memberof Animation
* @property {Boolean} loop
*/
this.loop = loop;

constructor({ spriteSheet, frames, frameRate, loop = true, name }) {
let { width, height, margin = 0 } = spriteSheet.frame;

/**
* The width of an individual frame. Taken from the [frame width value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} width
*/
this.width = width;

/**
* The height of an individual frame. Taken from the [frame height value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} height
*/
this.height = height;

/**
* The space between each frame. Taken from the [frame margin value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} margin
*/
this.margin = margin;

// f = frame, a = accumulator
this._f = 0;
this._a = 0;
Object.assign(this, {
/**
* The sprite sheet to use for the animation.
* @memberof Animation
* @property {SpriteSheet} spriteSheet
*/
spriteSheet,

/**
* Sequence of frames to use from the sprite sheet.
* @memberof Animation
* @property {Number[]} frames
*/
frames,

/**
* Number of frames to display per second. Adjusting this value will change the speed of the animation.
* @memberof Animation
* @property {Number} frameRate
*/
frameRate,

/**
* If the animation should loop back to the beginning once completed.
* @memberof Animation
* @property {Boolean} loop
*/
loop,

/**
* The name of the animation.
* @memberof Animation
* @property {String} name
*/
name,

/**
* The width of an individual frame. Taken from the [frame width value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} width
*/
width,

/**
* The height of an individual frame. Taken from the [frame height value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} height
*/
height,

/**
* The space between each frame. Taken from the [frame margin value](api/spriteSheet#frame) of the sprite sheet.
* @memberof Animation
* @property {Number} margin
*/
margin,

/**
* If the animation is currently stopped. Stopped animations will not update when the [update()](api/animation#update) function is called.
*
* Animations are not considered stopped until either the [stop()](api/animation#stop) function is called or the animation gets to the last frame and does not loop.
*
* ```js
* import { Animation } from 'kontra';
*
* let animation = Animation({
* // ...
* });
* console.log(animation.isStopped); //=> false
*
* animation.start();
* console.log(animation.isStopped); //=> false
*
* animation.stop();
* console.log(animation.isStopped); //=> true
* ```
* @memberof Animation
* @property {Boolean} isStopped
*/
isStopped: false,

// f = frame, a = accumulator
_f: 0,
_a: 0
});
}

/**
Expand All @@ -104,6 +138,28 @@ class Animation {
return new Animation(this);
}

/**
* Start the animation.
* @memberof Animation
* @function start
*/
start() {
this.isStopped = false;

if (!this.loop) {
this.reset();
}
}

/**
* Stop the animation.
* @memberof Animation
* @function stop
*/
stop() {
this.isStopped = true;
}

/**
* Reset an animation to the first frame.
* @memberof Animation
Expand All @@ -122,8 +178,15 @@ class Animation {
* @param {Number} [dt=1/60] - Time since last update.
*/
update(dt = 1 / 60) {
if (this.isStopped) {
return;
}

// if the animation doesn't loop we stop at the last frame
if (!this.loop && this._f == this.frames.length - 1) return;
if (!this.loop && this._f == this.frames.length - 1) {
this.stop();
return;
}

this._a += dt;

Expand Down
11 changes: 3 additions & 8 deletions src/sprite.js
Expand Up @@ -147,19 +147,14 @@ class Sprite extends GameObjectClass {
* @param {String} name - Name of the animation to play.
*/
playAnimation(name) {
this.currentAnimation?.stop();
this.currentAnimation = this.animations[name];

if (!this.currentAnimation.loop) {
this.currentAnimation.reset();
}
this.currentAnimation.start();
}

advance(dt) {
super.advance(dt);

if (this.currentAnimation) {
this.currentAnimation.update(dt);
}
this.currentAnimation?.update(dt);
}
// @endif

Expand Down
3 changes: 2 additions & 1 deletion src/spriteSheet.js
Expand Up @@ -223,7 +223,8 @@ class SpriteSheet {
spriteSheet: this,
frames: sequence,
frameRate,
loop
loop,
name
});
}
}
Expand Down
5 changes: 5 additions & 0 deletions test/typings/animation.ts
Expand Up @@ -8,6 +8,7 @@ let spriteSheet: kontra.SpriteSheet = kontra.SpriteSheet({
});

let animation: kontra.Animation = kontra.Animation({
name: 'anim',
spriteSheet: spriteSheet,
frames: [1,2,3,4],
frameRate: 35
Expand All @@ -17,6 +18,8 @@ animation.update();
animation.update(1/60);
animation.render({x: 10, y: 20});
animation.reset();
animation.stop();
animation.start();

let spriteSheetAnim: kontra.SpriteSheet = animation.spriteSheet;
let frames: number[] = animation.frames;
Expand All @@ -25,6 +28,8 @@ let loop: boolean = animation.loop;
let width: number = animation.width;
let height: number = animation.height;
let margin: number = animation.margin;
let stopped: boolean = animation.isStopped;
let name: string = animation.name;

// clone
let clone: kontra.Animation = animation.clone();
Expand Down
53 changes: 53 additions & 0 deletions test/unit/animation.spec.js
Expand Up @@ -9,6 +9,7 @@ describe('animation', () => {

beforeEach(() => {
animation = Animation({
name: 'walk',
frames: [1, 2, 3, 4],
frameRate: 30,
spriteSheet: {
Expand All @@ -32,12 +33,14 @@ describe('animation', () => {
// --------------------------------------------------
describe('init', () => {
it('should set properties on the animation', () => {
expect(animation.name).to.equal('walk');
expect(animation.frames).to.deep.equal([1, 2, 3, 4]);
expect(animation.frameRate).to.equal(30);
expect(animation.width).to.equal(5);
expect(animation.height).to.equal(5);
expect(animation.loop).to.equal(true);
expect(animation.margin).to.equal(0);
expect(animation.isStopped).to.equal(false);
});
});

Expand Down Expand Up @@ -68,6 +71,45 @@ describe('animation', () => {
});
});

// --------------------------------------------------
// start
// --------------------------------------------------
describe('start', () => {
it('should start the animation', () => {
animation.start();

expect(animation.isStopped).to.equal(false);
});

it("should reset if the animation doesn't loop", () => {
animation.loop = false;
sinon.spy(animation, 'reset');
animation.start();

expect(animation.reset.called).to.be.true;
});

it('should not reset if the animation loops', () => {
animation.loop = true;
sinon.spy(animation, 'reset');
animation.start();

expect(animation.reset.called).to.be.false;
});
});

// --------------------------------------------------
// stop
// --------------------------------------------------
describe('stop', () => {
it('should stop the animation', () => {
animation.start();
animation.stop();

expect(animation.isStopped).to.equal(true);
});
});

// --------------------------------------------------
// update
// --------------------------------------------------
Expand Down Expand Up @@ -116,6 +158,17 @@ describe('animation', () => {
animation.update();

expect(animation._f).to.equal(3);
expect(animation.isStopped).to.be.true;
});

it('should not update the animation if is stopped', () => {
animation.stop();

for (let i = 0; i < 10; i++) {
animation.update();
}

expect(animation._f).to.equal(0);
});
});

Expand Down
30 changes: 6 additions & 24 deletions test/unit/sprite.spec.js
Expand Up @@ -243,15 +243,19 @@ describe(
reset: sinon.spy(),
clone() {
return this;
}
},
stop: noop,
start: noop
},
idle: {
width: 10,
height: 20,
reset: sinon.spy(),
clone() {
return this;
}
},
stop: noop,
start: noop
}
};

Expand All @@ -265,28 +269,6 @@ describe(

expect(sprite.currentAnimation).to.equal(animations.idle);
});

it("should reset the animation if it doesn't loop", () => {
let animations = {
walk: {
width: 10,
height: 20,
loop: false,
reset: sinon.spy(),
clone() {
return this;
}
}
};

let sprite = Sprite({
animations
});

sprite.playAnimation('walk');

expect(animations.walk.reset.called).to.be.true;
});
} else {
it('should not have animation property', () => {
let sprite = Sprite();
Expand Down

0 comments on commit 5b7486a

Please sign in to comment.