I am writing an HTML5 game using the engine Phaser, in which I am implementing what are essentially live backgrounds, backgrounds that respond to the movements of the game objects. The first I am working with is a water ripple effect that uses area sampling on the bitmapData object. I thought I had a performance issue in my code, but it turns out that Firefox runs it like a dream. Chrome runs a little slower to begin with and slows to less than 10 FPS when my game objects go too close to the top or bottom of the screen. (I am at a loss for why that makes a difference.)
This thread suggests that Chrome has poor image processing performance and suggests to break large image data up into smaller pieces. I don't know if this is possible in my case, because this is not simply an image displaying on the screen but an effect based on pixels next to each other that refreshes each frame. Even if it is possible, I think Chrome would end up having to do the same amount of work or more to get the four individual bitmaps to interact with each other as if they were one.
I've been doing performance tests in Chrome for a few hours, and the issue is definitely that it is getting caught up on the method that actually creates the effect by reading pixels from a source imageData and writing them to another location in a target imageData (the ws.displace(x,y) method below).
function waterStage(canvas) {
var ws = new Object();
ws.dampFactor = 16;
ws.magFactor = 150;
ws.dispFactor = 0.5;
ws.lumFactor = 1;
ws.width = canvas.width;
ws.height = canvas.height;
// Initialize height data caches
ws.pMaps = [];
var map1 = new Array(ws.width+2);
var map2 = new Array(ws.width+2);
for (x=0; x < map1.length; x++) {
map1[x] = new Array(ws.height+2);
map2[x] = new Array(ws.height+2);
}
for (x=0; x < map1.length; x++) {
for (y=0; y < map1[x].length; y++) {
map1[x][y] = 0;
map2[x][y] = 0;
}
}
ws.pMaps.push(map1, map2);
ws.stageInit = function(canvas) {
canvas.fill(100,100,100);
canvas.ctx.strokeStyle = "#000000";
canvas.ctx.lineWidth = 2;
canvas.ctx.moveTo(0,0);
for (y=0; y < ws.height; y+=10) {
canvas.ctx.beginPath();
canvas.ctx.moveTo(0,y);
canvas.ctx.lineTo(ws.width,y);
canvas.ctx.closePath();
canvas.ctx.stroke();
}
ws.sourceData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
ws.targetData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
}
ws.setWave = function(pnt) {
ws.pMaps[0][pnt.x-1][pnt.y-1] = ws.magFactor//*pnt.magnitude;
}
ws.resolveWaves = function(x,y) {
// Calculate the net result of the wave heights
ws.pMaps[1][x][y] = ((ws.pMaps[0][x-1][y]+ws.pMaps[0][x+1][y]+ws.pMaps[0][x][y-1]+ws.pMaps[0][x][y+1]) / 2)
-ws.pMaps[1][x][y];
ws.pMaps[1][x][y] -= (ws.pMaps[1][x][y]/ws.dampFactor);
}
ws.displace = function(x,y) {
var displace = Math.floor(ws.pMaps[1][x][y]*ws.dispFactor);
var xCorrect = x-1, yCorrect = y-1;
var targetIndex = (xCorrect + yCorrect * ws.width)*4;
if (displace == 0) {
ws.targetData.data[targetIndex] = ws.sourceData.data[targetIndex];
ws.targetData.data[targetIndex+1] = ws.sourceData.data[targetIndex+1];
ws.targetData.data[targetIndex+2] = ws.sourceData.data[targetIndex+2];
}
else {
if (displace < 0) {
displace += 1;
}
var sourceX = displace+xCorrect;
var sourceY = displace+yCorrect;
var sourceIndex = (sourceX + sourceY * ws.width)*4;
//var lum = ws.pMaps[1][x][y]*ws.lumFactor;
ws.targetData.data[targetIndex] = ws.sourceData.data[sourceIndex];//+lum;
ws.targetData.data[targetIndex+1] = ws.sourceData.data[sourceIndex+1];//+lum;
ws.targetData.data[targetIndex+2] = ws.sourceData.data[sourceIndex+2];//+lum;
}
}
ws.stageRefresh = function(moves, canvas) {
canvas.clear();
for (j=0; j < moves.length; j++) {
ws.setWave(moves[j]);
}
for (x=1; x <= ws.width; x++) {
if (ws.pMaps[1][x][0] != 0 || ws.pMaps[0][x][0] != 0) {
alert("TOP ROW ANOMALY");
}
for (y=1; y <= ws.height; y++) {
ws.resolveWaves(x,y);
ws.displace(x,y);
}
}
ws.pMaps.sort(function(a,b) { return 1 });
//ws.pMaps[0] = ws.pMaps[1];
//ws.pMaps[1] = temp;
canvas.ctx.putImageData(ws.targetData, 0, 0);
}
return ws;
}
canvas is the bitmapData that is given as the texture for the background (not an HTML5 canvas; sorry if that's confusing). ws.stageRefresh(moves,canvas) is called on every frame update.
Before I try to make the split-into-four-bitmaps solution work, does anyone have any guidance for other ways to improve the performance of this effect on Chrome?
Related
My brain is not working properly today and I can't seem to figure this out.
I have a RGBA image data stored in an Uint8Array() and need to scale the width only.
var w = 160;
var h = 200;
var depth=4;
var pixels = new Uint8Array(w*h*depth);
I need to scale the pixels array to 320x200 and every attempt I did ended up with a garbled image.
Thanks to Yves Daoust I revisited some of my old attempts at solving this by duplicating every chunk of 4 to the destination, and now I got it working. So thank you Yves :) I do not know what I did wrong earlier.
This is the working code that I ended up with. I'm 100% sure it can be done differently and better, but at this point I am satisfied :)
Utils.prototype.scalePixelsInWidth = function(pixels) {
var w = 320;
var h = 200;
var scanlineWidth = w*4;
var scaledPixels = new Uint8Array(w*h*4);
var a = 0;
for(let row=0;row<h;row++) {
var col2 = 0;
for(let col=0;col<w;col++) {
var srcIndex = col2*4 + (row*(w/2)*4);
var destIndex = col*4 + (row * scanlineWidth);
scaledPixels[destIndex+0] = pixels[srcIndex+0];
scaledPixels[destIndex+1] = pixels[srcIndex+1];
scaledPixels[destIndex+2] = pixels[srcIndex+2];
scaledPixels[destIndex+3] = pixels[srcIndex+3];
a++;
if (a > 1) {
a = 0;
col2++;
}
}
}
return scaledPixels;
}
My friends and I are working on a game. However, I've run into difficulty with the star sprite management. Sometimes, there are gaps (areas where sprites should be graphed but instead are not). I have included the entire program as a code snippet but below are also code blocks which are likely directly responsible for the problem.
There are coordinate pairs for each sprite. The coordinate map is presented below:
[{x:xCoordinate,y:yCoordinate},{x:xCoordinate,y:yCoordinate},...];
This code spawns and removes sprites...
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
'use strict';
//global variables
var canvas, c;
//viewport variables
var viewportPosition = {
x:false,
y:false
};
//game matrix settings
var gameMatrixConfig = {
width:20000,
height:20000
};
//cursor position
var cursorPosition = {
x:false,
y:false
};
//spaceship position
var spaceship = {
x:false,
y:false,
rotation:0,
gameMatrixPositionX:false,
gameMatrixPositionY:false
};
//fps monitor (for monitoring frame rate for development purposes)...
var fps = {
lastFrameTime:undefined,
timeSinceLastFrame:undefined,
startDisplayTimeInterval:function(){
setInterval(function(){
document.getElementById('fpsLabel').innerHTML = Math.floor(1000/getTimeSinceLastFrame());
},500);
}
};
function getTimeSinceLastFrame(){
return fps.timeSinceLastFrame;
}
//resize throttle timer global variable holder
var resizeTimer = false;
//the drawing frame:
var renderFrame = false;
//global events
window.addEventListener('load',function(){
initialize('load');
});
window.addEventListener('resize',function(){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function(){
initialize('resize');
},100);
});
window.addEventListener('mousemove',function(e){
cursorPosition.x = e.clientX;
cursorPosition.y = e.clientY;
});
//global functions
function initialize(type){
if(type == 'load'){
preLoadSprites();
}
initializeCanvas();
}
function initializeCanvas(){
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
c.width = canvas.width;
c.height = canvas.height;
}
/*a class with a filePath argument.
This class is used to create new sprite images
from an instance of the class.*/
function sprite(filePath){
//create a new image preload
this.sprite = new Image();
this.sprite.src = filePath;
//load status
this.loaded = false;
/*
holds all current positions of the sprite.
default start coordinates are 0,0
*/
this.positions = [];
//original associative array data structure...
//this.positions = {};
//the image has been preloaded
this.sprite.addEventListener('load',function(){
this.loaded = true;
/*
bind the "this" reference of the constructor to avoid referencing
the load event function
*/
}.bind(this));
}
function preLoadSprites(){
//create new objects for all the sprites to be used in the game
var sprites = {
stars:new sprite('https://drive.google.com/uc?export=download&id=0B821h2dKD0r_bERZb0RHeVRTQnM')
};
/*
check the load status of the sprites
loop through all properties of sprites object twice per second
check for load status. Load flag default is true. Set flag to false
when an unloaded image is discovered.
*/
var interval = setInterval(function(){
var x, loaded = true;
for(x in sprites){
if(!sprites[x].loaded){
loaded = false;
}
}
if(loaded){
clearInterval(interval);
//complete other tasks such as hiding a load spinner...
//ready sprites and context for graphing and provide access to sprites....
initializeGraphing(sprites);
}
},50);
}
function initializeGraphing(sprites){
//set initial viewport position in game matrix....
viewportPosition.x = gameMatrixConfig.width/2;
viewportPosition.y = gameMatrixConfig.height/2;
//start graph animation loop; provide access to sprites....
graph(sprites);
}
/*research how to inherit values from another object with
local variables.*/
function graph(sprites){
updateSpritePositions(sprites);
//testing frame rate...
setInterval(function(){draw()},16.67);
//60fps interval code. Uncomment when testing is complete...
//setInterval(function(){draw()},16.67);
//calculate sprite requirements for viewport and configure a sprite matrix...
initializeStars(sprites.stars);
fps.startDisplayTimeInterval();
//render the graphic frame
function draw(){
//console.log(fps.timeSinceLastFrame)
fps.timeSinceLastFrame = Date.now()-fps.lastFrameTime;
//fps.displayTimeInterval();
//fps.lastFrameTime = Date.now();
//clear the canvas
c.clearRect(0,0,window.innerWidth,window.innerHeight);
//graph the stars...
graphStars(sprites.stars);
updateSpritePositions(sprites);
fps.lastFrameTime = Date.now();
}
}
function initializeStars(stars){
/*
calculate sprite requirements for viewport and configure a sprite matrix
this only needs to happen once unless the viewport is resized
*/
/*
meta data used for various calculations throughout the script...
*/
stars.meta = {
//required sprites to fill the viewport width (based on sprite width)...
imagesXRequired:Math.ceil(window.innerWidth/(stars.sprite.width))+2,
//required sprites to fill the viewport height (based on sprite height)...
imagesYRequired:Math.ceil(window.innerHeight/(stars.sprite.height))+2,
//required sprites to fill the entire viewport...
get requiredSprites(){
return this.imagesXRequired*this.imagesYRequired;
},
//the sprite width...
spriteWidth:stars.sprite.width,
//the sprite height...
spriteHeight:stars.sprite.height,
//the lowest x value in stars.positions...
get lowestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.min.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the highest x value in stars.positions...
get highestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.max.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the lowest y value in stars.positions...
get lowestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.min.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
},
//the highest y value in stars.positions...
get highestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.max.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
}
};
//the y coordinate in a scaled matrix system for sprites...
var y = 0;
//the x coordinate in a scaled matrix system for sprites...
var x = 0;
//loop through the number of required sprites and graph...
for(var i = 0; i < stars.meta.requiredSprites; i++){
//calculate when a new row is necessary
if((i)%stars.meta.imagesXRequired == 0){
x = 0;
y++;
}
//set actual starting viewport matrix coordinate positions....
stars.positions[i] = {
x:x*stars.meta.spriteWidth,
y:y*stars.meta.spriteWidth
};
x++;
}
}
function graphStars(stars){
/*
prior to graphing, determine if a sprite is no longer within
the viewport matrix and remove it...
if new sprites are necessary, add new coordinates accordingly...
*/
/*
==============IMPORTANT NOTE==================
*/
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
c.drawImage(stars.sprite,x,y);
}
}
function updateViewportPosition(){
}
function updateSpritePositions(sprites){
/*gather information from the cursor to influence the next frame data
cursor is a local object variable template for use within this function only
cursor stores information about the cursor's position
*/
var cursor = {
distance:{
//the cursor's distances on the planes from the origin for X and Y
cursorXDistance:Math.abs(window.innerWidth/2-cursorPosition.x),
cursorYDistance:Math.abs(window.innerHeight/2-cursorPosition.y)
},
quadrant:function(){
//method returns the appropriate quadrant number for the unit circle
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//first quadrant
return 1;
}
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//fourth quadrant
return 4;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//second quadrant
return 2;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//third quadrant
return 3;
}
}
};
//calculate the velocity (the number of pixels to move for the next frame)...
function velocity(){
/*
To calculate velocity ratio, divide the hypotenuse of the cursor's position
by the viewport hypotenuse.
*/
return Math.sqrt(Math.pow(cursor.distance.cursorXDistance,2) + Math.pow(cursor.distance.cursorYDistance,2))/100;
}
//calculate the movement ratio: the number of x pixels per y pixels
function movementRatio(){
var xRatio = cursor.distance.cursorXDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
var yRatio = cursor.distance.cursorYDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
return {xRatio,yRatio};
}
//update positions of sprites...
//retrieve the current movement ratio object...
var coordinateChange = movementRatio();
//retrieve the current quadrant of the unit circle for the cursor's position...
var quadrant = cursor.quadrant();
//retrieve velocity coefficient...
var velocity = velocity();
//update viewport position based on quadrant position...
var i;
for(i in sprites.stars.positions){
if(quadrant == 1){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 2){
//update star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 3){
//update the star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
if(quadrant == 4){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
}
}
html,body{
width:100%;
height:100%;
margin:0;
padding:0;
}
canvas{
width:100%;
height:100%;
margin-bottom:-8px;
background:#434343;
}
#gameInterface{
width:100%;
height:100%;
}
.hidden{
width:1px;
height:1px;
position:fixed;
top:0;
left:0;
z-index:-100;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Game v0.35</title>
<!--<link rel="stylesheet" href="css/index.css" type="text/css">-->
<!--<script type="text/javascript" src="js/index.js"></script>-->
</head>
<body>
<div id="fpsLabel" style="position:fixed; top:0; left:0; display:table; background:#fff;">
</div>
<div id="intro">
</div>
<div id="gameInterface">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
I've brainstormed a list of possible causes for the problem:
Gaps are caused by sprites being removed too soon.
Gaps are caused by sprites not being spawned.
(sub item) Spawning conditional is correct but the loop to create a new row is flawed.
(sub item) Spawning conditional is flawed.
Could someone help me understand why there are occasionally gaps and help me understand how to fix it? Thank you.
I'm creating a small 2d-minecraft clone, in Phaser js, on my own as a learning experience. So far I have gotten player movement and random level seeds to work ok.
I am using Phasers P2JS engine and have sprites that are box based. What I'm struggling with now Is I want the player to be able to walk unhindered up small elevations, (1-tile high) but I don't have any good idea of how I should Implement this.
I have tried changing the bounding box of the player so that it had a slope at the bottom but this gets me in to a bunch of trouble with wall climbing. I want a way to do this where it gets as seamless as possible. Preferably the player speed is not altered much by climbing the steps.
I am concidering writing some kind of collision detection function to handle this but I am uncertain if this is the best way to do it.
Thanks for your help.
Below is my code and an image that shows the kind of step I want to beable to walk up. Its the first elevation to the left in the image.
var pablo = require('../generators/pablo.js');
var destiny = {};
var socket;
var player;
var jumpButton;
var levelCollisionGroup;
var playerCollisionGroup;
destiny.create = function () {
console.info("game loaded");
// World
this.game.world.setBounds(0, 0, 4000, 1000);
this.game.physics.startSystem(Phaser.Physics.P2JS);
this.game.physics.p2.gravity.y = 600;
this.game.physics.p2.applySpringForces= false;
this.game.physics.p2.applyDamping= false;
this.game.physics.p2.restitution = 0;
this.game.physics.p2.friction = 0.01;
// Player
playerCollisionGroup = this.game.physics.p2.createCollisionGroup();
player = this.game.add.sprite(this.game.world.centerX, 800, 'player');
this.game.physics.p2.enable(player,true);
player.body.fixedRotation = true;
player.body.setCollisionGroup(playerCollisionGroup);
player.body.mass = 2;
// Camera
this.game.camera.follow(player);
this.game.camera.deadzone = new Phaser.Rectangle(200, 0, 400, 100);
// Controls
jumpButton = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
leftButton = this.game.input.keyboard.addKey(Phaser.Keyboard.A);
rightButton = this.game.input.keyboard.addKey(Phaser.Keyboard.D);
// Level
levelCollisionGroup = this.game.physics.p2.createCollisionGroup();
this.game.physics.p2.updateBoundsCollisionGroup();
for (i = 0; i < 280; i = i + 1) {
var block;
var height = pablo.getHeight(i);
for(j = 0; j < height; j = j + 1){
if(j === height-1){
block = this.game.add.sprite(15*i, 993-15*j, 'grass');
} else {
block = this.game.add.sprite(15*i, 993-15*j, 'dirt');
}
block.width = 15;
block.height = 15;
this.game.physics.p2.enable(block);
block.body.static=true;
block.body.immovable = true;
block.body.collides([levelCollisionGroup, playerCollisionGroup]);
block.body.setCollisionGroup(levelCollisionGroup);
if(j == height){
}
}
}
player.body.collides(levelCollisionGroup);
this.game.stage.backgroundColor = "#5599CC";
};
destiny.update = function() {
player.body.velocity.x=0;
if (leftButton.isDown) {
player.body.velocity.x = -200;
} else if (rightButton.isDown) {
player.body.velocity.x = 200;
}
if (jumpButton.isDown && this.checkIfCanJump()) {
player.body.velocity.y = -400;
}
};
destiny.render = function() {
this.game.debug.cameraInfo(this.game.camera, 32, 32);
this.game.debug.spriteCoords(player, 32, 550);
};
destiny.checkIfCanJump = function() {
var result = false;
for (var i=0; i < this.game.physics.p2.world.narrowphase.contactEquations.length; i++) {
var c = this.game.physics.p2.world.narrowphase.contactEquations[i];
if (c.bodyA === player.body.data || c.bodyB === player.body.data) {
var d = p2.vec2.dot(c.normalA, p2.vec2.fromValues(0, 1));
if (c.bodyA === player.body.data) {
d *= -1;
}
if (d > 0.5) {
result = true;
}
}
}
return result;
};
module.exports = destiny;
===================== Edit =====================
I have now tried creating slopes of the edge pieces when generating the world. But I realized that this makes me have to regenerate the world when I later add the feature for hacking away blocks. Thus this is not the solution. I think I will need to do some collision detection and move the player up when I hit an edge. But I'm not quite sure how to do this in phaser. Any help is still appreciated.
!!! Here is an image of what not to do !!!
Emanuele Feronato has a post on replicating the game Magick in Phaser.
There he covers the case of a block colliding with a barrier/wall, with the ability of the block to climb one level up.
You can check the tutorial, but what he appears to be doing is checking to see if the diagonal tile is empty (in other words, is it just a 'step' up), and if it is, running a 'jump' function, which looks more like a climb.
Depending upon how you want your character to step, you could potentially look at both the next tile (on the x-axis) as well as the one after it to check for the height.
So for example, if moving right and the next tile is flat, but the second tile has a step, you might start moving your character up on the y-axis.
I'm trying to use webworkers to render parts of the frames for an animated mandelbrot zoomer, since there is a lot of calculating involved, and since this can be easily split up in blocks this should be an ideal situation for parallel processing.
But no matter what I try I do not get any performance in return for the extra cpu the workers use. Compared to a non worker version, in Chrome my benchmark is somewhat slower, in Firefox it is much slower.
My guess is that transferring the image data to the webworkers is incredibly expensive, I tried just receiving raw data and using that to render frames but the result is much the same. I don't think this is the ideal way to send and receive imagedata to the workers (in fact I only need to receive it, but I have not been able to create a buffer inside the workers that can be used for the canvas directly). So at it stands sending any serious amount of data creates a real bottleneck.
Dear stackoverflow, please help me answer these two questions: What am I doing wrong here, and what can be improved?
A demo can be found here for workers, and for reference a non worker version on jsfiddle.
Code is as follows:
"use strict";
/*global $*/
$(function() {
var mandelbrot = new Mandelbrot();
});
var Mandelbrot = function() {
// set some values
this.width = 500;
this.height = 500;
this.x_center = -1.407566731001088;
this.y_center = 2.741525895538953e-10;
this.iterations = 250;
this.escape = 4,
this.zoom = 10;
this.count = 0;
this.worker_size = 10;
this.received = 0;
this.refresh = true;
//let's go - create canvas, image data and workers
this.init();
//start animation loop
this.animate();
};
Mandelbrot.prototype = {
init: function() {
var self = this;
//create main canvas and append it to div
var container = $("#content");
this.canvas = document.createElement("canvas");
this.canvas.width = this.width;
this.canvas.height = this.height;
container.append(this.canvas);
//create imagedata
this.context = this.canvas.getContext("2d");
this.image = this.context.getImageData(0, 0, this.width, this.height);
this.data = new Int32Array(this.image.data.buffer);
//create imagedata for webworkers
this.worker_data = this.context.getImageData(0, 0, this.width, this.height / this.worker_size);
//create webworkers drop them in array
this.pool = [];
for (var i = 0; i < this.worker_size; i++) {
this.pool[i] = new Worker("js/worker.js");
this.pool[i].idle = true;
this.pool[i].id = i;
//on webworker finished
this.pool[i].onmessage = function(e) {
self.context.putImageData(e.data, 0, self.height / self.worker_size * e.target.id);
self.received++;
};
}
},
iterate: function() {
for (var i = 0; i < this.pool.length; i++) {
this.pool[i].postMessage({
image: this.worker_data,
id: this.pool[i].id,
worker_size: this.worker_size,
width: this.width,
height: this.height,
x_center: this.x_center,
y_center: this.y_center,
iterations: this.iterations,
escape: this.escape,
zoom: this.zoom
});
}
},
animate: function() {
requestAnimationFrame(this.animate.bind(this));
//poor man's benchmark over 250 frames
if (this.count === 0) {
console.time("timer");
}
if (this.count === 250) {
console.timeEnd("timer");
}
//refresh at init, then refresh when all webworkers are done and reset
if (this.received === this.worker_size | this.refresh) {
this.received = 0;
this.refresh = false;
this.count++;
this.zoom *= 0.95;
this.iterate();
}
}
};
and worker.js:
self.onmessage = function(e) {
"use strict";
var x_step = e.data.zoom / e.data.width;
var y_step = e.data.zoom / e.data.height;
var y_start = e.data.height / e.data.worker_size * e.data.id;
var y_end = e.data.height / e.data.worker_size;
var data = new Int32Array(e.data.image.data.buffer);
for (var y = 0; y < y_end; y++) {
var iy = e.data.y_center - e.data.zoom / 2 + (y + y_start) * y_step;
for (var x = 0; x < e.data.width; x++) {
var rx = e.data.x_center - e.data.zoom / 2 + x * x_step;
var zx = rx;
var zy = iy;
var zx2 = 0;
var zy2 = 0;
for (var i = 0; zx2 + zy2 < e.data.escape && i < e.data.iterations; ++i) {
zx2 = zx * zx;
zy2 = zy * zy;
zy = (zx + zx) * zy + iy;
zx = zx2 - zy2 + rx;
}
data[y * e.data.width + x] = (255 << 24) | (i << 16) | (i << 8) | i;
}
}
self.postMessage(e.data.image);
};
The problem is that you are iterating over every pixel in the parent picture. If you restrict the iteration to the smaller of the two images, things will be much faster. Also, if you tile the drawing, each tile could be handled in a separate web worker, thus increasing the palletization of each section of the image. I wrote this: http://robertleeplummerjr.github.io/CanvasWorker/ which does exactly what you want.
I actually tried the same thing on this experiment, this is a displacement filter:
http://www.soundstep.com/blog/experiments/displacement-js/heart/
http://www.soundstep.com/blog/2012/04/25/javascript-displacement-mapping/
I created a worker in the filter and I compute the pixel together before posting them back to the main app. Basically iterating on all the pixels inside a worker.
Before the worker, I have in a loop 4 getImageData, this can't be done in the worker. It takes around 15% CPU on chrome no matter what.
So, overall I get 70% CPU without the worker, and I get 90% CPU with the worker.
I suppose the actions that cannot be done in the worker, such as "getImageData" AND "putImageData", plus the fact of having the worker itself, takes more CPU than not having a worker.
It would probably be better if we were able to send other types of data so we could do the getImageData and putImageData inside the worker.
Not sure there's another way sending and receiving bytes to treat and reconstruct the canvas content.
http://typedarray.org/concurrency-in-javascript/
I have an interactive application mockup made with PaperJS but it still lacks a small feature. I need to draw a 2D grid (you know... that uniform mesh of lines that repeat endlessly over a surface), it will be used as guides for user interactions when dragging things over the screen (but the grid itself can be completely static).
I just don't know how to implement it in PaperJS. It can't be just a background image since it will be presented in different scales, also I wanted it to be rendered very fast since it will always be visible.
The type of grid I would like to draw is a 2D mesh centered grid, like in the example (a) of this picture:
Any enlightenment is welcome.
If all you want is lines:
var drawGridLines = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i <= num_rectangles_wide; i++) {
var xPos = boundingRect.left + i * width_per_rectangle;
var topPoint = new paper.Point(xPos, boundingRect.top);
var bottomPoint = new paper.Point(xPos, boundingRect.bottom);
var aLine = new paper.Path.Line(topPoint, bottomPoint);
aLine.strokeColor = 'black';
}
for (var i = 0; i <= num_rectangles_tall; i++) {
var yPos = boundingRect.top + i * height_per_rectangle;
var leftPoint = new paper.Point(boundingRect.left, yPos);
var rightPoint = new paper.Point(boundingRect.right, yPos);
var aLine = new paper.Path.Line(leftPoint, rightPoint);
aLine.strokeColor = 'black';
}
}
drawGridLines(4, 4, paper.view.bounds);
If you want each rectangle to be a separate Path to hitTest for the individual rectangles:
var drawGridRects = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i < num_rectangles_wide; i++) {
for (var j = 0; j < num_rectangles_tall; j++) {
var aRect = new paper.Path.Rectangle(boundingRect.left + i * width_per_rectangle, boundingRect.top + j * height_per_rectangle, width_per_rectangle, height_per_rectangle);
aRect.strokeColor = 'white';
aRect.fillColor = 'black';
}
}
}
drawGridRects(4, 4, paper.view.bounds);