Run a function when animation completes using Curtains.js - javascript

I'm looking to call a function right at the end of the wobble effect.
That is, at the end of the damping effect (when the wobble stops), I'd like to execute a GSAP timeline function. I'd assume this type of "onComplete" function would need to be called inside the onReady() of Curtains and perhaps by tracking the damping effect. I'm only familiar with GSAP's onComplete function, but don't know how I would implement it here. Maybe something that checks if deltas.applied is less than 0.001, then the function is called?
Below is the code snippet (without the fragment and vertex shaders). Full working code here:
CodePen
class Img {
constructor() {
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
//console.log("complete") <-- need an onComplete type function~!
}
}
window.onload = function() {
const img = new Img();
}

What you could use is some algebra :)
First off, you should simplify your deltas.max function like so:
deltas.max += (0 - deltas.max) * 0.07;
// Simplifies to
deltas.max -= deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max - deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max * (1 - 0.07);
// Simplifies to
deltas.max *= 0.93; // Much nicer :)
That is actually pretty important to do because it makes our work of calculating the end value of our time variable and the duration of our animation significantly Easier:
// Given deltas.max *= 0.93, need to calculate end time value
// endVal = startVal * reductionFactor^n
// Rewrite as
// n = ln(endVal / startVal) / ln(reductionFactor) // for more see https://www.purplemath.com/modules/solvexpo2.htm
// n = ln(0.001 / 8) / ln(0.93)
const n = 123.84;
// Assuming 60fps normally: n / 60
const dur = 2.064;
Once we have those values all we have to do is create a linear tween animating our time to that value with that duration and update the max and prog values in the onUpdate:
gsap.to(this.plane.uniforms.time, {
value: n,
duration: dur,
ease: "none",
onUpdate: () => {
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max *= 0.93;
this.plane.uniforms.prog.value = this.deltas.applied;
},
onComplete: () => console.log("complete!")
});
Then you get "complete!" when the animation finishes!
To make sure that your Curtains animations run at the proper rate even with monitors with high refresh rates (even the ones not directly animated with GSAP) it's also a good idea to turn off Curtain's autoRendering and use GSAP's ticker instead:
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
curtains.render();
}
gsap.ticker.add(renderScene);
Altogether you get this demo.

This won't be the best answer possible but you can take some ideas and insights from it.
Open the console and see that when the animation gets completed it gets fired only once.
//Fire an onComplete event and listen for that
const event = new Event('onComplete');
class Img {
constructor() {
// Added a instance variable for encapsulation
this.animComplete = {anim1: false}
//Changed code above
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
document.addEventListener('onComplete', ()=>{
//Do damping effects here
console.log('complete')
})
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
if(deltas.applied<0.001 && !this.animComplete.anim1){
document.dispatchEvent(event)
this.animComplete.anim1 = true
}
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
}
}
window.onload = function() {
const img = new Img();
}

I've found a solution to call a function at the end of the damping (wobble) effect, that doesn't use GSAP, but uses the Curtains onRender method. Because the uTime value goes up infinitely and the uProg value approaches 0, By tracking both the uTime and uProg values inside the Curtains onRender method we can find a point (2 thresholds) at which the damping effect has essentially completed. Not sure if this is the most efficient way, but it seems to work.
.onRender(() => {
if (this.plane.uniforms.prog.value < 0.008 && this.plane.uniforms.time.value > 50) { console.log("complete")}
})

Thanks to the Curtains docs re asynchronous-textures, I was able to better control the timing of the wobble effect with the desired result every time. That is, on computers with lower FPS, the entire damping effect takes place smoothly, with an onComplete function called at the end, as well as on comps with higher frame rates.
Although, as mentioned there is less control over the length of the effect, as we are not using GSAP to control the Utime values. Thanks #Zach! However, using a "threshold check" inside the curtains onRender this way, means the damping wobble effect is never compromised, if we were to disable the drawing at the on complete call.
By enabling the drawing at the same time the image is loaded we avoid any erratic behaviour. The following works now with hard refresh as well.
export default class Img {
constructor() {
this.deltas = {
max: 0,
applied: 0,
};
this.curtain = new Curtains({
container: "canvas",
watchScroll: false,
pixelRatio: Math.min(1.5, window.devicePixelRatio),
});
this.params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
},
},
};
this.planeElements = document.getElementsByClassName("plane")[0];
this.curtain.onError(() => document.body.classList.add("no-curtains"));
this.curtain.disableDrawing(); // disable drawing to begin with to prevent erratic timing issues
this.init();
}
init() {
this.plane = new Plane(this.curtain, this.planeElements, this.params);
this.playWobble();
}
loaded() {
return new Promise((resolve) => {
// load image and enable drawing as soon as it's ready
const asyncImgElements = document
.getElementById("async-textures-wrapper")
.getElementsByTagName("img");
// track image loading
let imagesLoaded = 0;
const imagesToLoad = asyncImgElements.length;
// load the images
this.plane.loadImages(asyncImgElements, {
// textures options
// improve texture rendering on small screens with LINEAR_MIPMAP_NEAREST minFilter
minFilter: this.curtain.gl.LINEAR_MIPMAP_NEAREST,
});
this.plane.onLoading(() => {
imagesLoaded++;
if (imagesLoaded === imagesToLoad) {
console.log("loaded");
// everything is ready, we need to render at least one frame
this.curtain.needRender();
// if window has been resized between plane creation and image loading, we need to trigger a resize
this.plane.resize();
// show our plane now
this.plane.visible = true;
this.curtain.enableDrawing();
resolve();
}
});
});
}
playWobble() {
if (this.plane) {
this.plane
.onReady(() => {
this.deltas.max = 7; // 7
})
.onRender(() => {
this.plane.uniforms.time.value++;
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max += (0 - this.deltas.max) * 0.07;
this.plane.uniforms.prog.value = this.deltas.applied;
console.log(this.plane.uniforms.prog.value);
// ---- "on complete" working!! ( even on hard refresh) -----//
if (
this.plane.uniforms.prog.value < 0.001 &&
this.plane.uniforms.time.value > 50
) {
console.log("complete");
this.curtain.disableDrawing();
}
});
}
}
destroy() {
if (this.plane) {
this.curtain.disableDrawing();
this.curtain.dispose();
this.plane.remove();
}
}
}
const img = new Img();
Promise.all([img.loaded()]).then(() => {
console.log("animation started");
});

Related

Sprite animation stuck on first frame?

Working with Phaser 3, I've preloaded a spritesheet and created a few animations...
import {Scene} from 'phaser';
class BootScene extends Scene {
constructor() {
super("scene-boot");
}
preload() {
this.load.spritesheet('px-hero', 'assets/sprites/px-hero.png', {
frameWidth: 16,
frameHeight: 16
});
// ...
}
create() {
// ...
this.anims.create({
key: 'px-hero-idle',
frames: this.anims.generateFrameNumbers('px-hero', {
start: 0,
end: 2
}),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'px-hero-run',
frames: this.anims.generateFrameNumbers('px-hero', {
start: 3,
end: 6
}),
frameRate: 10,
repeat: -1
});
// ...
}
}
export default BootScene;
Then inside my Sprite class (which is being instantiated in another scene that the BootScene links to), I'm trying to play the animations...
import {GameObjects} from 'phaser';
const {Sprite} = GameObjects;
class PxHero extends Sprite {
constructor(config) {
super(config.scene, config.x, config.y, "px-hero");
// Add self to scene's physics
config.scene.physics.world.enable(this);
config.scene.add.existing(this);
this.scene = config.scene;
this.keys = this.scene.input.keyboard.addKeys('W,S,A,D');
this.speed = 100;
this.jumpHeight = 300;
}
preUpdate(time, delta) {
const {W, S, A, D} = this.keys;
const {speed, jumpHeight, body} = this;
const touchingGround = body.blocked.down;
if (A.isDown) {
this.body.setVelocityX(-speed);
this.setFlipX(true);
}
else if (D.isDown) {
this.body.setVelocityX(speed);
this.setFlipX(false);
}
else {
this.body.setVelocityX(0);
}
if (W.isDown && touchingGround) {
this.body.setVelocityY(-jumpHeight);
}
// Animations
if (touchingGround) {
if (body.velocity.x !== 0) {
this.anims.play('px-hero-run', true); // here
}
else {
this.anims.play('px-hero-idle', true); // and here
}
}
}
}
export default PxHero;
But for some reason they just play the first frame of the animation then get stuck there.
Has anyone encountered this before? I haven't been able to find any solutions thus far.
Per frame animations are working based on preUpdate function, it's not the best idea to do something yours in that function without calling super.preUpdate. That's first, second in place of preUpdate try to use update function, and don't forget to call super.update in it.
And third, try to do what you want without using preUpdate and update functions. If you even need to use update function, you can do that with setting listener on scene to update (Phaser.Scenes.Events.UPDATE) event. Which will make your code more clear and understandable, and you'll never harm Phaser's main functionality by mistake.
What happens in your code is the following, on every frame rendering, your game checks and decides to run an animation from start, that's why you're seeing first frames only, just because when it tries to show you next frame you're telling him to start playing animation again )
export default class PxHero extends Phaser.GameObjects.Sprite{
constructor(config) {
super(config.scene, config.x, config.y, "px-hero");
// Add self to scene's physics
// ## you've `this` here too
this.scene.add.existing(this);
this.scene.physics.world.enable(this);
this.prepare();
this.setListeners();
}
prepare(){
this.keys = this.scene.input.keyboard.addKeys('W,S,A,D');
this.speed = 100;
this.jumpHeight = 300;
}
setListeners(){
this.scene.events.on(Phaser.Scenes.Events.UPDATE, this.handleMovement, this)
}
handleMovement() {
const {W, S, A, D} = this.keys;
const {speed, jumpHeight, body} = this;
const touchingGround = body.blocked.down;
switch(true){
case A.isDown:
this.body.setVelocityX(-speed);
this.setFlipX(true);
break;
case D.isDown:
this.body.setVelocityX(speed);
this.setFlipX(false);
break;
case S.isDown:
this.body.setVelocityX(0);
break;
case W.isDown && touchingGround:
this.body.setVelocityY(-jumpHeight);
break;
}
// Animations
if (touchingGround) {
if (body.velocity.x !== 0) {
(!this.anims.isPlaying || this.anims.key !== 'px-her-run') &&
this.anims.play('px-hero-run', true); // here
}
else {
(!this.anims.isPlaying || this.anims.key !== 'px-her-idle') &&
this.anims.play('px-hero-idle', true); // and here
}
}
}
}

How can i get aframe box vertex coordinates?

I need to get vertex from an object (box) in an AFRAME or i need to calculate intersection between raycaster and the face of an entity.
Someone can help me? Thank you all!ç
EDIT: I added code below.
AFRAME.registerComponent("intersection-handler", {
schema: {
fps: { type: 'number', default: 15 }
},
init: function() {
this.bindMethods();
},
tick: function(){
if(new Date().getTime()-this.lastTick<(1000/this.data.fps))return;
if(this.isIntersecting){
let distance = this.calculateDistance(this.el.object3D.position, this.hittedElem.object3D.position);
this.el.emit('intersection-detected-' + this.el.id, distance );
}else{
this.el.emit('intersection-cleared-' + this.el.id);
}
this.lastTick = new Date().getTime();
},
bindMethods(){ // You could do all of this directly in your init() method, but I like to separate it.
this.onIntersection = this.onIntersection.bind(this);
this.onIntersectionClear = this.onIntersectionClear.bind(this);
},
play: function() {
this.registerEventListeners(); // It's a good practice in general to enable your event listeners here.
},
pause: function() {
this.deregisterEventListeners(); // Similarly a good practice to remove them here so that they don't stay bound while the scene isn't actually 'running'
},
registerEventListeners() {
this.el.addEventListener('raycaster-intersection', this.onIntersection);
this.el.addEventListener('raycaster-intersection-cleared', this.onIntersectionClear);
},
deregisterEventListeners() {
this.el.removeEventListener('raycaster-intersection', this.onIntersection);
this.el.removeEventListener('raycaster-intersection-cleared', this.onIntersectionClear);
},
onIntersection: function(e) {
console.log(this.el.components.raycaster.getIntersection(e.detail.els[0]))
this.isIntersecting = true;
if(e.detail.intersections[0].object){
this.hittedElem = e.detail.intersections[0].object.el;
}
},
onIntersectionClear: function(e) {
this.isIntersecting = false;
},
calculateDistance: function(myElposition, hittedElposition) {
// distance = sqrt((x2 - x1)^2 + (y2 - y1)^2) - rolloff
let rolloffFactor = 0.5;
let d = Math.sqrt(Math.pow((hittedElposition.x - myElposition.x), 2) + Math.pow((hittedElposition.y - myElposition.y), 2)) - rolloffFactor;
return d;
}
});
I can't use the raycaster-intersected event, i'm managing intersections on raycaster side not in the other entities.
no need for the vertex coordinates. The raycasters intersection.point contains a Vector3 with the intersection position.
On the other hand if you get your camera (raycasters origin) position, you get another Vector3.
From the three.js docs, you can get the distance between them with a simple a.distanceTo(b).
Fiddle here.

JavaScript game switchable sprites

Any ideas how to make switchable characters I have a html game it's finished but I want to implement a way to switch my main character.
Simple coding using Phaser framework
upload function() {
this.game.load.sprite ("bird" assets/bird.png);
this.game.load.sprite ("bird2" assets/bird2.png);
this.game.load.sprite ("bird3" assets/bird3.png);
},
create function() {
this.game.add.sprite (0, 0 "bird" );
},
I want to be able to switch my playable character the "bird" with the "bird2" or "bird3" through a selection button if a player selects a switch character button for the playable character to switch to that. I'm pretty sure this is something simple but I'm still pretty new with coding.
I want a button where I press then I can switch the character
(Button 1) switches to bird2
"if button 1 is selected button two and current bird are disabled"-only bird2 is visible
(Button 2) switches to bird3
"if button 2 is selected button one and current bird are disabled"-only bird3 is visible
Edit This is My current code and states
var MainState = {
//load the game assets before the game starts
preload: function () {
this.load.image('background', 'assets/spring2.png');
this.load.spritesheet('bird', 'assets/bird.png',52 ,28, 7);
this.load.spritesheet('bird2', 'assets/bird2.png',52 ,28, 7);
this.load.spritesheet('bird3', 'assets/bird3.png',52 ,28, 7);
this.load.image('pipe', 'assets/pipe4.png');
},
//executed after everything is loaded
create: function () {
this.background = game.add.tileSprite(0, game.height-736,game.width, 736, 'background');
this.background.autoScroll(-20,0);
/////Bird///////////////////////////////////////////////////
this.bird = this.game.add.sprite(100, 200, 'bird');
this.bird.animations.add('fly');
this.bird.animations.play('fly', 50, true);
game.physics.startSystem(Phaser.Physics.ARCADE);
game.physics.arcade.enable(this.bird);
this.bird.body.gravity.y = 1000;
var spaceKey = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
this.bird.body.collideWorldBounds=true;
this.bird.body.immovable= true;
game.input.onDown.add(this.jump, this); //////touch screen jump
spaceKey.onDown.add(this.jump, this);
///////////////////////////////////////////////////////Pipes
this.pipes = game.add.group();
//timer
this.timer = game.time.events.loop(1600, this.addRowOfPipes, this); /////////////timer for pipes
///Bird anchor
this.bird.anchor.setTo(-0.2, 0.5)
},
// this is execated multiple times per second
update: function () {
if (this.bird.y < 0 || this.bird.y > 480)
game.state.start("StateOver");
///Collision
game.physics.arcade.overlap(
this.bird, this.pipes, this.restartGame, null, this);
///Bird Angle
if (this.bird.angle < 30)
this.bird.angle += 1;
///////////////music stop w top+bottom collision
if (this.bird.y < 0 || this.bird.y > 479)
music.stop();
},
jump: function () {
//this is for so the bird wount fly once dead
if (this.bird.alive == false)
return;
// Add a vertical velocity to the bird
this.bird.body.velocity.y = -350;
// Jump Animation
var animation = game.add.tween(this.bird);
// Change the angle of the bird to -20° in 100 milliseconds
animation.to({angle: -20}, 100);
// And start the animation
animation.start();
game.add.tween(this.bird).to({angle: -20}, 100).start();
},
restartGame: function () {
// Start the 'main' state, which restarts the game
game.state.start(game.state.StateOver); /////////////////////changed from current #########
///Hit pipe Null
game.physics.arcade.overlap(
this.bird, this.pipes, this.hitPipe, null, this);
},
addRowOfPipes: function() {
var hole = Math.floor(Math.random() * 5) + 1; ///Math.floor(Math.random() * 5) + 1;
for (var i = 0; i < 10 ; i++) ///// (var i = 0; i < 8; i++)
if (i != hole && i != hole + 1) ///// if (i != hole && i != hole + 1)
this.addOnePipe(440, i * 50 ); ///// 640 starting point of pipe 240 point of down ////this.addOnePipe(480, i * 60 + 10);
},
addOnePipe: function(x, y) {
var pipe = game.add.sprite(x, y, 'pipe');
this.pipes.add(pipe);
game.physics.arcade.enable(pipe);
pipe.body.velocity.x = -200;
pipe.checkWorldBounds = true;
pipe.outOfBoundsKill = true;
},
hitPipe: function() {
// If the bird has already hit a pipe, do nothing
// It means the bird is already falling off the screen
if (this.bird.alive == false)
return;
else {
game.state.start("StateOver");
}
// Set the alive property of the bird to false
this.bird.alive = false;
// Prevent new pipes from appearing
game.time.events.remove(this.timer);
// Go through all the pipes, and stop their movement
this.pipes.forEach(function(p){
p.body.velocity.x = 0;
}, this);
},
};
character.js
var characters={
preload:function()
{
game.load.spritesheet('button', 'assets/button.png', 215, 53, 8);
game.load.image("background", "assets/characterbackground.png");
game.load.image("pajaro", "assets/storeicon.png");
game.load.image("logo", "assets/extra/storef.png");
this.load.spritesheet('bird', 'assets/bird.png',52 ,28, 7);
this.load.spritesheet('bird2', 'assets/bird2.png',52 ,28, 7);
this.load.spritesheet('bird3', 'assets/bird3.png',52 ,28, 7);
game.load.spritesheet("button2", 'assets/button2.png', 100, 10, 10);
},
create:function()
{
bird = game.add.image(140, 150, 'pajaro');
logo = game.add.image (20, 350, 'logo');
this.background = game.add.tileSprite(0, game.height-736,game.width, 736, 'background');
this.background.autoScroll(-100,0);
this.btnMainMenu=game.add.button(130,500,'button',this.mainMenu,this,4,5,4);
this.btnbird=game.add.button(180,600,"button2",this.changebird2,this,0,1,0);
},
mainMenu:function()
{
game.state.start("stateTitle");
},
update:function()
{
// bird.x +=1;
},
changebird2: function(){
},
};
Instead of creating three sprites that you either hide or show, I might recommend just changing what texture is loaded when the sprite is created/added.
To do this you'll need to store a reference to the playable character, which you probably already have.
// On the game itself, add a reference.
this.bird = null;
// In your preload, load the different images.
this.load.image('bird', 'assets/bird.png');
this.load.image('bird2', 'assets/bird2.png');
this.load.image('bird3', 'assets/bird3.png');
// When creating, default to one.
this.bird = this.game.add.sprite(0, 0, 'bird');
// In your function where they select a new skin, you can load in a different texture.
this.bird.loadTexture('bird3');
Alternatively, you could store the key that should be used on the game.
// On the game itself, track which key to use.
this.birdSkin = 'bird';
// You'll still have to load your possible textures.
this.load.image('bird', 'assets/bird.png');
this.load.image('bird2', 'assets/bird2.png');
this.load.image('bird3', 'assets/bird3.png');
// Now when creating just use the variable.
this.bird.loadTexture(this.birdSkin);
The Phaser init() will allow 0 or more parameters to be passed in (see the end of Phaser Tutorial: understanding Phaser states), which is where you could populate this.birdSkin.
I would look at what states you're using to determine what's best for you. If you have one state for the game and another for selecting which image/texture is used, than the second option might be better.
Update for Character State
Given your comments and what I saw in your code, I created a short example that you could tweak for your use.
There's a JSFiddle available, but the code is also included below.
var mainState = {
preload: function() {
// Load the three sprites that they can choose between.
this.load.crossOrigin = 'anonymous';
this.load.image('ball', 'https://raw.githubusercontent.com/photonstorm/phaser-examples/master/examples/assets/sprites/orb-blue.png');
this.load.image('ball2', 'https://raw.githubusercontent.com/photonstorm/phaser-examples/master/examples/assets/sprites/orb-green.png');
this.load.image('ball3', 'https://raw.githubusercontent.com/photonstorm/phaser-examples/master/examples/assets/sprites/orb-red.png');
},
create: function() {
this.ball = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, this.game.global.skin);
this.ball.anchor.setTo(0.5);
// Let the ball be acted upon. This will allow the player to change the sprite used.
this.ball.inputEnabled = true;
this.ball.events.onInputDown.add(this.changeCharacter, this);
},
update: function() {
},
changeCharacter: function() {
game.state.start('character');
}
};
var characterState = {
preload: function() {
},
create: function() {
// For this, add our three possible ball skins.
this.ball1 = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY / 2, 'ball');
this.ball1.anchor.setTo(0.5);
this.ball1.inputEnabled = true;
this.ball2 = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'ball2');
this.ball2.anchor.setTo(0.5);
this.ball2.inputEnabled = true;
this.ball3 = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY * 1.5, 'ball3');
this.ball3.anchor.setTo(0.5);
this.ball3.inputEnabled = true;
// Use the selected ball's sprite in our main game.
this.ball1.events.onInputDown.add(this.selectBall, this);
this.ball2.events.onInputDown.add(this.selectBall, this);
this.ball3.events.onInputDown.add(this.selectBall, this);
},
update: function() {
},
selectBall: function(sprite, pointer) {
// Grab the key of the sprite and save it to our global variable.
this.game.global.skin = sprite.key;
this.game.state.start('main');
}
};
var game = new Phaser.Game(200, 200);
// Create a global object that we can add custom variables to.
game.global = {
skin: 'ball'
};
game.state.add('main', mainState);
game.state.add('character', characterState);
game.state.start('main');
This actually simplifies things a bit, in that it just uses a global variable (I've been using TypeScript the last handful of months, so there's probably a better way to declare this).

THREE.js - Updating BufferGeometry position twice, causes my FPS to suffer

I have an array of TubeBufferGeometrys that im making animate to look as if they're growing out in length. When the animation runs the first time, it works really smoothly at 60fps. But once i set the geometrys position attributes back to zero, the FPS drop to 30.
Ive isolated my animation to run and then rerun once it finished with only the below changing. Heres the basics of my code:
Animation control view
stop() {
this.play = false;
// Start it again
setTimeout(() => {
let i = this.tubeCount;
while (i--) {
this.tubes[i].lastSegmentSet = 0;
this.tubes[i].updateToPercent(0);
}
this.elapsedTime = 0;
this.startTime = Date.now();
this.play = true;
}, 2000)
}
update() {
requestAnimationFrame(this.animate);
// ..render stuff + composer that ive disabled without effect
if (this.play) {
let percent = (Date.now() - this.startTime) / ANIMATE_DURATION;
if (percent >= 1) {
this.stop();
}
let i = this.lineCount;
while (i--) {
this.tubes[i].updateToPercent(percent);
}
}
}
Tube class (The main animation code)
constructor() {
//..other stuff
this.lastSegmentSet = 0;
}
// I first build the paths, then store the position data to use later to animate to. Then i set all the position data to zero
storeVerticies() {
this.positions = this.tube.geometry.attributes.position.array.slice(0);
const length = this.tube.geometry.attributes.position.array.length;
this.tube.geometry.attributes.position.array = new Float32Array(length);
}
setSegment(segment) {
this.setSegmentTo(segment, segment);
}
setSegmentTo(segment, target) {
let position = this.tube.geometry.attributes.position.array;
let startPoint = segment * JOINT_DATA_LENGTH; //JOINT_DATA_LENGTH is the number of values in the buffer geometry to update a segment
let targetPoint = target * JOINT_DATA_LENGTH;
let n = JOINT_DATA_LENGTH;
while (n--) {
position[startPoint + n] = this.positions[targetPoint + n];
}
}
updateToPercent(percent) {
let endSegment = Math.floor(percent * this.segmentCount);
while (this.lastSegmentSet <= endSegment) {
this.setSegment(this.lastSegmentSet++);
}
let n = this.lastSegmentSet;
while (n <= this.segmentCount + 1) {
this.setSegmentTo(n++, this.lastSegmentSet);
}
this.tube.geometry.attributes.position.needsUpdate = true;
}
Will put bounty when possible

Generating enemies while countdown is running

I'm currently trying to create a game during a gamejam using HTML CSS Javascript and jQuery.
I have a countdown that shows to the player how many time he has left, and that loses 2 seconds when the main character is getting hit by the enemies.
What I'd like to do is to find a way to generate enemies every x seconds as long as the timer hasn't reached 30s (where he wins) or 0s (where he loses)
I used two libraries so far, QuintusJS (link here) for the physic and jchavannes's countdown (link here), trying to make them work together.
So far, everything has been great, but I can't manage to make a jQuery loop that would look like the following,
while(currentTime < 30000){ //30s in milliseconds
stage.insert(new Q.Enemy({ x: 700, y: 0 })); //Allows ennemies to be generated by QuintusJS
//TODO : adding a delay between enemies generation
}
Because the canvas that Quintus creates becomes totally blank. I also tried to create a function that sets checkCurrentTime to true while currentTime isn't equal to 0, but changes it to false when it's the case, but it still doesn't work.
Am I doing this wrong? If so, how can I manage to have a loop that generates enemies properly?
You can create a looper that does something every N milliseconds. For example:
var looper = function (delay, callImmediately, callback) {
var self = this;
var deferred = $.Deferred();
if (callImmediately) {
callback.call(self);
}
var iv = setInterval(function () {
if (deferred.state() === "resolved" || deferred.state() === "rejected") {
clearInterval(iv);
} else {
callback.call(self);
}
}, delay);
return deferred;
};
You can then start and stop it whenever you like:
// Every second add a new enemy
var loop = looper(1000, false, function () {
console.log("Adding an enemy");
//stage.insert(new Q.Enemy({ x: 700, y: 0 }));
});
// Handle done and fail
loop.done(function () { console.log("All enemies were added!"); });
loop.fail(function () { console.log("The loop was stopped before all enemies were added!"); });
// Resolve the looper after 30 seconds (calls .done())
setTimeout(loop.resolve, 30000);
// Or if something went wrong you can also reject it (calls .fail())...
setTimeout(loop.reject, 5000);
Here is a small example:
var player = {
health: 100,
};
// Damage the player every second
var iv = setInterval(function () {
player.health -= Math.floor(Math.random() * 10);
if (player.health <= 0) {
clearInterval(iv);
}
}, 1000);
// Every second add a new enemy
var loop = looper(1000, false, function () {
if (player.health <= 0) {
loop.reject("The player is dead. Stopping.");
} else {
//stage.insert(new Q.Enemy({ x: 700, y: 0 }));
console.log("Inserting an enemy; health is", player.health);
}
});
loop.done(function () { console.log("All enemies were added, and player is still alive!"); });
loop.fail(function (message) { console.log(message) });
// Stop the looper after 30 seconds
setTimeout(loop.resolve, 30000);

Categories

Resources