Hello Stackoverflow community
As I am trying to build a small game, i came to a problem.
Somehow, when i try to fade out multiple shapes, respectivly a group with the shapes in it, some of the shapes will not fade out or browser gets a stack overflow.
So as i tried out several hours to fix the problem i need your help.
Heres the link to a little fiddle i made: http://jsfiddle.net/hnBPT/
As you can see theres a function newFadeShapesOut() which needs the nodes that should be fade out and also the layer of the nodes.
It moves the nodes into a group and fades the group out. Somehow and sometimes, one or more shapes will not fade out or there occurs a fatal error.
Function for fadeout:
function newFadeShapesOut(shapes, layer, callback, speed){
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
console.log(layer.getChildren().length);
console.log(shapes.length);
layer.add(g);
shapes.each(function(shape){
shape.moveTo(g);
});
console.log(layer.getChildren().length);
console.log(shapes.length);
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
PS: Google Chrome is recommend, firefox tends to crash.
Thank you for your help.
EDIT: Sorry i forgot about that, you can activate the script by clicking the red square.
There's some strange behaviour going on here. Look at my comments as I tried to rewrite your function:
function fadeShapesOut(layer, callback, speed) {
var children = layer.children;
//The layer here already shows some children have moved.
//2 children remain, 1 text and 1 rect.
console.log("LAYER");
console.log(layer);
//Again, children shows that there are only 2 children of layer at this point: Test 2 and Button Rect
console.log('CHILDREN');
console.log(children);
if(typeof(speed) == 'undefined'){
speed = 1;
}
var group = new Kinetic.Group();
layer.add(group);
children.each(function(child) {
console.log("CHILD");
console.log(child); //This spits out Test 1, Test 3 and the newly added Group. (Strange order???
child.moveTo(group);
});
//Since group is already added to the layer, you're all of layer's children to group, including group itself. Which is causing a never ending loop of references to group including itself - causing the stack overflow.
var tween = new Kinetic.Tween({
node: group,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
What's messing you up is that the group is being considered a child of layer (Even though it hasn't been added yet in the order of function calls, which is strange behaviour to me). So when you loop through the children of layer in the each function, you're trying to move group --> group which screws up the reference in a never ending loop.
I logged a bunch of things in my fiddle, so go ahead and take a look to see some of the strange behaviour I was talking about above.
Anyways, if your callback is going to destroy the layer, what is the point of moving everything to a new group in the function? That Group is messing your code up and I don't see the point of it if you're just going to destroy the layer.
Instead you can achieve the effect you want by just tweening the layer itself:
function fadeLayer(layer, callback, speed) {
var tween = new Kinetic.Tween({
node: layer,
opacity: 0,
duration: 2,
onFinish: function(){
layer.destroy();
tween.destroy();
}
}).play();
}
If you must stick with your original function format, then you can grab children by using names:
newsobj[n] = new Kinetic.Text({
nid: n,
x: 140,
y: ((n == 0) ? 294.5 : 304.5 ),
text: news[n],
fill: 'white',
fontFamily: 'Corbel W01 Regular',
fontSize: 11.26,
name: 'fadeThisAway'
});
button = new Kinetic.Rect({
x: 10,
y: 10,
width: 100,
height: 100,
fill: 'red',
name: 'fadeThisAway'
});
In my example, I used the name fadeThisAway. And then, using your old function:
function newFadeShapesOut(layer, callback, speed){
var shapes = layer.get('.fadeThisAway');
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
console.log(layer.getChildren().length);
console.log(shapes.length);
layer.add(g);
shapes.each(function(shape){
shape.moveTo(g);
});
console.log(layer.getChildren().length);
console.log(shapes.length);
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
Instead of passing shapes through the function, just call
var shapes = layer.get('.fadeThisAway');
at the beginning of the function (you're passing layer through the function already anyways) to grab the children that are named fadeThisAway. (Note: This works because the group is not named fadeThisAway)
Working example and comments inside: JSFIDDLE
UPDATE
Okay so I made a basic example of the issue with layer.children
2nd JSFIDDLE
And it looks like that's just how the children of layer works. This proves that you definitely have to distinguish between shapes and group, because the group will always be considered a child of layer.
The naming method works to distinguish your shapes between layers by giving all shapes a common name that excludes groups.
After several attempts to bend projeqht's function to my way i finally did it!
Somehow, the collection shapes just updates itself when adding the group to the layer!
If i use an array instead, it works.
Hope it helps someone!
So here my solution which works like a charm.
function fadeShapesOut(shapes, callback, speed){
layer = shapes[0].getLayer();
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
layer.add(g);
for(i in shapes){
shapes[i].moveTo(g);
}
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
}
tween.destroy();
}
}).play();
}
If you have further questions, don't mind contacting me.
Related
I'm currently using Phaser 3 to represent my server's state.
Every x amount of time, I am sent the server's game state, this is what the client looks like:
var t1 = Date.now();
var serverUpdateDelta = 0;
Client.socket.on('usersPool', usersPool => {
// usersPool is an object containing all the user data of sockets connected on the server. Looks something like this:
/*
usersPool = {
"user1234": { x: 0, y: 0, direction: "right", moving: true },
"testuser": { x: 200, y: 250, direction: "down", moving: false }
}
*/
// keeping count of milliseconds between updates (usually around 500m)
serverUpdateDelta = Date.now() - t1;
// for every user connected on the server...
for(id in usersPool) {
let data = usersPool[id]; // this is the user's data
if(/* the player exists as a sprite in the game...*/) {
// THIS IS WHERE THE MAGIC SHOULD HAPPEN
} else {
genSprite(player);
}
}
});
The player's data contains a movementQueue, which is just an array of coordinates the user has been at. It might look a little like this:
[
{ x: 0, y: 0, direction: 'down', moving: false },
{ x: 5, y: 0, direction: 'right', moving: true },
{ x: 6, y: 0, direction: 'right', moving: false }
]
This is calculated on the server, but each movementStack (item in the movementQueue`) is generated every 25 milliseconds or so on the server.
The job now is, when receiving this movementQueue, to interpolate the values and move the sprite accordingly...
Attempt 1
I first tried making a function which would interpolate once the update was received, like so:
// THIS IS WHERE THE MAGIC SHOULD HAPPEN
// user's state on the client is set to an interpolated version of the one on the server
player.movementQueue = buffer(data.movementQueue);
The buffer will simply generate an interpolated array based on the serverUpdateDelta and game.loop.actualFps
then, in the Game.update function I ran the following:
for(id in spawnedPlayers) {
// this will remove the first movementStack from the queue and returns in
movementStack = spawnedPlayers[id].movementQueue.shift();
// we then take this movementStack and update the user to that position (and play the walking animation)
spawnedPlayers[id].update(movementStack);
}
So every game loop, we would remove a stack from the queue and set the user to it.
This did NOT work. The game loop seemed to run Way more times than there were frames in the queue, making the player look like they were moving a small distance very slowly...*:
player.movementQueue = player.movementQueue.concat(buffer(data.movementQueue));
But then something weird happened, where the game loop could not keep up with the movementQueue and the player would move extremely slowly...
Attempt 2
I then tried using tweens which would be really easy to implement, simply run:
// THIS IS WHERE THE MAGIC SHOULD HAPPEN
_this.tweens.timeline({
targets: player.sprite,
tweens: data.movementQueue, // [{x, y}, {x, y}, {x, y}]
duration: serverDeltaTime/movementQueue.length, // duration between each tween in ms
});
This worked ALMOST perfectly, except for one small detail:
Before, we would run a method for the player on each movementStack: player.update(movementStack), this method would take the direction of the user and animate the sprite accordingly. Now we have no way of doing this...
SO
What methods or techniques could I use? What am I missing? What could I implement? I ask this because I'm stuck at this point.
Thank you in advance for the help.
So, I am just diving into simple web animations for a game, and I am looking for advice. Eventually, I'll get a good grip on beziers and arcs and learn how to animate along a path to get some nice Diablo III-esque curving numbers but, for now, I am just trying to get the fundamentals down.
First (real) attempt
The key code is pretty simple-
paper.text(170, 95, dmgValue).attr({fill:"white", "font-size":16}).animate({
transform:"t0,-50", "fill-opacity":0} ,500).node.setAttribute("class", "no-select");
A CSS styling prevents the text from being highlighted (thanks to a user here for the help). The main issue, is that the text is still there with no opacity- you can hover over it and see the text cursor. Although it works, it' kind of messy looking. Also, since there is no variable assigned, I don't think I can dispose of it with Element.remove();
Where I am at now
There were a lot of small revisions I made in-between saved versions that made the code to the bulkiness that it is now. I wanted the ability to limit the number of numbers flying around at once (for slower computers), so I put them into an array that can be looped endlessly and used, although that probably isn't needed and it wouldn't be a big deal to leave it out.
Also moved from using transform, to setting the y-coords, and placing the .hide() into a separate function for the callback (which, for some reason worked instead of placing it at the end of the animation).
This version appears to work at first, but the animations get interrupted when you click too many times and I'm not sure why. I am sure I can figure it out in the end with enough time, but I might be making this too complicated, anyway. The full code-
var paper = Raphael(0, 0, 350, 350);
paper.canvas.style.backgroundColor = "Black";
var dmgValues = [],
dmgValuesIndex = 0,
maxMsgs = 15,
dmgXMaxOffset = 25,
dmgYMaxOffset = 25,
dmgXRef = 170 - dmgXMaxOffset,
dmgYRef = 250 - dmgYMaxOffset,
dmgMaxDistance = 50;
for (i=0; i< maxMsgs; i++) {
dmgValues[i] = paper.text().attr({fill:"white", "font-size":16});
dmgValues[i].node.setAttribute("class", "no-select");
dmgValues[i].hide();
}
var toggle = paper.rect(150, 270, 50, 25).attr({fill:"green"});
toggle.click(function() { doHit(); });
function doHit() {
var dmgHit = Math.floor(Math.random() * 99) + 1,
xPos = Math.floor(Math.random() * dmgXMaxOffset) + 1,
yPos = Math.floor(Math.random() * dmgYMaxOffset) + 1;
dmgValues[dmgValuesIndex].show();
if (dmgValues[dmgValuesIndex].status() == 1) { dmgValues[dmgValuesIndex].stop(); }
dmgValues[dmgValuesIndex].attr({x:dmgXRef + xPos, y:dmgYRef + yPos, text:dmgHit,
"fill-opacity":1}).animate({y:dmgYRef - dmgMaxDistance, "fill-opacity":0}, 600,
"linear", function() { afterEffects(dmgValues[dmgValuesIndex]) });
}
function afterEffects (afterTarget) {
afterTarget.hide();
dmgValuesIndex++;
if (dmgValuesIndex >= maxMsgs) { dmgValuesIndex = 0; }
}
CSS:
.no-select {
-moz-user-select: none;
-webkit-user-select: none;
}
I think I figured it out!
http://jsfiddle.net/rLcwax9k/10/
One thing I noticed was that that the incrementer was in the callback function that occurred after the animation, so it wasn't really counting right. But, mainly, because the dmgValuesIndex was global, and was getting incremented on each click. So, by the time the animation was done, it was doing functions based on whatever the current count was at the end of the animation in the callback, which may not have been the right one. So, I just put a parameter on the function and used that as the reference throughout the call and passed it to the callback.
Heh, I am sort of beginning to see why a lot of languages need setter and getter methods on their objects. This should be a good lesson to noobs like me on operating with global variable scope and their possible side-effects.
However, before I accept an answer, I am still looking for any other methods that may be more efficient.
Main code-
function doHit(iter) {
this.iter = iter;
var dmgHit = Math.floor(Math.random() * 99) + 1,
xPos = Math.floor(Math.random() * dmgXMaxOffset) + 1,
yPos = Math.floor(Math.random() * dmgYMaxOffset) + 1;
dmgValues[iter].show();
if (dmgValues[iter].status() == 1) { dmgValues[iter].stop(); }
dmgValues[iter].attr({
x:dmgXRef + xPos,
y:dmgYRef + yPos,
text:dmgHit,
"fill-opacity":1
})
.animate({
y:(dmgYRef + yPos) - dmgMaxDistance,
"fill-opacity":0},
1000,
">",
function() {
dmgValues[iter].hide();
}
);
}
I'm trying to change the length of a cylinder or the extrudedHeight of a circle when it has been added to the primitives and is shown in the cesium widget/viewer. For example this cylinder:
var length = 100;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(
Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
var primitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
widget.scene.primitives.add(primitive);
Because it's added to the primitives array it will be shown in the widget, but after 2 seconds for example I get a notification that the length should be halved (that means set to 50). Is there any way to do this? Simply changing it in cylinderGeometry doesn't seem to do the job.
I kind of have it working by creating a new cylinder with the new height, adding it and removing the old one. This however tends to flicker the cylinder (it's gone for a fraction of a second) before the new one is shown. I fixed this problem by removing the old instance after a set time after the new one is added. This whole solution isn't very elegant and doesn't work very well on devices with a small amount of computing power, hence my search for a better solution.
I don't care if this is achieved using cylinders or extruded circles. If you need any more information don't hesitate to ask in the comments below the question.
EDIT
I implemented the second solution Matthew suggested but after a while of it running perfectly the cylinders stop changing height (which didn't occur when I used my solution. The callback in the interval does get called. Here is some code showing what my new solution is (not working):
primitives.add(prim);
window.nodeValuesInterval = setInterval(function () {
if (prim._state == Cesium.PrimitiveState.COMPLETE) {
clearInterval(window.nodeValuesInterval);
clearTimeout(window.nodeValuesTimeout);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}
}, cylindervalueinterval);
window.nodeValuesTimeout = setTimeout(function () {
clearInterval(window.nodeValuesInterval);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}, cylindervaluedelay);
Cesium's geometry is currently optimized for static data. Some attributes, such as visibility, color, and material can be changed on the fly, but items that actually modify the geometry (like cylinder height) require you to remove the primitive and recompute the geometry. The flickering your seeing is the result of asynchronous primitive creation being on by default. There are two ways to do want you want.
Disable asynchronous primitive create by passing [options.asynchronous: false to the Primitive constructor. This means that when you add a new primitive, Cesium will not render until it is ready. For one or two objects, you won't notice anything. For lots of objects it will lock up the browser until everything is ready. This does guarantee that you can remove old/add new primitives without any flicker.
The second option is to add your new primitive (without removing the old one) and then every frame, check the _state property of your new Primitive (I thought this was part of the public API but apparently it's not). When the _state is equal to Cesium.PrimitiveState.COMPLETE you can safely remove the old primitive and your guaranteed the new one will render (hence no flicker).
I think we have a bug/feature request to expose the state variable publicly or otherwise notify when the Primitive is ready; but using _state should be fine for the forseeable future. I'll update this issue if we add an official way sometime soon.
Hope that helps.
EDIT: Since more help was requested; here's a complete example. You can copy and paste the below code into Sandcastle using this link.
Basically it uses the scene.preRender event instead of a timeout (preRender is almost always the better answer here). Also, if you receive a new update before the old one is finished processing, it's important to remove that one before computing the new one. Let me know if you are still having problems.
require(['Cesium'], function(Cesium) {
"use strict";
var widget = new Cesium.CesiumWidget('cesiumContainer');
var ellipsoid = widget.scene.globe.ellipsoid;
var lon = 0;
var lat = 0;
var cylinderradius = 30000;
var length = 10000000;
var cylinderslices = 32;
var newPrimitive;
var currentPrimitive;
//This function creates a new cylinder that is half the length of the old one.
function decreaseLength() {
//If there's a pending primitive already, remove it.
if(Cesium.defined(newPrimitive)){
widget.scene.primitives.remove(newPrimitive);
}
length /= 2;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
newPrimitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
//We add the new cylinder immediately, but don't remove the old one yet.
widget.scene.primitives.add(newPrimitive);
}
//Create the initial cylinder.
decreaseLength();
//Subscribe to the preRender event so we can check the primitive every frame.
widget.scene.preRender.addEventListener(function(scene, time) {
//Remove the old cylinder once the new one is ready.
if(Cesium.defined(newPrimitive) && newPrimitive._state === Cesium.PrimitiveState.COMPLETE){
if(Cesium.defined(currentPrimitive)){
widget.scene.primitives.remove(currentPrimitive);
}
currentPrimitive = newPrimitive;
newPrimitive = undefined;
}
});
Sandcastle.addToolbarButton('Decrease Length', decreaseLength);
Sandcastle.finishedLoading();
});
is it possible to create tweens sequence in KineticJS according to data saved in array without recursion?
This is my current solution including recursion:
function tweenFunc( image )
{
setGlobalCoordinates();
tween = new Kinetic.Tween({
node: image,
duration: 1,
x: x,
y: y,
rotation: rot,
onFinish: function(){
if ( counter++ < arr.length )
{
tweenFunc( image );
}
}
});
tween.play();
}
I would like to have solution which creates tweens before first animation and following tweens start from final position of predecessor.
This project includes animation of 4 images in the same time for many position sequence.
I have a list of colors, that needs to be animated as a document body background-color.
var bgs = [
"BlanchedAlmond",
"Blue",
"BlueViolet",
"Brown",
"BurlyWood",
"CadetBlue",
"Chartreuse",
"Chocolate",
"Coral",
"CornflowerBlue",
"Cornsilk",
"Crimson",
"Cyan",
"DarkBlue",
"DarkCyan"
];
Now, using colorToHex() custom function for mootools, I ended up with the following code:
window.addEvent('domready', function() {
var current;
(function() {
selected = ~~(Math.random() * bgs.length);
// is it a right way to avoid the repetition?
current = (selected == current) ? ((bgs.length-1) % (selected+1)) : selected;
// -1 is to avoid the edge case,
// +1 is to avoid the NaN in case select is 0
$(document.body).set('tween', {duration: '1500'})
.tween("background-color",bgs[current].colorToHex());
}).periodical(1000);
});
Questions
(optimization of the aforementioned chunks of code) From the performance optimization perspective, is there a better way to perform this animation?
(vs. jQuery) Would the jQuery counterpart be more efficient and elegant?
Ok, I had 2 minutes to check it and try to make it better :)
..here is the working example : http://jsbin.com/evofuw/2 (and the code here http://jsbin.com/evofuw/2/edit#javascript)
..btw, I found some problems in your version:
selected = ~~(Math.random() * bgs.length); you haven't defined selected, + there is a built in getRandom() method available for Arrays in MooTools.
to avoid repetition and all that 'mess' you did, delete that random element from that array ;)
Why you're not using the onComplete tween callback? Using periodical (setInterval) is never the same as using callbacks (and most of the times is not correct).
Each time you're running that anonym function you're retrieving (by $) the body that is not cached. Ok, it's the body but it's a good habit to cache elements :)
About q#2, definitely not. :)
Here is my version:
// a namespace, more elegant :)
var app = {};
// the array with colors
app.bgs = [
"BlanchedAlmond",
"Blue",
"BlueViolet",
"Brown",
"BurlyWood",
"CadetBlue",
"Chartreuse",
"Chocolate",
"Coral",
"CornflowerBlue",
"Cornsilk",
"Crimson",
"Cyan",
"DarkBlue",
"DarkCyan"
];
// the function to change bg color
app.changeBGColor = function( b ){
// random select element
var selected = app.bgs.getRandom();
// delete that element from the array
app.bgs.erase(selected);
// tween background color
b.tween('background-color',selected.colorToHex());
}
window.addEvent('domready', function() {
// cache body element
var b = $(document.body);
// set tween stuff
b.set('tween', {
duration : 1500,
onComplete : function(){
if( app.bgs.length ) { // if the array contains elements, change color
app.changeBGColor(b);
} else { // otherwise do nothing
console.log('array empty! end!');
}
}
});
// start 1st time
app.changeBGColor(b);
});
ps. if you want to animate 'forever', just use another array (where to push the deleted values) and then swap array when the other one is empty
Answer 1:
I agree with stecb; You can cache the values and make use of getRandom(). But in order to continue the animation indefinitely, you don't want to delete the element from array. Therefore, to avoid the duplicate selection consecutively, you just need to switch the places of (cached_length-1) and (selected+1).
Also, the method colorToHex suggested by csuwldcat (the one you cited) is most costly in the entire animation in terms of performance. I would highly suggest you use Hex code in the bgs array. If that is not an option, the you must use colourNameToHex() function by Greg on the same page.
Finally periodical( _interval ) is for setting the delay between the adjacent tween ops whereas duration is the time taken by one color transition. Mootools also provides a delay() function to pause the sequential flow. But in this case, use of priodical() to fire the transition after fixed interval makes sense.
Here is another version of your code:
window.addEvent('domready', function() {
var cached_length = bgs.length;
var myElement = new Fx.Tween(document.body, {duration: '1500'});
var current, selected;
(function() {
selected = ~~(Math.random() * bgs.length);
current = (selected == current) ?
((selected + 1) % (cached_length - 1)) : selected;
/* console.info("current: "+bgs[current]); */
myElement.start("background-color",bgs[current]); // if you use Hex codes
// instead of color names
}).periodical(1000);
});
Answer 2:
Since jQuery would require a plugin jQuery.Color to animate the background-color, that kind of an extra layered complexity may effect the performance, but it cannot compete the performance of Mootools (which is an extended Javascript core as opposed to a layered framework).