Related
I am trying to move multiple balls at once in three.js. I made a function that creates a ball and created three balls using it. Afterwards I created a function that moves balls and it works for one ball but whenever I try to move them all at once it doesn't work. Any help would be much appreciated.
Here is the code:
ball function:
function Ball(valuex, valuey, valuez, ballname, color)
{
var balMaterial, balGeometry, balMesh;
balMaterial = new THREE.MeshLambertMaterial({ color: color});
balGeometry = new THREE.SphereGeometry(0.3,50,50);
balMesh = new THREE.Mesh(balGeometry, balMaterial);
balMesh.position.set(valuex,valuey,valuez);
balMesh.name = ballname;
balMesh.ballDirection = new THREE.Vector3();
balMesh.ballDirection.x = -5;
balMesh.ballDirection.z = 1;
balMesh.ballDirection.normalize();
balMesh.moveSpeed = 25;
scene.add(balMesh);
}
move balls:
function moveBalls (ball) {
var tempbal = scene.getObjectByName(ball);
var ballDirection = tempbal.ballDirection;
tempbal.position.add(speed.copy(ballDirection).multiplyScalar(clock.getDelta() * tempbal.moveSpeed));
if (tempbal.position.x < -4.7) {
ballDirection.x = Math.abs(ballDirection.x);
}
if (tempbal.position.x > 4.7) {
ballDirection.x = -Math.abs(ballDirection.x);
}
if (tempbal.position.z > 12.2) {
ballDirection.z = -Math.abs(ballDirection.z);
}
if (tempbal.position.z < -12.2) {
ballDirection.z = Math.abs(ballDirection.z);
}
if (tempbal.moveSpeed > 0)
{
tempbal.moveSpeed = tempbal.moveSpeed - 0.002;
}
}
Create balls:
Ball(0,4.5,0, "ball1", 0xffffff);
Ball(2,4.5,0, "ball2", 0xffffff);
Ball(0,4.5,6, "ball3", 0xff0000);
Animate Scene:
function animateScene()
{
moveBalls("ball1");
moveBalls("ball2");
moveBalls("ball3");
requestAnimationFrame(animateScene);
renderer.render(scene, camera);
}
PS: I am new here and this is my first post so if I did anything wrong in this post please tell me so I can learn from it.
The function clock.getDelta() returns the seconds passed since the last call to clock.getDelta() which means only your first ball may move. Indeed, the first ball calls getDelta() (which returns something greater than 0). In the same frame (meaning approximately at the same time), you call clock.getDelta() for the 2nd ball, which returns 0. The same happens to the 3rd ball.
Try to do the following :
function moveBalls (ball, deltaTime) {
var tempbal = scene.getObjectByName(ball);
var ballDirection = tempbal.ballDirection;
tempbal.position.add(speed.copy(ballDirection).multiplyScalar(deltaTime
* tempbal.moveSpeed));
if (tempbal.position.x < -4.7) {
ballDirection.x = Math.abs(ballDirection.x);
}
if (tempbal.position.x > 4.7) {
ballDirection.x = -Math.abs(ballDirection.x);
}
if (tempbal.position.z > 12.2) {
ballDirection.z = -Math.abs(ballDirection.z);
}
if (tempbal.position.z < -12.2) {
ballDirection.z = Math.abs(ballDirection.z);
}
if (tempbal.moveSpeed > 0)
{
tempbal.moveSpeed = tempbal.moveSpeed - 0.002;
}
}
// ...
function animateScene()
{
var deltaTime = clock.getDelta() ;
moveBalls("ball1", deltaTime);
moveBalls("ball2", deltaTime);
moveBalls("ball3", deltaTime);
requestAnimationFrame(animateScene);
renderer.render(scene, camera);
}
So I am trying to make a simple space game. You will have a ship that moves left and right, and Asteroids will be generated above the top of the canvas at random X position and size and they will move down towards the ship.
How can I create Asteroid objects in seperate positions? Like having more than one existing in the canvas at once, without creating them as totally seperate objects with seperate variables?
This sets the variables I would like the asteroid to be created on.
var asteroids = {
size: Math.floor((Math.random() * 40) + 15),
startY: 100,
startX: Math.floor((Math.random() * canvas.width-200) + 200),
speed: 1
}
This is what I used to draw the asteroid. (It makes a hexagon shape with random size at a random x coordinate)
function drawasteroid() {
this.x = asteroids.startX;
this.y = 100;
this.size = asteroids.size;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(this.x,this.y-this.size*0.5);
ctx.lineTo(this.x+this.size*0.9,this.y);
ctx.lineTo(this.x+this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x,this.y+this.size*1.5);
ctx.lineTo(this.x-this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x-this.size*0.9,this.y);
ctx.fill();
}
I included ALL of my code in this snippet. Upon running it, you will see that I currently have a ship that moves and the asteroid is drawn at a random size and random x coordinate. I just need to know about how to go about making the asteroid move down while creating other new asteroids that will also move down.
Thank You for all your help! I am new to javascript.
// JavaScript Document
////// Variables //////
var canvas = {width:300, height:500, fps:30};
var score = 0;
var player = {
x:canvas.width/2,
y:canvas.height-100,
defaultSpeed: 5,
speed: 10
};
var asteroids = {
size: Math.floor((Math.random() * 40) + 15),
startY: 100,
startX: Math.floor((Math.random() * canvas.width-200) + 200),
speed: 1
}
var left = false;
var right = false;
////// Arrow keys //////
function onkeydown(e) {
if(e.keyCode === 37) {
left = true;
}
if(e.keyCode === 39) {
right = true;
}
}
function onkeyup(e) {
if (e.keyCode === 37) {
left = false;
}
if(e.keyCode === 39) {
right = false;
}
}
////// other functions //////
//function to clear canvas
function clearCanvas() {
ctx.clearRect(0,0,canvas.width,canvas.height);
}
// draw the score in the upper left corner
function drawscore(score) {
var score = 0;
ctx.fillStyle = "#FFFFFF";
ctx.fillText(score,50,50);
}
// Draw Player ship.
function ship(x,y) {
var x = player.x;
var y = player.y;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x+15,y+50);
ctx.lineTo(x-15,y+50);
ctx.fill();
}
// move player ship.
function moveShip() {
document.onkeydown = onkeydown;
document.onkeyup = onkeyup;
if (left === true && player.x > 50) {
player.x -= player.speed;
}
if (right === true && player.x < canvas.width - 50) {
player.x += player.speed;
}
}
// Draw Asteroid
function drawasteroid() {
this.x = asteroids.startX;
this.y = 100;
this.size = asteroids.size;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(this.x,this.y-this.size*0.5);
ctx.lineTo(this.x+this.size*0.9,this.y);
ctx.lineTo(this.x+this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x,this.y+this.size*1.5);
ctx.lineTo(this.x-this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x-this.size*0.9,this.y);
ctx.fill();
}
// move Asteroid
function moveAsteroid() {
//don't know how I should go about this.
}
// update
setInterval (update, 1000/canvas.fps);
function update() {
// test collisions and key inputs
moveShip();
// redraw the next frame of the animation
clearCanvas();
drawasteroid();
drawscore();
ship();
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>My Game</title>
<script src="game-functions.js"></script>
<!--
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
-->
</head>
<body>
<canvas id="ctx" width="300" height="500" style="border: thin solid black; background-color: black;"></canvas>
<br>
<script>
////// Canvas setup //////
var ctx = document.getElementById("ctx").getContext("2d");
</script>
</body>
</html>
You want to make the creation of asteroids dynamic...so why not set up a setInterval that gets called at random intervals as below. You don't need a separate declaration for each Asteroids object you create. You can just declare a temporary one in a setInterval function. This will instantiate multiple different objects with the same declaration. Of course you need to store each object somewhere which is precisely what the array is for.
You also have to make sure that asteroids get removed from the array whenever the moveAsteroid function is called if they are off of the canvas. The setInterval function below should be called on window load and exists alongside your main rendering setInterval function.
You are also going to have to change your moveAsteroid function a bit to be able to point to a specific Asteroids object from the array. You can do this by adding the Asteroids object as a parameter of the function or by making the function a property of the Asteroids class and using this. I did the latter in the example below.
var astArray = [];
var manageAsteroidFrequency = 2000;
var Asteroids {
X: //something
Y://something
speed:1
move: function() {
this.X -= speed;
}
}
var mainRenderingFunction = setInterval( function() {
for (var i = astArray.length-1 ; i > -1; i --){
if(astArray[i].Y < 0){
astArray.splice(i, 1)
}else{
astArray[i].move;
}
}
}, 40);
var manageAsteroids = setInterval( function () {
if (astArray.length < 4){
var tmpAst = new Asteroids();
astArray.push(tmpAst);
}
manageAsteroidFrequency = Math.floor(Math.random()*10000);
}, manageAsteroidFrequency);
I've tried a few different ways that I have seen on here, but I can't quite get my image to move. Whenever I try adapting code for arrow key presses, it just seems to make my canvas shrink and my player model (spaceperson) disappear.
here is the "drawing board" I keep returning to, and what I have so far.
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
// Create the image object
var spaceperson = new Image();
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, 280, 300);
};
// artwork by Harrison Marley (using make8bitart.com)
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";`
I am quite new to javascript, and I am just trying to work out how I can move the specperson image using arrow keys. I was trying to make a class for space person to access their x,y values, but I can't seem to draw the image without using .onload
here a more complete example:
//just a utility
function image(url, callback){
var img = new Image();
if(typeof callback === "function"){
img.onload = function(){
//just to ensure that the callback is executed async
setTimeout(function(){ callback(img, url) }, 0)
}
}
img.src = url;
return img;
}
//a utility to keep a value constrained between a min and a max
function clamp(v, min, max){
return v > min? v < max? v: max: min;
}
//returns a function that can be called with a keyCode or one of the known aliases
//and returns true||false wether the button is down
var isKeyDown = (function(aliases){
for(var i=256, keyDown=Array(i); i--; )keyDown[i]=false;
var handler = function(e){
keyDown[e.keyCode] = e.type === "keydown";
e.preventDefault(); //scrolling; if you have to suppress it
};
addEventListener("keydown", handler, false);
addEventListener("keyup", handler, false);
return function(key){
return(true === keyDown[ key in aliases? aliases[ key ]: key ])
}
})({
//some aliases, to be extended
up: 38,
down: 40,
left: 37,
right: 39
});
// Get the canvas and context
var canvas = document.getElementById("space");
canvas.width = 1920;
canvas.height = 700;
var ctx = canvas.getContext("2d");
//the acutal image is just a little-part of what defines your figue
var spaceperson = {
image: image("//i.imgur.com/Eh9Dpq2.png", function(img){
spaceperson.width = img.naturalWidth;
spaceperson.height = img.naturalHeight;
//start the rendering by calling update
update();
}),
//position
x: 60, y: 310,
width: 0, height: 0,
speed: 200 // 200px/s
};
var lastCall = 0; //to calculate the (real) time between two update-calls
//the render-fucntion
function update(){
//taking account for (sometimes changing) framerates
var now = Date.now(), time = lastCall|0 && (now-lastCall)/1000;
lastCall = now;
requestAnimationFrame(update);
var sp = spaceperson,
speed = sp.speed;
//checking the pressed buttons and calculates the direction
//two opposite buttons cancel out each other, like left and right
var dx = (isKeyDown('right') - isKeyDown('left')) * time,
dy = (isKeyDown('down') - isKeyDown('up')) * time;
//fix the speed for diagonals
if(dx && dy) speed *= 0.7071067811865475; // * 1 / Math.sqrt(2)
if(dx) { //there is some movement on the x-axes
sp.x = clamp(
//calculate the new x-Position
//currentPos + direction * speed
sp.x + dx * sp.speed,
//restraining the result to the bounds of the map
0, canvas.width - sp.width
);
}
//same for y
if(dy) sp.y = clamp(sp.y + dy * sp.speed, 0, canvas.height - sp.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(sp.image, sp.x, sp.y);
}
Edit:
A quick question (I hope); if I was to later add other objects, would I check for collisions in update()?
This is still just a very basic example. The main purpose of the update()-function should be to work as the main event-loop.
To trigger all Events that have to happen each frame in the order they have to happen.
var lastCall = 0;
function update(){
//I always want a next frame
requestAnimationFrame(update);
//handle timing
var now = Date.now(),
//time since the last call in seconds
//cause usually it's easier for us to think in
//tems like 50px/s than 0.05px/ms or 0.8333px/frame
time = lastCall|0 && (now-lastCall) / 1000;
lastCall = now;
movePlayer(time);
moveEnemies(time);
moveBullets(time);
collisionDetection();
render();
}
function render(){
ctx.clear(0, 0, canvas.width, canvas.height);
drawBackground(ctx);
for(var i=0; i<enemies.length; ++i)
enemies[i].render(ctx);
player.render(ctx);
}
Not saying that you have to implement all these functions now, but to give you an idea of a possible structure.
Don't be scared to break big tasks (functions) up into subtasks.
And it might make sense to give each enemy a move()-function so you can implement different movement-patterns per enemy,
or you say that the pattern is (and will be) all the same for each enemy, parameterized at the best, then you can handle that in a loop.
Same thing for rendering, as I'm showing in the last part of code.
Here's some slightly modified code from a game I was noodling around with a while back. If you want to see more code, check out the complete JS on GitHub. The game is incomplete but you should gather some helpful clues as to how to move an image around the canvas.
var spaceperson = {
speed: 256,
other_stuff: ''
},
keysDown = [],
update,
main;
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
update = function (modifier) {
if (38 in keysDown && spaceperson.y > 0) { // UP
spaceperson.y -= spaceperson.speed * modifier;
}
if (40 in keysDown && spaceperson.y < CANVAS_HEIGHT - SPACEPERSON_HEIGHT) { // DOWN
spaceperson.y += spaceperson.speed * modifier;
}
if (37 in keysDown && spaceperson.x > 0) { // LEFT
spaceperson.x -= spaceperson.speed * modifier;
}
if (39 in keysDown && spaceperson.x < CANVAS_WIDTH - SPACEPERSON_WIDTH) { // RIGHT
spaceperson.x += spaceperson.speed * modifier;
}
}
I'm not sure but i think this can help.
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
var x = 280;
var y = 300;
// Create the image object
var spaceperson = new Image();
spaceperson.addEventListener("keypress", press);
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, x, y);
};
function press(event) {
if(event.keyCode == 37) {//LEFT
x = x - 1;
} else if(event.keyCode == 38) {//UP
y = y - 1;
} else if(event.keyCode ==39) {//RIGHT
x = x + 1;
} else if(event.keyCode == 40) {//DOWN
y = y + 1;
}
draw();
}
function draw(){
ctx.drawImage(spaceperson,x,y);
}
// artwork by Harrison Marley (using make8bitart.com)
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";
I found a solution!
// Get the canvas and context
var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;
var xPos = 60;
var yPos = 310;
// Create the image object
var spaceperson = new Image();
// Add onload event handler
spaceperson.onload = function () {
// Done loading, now we can use the image
ctx.drawImage(spaceperson, xPos, yPos);
};
function move(e){
if(e.keyCode==39){
xPos+=10;
}
if(e.keyCode==37){
xPos-=10;
}
if(e.keyCode==38){
yPos-=10;
}
if(e.keyCode==40){
yPos+=10;
}
canvas.width=canvas.width;
ctx.drawImage(spaceperson, xPos, yPos);
}
document.onkeydown = move;
// artwork by Harrison Marley
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";
I am trying to add some obstacles to the canvas game that I've got but something seems to be wrong and I can't seem to put my finger on it.
I just want some simple walls here and there to make the game harder and collision with the walls (so that if the player hits the wall it's game over).
var
// variables
COLS = 25, // Columns
ROWS = 25, // Rows
EMPTY = 0, // Empty Cell
SNAKE = 1, // Snake
FRUIT = 2, // Fruit
LEFT = 0, // left direction (key)
UP = 1, // up direction (key)
RIGHT = 2, // right direction (key)
DOWN = 3, // down direction (key)
KEY_LEFT = 37, // key codes for keyboard input (Codes can be found online)
KEY_UP = 38, // key codes for keyboard input (Codes can be found online)
KEY_RIGHT = 39, // key codes for keyboard input (Codes can be found online)
KEY_DOWN = 40, // key codes for keyboard input (Codes can be found online)
obstacle = [[0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0]],
// [0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0],
// [0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0],
// [0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0],
// [0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,2,0,0]],
// Objects
canvas, // Canvas
ctx, // Canvas render
keystate, // Key inputs
frames, // frames per second
score; // player score
grid = {
width: null, // Amount of columns
height: null, // Amount of rows
_grid: null, // Array
init: function(d, c, r) { // initiation with direction, columns and rows.
this.width = c; // Set width to number of columns (c)
this.height = r; // set height to number of rows (r)
this._grid = []; // Initiate grid with empty array
for (var x=0; x < c; x++) {
this._grid.push([]);
for (var y=0; y < r; y++) {
this._grid[x].push(d); // set current column and push new value for each row in column
}
}
},
set: function(val, x, y) { // set values for the grid cells with x and y position
this._grid[x][y] = val;
},
get: function(x, y) { // get the value of x and y position
return this._grid[x][y];
}
}
snake = { // Creating snake
direction: null, // Direction of snake
last: null, // last element in queue pointer
_queue: null, // queue array
// Sets start position of snake, same initiation method as before
init: function(d, x, y) {
this.direction = d; // Direction set to d
this._queue = []; // Another empty queue array
this.insert(x, y); // Inserting x & y position
},
// Insert method that adds elements to queue with x and y position
insert: function(x, y) {
this._queue.unshift({x:x, y:y}); // unshift prepends an element to array
this.last = this._queue[0];
},
// Remove function to remove and return element to queue
remove: function() {
return this._queue.pop(); // pop returns the last element of array
}
};
function obstacle() {
empty.push({obstacle});
ctx.beginPath();
ctx.rect(obstacle);
ctx.fillStyle = "#7a26ce";
ctx.fill();
ctx.closePath();
}
function setFood() { // Food for hungry snake
var empty = []; // tracks all empty places in the grid
// for loop to find all empty cells in grid
for (var x=0; x < grid.width; x++) {
for (var y=0; y < grid.height; y++) {
if (grid.get(x, y) === EMPTY) {
empty.push({x:x, y:y});
}
}
}
// variable randomposition to pick random empty cell
var randpos = empty[Math.round(Math.random()*(empty.length - 1))];
grid.set(FRUIT, randpos.x, randpos.y);
}
function main() { // call all the functions that we will use in the game
// canvas
canvas = document.createElement("canvas");
canvas.width = COLS*20; // Sets canvas width to columns * 20
canvas.height = ROWS*20; // Sets canvas height to columns * 20
ctx = canvas.getContext("2d");
document.body.appendChild(canvas); // Adds canvas element to the body of the document
ctx.font = "12px sans-serif"; // font
frames = 0;
keystate = {};
document.addEventListener("keydown", function(evt) { // Track all keyboard input
keystate[evt.keyCode] = true;
});
document.addEventListener("keyup", function(evt) { // Track all keyboard input
delete keystate[evt.keyCode];
});
init(); // Initiate Game Loop
loop(); // Start Game Loop
}
function init() { // Reset and intiate game objects
score = 0; // set start score to 0
grid.init(EMPTY, COLS, ROWS);
var sp = {x:Math.floor(COLS/2), y:ROWS-1};
snake.init(UP, sp.x, sp.y); // Start direction
grid.set(SNAKE, sp.x, sp.y);
setFood();
grid._grid = grid._grid.concat(obstacle);
}
function loop() { // Game loop for rendering and objects
update();
draw();
window.requestAnimationFrame(loop, canvas); // Canvas will call loop function when it needs to redraw
}
function update() { // update function
frames++;
// Keyboard input
if (keystate[KEY_LEFT] && snake.direction !== RIGHT) {
snake.direction = LEFT;
}
if (keystate[KEY_UP] && snake.direction !== DOWN) {
snake.direction = UP;
}
if (keystate[KEY_RIGHT] && snake.direction !== LEFT) {
snake.direction = RIGHT;
}
if (keystate[KEY_DOWN] && snake.direction !== UP) {
snake.direction = DOWN;
}
// Update game every 5 frames.
if (frames%5 === 0) {
// last element from the snake queue
var nx = snake.last.x;
var ny = snake.last.y;
// Updating the position of snake depending on the direction it is heading
switch (snake.direction) {
case LEFT:
nx--;
break;
case UP:
ny--;
break;
case RIGHT:
nx++;
break;
case DOWN:
ny++;
break;
}
// if statement checking conditions whether game should keep running or reset aka game over
if (0 > nx || nx > grid.width-1 ||
0 > ny || ny > grid.height-1 ||
grid.get(nx, ny) === SNAKE
) {
return init();
}
// Checks the new position of the snake and if it's on a fruit item or not.
if (grid.get(nx, ny) === FRUIT) {
// If it is on a fruit item it will increase your score and create a new food in a random cell.
score++;
setFood();
} else {
// Takes out the tail (first item) from queue and removes identifier from the grid.
var tail = snake.remove();
grid.set(EMPTY, tail.x, tail.y);
}
// Snake identifier that is created at the new position and is added to the queue
grid.set(SNAKE, nx, ny);
snake.insert(nx, ny);
}
}
function draw() { // render grid to canvas
var tw = canvas.width/grid.width; // Calculate tile width
var th = canvas.height/grid.height; // Calculate tile height
for (var x=0; x < grid.width; x++) { // for-loop loops through entire grid to draw cells
for (var y=0; y < grid.height; y++) {
// Depending on the identifier of each cell sets certain fillstyle defined below.
switch (grid.get(x, y)) {
case EMPTY:
ctx.fillStyle = "#5a5a5a";
break;
case SNAKE:
ctx.fillStyle = "#B54548";
break;
case FRUIT:
ctx.fillStyle = "lightblue";
break;
case obstacle:
ctx.fillStyle = "yellow";
break;
}
ctx.fillRect(x*tw, y*th, tw, th);
}
}
// Change fillstyle and show score on the screen.
ctx.fillStyle = "#000";
ctx.fillText("SCORE: " + score, 10, canvas.height-10);
}
// Game Start!
main();
canvas {
display: block;
position: absolute;
border: 2px solid #000;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
or Fiddle.
I have been making games with collisions for a while now, and I find that using a collision map is the easiest way, instead of making one object that stores all entities at once. Here's an example:
var collisions = [];
function addCollision(x,y){
if(typeof collisions[x] == "undefined")collisions[x] = []; //If the row is empty, create it
collisions[x][y] = true; //Set the row and column to true
};
function checkCollision(x,y){
return (typeof collisions[x] != "undefined")?//If the row is undefined, there's nothing in it, so return false
((typeof collisions[x][y] != "undefined") //If not, but the column is undefined, return false
?true:false):false; //If the row and column is true, return true.
};
Now, addCollision(x,y) will put a new piece to collide with, and checkCollision(x,y) will see there is anything at x,y.
This is good for (relatively) small maps, as a large one will eat up a lot of memory. I have been able to do 500x500, but I'm not sure what the max size is.
I've spent about 12 hours looking through this code, and fiddling with it, trying to find out where there's a recursion problem because I'm getting the, "maximum call stack size exceeded," error, and haven't found it. Someone smarter than me please help me!
so far, all I found was that when I make the object, spot, a circle, object, the problem disappears, but when I make it a, 'pip', I get this stack overflow error. I've gone over the pip class with a friggin' microscope, and still have no idea why this is happening!
var canvas = document.getElementById('myCanvas');
//-------------------------------------------------------------------------------------
// Classes
//-------------------------------------------------------------------------------------
//=====================================================================================
//CLASS - point
function point(x,y){
this.x = x;
this.y = y;
}
//=====================================================================================
// CLASS - drawableItem
function drawableItem() {
var size = 0;
this.center = new point(0,0);
this.lineWidth = 1;
this.dependentDrawableItems = new Array();
}
//returns the size
drawableItem.prototype.getSize = function getSize(){
return this.size;
}
// changes the size of this item and the relative size of all dependents
drawableItem.prototype.changeSize = function(newSize){
var relativeItemSizes = new Array;
relativeItemSizes.length = this.dependentDrawableItems.length;
// get the relative size of all dependent items
for (var i = 0; i < this.dependentDrawableItems.length; i++){
relativeItemSizes[i] = this.dependentDrawableItems[i].getSize() / this.size;
}
// change the size
this.size = newSize;
// apply the ratio of change back to all dependent items
for (var i = 0; i < relativeItemSizes.length; i++){
this.dependentDrawableItems[i].changeSize(relativeItemSizes[i] * newSize);
}
}
//moves all the vertices and every dependent to an absolute point based on center
drawableItem.prototype.moveTo = function(moveX,moveY){
//record relative coordinates
var relativeItems = new Array;
relativeItems.length = this.dependentDrawableItems.length;
for (var i = 0; i < relativeItems.length; i++){
relativeItems[i] = new point;
relativeItems[i].x = this.dependentDrawableItems[i].center.x - this.center.x;
relativeItems[i].y = this.dependentDrawableItems[i].center.y - this.center.y;
}
//move the center
this.center.x = moveX;
this.center.y = moveY;
//move all the items relative to the center
for (var i = 0; i < relativeItems.length; i++){
this.dependentDrawableItems[i].moveItemTo(this.center.x + relativeItems[i].x,
this.center.y + relativeItems[i].y);
}
}
// draws every object in dependentDrawableItems
drawableItem.prototype.draw = function(ctx){
for (var i = 0; i < this.dependentDrawableItems.length; i++) {
this.dependentDrawableItems[i].draw(ctx);
}
}
//=====================================================================================
//CLASS - circle
function circle(isFilledCircle){
drawableItem.call(this);
this.isFilled = isFilledCircle
}
circle.prototype = new drawableItem();
circle.prototype.parent = drawableItem.prototype;
circle.prototype.constructor = circle;
circle.prototype.draw = function(ctx){
ctx.moveTo(this.center.x,this.center.y);
ctx.beginPath();
ctx.arc(this.center.x, this.center.y, this.size, 0, 2*Math.PI);
ctx.closePath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.outlineColor;
if (this.isFilled === true){
ctx.fill();
}else {
ctx.stroke();
}
this.parent.draw.call(this,ctx);
}
//=====================================================================================
//CLASS - pip
function pip(size){
circle.call(this,true);
}
pip.prototype = new circle(false);
pip.prototype.parent = circle.prototype;
pip.prototype.constructor = pip;
//----------------------------------------------------------------------
// Objects/variables - top layer is last (except drawable area is first)
//----------------------------------------------------------------------
var drawableArea = new drawableItem();
var spot = new pip();
spot.changeSize(20);
drawableArea.dependentDrawableItems[drawableArea.dependentDrawableItems.length] = spot;
//------------------------------------------
// Draw loop
//------------------------------------------
function drawScreen() {
var context = canvas.getContext('2d');
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
spot.moveTo(context.canvas.width/2, context.canvas.height/2);
drawableArea.draw(context);
}
window.addEventListener('resize', drawScreen);
Here's the demo: http://jsfiddle.net/DSU8w/
this.parent.draw.call(this,ctx);
is your problem. On a pip object, the parent will be circle.prototype. So when you now call spot.draw(), it will call spot.parent.draw.call(spot), where this.parent is still the circle.prototype…
You will need to explicitly invoke drawableItem.prototype.draw.call(this) from circle.prototype.draw. Btw, you should not use new for the prototype chain.
Why would you write code like that? It's so difficult to understand and debug. When I'm creating lots of classes I usually use augment to structure my code. This is how I would rewrite your code:
var Point = Object.augment(function () {
this.constructor = function (x, y) {
this.x = x;
this.y = y;
};
});
Using augment you can create classes cleanly. For example your drawableItem class could be restructured as follows:
var DrawableItem = Object.augment(function () {
this.constructor = function () {
this.size = 0;
this.lineWidth = 1;
this.dependencies = [];
this.center = new Point(0, 0);
};
this.changeSize = function (toSize) {
var fromSize = this.size;
var ratio = toSize / fromSize;
this.size = toSize;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
dependency.changeSize(dependency.size * ratio);
}
};
this.moveTo = function (x, y) {
var center = this.center;
var dx = x - center.x;
var dy = y - center.y;
center.x = x;
center.y = y;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
var center = dependency.center;
dependency.moveTo(center.x + dx, center.y + dy);
}
};
this.draw = function (context) {
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) dependencies[index++].draw(context);
};
});
Inheritance is also very simple. For example you can restructure your circle and pip classes as follows:
var Circle = DrawableItem.augment(function (base) {
this.constructor = function (filled) {
base.constructor.call(this);
this.filled = filled;
};
this.draw = function (context) {
var center = this.center;
var x = center.x;
var y = center.y;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, this.size, 0, 2 * Math.PI);
context.closePath();
context.lineWidth = this.lineWidth;
context[this.filled ? "fill" : "stroke"]();
base.draw.call(this, context);
};
});
var Pip = Circle.augment(function (base) {
this.constructor = function () {
base.constructor.call(this, true);
};
});
Now that you've created all your classes you can finally get down to the drawing:
window.addEventListener("DOMContentLoaded", function () {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var drawableArea = new DrawableItem;
var spot = new Pip;
spot.changeSize(20);
drawableArea.dependencies.push(spot);
window.addEventListener("resize", drawScreen, false);
drawScreen();
function drawScreen() {
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
spot.moveTo(width / 2, height / 2);
drawableArea.draw(context);
}
}, false);
We're done. See the demo for yourself: http://jsfiddle.net/b5vNk/
Not only have we made your code more readable, understandable and maintainable but we have also solved your recursion problem.
As Bergi mentioned the problem was with the statement this.parent.draw.call(this,ctx) in the circle.prototype.draw function. Since spot.parent is circle.prototype the this.parent.draw.call(this,ctx) statement is equivalent to circle.prototype.draw.call(this,ctx). As you can see the circle.prototype.draw function now calls itself recursively until it exceeds the maximum recursion depth and throws an error.
The augment library solves this problem elegantly. Instead of having to create a parent property on every prototype when you augment a class augment provides you the prototype of that class as a argument (we call it base):
var DerivedClass = BaseClass.augment(function (base) {
console.log(base === BaseClass.prototype); // true
});
The base argument should be treated as a constant. Because it's a constant base.draw.call(this, context) in the Circle class above will always be equivalent to DrawableItem.prototype.draw.call(this, context). Hence you will never have unwanted recursion. Unlike this.parent the base argument will alway point to the correct prototype.
Bergi's answer is correct, if you don't want to hard code the parent name multiple times you could use a helper function to set up inheritance:
function inherits(Child,Parent){
Child.prototype=Object.create(Parent.prototype);
Child.parent=Parent.prototype;
Child.prototype.constructor=Child;
};
function DrawableItem() {
this.name="DrawableItem";
}
DrawableItem.prototype.changeSize = function(newSize){
console.log("changeSize from DrawableItem");
console.log("invoking object is:",this.name);
}
function Circle(isFilledCircle){
Circle.parent.constructor.call(this);
this.name="Circle";//override name
}
inherits(Circle,DrawableItem);
Circle.prototype.changeSize = function(newSize){
Circle.parent.changeSize.call(this);
console.log("and some more from circle");
};
function Pip(size){
Pip.parent.constructor.call(this,true);
this.name="Pip";
}
inherits(Pip,Circle);
var spot = new Pip();
spot.changeSize();
For a polyfill on Object.create look here.