Cluster array for X and Y axis - javascript

I have a array that looks like this
tiles = [{
x: 0,
y: 0,
land: false //true = land false = water
}];
The goal is to auto generate a planet on a canvas, but i want to cluster the land and sea masses (by a random amount). I have achieved to make it look kinda good but i want bigger land and sea masses and not any "holes" single tiles with either land/water.
I tried some if statements with a % of being water or land but i cant get it to cluster more then what i have now. I made a smaller one and that turned out good and i want the one in the snippet to cluster something like in the image and link. Codepen for the img example
tldr: I want the array to be clustered by the bool land
How can i proceed with this?
var tileNum = 0;
var tiles;
var colorsLand;
var colorsWater;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var tileSize = 1;
canvas.width = canvas.height = 730;
// 'size' is grid size, not the actual pixel size painted on screen
var size = Math.ceil(canvas.width / tileSize);
function generatePlanet() {
tileNum = 0;
tiles = [{
x: 0,
y: 0,
land: false
}];
//Retrive colors
colorsLand = interpolateColors(getColor(true), getColor(true), 6000);
colorsWater = interpolateColors(getColor(false), getColor(false), 6000);
//Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
for (var i = 0; i < (size * size); i++) {
var currentTile = tiles[tiles.length - 1];
if (currentTile.x <= (size - 1)) {
var isLand = false;
if (tiles.length >= (size * 2) && (
tiles[tiles.length - 1].land ||
tiles[tiles.length - 2].land ||
tiles[tiles.length - (size)].land ||
tiles[tiles.length - (size * 2)].land ||
tiles[tiles.length - (size - 1)].land ||
tiles[tiles.length - (size - 2)].land
)) {
isLand = (Math.floor(Math.random() * 100) + 1) > 66;
} else if (tiles.length >= (size * 2) && (!tiles[tiles.length - 1].land ||
!tiles[tiles.length - 2].land ||
!tiles[tiles.length - (size)].land ||
!tiles[tiles.length - (size * 2)].land ||
!tiles[tiles.length - (size - 1)].land ||
!tiles[tiles.length - (size - 2)].land
)) {
isLand = (Math.floor(Math.random() * 1000) + 1) > 999;
}
tiles.push({
x: currentTile.x + 1,
y: currentTile.y,
land: isLand
});
} else {
tiles.push({
x: 1,
y: currentTile.y + 1,
land: isLand
});
}
}
drawPlanet()
}
//retrive a random color if it's a land tile i want it dark water i want light
function getColor(land) {
while (true) {
var r = Math.floor(Math.random() * 256) + 1
var g = Math.floor(Math.random() * 256) + 1
var b = Math.floor(Math.random() * 256) + 1
var hsp = Math.sqrt(
0.299 * (r * r) +
0.587 * (g * g) +
0.114 * (b * b)
);
//light color
if (hsp > 127.5 && land == false) {
return [r, g, b];
}
//dark color
else if (hsp < 127.5 && land == true) {
return [r, g, b];
}
}
}
//these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
function interpolateColors(color1, color2, steps) {
var stepFactor = 1 / (steps - 1),
interpolatedColorArray = [];
for (var i = 0; i < steps; i++) {
interpolatedColorArray.push(toUint32AARRGGBB(interpolateColor(color1, color2, stepFactor * i)));
}
return interpolatedColorArray;
}
function toUint32AARRGGBB(arr) {
return Number('0xFF' + arr.map(toHexString2).join(''))
}
function toHexString2(val) {
return val.toString(16)
.padStart(2, '0'); // padStart may need a polyfill
}
function interpolateColor(color1, color2, factor) {
if (arguments.length < 3) {
factor = 0.5;
}
var result = color1.slice();
for (var i = 0; i < 3; i++) {
result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
}
return result;
};
//retrives a random color for land
function rndLandColor() {
return colorsLand[Math.floor(Math.random() * 5999) + 1];
}
//retrives a random color for water
function rndWaterColor() {
return colorsWater[Math.floor(Math.random() * 5999) + 1];
}
// now drawing synchronously:
function drawPlanet() {
var gridsize = size;
// var rad = gridsize / 2;
// generate an ImageData, the size of our pixel grid
var imgData = new ImageData(gridsize, gridsize);
// work directly on Uint32 values (0xAARRGGBB on LittleEndian)
var data = new Uint32Array(imgData.data.buffer);
var score, y, x;
for (y = 0; y < gridsize; y++) {
for (x = 0; x < gridsize; x++) {
score = 0;
//fill in holes in the land that is bigger then 1
if (tiles[tileNum - (gridsize + 1)] !== undefined && tiles[tileNum + (size + 1)] !== undefined) {
if (tiles[tileNum].land == false) {
score++;
}
if (tiles[tileNum - 1].land == true) {
score++;
}
if (tiles[tileNum + 1].land == true) {
score++;
}
if (tiles[tileNum + (gridsize + 1)].land == true) {
score++;
}
if (tiles[tileNum - (gridsize + 1)].land == true) {
score++;
}
}
if (score >= 2) {
color = rndLandColor();
}
//cover single land tiles with water (if water tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (gridsize + 1)] !== undefined &&
tiles[tileNum + (gridsize + 1)] !== undefined &&
tiles[tileNum - 1].land == false &&
tiles[tileNum + 1].land == false &&
tiles[tileNum - (gridsize + 1)].land == false &&
tiles[tileNum + (gridsize + 1)].land == false) {
color = rndWaterColor();
}
//cover single water tiles with land (if land tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (gridsize + 1)] !== undefined &&
tiles[tileNum + (gridsize + 1)] !== undefined &&
tiles[tileNum - 1].land == true &&
tiles[tileNum + 1].land == true &&
tiles[tileNum - (gridsize + 1)].land == true &&
tiles[tileNum + (gridsize + 1)].land == true) {
color = rndLandColor();
}
//cover tile with land
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
color = rndLandColor();
}
//cover tile with water
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
color = rndWaterColor();
}
tileNum++;
data[(y * gridsize) + x] = color;
}
}
// all done populating the ImageData
// put it on the context at scale(1,1)
ctx.putImageData(imgData, 0, 0);
// remove antialiasing
ctx.imageSmoothingEnabled = false;
// up-scale
ctx.scale(tileSize, tileSize);
// draw the canvas over itself
ctx.drawImage(ctx.canvas, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
generatePlanet();
#canvas {
border: 10px solid #000000;
border-radius: 50%;
/* background-color: aquamarine; */
}
.container {
width: 720px;
height: 720px;
position: relative;
}
.gradient {
position: absolute;
height: 745px;
width: 745px;
top: 0;
left: 0;
border-radius: 50%;
opacity: 0.8;
}
<button onclick="generatePlanet()">GENERATE</button>
<div class="container">
<img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
<canvas id="canvas" width="710" height="710"></canvas>
</div>

Related

why is contact detection code not working in JS

I have a game that uses contact detection in html canvas, but none of the code works until the character passes that blocks x-position.
Example: If character x-pos is 500, and block x-pos is 600, character can go through block. But once character x-pos is greater than 600, it can't go through the block until the game restarts.
Second Example: If there are 2 blocks, if the character is past the first block but not the second, it can still go through the second block.
I don't know what the problem is, so could you help me?
Code:
html code:
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100vh;
width: 100vw;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas></canvas>
</body>
<script src='SideScrollerInitilize.js'></script>
<script src='SideScrollerOther.js'></script>
<script src='SideScrollerClasses.js'></script>
<script src='SideScrollerKeyDectection.js'></script>
<script src='SideScrollerContact.js'></script>
<script src='SideScrollerUpdate.js'></script>
<script src='SideScrollerMain.js'></script>
</html>
Contact Detection(JS) code:
function ContactDetection() {
platforms.forEach((platform)=>{
if (player.position.y + player.height <= platform.position.y && player.position.y + player.height + player.velocity.y >= platform.position.y && player.position.x + player.width >= platform.position.x && player.position.x <= platform.position.x + platform.width) {
player.velocity.y = 0
player.touchingGround = true
}
if (player.position.y >= platform.position.y + platform.height && player.position.y + player.velocity.y < platform.position.y + platform.height && player.position.x + player.width >= platform.position.x && player.position.x <= platform.position.x + platform.width) {
player.position.y += player.velocity.y
player.velocity.y = 0
}
})
platforms.forEach((platform)=>{
if (player.position.y + player.height > platform.position.y && player.position.y < platform.position.y + platform.height && player.position.x + player.width <= platform.position.x && player.position.x + player.width + player.velocity.x > platform.position.x) {
player.velocity.x = 0
player.moveRight = false
} else {
player.moveRight = true
}
})
platforms.forEach((platform)=>{
if (player.position.y + player.height > platform.position.y && player.position.y < platform.position.y + platform.height && player.position.x >= platform.position.x + platform.width && player.position.x + player.velocity.x < platform.position.x + platform.width) {
player.velocity.x = 0
player.moveLeft = false
} else {
player.moveLeft = true
}
})
if (distance >= 2000) {
console.log('you win')
}
if (player.position.y > canvas.height) {
init()
}
}
update code:
const canvas = document.querySelector('canvas')
const c = canvas.getContext('2d')
const gravity = 0.5
let distance = 0
let playerSpeed = 5
let player
let genericObjects = []
let platforms = []
canvas.height = 500
canvas.width = 700
function CombinedUpdate() {
update()
ContactDetection()
}
init()
setInterval(CombinedUpdate, 16)
Initilization code:
function init() {
distance = 0
genericObjects = [new GenericObject(0, 0, canvas.height*(7/8), createImage('mountain.png'))]
platforms = [new Platform(0, 363, 800, 40), new Platform(880, 250, 200, 200), new Platform(1200, 210, 100, 209), new Platform(1500, 393, 200, 40), new Platform(1900, 273, 400, 20)]
player = new Player('stickman-removebg.png', 20)
}
Key detection code:
addEventListener('keydown', (event) => {
let key = event.key
switch (key) {
case 'ArrowRight':
case 'd':
case 'D':
player.keys.right = true
break
case 'ArrowLeft':
case 'a':
case 'A':
player.keys.left = true
break
case 'ArrowUp':
case 'w':
case 'W':
player.keys.jump = true
break
}
})
addEventListener('keyup', (event) => {
let key = event.key
switch (key) {
case 'ArrowRight':
case 'd':
case 'D':
player.keys.right = false
break
case 'ArrowLeft':
case 'a':
case 'A':
player.keys.left = false
break
case 'ArrowUp':
case 'w':
case 'W':
player.keys.jump = false
break
}
})
more update code:
function update() {
c.clearRect(0, 0, canvas.width, canvas.height)
c.fillStyle = 'skyBlue'
c.fillRect(0, 0, canvas.width, canvas.height)
genericObjects.forEach((object) => {
object.draw()
})
platforms.forEach((platform)=>{
platform.draw()
})
player.update()
}
object template code:
class Player {
constructor(image, width) {
this.position = {x: 100, y:100}
this.velocity = {
x: 0,
y: 0
}
this.width = width
this.moveRight = true
this.moveLeft = true
this.height = width*2
this.image = createImage(image)
this.touchingGround = false
this.keys = {
jump: false,
left: false,
right: false
}
}
keyMove() {
if (this.keys.left == true && this.moveLeft == true) {
player.velocity.x = -playerSpeed
distance -= playerSpeed
} else if (this.keys.left == false && player.velocity.x < 0) {
player.velocity.x = 0
} else if (this.keys.right == true && this.moveRight == true) {
player.velocity.x = playerSpeed
distance += playerSpeed
} else if (this.keys.right == false && player.velocity.x > 0) {
player.velocity.x = 0
}
if (this.position.x < 0) {
this.velocity.x = 0
this.position.x = 0
}
if (this.keys.jump == true && this.touchingGround == true) {
player.velocity.y -= 12
this.keys.jump == false
}
}
draw() {
c.drawImage(this.image, this.position.x, this.position.y, this.width, this.height)
}
update() {
this.position.y += this.velocity.y
this.position.x += this.velocity.x
this.keyMove()
if (this.keys.right == true && player.position.x > 400 && distance <= 2000 && this.moveRight == true) {
this.velocity.x = 0
genericObjects.forEach((object)=>{
object.position.x -= playerSpeed * 0.66
})
platforms.forEach((platform)=>{
platform.position.x -= playerSpeed
})
}
if (this.keys.left == true && player.position.x < 200 && distance > 100 && this.moveLeft == true) {
this.velocity.x = 0
platforms.forEach((platform)=>{
platform.position.x += playerSpeed
})
genericObjects.forEach((object)=>{
object.position.x += playerSpeed * 0.66
})
}
this.draw()
if (this.position.y + this.height + this.velocity.y < canvas.height) {
this.velocity.y += gravity
this.touchingGround = false
}
}
}
class Platform {
constructor(x, y, width, height) {
this.position = {
x,
y
}
this.image = createImage('file:///C:/Users/fishm/Desktop/coding/platform.png')
this.width = width
this.height = height
}
draw() {
c.drawImage(this.image, this.position.x, this.position.y, this.width, this.height)
}
}
class GenericObject {
constructor(x, y, width, image) {
this.position = {
x,
y
}
this.image = image
this.width = width
this.height = this.width*(8/7)
}
draw() {
c.drawImage(this.image, this.position.x, this.position.y, this.width, this.height)
}
}
Code to create images:
function createImage(imageSrc) {
let image = new Image()
image.src = imageSrc
return image
}
Also, any suggestion to shorten or make this question better would be helpful too.

Is there a way to reference the opposite corner of a square made with getContext("2d")?

In vanilla javascript, if I made two squares, one square with movement like:
const context = document.getElementById("canvasmulti").getContext("2d");
canvasmulti.width = window.innerWidth;
canvasmulti.height = window.innerHeight;
//CHARACTER:
const square = {
height: 75,
jumping: true,
width: 75,
x: canvasmulti.width - 75,
xVelocity: 0,
y: canvasmulti.height / 2,
yVelocity: 0,
jumpHeight: 30
};
const square2 = {
height: 75,
jumping: true,
width: 75,
x: 500,
xVelocity: 0,
y: canvasmulti.height - 75,
yVelocity: 0,
jumpHeight: 30
};
//MOVEMENT:
const controller = {
left: false,
right: false,
up: false,
keyListener: function (event) {
let key_state = (event.type == "keydown") ? true : false;
switch (event.keyCode) {
case 37: // left arrow
controller.left = key_state;
break;
case 38: // up arrow
controller.up = key_state;
break;
case 39: // right arrow
controller.right = key_state;
break;
}
}
};
const loop = function () {
//controller one
if (controller.up && square.jumping == false) {
square.yVelocity -=square.jumpHeight;
square.jumping = true;}
if (controller.left) {
square.xVelocity -= 0.5;}
if (controller.right) {
square.xVelocity += 0.5;}
//controller one
square.yVelocity += 1.5;// gravity
square.x += square.xVelocity;
square.y += square.yVelocity;
square.xVelocity *= 0.9;// friction
square.yVelocity *= 0.9;// friction
// if square1 is falling below floor line
if (square.y > canvasmulti.height - 75) {
square.jumping = false;
square.y = canvasmulti.height - 75;
square.yVelocity = 0;
}
// Creates and fills square1 for each frame
context.fillStyle = "#8DAA9D"; // hex for cube color
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
context.fill();
//creates and full square2 for each frame
context.fillStyle = "#781818"; // hex for cube color
context.beginPath();
context.rect(square2.x, square2.y, square2.width, square2.height);
context.fill();
if (square.x <= square2.x + square2.width &&
square.x >= square2.x + square2.width - square2.width && square.y >= square2.y &&
square.y <= square2.y + square2.height)
{square.x = square2.x + square2.width; // set it to a position where they don't overlap
square.xVelocity = 0;}; // left square1 touching right square2
if (square.x + square.width >= square2.x && square.x + square.width <= square2.x + square2.width &&
square.y >= square2.y && square.y <= square2.y + square2.height ||
square.x + square.width >= square2.x && square.x + square.width <= square2.x + square2.width &&
square.y + square.height >= square2.y && square.y + square.height >= square2.y + square2.height)
{square.x = square2.x - square.width;
square.xVelocity = 0;}; // right square1 touching left square2
if (square.x <= square2.x + square2.width - 0.1 &&
square.x >= square2.x && square.y + square.height >= square2.y)
{
square.y = square2.y - square.height;
square.yVelocity = 0;}; // bottom left square1 touching top square2
if (square.x + square.width <= square2.x + square2.width &&
square.x + square.width >= square2.x + 0.1 && square.y + square.height >= square2.y)
{square.y = square2.y - square.height;
square.yVelocity = 0;}; // bottom right square1 touching top square2
window.requestAnimationFrame(loop);
};
//square1
window.addEventListener("keydown", controller.keyListener)
window.addEventListener("keyup", controller.keyListener);
window.requestAnimationFrame(loop);
Since the starting co-ordinate of the square is in the top left, if I wanted to make an if statement saying if the opposite corner was past a point (say square2.x), but also over a point(say square2.y), how would I do that?? You can't add or multiply the co-ordinates, because that would just make one of the co-ordinates larger rather than combining them both.

Canvas generate problem with maximum call stack

I am making a script that auto generates planets see codepen for example.
But the problem I have is that i want to make it less pixelated and I am having some problems doing that if i make the tiles 70 * 70 and tile size to 10 * 10 pixels it works fine. But i want to have it set to something like tiles 360 * 360 and size to 1 or 2 pixels. But when I try to do that I get maximum call stack error. So I tried to use the requestAnimationFrame but then it take ages to load is there a way to speed up the process?
var tileNum = 0;
var tiles;
var colorsLand;
var colorsWater;
var size = 360;
var tileSize = 2;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
window.onload = function () {
generatePlanet();
}
function generatePlanet() {
tileNum = 0;
tiles = [{ x: 0, y: 0, land: false }];
//Retrive colors
colorsLand = interpolateColors("rgb(" + getColor(true) + ")", "rgb(" + getColor(true) + ")", 6000);
colorsWater = interpolateColors("rgb(" + getColor(false) + ")", "rgb(" + getColor(false) + ")", 6000);
//Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
for (var i = 0; i < (size * size); i++) {
var currentTile = tiles[tiles.length - 1];
if (currentTile.x <= (size - 1)) {
var isLand = false;
if (currentTile.land == true || tiles.length > size && tiles[tiles.length - size].land == true) {
isLand = (Math.floor(Math.random() * 100) + 1) > 35;
}
else if (currentTile.land == true || tiles.length > size &&
(tiles[tiles.length - 1].land == true ||
tiles[tiles.length - size].land == true)) {
isLand = (Math.floor(Math.random() * 100) + 1) > size;
}
else {
isLand = (Math.floor(Math.random() * 100) + 1) > 99;
}
tiles.push({ x: currentTile.x + 1, y: currentTile.y, land: isLand });
}
else {
tiles.push({ x: 0, y: currentTile.y + 1, land: isLand });
}
}
drawPlanet()
}
//retrive a random color if it's a land tile i want it dark water i want light
function getColor(land) {
while (true) {
var r = Math.floor(Math.random() * 256) + 1
var g = Math.floor(Math.random() * 256) + 1
var b = Math.floor(Math.random() * 256) + 1
var hsp = Math.sqrt(
0.299 * (r * r) +
0.587 * (g * g) +
0.114 * (b * b)
);
//light color
if (hsp > 127.5 && land == false) {
return r + "," + g + "," + b;
}
//dark color
else if (hsp < 127.5 && land == true) {
return r + "," + g + "," + b;
}
}
}
//these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
function interpolateColors(color1, color2, steps) {
var stepFactor = 1 / (steps - 1),
interpolatedColorArray = [];
color1 = color1.match(/\d+/g).map(Number);
color2 = color2.match(/\d+/g).map(Number);
for (var i = 0; i < steps; i++) {
interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
}
return interpolatedColorArray;
}
function interpolateColor(color1, color2, factor) {
if (arguments.length < 3) {
factor = 0.5;
}
var result = color1.slice();
for (var i = 0; i < 3; i++) {
result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
}
return result;
};
//retrives a random color for land
function rndLandColor() {
return 'rgb(' + colorsLand[Math.floor(Math.random() * 5999) + 1] + ')';
}
//retrives a random color for water
function rndWaterColor() {
return 'rgb(' + colorsWater[Math.floor(Math.random() * 5999) + 1] + ')';
}
function drawPlanet() {
var RAF;
var i = 0, j = 0;
function animate() {
ctx.beginPath();
//fill in holes in the land that is bigger then 1
var score = 0;
if (tiles[tileNum - (size + 1)] !== undefined && tiles[tileNum + (size + 1)] !== undefined) {
if (tiles[tileNum].land == false) {
score++;
}
if (tiles[tileNum - 1].land == true) {
score++;
}
if (tiles[tileNum + 1].land == true) {
score++;
}
if (tiles[tileNum + (size + 1)].land == true) {
score++;
}
if (tiles[tileNum - (size + 1)].land == true) {
score++;
}
}
if (score >= 3) {
ctx.fillStyle = rndLandColor();
}
//cover single land tiles with water (if water tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (size + 1)] !== undefined &&
tiles[tileNum + (size + 1)] !== undefined &&
tiles[tileNum - 1].land == false &&
tiles[tileNum + 1].land == false &&
tiles[tileNum - (size + 1)].land == false &&
tiles[tileNum + (size + 1)].land == false) {
ctx.fillStyle = rndWaterColor();
}
//cover single water tiles with land (if land tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (size + 1)] !== undefined &&
tiles[tileNum + (size + 1)] !== undefined &&
tiles[tileNum - 1].land == true &&
tiles[tileNum + 1].land == true &&
tiles[tileNum - (size + 1)].land == true &&
tiles[tileNum + (size + 1)].land == true) {
ctx.fillStyle = rndLandColor();
}
//cover tile with land
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
ctx.fillStyle = rndLandColor();
}
//cover tile with water
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
ctx.fillStyle = rndWaterColor();
}
tileNum++;
ctx.fill();
ctx.closePath();
ctx.fillRect(tileSize * j, tileSize * i, tileSize, tileSize);
j++;
if (j >= (size + 1)) {
i += 1;
j = 0;
if (i >= (size + 1)) {
cancelAnimationFrame(RAF);
}
}
RAF = requestAnimationFrame(function () {
animate();
});
}
animate();
}
#canvas {
border: 10px solid #000000;
border-radius: 50%;
background-color: aquamarine;
}
.container {
width: 720px;
height: 720px;
position: relative;
}
.gradient {
position: absolute;
height: 730px;
width: 730px;
top: 0;
left: 0;
border-radius: 50%;
opacity: 0.8;
}
<div class="container">
<img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
<canvas id="canvas" width="710" height="710"></canvas>
</div>
Do not use canvas drawing methods to perform pixel art.
Filling a path is a relatively slow operation, to draw pixels through fillRect(), is almost never the correct way.
Instead one should prefer manipulating an ImageData object directly, and paint it on the canvas only once.
If you need to set up a scale, then use an unscaled ImageBitmap, put it on your context and then upscale it using drawImage.
Here is an updated version of your script, where I did apply some not-so minor improvements like not generating colors for out-of-screen pixels, along with this ImageData manipulation technique.
It now runs fast enough to be launched synchronously. But if you need to improve it even more, note that your getColor seems rather inneficient, but I didn't touch it.
var tileNum = 0;
var tiles;
var colorsLand;
var colorsWater;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var tileSize = 2;
canvas.width = canvas.height = 710;
// 'size' should be your grid size, not the actual pixel size painted on screen
var size = Math.ceil(canvas.width / tileSize);
function generatePlanet() {
tileNum = 0;
tiles = [{
x: 0,
y: 0,
land: false
}];
//Retrive colors
colorsLand = interpolateColors(getColor(true), getColor(true), 6000);
colorsWater = interpolateColors(getColor(false), getColor(false), 6000);
//Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
for (var i = 0; i < (size * size); i++) {
var currentTile = tiles[tiles.length - 1];
if (currentTile.x <= (size - 1)) {
var isLand = false;
if (currentTile.land == true || tiles.length > size && tiles[tiles.length - size].land == true) {
isLand = (Math.floor(Math.random() * 100) + 1) > 35;
} else if (currentTile.land == true || tiles.length > size &&
(tiles[tiles.length - 1].land == true ||
tiles[tiles.length - size].land == true)) {
isLand = (Math.floor(Math.random() * 100) + 1) > size;
} else {
isLand = (Math.floor(Math.random() * 100) + 1) > 99;
}
tiles.push({
x: currentTile.x + 1,
y: currentTile.y,
land: isLand
});
} else {
tiles.push({
x: 0,
y: currentTile.y + 1,
land: isLand
});
}
}
drawPlanet()
}
//retrive a random color if it's a land tile i want it dark water i want light
function getColor(land) {
while (true) {
var r = Math.floor(Math.random() * 256) + 1
var g = Math.floor(Math.random() * 256) + 1
var b = Math.floor(Math.random() * 256) + 1
var hsp = Math.sqrt(
0.299 * (r * r) +
0.587 * (g * g) +
0.114 * (b * b)
);
//light color
if (hsp > 127.5 && land == false) {
return [r,g,b];
}
//dark color
else if (hsp < 127.5 && land == true) {
return [r,g,b];
}
}
}
//these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
function interpolateColors(color1, color2, steps) {
var stepFactor = 1 / (steps - 1),
interpolatedColorArray = [];
for (var i = 0; i < steps; i++) {
interpolatedColorArray.push(toUint32AARRGGBB(interpolateColor(color1, color2, stepFactor * i)));
}
return interpolatedColorArray;
}
function toUint32AARRGGBB(arr) {
return Number('0xFF' + arr.map(toHexString2).join(''))
}
function toHexString2(val) {
return val.toString(16)
.padStart(2, '0'); // padStart may need a polyfill
}
function interpolateColor(color1, color2, factor) {
if (arguments.length < 3) {
factor = 0.5;
}
var result = color1.slice();
for (var i = 0; i < 3; i++) {
result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
}
return result;
};
//retrives a random color for land
function rndLandColor() {
return colorsLand[Math.floor(Math.random() * 5999) + 1];
}
//retrives a random color for water
function rndWaterColor() {
return colorsWater[Math.floor(Math.random() * 5999) + 1];
}
// now drawing synchronously:
function drawPlanet() {
var gridsize = size;
var rad = gridsize / 2;
// generate an ImageData, the size of our pixel grid
var imgData = new ImageData(gridsize, gridsize);
// work directly on Uint32 values (0xAARRGGBB on LittleEndian)
var data = new Uint32Array(imgData.data.buffer);
var score, y, x;
for (y = 0; y < gridsize; y++) {
for (x = 0; x < gridsize; x++) {
score = 0;
// if we are outside of the inner area
if (Math.hypot(rad - x, rad - y) > rad + 2) {
tileNum++;
continue;
}
//fill in holes in the land that is bigger then 1
if (tiles[tileNum - (gridsize + 1)] !== undefined && tiles[tileNum + (size + 1)] !== undefined) {
if (tiles[tileNum].land == false) {
score++;
}
if (tiles[tileNum - 1].land == true) {
score++;
}
if (tiles[tileNum + 1].land == true) {
score++;
}
if (tiles[tileNum + (gridsize + 1)].land == true) {
score++;
}
if (tiles[tileNum - (gridsize + 1)].land == true) {
score++;
}
}
if (score >= 3) {
color = rndLandColor();
}
//cover single land tiles with water (if water tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (gridsize + 1)] !== undefined &&
tiles[tileNum + (gridsize + 1)] !== undefined &&
tiles[tileNum - 1].land == false &&
tiles[tileNum + 1].land == false &&
tiles[tileNum - (gridsize + 1)].land == false &&
tiles[tileNum + (gridsize + 1)].land == false) {
color = rndWaterColor();
}
//cover single water tiles with land (if land tile is up,down,left and right of this tile)
else if (
tiles[tileNum - (gridsize + 1)] !== undefined &&
tiles[tileNum + (gridsize + 1)] !== undefined &&
tiles[tileNum - 1].land == true &&
tiles[tileNum + 1].land == true &&
tiles[tileNum - (gridsize + 1)].land == true &&
tiles[tileNum + (gridsize + 1)].land == true) {
color = rndLandColor();
}
//cover tile with land
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
color = rndLandColor();
}
//cover tile with water
else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
color = rndWaterColor();
}
tileNum++;
data[(y * gridsize) + x] = color;
}
}
// all done populating the ImageData
// put it on the context at scale(1,1)
ctx.putImageData(imgData, 0, 0);
// remove antialiasing
ctx.imageSmoothingEnabled = false;
// up-scale
ctx.scale(tileSize, tileSize);
// draw the canvas over itself
ctx.drawImage(ctx.canvas, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
generatePlanet();
#canvas {
border: 10px solid #000000;
border-radius: 50%;
background-color: aquamarine;
}
.container {
width: 720px;
height: 720px;
position: relative;
}
.gradient {
position: absolute;
height: 730px;
width: 730px;
top: 0;
left: 0;
border-radius: 50%;
opacity: 0.8;
}
<div class="container">
<img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
<canvas id="canvas" width="710" height="710"></canvas>
</div>
Now, if I were in your position, I think I would even start looking somewhere else completely. For what you want to do, it seems that some noise generator could be more efficient with a more realistic output.
There is one such noise generator available in SVG filters, and hence accessible to Canvas2D API, however I have to admit that controlling it is not that easy.
But if you wish to take a look at it, here is a rough playground:
const controls = new Set();
function randColor() {
return '#' + (Math.floor((Math.random()*0xFFFFFF)))
.toString(16)
.padStart(6, 0);
}
function makeInput(type, options) {
return Object.assign(document.createElement('input'), {type}, options);
}
class Control {
constructor() {
this.color = makeInput('color', {value: randColor()});
this.freq = makeInput('range', {min: 0.0001, max:1, step: 0.0001, value: Math.random() / 20});
this.numOctaves = makeInput('range', {min: 1, max:10, step: 1, value: 7});
this.opacity = makeInput('range', {min:0.01, max:1, step: 0.001, value:1});
this.seed = Math.random() * 1000;
const remover = document.createElement('span');
remover.textContent = 'x';
remover.classList.add('remover');
const container = document.createElement('div');
container.classList.add('control');
container.append(
"color: ", this.color,
"baseFrequency: ", this.freq,
"numOctaves: ", this.numOctaves,
"opacity", this.opacity,
remover
);
document.querySelector('.controls').append(container);
remover.onclick = e => {
container.remove();
controls.delete(this);
draw();
};
this.color.oninput = this.freq.oninput = this.numOctaves.oninput = this.opacity.oninput = draw;
}
}
for(let i=0; i<3; i++) {
controls.add(new Control());
}
const main = c.getContext('2d');
const ctx = c.cloneNode().getContext('2d');
main.arc(c.width/2, c.height/2, Math.min(c.width, c.height)/2,0,Math.PI*2);
draw();
add_control.onclick = e => {
controls.add(new Control());
draw();
}
function draw() {
main.globalCompositeOperation = 'source-over';
main.clearRect(0,0,c.width,c.height);
controls.forEach(control => {
ctx.globalCompositeOperation = 'source-over';
ctx.filter = "none";
ctx.clearRect(0,0,c.width,c.height);
// update <filter>
turb.setAttribute('seed', control.seed);
turb.setAttribute('baseFrequency', control.freq.value);
turb.setAttribute('numOctaves', control.numOctaves.value);
// draw black and transp
ctx.filter = "url(#myFilter)"
ctx.fillRect(0,0,c.width, c.width);
// do the composition with solid color
ctx.filter = "none"
ctx.fillStyle = control.color.value;
ctx.globalCompositeOperation = 'source-in'
ctx.fillRect(0,0,c.width, c.width);
main.globalAlpha = control.opacity.value;
// draw on visible context
main.drawImage(ctx.canvas, 0,0)
main.globalAlpha = 1;
});
// cut-out as a circle
main.globalCompositeOperation = 'destination-in';
main.fill()
}
.control {
display: inline-block;
border: 1px solid;
padding: 6px;
position: relative
}
.control input {
display: block;
}
.control span {
position: absolute;
top: 6px;
right: 6px;
cursor: pointer;
}
#canvas {
border: 10px solid #000000;
border-radius: 50%;
background-color: aquamarine;
}
.container {
width: 360px;
height: 360px;
position: relative;
}
.gradient {
position: absolute;
height: 360px;
width: 360px;
top: 0;
left: 0;
border-radius: 50%;
opacity: 0.8;
}
<div class="controls">
<button id="add_control">add new layer</button><br>
</div>
<div class="container">
<canvas id="c" width="360" height="360"></canvas>
<svg>
<filter id="myFilter">
<feTurbulence type="fractalNoise" baseFrequency="0.045"
id="turb" result="turb"/>
<feComponentTransfer in="turb" result="contrast">
<feFuncR type="linear" slope="1.6" intercept="-0.15"/>
<feFuncG type="linear" slope="1.6" intercept="-0.15"/>
<feFuncB type="linear" slope="1.6" intercept="-0.15"/>
</feComponentTransfer>
<feColorMatrix in="contrast"
type="luminanceToAlpha" result="alpha"/>
</filter>
</svg>
<img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
</div>

Having trouble blocking player's path in Javascript

I'm having trouble blocking the player's path they already went through. I have the player leave a trail of slime that there are not suppose to go through. I'm using sublime text
// Put your global variables after this line
var GRIDWIDTH, GRIDHEIGHT;
GRIDWIDTH = 11;
GRIDHEIGHT = 13;
var player = new Object();
player.x = 0;
player.y = 0;
var reset = 0x0052;
var GAME_WON = false;
var foodValue = 1;
var PlayerInventory = {
key: 0,
food: 0,
foodLeft: 0,
}
// Put your function definitions after this line
function drawPlayer(x, y) {
PS.color(x, y, PS.COLOR_GREEN);
PS.glyphColor(x, y, PS.COLOR_WHITE);
PS.glyph(x, y, "ඬ");
player.x = x;
player.y = y;
}
function drawSlime(x, y, dir) {
PS.color(x, y, PS.COLOR_GREEN);
PS.data(x, y, dir);
PS.data(x, y, "wall")
}
function PlaceKey(x, y) {
PS.color(x, y, PS.COLOR_YELLOW);
PS.glyphColor(x, y, PS.COLOR_ORANGE);
PS.glyph(x, y, "K")
PS.data(x, y, "key");
}
function removePlayer(x, y) {
PS.glyph(x, y, 0);
}
function isInGrid(x, y) {
if (x < 0) return false;
else if (x >= GRIDWIDTH) return false;
else if (y < 0) return false;
else if (y >= GRIDHEIGHT) return false;
else return true;
}
function CreateButtons() {
PS.glyph(5, 12, reset);
PS.data(5, 12, "resetbutton");
}
function DrawInventory() {
PS.statusText(" key: " + PlayerInventory.key + "food: " + PlayerInventory.food);
}
function PlaceFood(x, y) {
PS.glyph(x, y, 0x25CE);
PS.glyphColor(x, y, 0xBA9A19);
PS.data(x, y, "food");
PlayerInventory.foodLeft += foodValue;
}
// PS.keyDown ( key, shift, ctrl, options )
// Called when a key on the keyboard is pressed
PS.keyDown = function(key, shift, ctrl, options) {
"use strict";
// Uncomment the following line to inspect parameters
// PS.debug( "PS.keyDown(): key = " + key + ", shift = " + shift + ", ctrl = " + ctrl + "\n" );
// Add code here for when a key is pressed
if (GAME_WON) {
GameSetup();
} else {
var oldX = player.x;
var oldY = player.y;
// Moves the player in the game with the key arrow
if (key == PS.KEY_ARROW_RIGHT && (player.x < (GRIDWIDTH - 1)))
player.x += 1;
else if (key == PS.KEY_ARROW_LEFT && (player.x > 0))
player.x -= 1;
else if (key == PS.KEY_ARROW_UP && (player.y > 0))
player.y -= 1;
else if (key == PS.KEY_ARROW_DOWN && (player.y < (GRIDHEIGHT - 1)))
player.y += 1;
var dataAtPlayer = PS.data(player.x, player.y);
if (dataAtPlayer == "wall") {
player.x = oldX;
player.y = oldY;
}
if (dataAtPlayer == "drawSlime") {
player.x = oldX;
player.y = oldY;
}
if (dataAtPlayer == "leaf") {
if (PlayerInventory.key > -0) {
PS.audioPlay("fx_blast4");
DrawInventory();
} else {
player.x = oldX;
player.y = oldY;
}
}
if (dataAtPlayer == "key") {
PlayerInventory.key += 1;
DrawInventory();
PS.audioPlay("fx_pop");
}
if (dataAtPlayer == "food") {
PlayerInventory.foodLeft -= foodValue;
PlayerInventory.foods += foodValue;
DrawInventory();
PS.audioPlay("fx_tada");
if (PlayerInventory.foodLeft == 0) {
GAME_WON = true;
PS.statusText("Click R to restar!");
}
}
drawPlayer(player.x, player.y);
}
};
I tried to fix this issue by changing the code to this:
// WASD keys to move The Snail
if (key == 119) {
//Check that up isn’t a wall
//Check that up isn’t off the screen
if (player.y - 1 >= 0) {
if (PS.data(player.x, player.y - 1) != "wall") {
//If both are true, remove player from current position
//If both are true, draw player in new position
removePlayer(player.x, player.y);
drawPlayer(player.x, player.y - 1);
drawSlime(player.x, player.y);
}
}
}
if (key == 115) {
//Check that down isn’t a wall
//Check that down isn’t off the screen
if (player.y + 1 < GRIDHEIGHT) {
if (PS.data(player.x, player.y + 1) != "wall") {
//If both are true, remove player from current position
//If both are true, draw player in new position
removePlayer(player.x, player.y);
drawPlayer(player.x, player.y + 1);
drawSlime(player.x, player.y);
}
}
}
if (key == 97) {
//Check that left isn’t a wall
//Check that left isn’t off the screen
if (player.x - 1 >= 0) {
if (PS.data(player.x - 1, player.y) != "wall") {
//If both are true, remove player from current position
//If both are true, draw player in new position
removePlayer(player.x, player.y);
drawPlayer(player.x - 1, player.y);
drawSlime(player.x, player.y);
}
}
}
if (key == 100) {
//Check that right isn’t a wall
//Check that right isn’t off the screen
if (player.x + 1 < GRIDWIDTH) {
if (PS.data(player.x + 1, player.y) != "wall") {
//If both are true, remove player from current position
//If both are true, draw player in new position
removePlayer(player.x, player.y);
drawPlayer(player.x + 1, player.y);
drawSlime(player.x, player.y);
}
}
}
And while this worked, I wasn't able to interact with the items in the game,

forEach inside Ticker makes Canvas slow

I'm creating a HTML5 minigame that uses collision-detection and I've recently discovered that it has a speed problem:
I think the reason of this problem is that...
Inside the 60fps ticker there are two forEach loops, one for the rects array and other for the lasers array. So, when there's 5 rects and 5 lasers in the canvas, it'll loop 5 times in the first forEach and five times in the second at each frame, and each forEach function has lots of ifs in it, making the game slow. How can I change that to something less CPU-intensive?
If you know a bigger speed problem in this minigame, feel free to help me to solve it too.
Here's my entire code:
I highly recommend you to see the JSFiddle instead of the code below, since there's more than 400 lines.
<!DOCTYPE html>
<html>
<head>
<title>VelJS α</title>
<!-- This app was coded by Tiago Marinho -->
<!-- Do not leech it! -->
<link rel="shortcut icon" href="http://i.imgur.com/Jja8mvg.png">
<!-- EaselJS: -->
<script src="http://static.tumblr.com/uzcr0ts/uzIn1l1v2/easeljs-0.7.1.min.js"></script>
<script src="http://pastebin.com/raw.php?i=W4S2mtCp"></script>
<!-- jQuery: -->
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
(function () {
// Primary vars (stage, circle, rects):
var stage,
circle, // Hero!
rects = [], // Platforms
lasers = [];
// Velocity vars:
var xvel = 0, // X Velocity
yvel = 0, // Y Velocity
xvelpast = 0,
yvelpast = 0;
// Keyvars (up, left, right, down):
var up = false, // W or arrow up
left = false, // A or arrow left
right = false, // D or arrow right
down = false; // S or arrow down
// Other vars (maxvel, col, pause):
var maxvel = 256, // Maximum velocity
col = false, // Collision detection helper (returns true if collided side-to-side)
pause = false;
// Volatility vars (rmdir, pastrmdir):
var rmdir = 0,
pastrmdir = 0;
// Main part (aka creating stage, creating circle, etc):
function init() {
stage = new createjs.Stage("canvas");
// Creating circle:
var circle = new createjs.Shape();
circle.radius = 11;
circle.graphics.beginFill("#fff").beginStroke("white").drawCircle(circle.radius - 0.5, circle.radius - 0.5, circle.radius);
circle.width = circle.radius * 2;
circle.height = circle.radius * 2;
stage.addChild(circle);
setTimeout(function () {
// newobj(W, H, X, Y)
newobj("laser", 3, 244, stage.canvas.width / 2 - 125, stage.canvas.height / 4 * 3 - 247);
newobj("rect", 125, 3, stage.canvas.width / 2 - 125, stage.canvas.height / 4 * 3 - 250);
}, 250); // Wait until first tick finishes and stage is resized to 100%, then calculate the middle of canvas.
// User Input (Redirect input to Input Handler):
// Keydown:
document.addEventListener("keydown", function (evt) {
if (evt.keyCode == 87 || evt.keyCode == 38) { // up
up = true;
}
if (evt.keyCode == 65 || evt.keyCode == 37) { // left
left = true;
}
if (evt.keyCode == 68 || evt.keyCode == 39) { // right
right = true;
}
if (evt.keyCode == 83 || evt.keyCode == 40) { // down
down = true;
}
if (evt.keyCode == 8 || evt.keyCode == 80) { // del/p
if (pause == false) {
xvelpast = xvel;
yvelpast = yvel;
pause = true;
var fadestep = 0;
for (var i = 1; i > 0; i -= 0.1) {
i = parseFloat(i.toFixed(1));
fadestep++;
fadeFill("circle", i, fadestep);
rects.forEach(function (rect) {
fadeFill("rect", i, fadestep);
});
}
} else {
pause = false;
xvel = xvelpast;
yvel = yvelpast;
var fadestep = 0;
for (var i = 0; i <= 1; i += 0.1) {
i = parseFloat(i.toFixed(1));
fadestep++;
fadeFill("circle", i, fadestep);
rects.forEach(function (rect) {
fadeFill("rect", i, fadestep);
});
}
}
}
});
// Keyup:
document.addEventListener("keyup", function (evt) {
if (evt.keyCode == 87 || evt.keyCode == 38) { // up
up = false;
}
if (evt.keyCode == 65 || evt.keyCode == 37) { // left
left = false;
}
if (evt.keyCode == 68 || evt.keyCode == 39) { // right
right = false;
}
if (evt.keyCode == 83 || evt.keyCode == 40) { // down
down = false;
}
});
// Functions:
// Fade beginFill to a lower alpha:
function fadeFill(obj, i, t) {
setTimeout(function () {
if (obj == "circle") {
circle.graphics.clear().beginFill("rgba(255,255,255," + i + ")").beginStroke("white").drawCircle(circle.radius, circle.radius, circle.radius).endFill();
}
if (obj == "rect") {
for (var r = 0; r < rects.length; r++) {
rects[r].graphics.clear().beginFill("rgba(255,255,255," + i + ")").beginStroke("white").drawRect(0, 0, rects[r].width, rects[r].height).endFill();
}
}
}, t * 20);
};
// To create new rects:
function newobj(type, w, h, x, y) {
if (type == "rect") {
var rect = new createjs.Shape();
rect.graphics.beginFill("#fff").beginStroke("white").drawRect(0, 0, w, h);
rect.width = w + 1;
rect.height = h + 1;
rect.y = Math.round(y) + 0.5;
rect.x = Math.round(x) + 0.5;
stage.addChild(rect);
rects.push(rect);
}
if (type == "laser") {
var laser = new createjs.Shape();
if (w >= h) {
laser.graphics.beginFill("#c22").drawRect(0, 0, w, 1);
laser.width = w;
laser.height = 1;
} else {
laser.graphics.beginFill("#c22").drawRect(0, 0, 1, h);
laser.width = 1;
laser.height = h;
}
laser.shadow = new createjs.Shadow("#ff0000", 0, 0, 5);
laser.y = Math.round(y);
laser.x = Math.round(x);
stage.addChild(laser);
lasers.push(laser);
}
}
// Collision recoil:
function cls(clsdir) {
if (clsdir == "top") {
if (yvel <= 4) {
yvel = 0;
} else {
yvel = Math.round(yvel * -0.5);
}
}
if (clsdir == "left") {
if (xvel <= 4) {
xvel = 0;
} else {
xvel = Math.round(xvel * -0.5);
}
}
if (clsdir == "right") {
if (xvel >= -4) {
xvel = 0;
} else {
xvel = Math.round(xvel * -0.5);
}
}
if (clsdir == "bottom") {
if (yvel >= -4) {
yvel = 0;
} else {
yvel = Math.round(yvel * -0.5);
}
}
col = true;
}
// Die:
function die() {
circle.alpha = 1;
createjs.Tween.get(circle).to({
alpha: 0
}, 250).call(handleComplete);
function handleComplete() {
circle.x = stage.canvas.width / 2 - circle.radius;
circle.y = stage.canvas.height / 2 - circle.radius;
createjs.Tween.get(circle).to({
alpha: 1
}, 250);
yvel = 0;
xvel = 0;
yvelpast = 0;
xvelpast = 0;
}
yvel = yvel/2;
xvel = xvel/2;
}
// Set Intervals:
// Speed/Score:
setInterval(function () {
if (pause == false) {
speed = Math.abs(xvel) + Math.abs(yvel);
$(".speed").html("Speed: " + speed);
} else {
speed = Math.abs(xvelpast) + Math.abs(yvelpast);
$(".speed").html("Speed: " + speed + " (Paused)");
}
}, 175);
// Tick:
createjs.Ticker.on("tick", tick);
createjs.Ticker.setFPS(60);
function tick(event) {
// Input Handler:
if (up == true) {
yvel -= 2;
} else {
if (yvel < 0) {
yvel++;
}
}
if (left == true) {
xvel -= 2;
} else {
if (xvel < 0) {
xvel++;
}
}
if (right == true) {
xvel += 2;
} else {
if (xvel > 0) {
xvel--;
}
}
if (down == true) {
yvel += 2;
} else {
if (yvel > 0) {
yvel--;
}
}
// Volatility:
pastrmdir = rmdir;
rmdir = Math.floor((Math.random() * 20) + 1);
if (rmdir == 1 && pastrmdir != 4) {
yvel--;
}
if (rmdir == 2 && pastrmdir != 3) {
xvel--;
}
if (rmdir == 3 && pastrmdir != 2) {
xvel++;
}
if (rmdir == 4 && pastrmdir != 1) {
yvel++;
}
// Velocity limiter:
if (xvel > maxvel || xvel < maxvel * -1) {
(xvel > 0) ? xvel = maxvel : xvel = maxvel * -1;
}
if (yvel > maxvel || yvel < maxvel * -1) {
(yvel > 0) ? yvel = maxvel : yvel = maxvel * -1;
}
// Collision handler:
// xvel and yvel modifications must be before this!
rects.forEach(function (rect) { // Affect all rects
// Collision detection:
// (This MUST BE after every change in xvel/yvel)
// Next circle position calculation:
nextposx = circle.x + event.delta / 1000 * xvel * 30,
nextposy = circle.y + event.delta / 1000 * yvel * 30;
// Collision between objects (Rect and Circle):
if (nextposy + circle.height > rect.y && circle.y + circle.height < rect.y && circle.x + circle.width > rect.x && circle.x < rect.x + rect.width) {
cls("top");
}
if (nextposx + circle.width > rect.x && circle.x + circle.width < rect.x && circle.y + circle.height > rect.y && circle.y < rect.y + rect.height) {
cls("left");
}
if (nextposx < rect.x + rect.width && circle.x > rect.x + rect.width && circle.y + circle.height > rect.y && circle.y < rect.y + rect.height) {
cls("right");
}
if (nextposy < rect.y + rect.height && circle.y > rect.y + rect.height && circle.x + circle.width > rect.x && circle.x < rect.x + rect.width) {
cls("bottom");
}
rects.forEach(function (rect) {
// Check side-to-side collisions with other rects:
if (nextposy + circle.height > rect.y && circle.y + circle.height < rect.y && circle.x + circle.width > rect.x && circle.x < rect.x + rect.width) {
col = true;
}
if (nextposx + circle.width > rect.x && circle.x + circle.width < rect.x && circle.y + circle.height > rect.y && circle.y < rect.y + rect.height) {
col = true;
}
if (nextposx < rect.x + rect.width && circle.x > rect.x + rect.width && circle.y + circle.height > rect.y && circle.y < rect.y + rect.height) {
col = true;
}
if (nextposy < rect.y + rect.height && circle.y > rect.y + rect.height && circle.x + circle.width > rect.x && circle.x < rect.x + rect.width) {
col = true;
}
});
// Edge-to-edge collision between objects (Rect and Circle) - Note that this will not occur if a side-to-side collision occurred in the current frame!:
if (nextposy + circle.height > rect.y &&
nextposx + circle.width > rect.x &&
nextposx < rect.x + rect.width &&
nextposy < rect.y + rect.height &&
col == false) {
if (circle.y + circle.height < rect.y &&
circle.x + circle.width < rect.x) {
cls("top");
cls("left");
}
if (circle.y > rect.y + rect.height &&
circle.x + circle.width < rect.x) {
cls("bottom");
cls("left");
}
if (circle.y + circle.height < rect.y &&
circle.x > rect.x + rect.width) {
cls("top");
cls("right");
}
if (circle.y > rect.y + rect.height &&
circle.x > rect.x + rect.width) {
cls("bottom");
cls("right");
}
}
col = false;
// Stage collision:
if (nextposy < 0) { // Collided with TOP of stage. Trust me.
cls("bottom"); // Inverted clsdir is proposital!
}
if (nextposx < 0) {
cls("right");
}
if (nextposx + circle.width > stage.canvas.width) {
cls("left");
}
if (nextposy + circle.height > stage.canvas.height) {
cls("top");
}
});
// Laser collision handler:
lasers.forEach(function (laser) {
laser.alpha = Math.random() + 0.5;
nextposx = circle.x + event.delta / 1000 * xvel * 30,
nextposy = circle.y + event.delta / 1000 * yvel * 30;
if (nextposy + circle.height > laser.y && circle.y + circle.height < laser.y && circle.x + circle.width > laser.x && circle.x < laser.x + laser.width) {
circle.y = laser.y-circle.height;
die();
}
if (nextposx + circle.width > laser.x && circle.x + circle.width < laser.x && circle.y + circle.height > laser.y && circle.y < laser.y + laser.height) {
circle.x = laser.x-circle.width;
die();
}
if (nextposx < laser.x + laser.width && circle.x > laser.x + laser.width && circle.y + circle.height > laser.y && circle.y < laser.y + laser.height) {
circle.x = laser.x+laser.width;
die();
}
if (nextposy < laser.y + laser.height && circle.y > laser.y + laser.height && circle.x + circle.width > laser.x && circle.x < laser.x + laser.width) {
circle.y = laser.y+laser.height;
die();
}
});
// Velocity:
if (pause == true) {
xvel = 0;
yvel = 0;
}
circle.x += event.delta / 1000 * xvel * 20;
circle.y += event.delta / 1000 * yvel * 20;
// Stage.canvas 100% width and height:
stage.canvas.width = window.innerWidth;
stage.canvas.height = window.innerHeight;
// Update stage:
stage.update(event);
}
setTimeout(function () {
// Centre circle:
circle.x = stage.canvas.width / 2 - circle.radius;
circle.y = stage.canvas.height / 2 - circle.radius;
// Fade-in after loading:
$(".speed").css({
opacity: 1
});
$("canvas").css({
opacity: 1
});
}, 500);
}
$(function () {
init();
});
})();
</script>
<style>
* {
margin: 0;
}
html,
body {
-webkit-font-smoothing: antialiased;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
color: #fff;
background-color: #181818
}
.build {
position: absolute;
bottom: 5px;
right: 5px;
color: rgba(255, 255, 255, 0.05)
}
canvas {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
opacity: 0;
position: absolute;
top: 0;
left: 0;
-moz-transition: 5s ease;
-o-transition: 5s ease;
-webkit-transition: 5s ease;
transition: 5s ease
}
.speed {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
opacity: 0;
position: absolute;
top: 5px;
left: 5px;
color: #fff;
font-size: 16px;
-moz-transition: 5s ease;
-o-transition: 5s ease;
-webkit-transition: 5s ease;
transition: 5s ease
}
h2 {
text-align: center;
font-size: 22px;
font-weight: 700
}
p {
font-size: 16px;
margin: 0
}
</style>
</head>
<body>
<p class="speed"></p>
<p class="build">α256</p>
<canvas id="canvas">
<h2>Your browser doesn't support Canvas.</h2>
<p>Switch to <b>Chrome 33</b>, <b>Firefox 27</b> or <b>Safari 7</b>.</p>
</canvas>
</body>
</html>
JSFiddle
The game logic isn't really a problem, the reason it's slow is because you "create" a new canvas every tick by setting the width and height:
stage.canvas.width = window.innerWidth;
stage.canvas.height = window.innerHeight;
So, even if you set the canvas width and height to the same values they had, under the hood pretty much a new canvas is constructed. If you remove the lines above from the game loop it should run smoothly.
Just set the canvas width and height once and then listen for window resize and set it when the browser window changes size.
Running logic in a tick can be expensive, as is updating the canvas each frame. If you can, a lower framerate could be advisable - since it often isn't necessary to run at 60fps. If you want to keep refreshing the canvas at that rate, and you have any particularly expensive functions, such as pathfinding, collision, etc - you could always decouple that from your update loop, so it isn't running quite as often.
Note that stage updating can be very expensive, especially with vectors. If you can, find a solution that lets you cache vector content as bitmaps, and update the vector as little as possible. In your case, since you are just fading the fill - you might separate the fill from the outline, cache them separately, and then use alpha on the fill object. If you have a lot of shapes, you can reuse the caches between them, and you will see huge performance gains.
Best of luck!

Categories

Resources