Phaserio Dynamic Sprite Creation - javascript

I am using PhaserIO in conjunction with Meteor to create a multiplayer html5 game and have run into a snag I cannot seem to figure out in a networking prototype I was making. First, the relevant code (also available as a gist):
if(Meteor.isClient) {
var game,
mainState,
mainStateInitialized,
characterData;
Template.game.game = function() {
characterData = Character.find().fetch();
if(!mainStateInitialized)
{
game = new Phaser.Game(500, 600, Phaser.AUTO, 'gameScreen');
createMainState()
}
}
}
function createMainState()
{
mainState = {
sprites: null,
playerLastFrame: characterData.length,
playerCurrentFrame: null,
charData: characterData,
preload: function() {
this.sprites = game.add.group();
game.stage.disableVisibilityChange = true;
game.load.image('hello', 'resources/hello.png');
},
create: function() {
$.each(characterData, function(index) {
var sprite = mainState.sprites.create(this.x, this.y, 'hello');
sprite.angle = this.angle;
});
},
update: function() {
this.charData = characterData;
this.playersCurrentFrame = this.charData.length;
if(this.playersLastFrame > this.playersCurrentFrame) {
//todo: remove player that left
}
else if(this.playersLastFrame < this.playersCurrentFrame) {
for(var i = playersLastFrame; i < playersCurrentFrame; i++) {
var thisData = this.charData[i],
sprite = null;
sprite = mainState.sprites.create(thisData.x, thisData.y, 'hello');
sprite.angle = thisData.angle;
}
}
for(var j = 0; j < mainState.sprites.length; j++) {
mainState.sprites.getAt(j).angle = this.charData[j].angle;
}
playersLastFrame = this.charData.length;
}
}
game.state.add('main', mainState);
game.state.start('main');
mainStateInitialized = true;
}
The idea of this prototype is to have a sprite shown in the canvas for each account in the DB. The main features I am testing are:
Dynamically adding sprites/player data seamlessly (as all proper multiplayer online games should be capable of. This will eventually pave the way for a proper join/leave system)
And to mess with creating efficient packets.
Right now, I am running into an issue with dynamically creating a new sprite when a player creates a new account. About 75% of the time, when a player makes a new account nothing happens. Meteor correctly pushes down the character data, which I can query, and mainState.sprites correctly shows the sprite data. However, nothing is rendered on the canvas.
The other 25% of the time, it works fine. In addition, if I have the code break-pointed it works 100% of the time as far as I can tell.
So, something intermittent is obviously occurring here but I can't figure out what the issue is. Is there something I am missing when adding a sprite during the update loop? Is there a better way to approach this?
I have my code on Nitrous.io, so I can run a localhost instance for you to hit if it would help in solving the problem.

The issue was I was not setting playersLastFrame = playersCurrentFrame.
I feel silly now considering this is a basic loop/compare structure. last = current at end of loop.
Sigh : (.

Related

Offscreen-Canvas not rendering some of the time

I am trying to implement a in browser raster drawing plugin for the leaflet library that that extends the leaflets GridLayer api. Essentially for every tile there is function createTile that returns a canvas with some drawing on it. and leaflet shows the tile in correct position.
initialize: function(raster_data){
this.raster_data = raster_data;
},
createTile: function (tile_coords) {
let _tile = document.createElement('canvas');
let _tile_ctx = _tile.getContext('2d');
// do some drawing here with values from this.raster_data
return _tile;
}
This implementation is so far working fine. Than I thought of offloading drawing with offscreen-canvas in a webworker. so I restructured the code like this
initialize: function(raster_data){
this.raster_data = raster_data;
this.tile_worker = new Worker('tile_renderer.js')
},
createTile: function (tile_coords) {
let _tile = document.createElement('canvas').transferControlToOffscreen();
this.tile_worker.postMessage({
_tile: _tile,
_raster_data: this.raster_data
},[_tile])
return _tile;
}
This works but every now and then i see a canvas that is just blank. That thing is quite random I don't know start from where and how should I debug this. can this be a problem that I am using a single worker for rendering every tile? any help is appreciated. Here is an example of a blank canvas.
This a known bug: https://crbug.com/1202481
The issue appears when too many OffscreenCanvases are sent to the Worker serially.
The workaround is then to batch send all these OffscreenCanvases in a single call to postMessage().
In your code you could achieve this by storing all the objects to be sent and use a simple debouncing strategy using a 0 timeout to send them all at once:
createTile: function (tile_coords) {
let _tile = document.createElement('canvas');
_tile.setAttribute('width', 512);
_tile.setAttribute('height', 512);
let _off_tile = _tile.transferControlToOffscreen();
this.tiles_to_add.push( _off_tile ); // store for later
clearTimeout( this.batch_timeout_id ); // so that the callback is called only once
this.batch_timeout_id = setTimeout( () => {
// send them all at once
this.tileWorker.postMessage( { tiles: this.tiles_to_add }, this.tiles_to_add );
this.tiles_to_add.length = 0;
});
return _tile;
}
Live example: https://artistic-quill-tote.glitch.me/

How to get around blocking issues with rendering 1000+ THREEJS sprite labels

I have close to 1000 (and at some point more) text sprite labels being rendered in a THREEJS scene. Creating the sprite objects is not an issue. When I go to add them to the scene it seems this is blocking and killing any other animations or user interactions that take place.
I have tried splitting the array of sprites into chunks and do a chunk at a time. Recently I have tried using setTimeout but am not having much luck. I am struggling with callbacks and setTimeout.
Here is what I have so far:
function addlabels(data){
label_array = [];
$.each(data.nodes, function (key, value) {
var position = value.pos;
var vector = new THREE.Vector3(position[0], position[1], position[2]);
value.label = makeTextSprite(key, {fontsize:32});
value.label.position.copy(vector);
label_array.push(value.label);
});
function chunk_labels(items, process, context){
var todo = items.concat();
setTimeout(function(){
do {
console.log(context, todo.shift());
scene.add(todo.shift());
} while(todo.length > 0);
if (todo.length > 0){
setTimeout(chunk_labels(todo), 25);
}else{
callback(items);
}
}, 25);
}
chunk_labels(label_array);
}

Optimizing easeljs line drawing for mobile

I'm trying to make a simple application where the user can draw lines by click-dragging the mouse or through gestures on their touch device screen.
It's perfectly fine on my desktop machine, but on my phone its very slow and jerky. It's not that performance degrades over time, it's immediately noticable.
I'm using easeljs and I extended shape. On mouse movements it records the points and on tick it draws them. The stage's autoClear is set to false and the graphics object clears before it draws so it doesn't redraw anything from the previous tick.
(function (window) {
function LineDrawer() {
this.initialize();
}
//Inheritance from Container
LineDrawer.prototype = new createjs.Shape();
LineDrawer.prototype.Shape_initialize = LineDrawer.prototype.initialize;
LineDrawer.prototype.Shape_tick = LineDrawer.prototype._tick;
LineDrawer.prototype.initialize = function () {
//call to initialize() method from parent class
this.Shape_initialize();
this.points = [];
this.mouseMoveEventListener = $.proxy(this.onMouseMove, this);
}
LineDrawer.prototype._tick = function (e) {
//call to _tick method from parent class
this.Shape_tick();
var points = this.points;
if (points.length > 0) {
var graphics = this.graphics.clear()
.setStrokeStyle(3, 'round', 'round')
.beginStroke("#000000")
.moveTo(points[0].x, points[0].y)
var pt;
for (var i = 1; i < points.length; i = i + 1) {
pt = points[i];
graphics.lineTo(pt.x, pt.y);
}
points.length = 0;
if (typeof pt !== 'undefined') {
points.push(new createjs.Point(pt.x, pt.y));
}
}
}
LineDrawer.prototype.onMouseDown = function (e) {
this.points.push(new createjs.Point(e.stageX, e.stageY));
this.parent.addEventListener("stagemousemove", this.mouseMoveEventListener);
}
LineDrawer.prototype.onMouseMove = function (e) {
this.points.push(new createjs.Point(e.stageX, e.stageY));
}
LineDrawer.prototype.onMouseUp = function (e) {
this.points.push(new createjs.Point(e.stageX, e.stageY));
this.parent.removeEventListener("stagemousemove", this.mouseMoveEventListener);
}
window.LineDrawer = LineDrawer;
}(window));
Here's the code for setting up the stage:
var stage,
lineDrawer;
$(document).ready(function () {
lineDrawer = new LineDrawer();
var $canvas = $('#canvasMain');
stage = new createjs.Stage($canvas[0]);
createjs.Touch.enable(stage);
stage.addChild(lineDrawer);
stage.autoClear = false;
stage.addEventListener("stagemousedown", $.proxy(lineDrawer.onMouseDown, lineDrawer));
stage.addEventListener("stagemouseup", $.proxy(lineDrawer.onMouseUp, lineDrawer));
createjs.Ticker.timingMode = createjs.Ticker.RAF;
createjs.Ticker.addEventListener("tick", stage);
})
Here's a fiddle for everything. Other information: I'm using jquery-1.8.3 and a RAF polyfill, these are my phone specs. I also got somebody to try with a much better Samsung phone with the same results.
Although admittedly my phone is on the low end of the spectrum, its not a dinosaur of a phone. It is android 4.0+ and what I'm doing really isn't that complicated as far as I can tell. Am I doing anything wrong and/or is there anything I can do to improve this? I wonder if maybe its a problem with the touch events and not the drawing speed as well.
EDIT: the other phone with laggy drawing was a Samsung S3
Answering my own question.
The problem was a combination of me not using the graphic's endStroke method after all the lines causing the drawing to be one tick behind, and a logic error where I would only draw lines if there was one or more points, but really it was supposed to be 2 or more points.
The second part was causing the most lag. Interestingly there was no error since the array index is never out of bounds because of the for loop condition, so I guess just having an impossible for condition was really slow on the Chrome for android browsers, without crashing.

Play Audio through JS with Overlapping

I have the following JS code for a canvas based game.
var EXPLOSION = "sounds/explosion.wav";
function playSound(str, vol) {
var snd = new Audio();
snd.src = str;
snd.volume = vol;
snd.play();
}
function createExplosion() {
playSound(EXPLOSION, 0.5);
}
This works, however it sends a server request to download the sound file every time it is called. Alternatively, if I declare the Audio object beforehand:
var snd = new Audio();
snd.src = EXPLOSION;
snd.volume = 0.5;
function createExplosion() {
snd.play();
}
This works, however if the createExplosion function is called before the sound is finished playing, it does not play the sound at all. This means that only a single playthrough of the sound file is allowed at a time - and in scenarios that multiple explosions are taking place it doesn't work at all.
Is there any way to properly play an audio file multiple times overlapping with itself?
I was looking for this for ages in a tetris game i'm building and I think this solution is the best.
function playSoundMove() {
var sound = document.getElementById("move");
sound.load();
sound.play();
}
just have it loaded and ready to go.
You could just duplicate the node with cloneNode() and play() that duplicate node.
My audio element looks like this:
<audio id="knight-audio" src="knight.ogg" preload="auto"></audio>
and I have an onClick listener that does just that:
function click() {
const origAudio = document.getElementById("knight-audio");
const newAudio = origAudio.cloneNode()
newAudio.play()
}
And since the audio element isn't going to be displayed, you don't actually have to attach the node to anything.
I verified client-side and server-side that Chrome only tries to download the audio file once.
Caveats: I'm not sure about performance impacts, since this on my site this clip doesn't get played more than ~40x maximum for a page. You might have to clean up the audio nodes if you're doing something much larger than that?
Try this:
(function() {
var snds = {};
window.playSound(str,vol) {
if( !snds[str]) (snds[str] = new Audio()).src = str;
snds[str].volume = vol;
snds[str].play();
}
})();
Then the first time you call it it will fetch the sound, but every time after that it will reuse the same sound object.
EDIT: You can also preload with duplicates to allow the sound to play more than once at a time:
(function() {
var snds = {}
window.playSound = function(str,vol) {
if( !snds[str]) {
snds[str] = [new Audio()];
snds[str][0].src = str;
}
var snd = snds[str], pointer = 0;
while( snd[pointer].playing) {
pointer++;
if( pointer >= snd.length) {
snd.push(new Audio());
snd[pointer].src = str;
}
}
snd[pointer].volume = vol;
snd[pointer].play();
};
})();
Note that this will send multiple requests if you play the sound overlapping itself too much, but it should return Not Modified very quickly and will only do so if you play it more times than you have previously.
In my game i'm using preoading but after the sound is initiated (its not so smart to not preload at all or preload everything on page load, some sound hasn't played in some gameplay at all, why to load them)
const audio {};
audio.dataload = {'entity':false,'entityes':[],'n':0};
audio.dataload.ordernum = function() {
audio.dataload.n = (audio.dataload.n + 1)%10;
return audio.dataload.n;
}
audio.dataload.play = function() {
audio.dataload.entity = new Audio('/some.mp3');
for (let i = 0; i<10;i++) {
audio.dataload.entityes.push(audio.dataload.entity.cloneNode());
}
audio.dataload.entityes[audio.dataload.ordernum()].play();
}
audio.dataload.play() // plays sound and preload sounds to memory when it isn't
I've created a class that allows for layered audio. This is very similar to other answers where it creates another node with the same src, but this class will only do that if necessary. If it has created a node already that has been completed, it will replay that existing node.
Another tweak to this is that initially fetch the audio and use the URL of the blob. I do this for efficiency; so the src doesn't have to be fetched externally every single time a new node is created.
class LayeredAudio {
url;
samples = [];
constructor(src){
fetch(src)
.then(response => response.blob())
.then((blob) => {
this.url = URL.createObjectURL(blob);
this.samples[0] = new Audio(this.url);
});
}
play(){
if(!this.samples.find(e => e.paused)?.play()){
this.samples.push(new Audio(this.url))
this.samples[this.samples.length - 1].play()
}
}
}
const aud = new LayeredAudio("URL");
aud.play()
Relying more on memory than process time, we can make an array of multiple clones of the Audio and then play them by order:
function gameSnd() {
tick_wav = new Audio('sounds/tick.wav');
victory_wav = new Audio('sounds/victory.wav');
counter = 0;
ticks = [];
for (var i = 0; i<10;i++)
ticks.push(tick_wav.cloneNode());
tick = function(){
counter = (counter + 1)%10;
ticks[counter].play();
}
victory = function(){
victory_wav.play();
}
}
When I tried some of the other solutions there was some delay, but I may have found a better alternative. This will plow through a good chunk of memory if you make the audio array's length high. I doubt you will need to play the same audio more than 10 times at the same time, but if you do just make the array length longer.
var audio = new Array(10);
// The length of the audio array is how many times
// the audio can overlap
for (var i = 0; i < audio.length; i++) {
audio[i] = new Audio("your audio");
}
function PlayAudio() {
// Whenever you want to play it call this function
audio[audioIndex].play();
audioIndex++;
if(audioIndex > audio.length - 1) {
audioIndex = 0;
}
}
I have found this to be the simples way to overlap the same audio over itself
<button id="btn" onclick="clickMe()">ding</button>
<script>
function clickMe() {
const newAudio = new Audio("./ding.mp3")
newAudio.play()
}

Issue with saving canvas data to an array

I am creating a web application using HTML5 canvas to draw images ( like paint web ), I try to implement the "undo" (ctrl+Z) and "redo" features and here I am facing a strange problem with an array of canvas elements.
Sometimes, when I hit ctrl+Z to undo, a blank image appears, however the data is in the array and I point to the correct element (because when I play with undo/redo I manage to have the corrects images in the right order).
If you can have a look at the following code I would be grateful, I've spent a lot of time already and I'm not able to locate the problem... :-(
function Stack(firstImg , size) {
var drawStack = new Array();
var stackIndex = 0;
var stackTop = 0;
var stackFloor = 0;
var stackSize = size;
drawStack[0] = firstImg;
this.add = function() {
drawStack[++stackIndex%stackSize] = cvs.toDataURL("image/png");
if (stackIndex >= stackSize) stackFloor = (stackIndex +1) % stackSize ;
stackTop = stackIndex % stackSize;
}
this.undo = function () {
if (stackIndex%stackSize == stackFloor ) return;
clearCanvas();
var tmpImg = new Image();
tmpImg.src = drawStack[--stackIndex%stackSize];
cvsCtx.drawImage(tmpImg, 0, 0);
}
this.redo = function () {
if (stackIndex%stackSize == stackTop) return;
clearCanvas();
var tmpImg = new Image();
tmpImg.src = drawStack[++stackIndex%stackSize];
cvsCtx.drawImage(tmpImg, 0, 0);
}
}
Any solution or workaround, I will take, thank you very much !!
I have implemented undo/redo functionality a few times and, while I cannot post the code for licensing reasons, I can give you some psuedocode that should demonstrate how simple undo/redo actually is:
first, you need two arrays. call these "undo" and "redo".
Every time the state changes, push that state to the undo stack.
When the user presses ctrl-z (undo), pop the last saved state from the undo stack. push this state to the redo queue, and also make it the current state.
When the user presses ctrl-y (redo), pop the last saved state from the redo queue.
If either of the arrays begins to fill up past the # of states you want to save, use shift to discard the oldest state.
For references on push, pop, and shift, see the MDN documentation.
Also, you will probably find yourself wishing arrays had peek, so here it is:
Array.prototype.peek = function () {
var theArray = this;
var temp = theArray.pop();
if (temp !== undefined) {
theArray.push(temp);
}
return temp;
};
edit: there was a bug in my snippet, calling .peek() on an empty array would push undefined.

Categories

Resources