I'm creating a Game Loop in javascript using var/function classes (for want of a better word). However, I have this strange error where javascript states that a variable is undefined immediately after declaring it...
main.js:39 Uncaught ReferenceError: game is not defined
In this case, that line is;
game.context.beginPath();
However, this line is not called until the init function calls game.balls.push(/../); Haven't I already declared 'game' by this point, or am I missing something?
Here is my code (Apologies for the length, hopefully most of it can be ignored):
/*
Keep This: #217398
*/
var Game = function () {
this.canvas = document.getElementById('canvas');
this.context = this.canvas.getContext('2d');
this.balls = [];
var that = this;
this.start = function () {
requestAnimationFrame(that.update);
};
this.update = function () {
that.draw();
requestAnimationFrame(that.update);
};
this.draw = function () {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
for(var x = 0; x < this.balls.length; x++){
this.balls[x].draw();
}
};
this.start();
};
var Ball = function (x, y) {
this.x = x;
this.y = y;
this.dx = 2;
this.dy = 2;
this.ballRadius = 10;
this.draw = function(){
game.context.beginPath();
game.context.arc(this.x, this.y, this.ballRadius, 0, Math.PI*2);
game.context.fillStyle = 'black';
game.context.fill();
game.context.closePath();
this.x += this.dx;
this.y += this.dy;
if(this.x + this.dx > game.canvas.width - this.ballRadius || this.x + this.dx < this.ballRadius)
this.dx = -this.dx;
if(this.y + this.dy > game.canvas.height - this.ballRadius || this.y + this.dy < this.ballRadius)
this.dy = -this.dy;
};
};
function init(){
var game = new Game();
game.canvas.addEventListener('click', function(){
game.balls.push(new Ball(100, 100));
});
}
Update Ball() so that you can explicitly pass in a reference to the Game() instance:
var Ball = function (game, x, y) {
this.x = x;
this.y = y;
// etc
};
Then:
function init(){
var game = new Game();
game.canvas.addEventListener('click', function(){
game.balls.push(new Ball(game, 100, 100));
});
}
Now the Ball() code has access to a reference to the Game() instance you created.
Because you declared the game variable using the var keyword within the init function, it will be scoped to the init function (and not available to other functions not nested within init).
function init(){
var game = new Game(); // will be scoped to init
game.canvas.addEventListener('click', function(){
game.balls.push(new Ball(100, 100));
});
}
So, one option would be to declare game outside of init which would broaden it's scope, or you could declare it as an instance variable to Ball.
The variable game is scoped to the init function in which it is created. This means that it can't be accessed outside of this function. There are many work arounds for this. You could make game a global variable, or pass it to the Ball constructor.
Another possible solution is having a global namespace which keeps track of these important objects.
var SomeNamespace= {};
SomeNamespace.game= new Game();
What I personally like to do is run my simple games in closures.
(function(){
var game = new Game();
var ball = new Ball(); // Ball now has access to game.
})()
Sidenote, you can create globally scoped variables within functions, by omitting the var keyword, but it's considered a bad practice.
Related
I am having problems with p5 js.
I am trying to add two ellipse objects that spin around the circumference of a circle.
The code below represents the constructor function of the object :
function Obj(){
this.ang = TWO_PI;
this.x = w/2 + cos(ang)*r;
this.y = h/2 + sin(ang)*r;
this.fil = 255;
this.size = 28;
this.show = function(){
noStroke();
fill(this.fil);
ellipse(this.x,this.y,this.size,this.size);
}
this.update = function(){
this.ang+=.02;
}
}
And this is the main file :
let w = innerWidth;
let h = innerHeight;
let xd = [];
let r = 200;
function setup() {
createCanvas(w, h);
for (let i = 0; i < 2; i++)
xd[i] = new Obj();
}
function draw(){
background(0,70,80);
noFill();
strokeWeight(7);
stroke(255);
ellipse(w/2, h/2, r*2, r*2);
xd[0].update();
xd[0].show();
}
The problem is that it says that ang is not defined even though i did clearly define it with this.ang = TWO_PI;. And if I declare it in the main file and in setup() I say ang = TWO_PI; the object stays in place. Can anyone help ?
Thank you.
The problem in the constructor function in this code, it should be like this :
this.x = w/2 + cos(this.ang)*r;
this.y = h/2 + sin(this.ang)*r;
Because you are using a property from the constructor function itself
Here is my code, it is supposed to be able to render a canvas with a snake and its food and whenever the snake is within 1 pixel of it, the food moves to a new location.
As I have said in the title the error reads/; TypeError: pos is undefined
var snake;
var scl = 10;
var food;
function setup()
{
//Sets the Canvas
createCanvas(700, 700);
//Creates a new object using the variable snake
snake = new Snake();
//Sets the frame rate
frameRate(10);
//Creates a vector called food
//setLocation();
}
function draw()
{
//Sets the Background, number implies the colour
background(50);
//Adds all the values set within the function to the snake
snake.updateSnake();
snake.showSnake();
snake.keyPressed();
if(snake.eatFood(food))
{
food.updateFood();
}
food.showFood();
food.updateFood();
}
/*Here we setup the food
//fill(255, 0, 10);
rect(food.x, food.y, scl, scl);
}
function setLocation()
//{
//var columns = floor(width/scl);
//var rows = floor(height/scl);
//food = createVector(floor(random(columns)), floor(random(rows)));
//food.mult(scl);
*/
function Food()
{
this.showFood = function()
{
fill(255, 0, 10);
rect(food.x, food.y, scl, scl);
}
this.updateFood = function()
{
var columns = floor(width/scl);
var rows = floor(height/scl);
food = createVector(floor(random(columns)), floor(random(rows)));
food.mult(scl);
}
}
function Snake()
{
this.x = 0;
this.y = 0;
this.xspeed = 0;
this.yspeed = 0;
this.updateSnake = function()
{
this.x = this.x + this.xspeed * scl;
this.y = this.y + this.yspeed * scl;
this.x = constrain(this.x, 0, width - scl);
this.y = constrain(this.y, 0, height - scl);
}
this.showSnake = function()
{
fill(255);
rect(this.x, this.y, scl, scl);
}
this.direction = function(x, y)
{
this.xspeed = x;
this.yspeed = y;
}
this.eatFood = function(pos)
{
var distance = dist(this.x, this.y, pos.x, pos.y);
if(distance < 1)
{
return true;
console.log("WITHIN RANGE");
}else
{
return false;
console.log("OUTSIDE RANGE");
}
}
this.keyPressed = function()
{
if (keyCode === UP_ARROW)
{
snake.direction(0, -1);
} else if (keyCode === DOWN_ARROW)
{
snake.direction(0, 1);
} else if (keyCode === RIGHT_ARROW)
{
snake.direction(1, 0);
} else if (keyCode === LEFT_ARROW)
{
snake.direction(-1, 0);
}
}
}
You never initialise food. You only declare the variable. You need to do: food = new Food(); somewhere in the program. Best to put it in the setup method. It also won't work because the Food function does not have a this.x and this.y.
I see you created the food variable, but I can't see where you actually initialize it. food is undefined, but you then pass it to the eatFood method, which gives you an error.
Try putting this in your setup function.
food = new Food()
Then you need to give it an initial x and y value. Perhaps using Math.random()
EDIT
You may need to rethink parts of your code.
Firstly, make the columns and rows a global variable or at least scoped to the Food class.
var snake;
var scl = 10;
var food;
var columns = floor(width/scl);
var rows = floor(height/scl);
You should call updateFood as soon as the Food is created. So put it in the constructor or call it immediately after creating food. Just make sure it comes after the this.updateFood method. Example:
function Food()
{
this.updateFood = ... // blah blah code here
...
this.updateFood();
}
To avoid writing over your food object, make sure you're setting that vector to something scoped to food. Such as a position.
this.updateFood = function()
{
this.pos = createVector(floor(random(columns)), floor(random(rows)));
this.pos.mult(scl);
}
Lastly, when the snake eats the food, make sure you're passing the food object and then grab it's position. (Unless you decide to give food a this.x and this.y field).
this.eatFood = function(food)
{
var distance = dist(this.x, this.y, food.pos.x, food.pos.y);
The benefit of doing it this way is that you can have multiple food items on the screen at once if you decide to.
It's calling the draw() function before food has been set. food is your pos variable, so it's crashing there. Make sure you initialize food to something before calling
if(snake.eatFood(food)){
food.updateFood();
}
Let's say I have two classes, one called Rectangle and one called Circle. Both classes have the values X and Y, is there a way to define the variables and functions once for both of the classes? Read the code below if it explains it better:
function Rectangle(msg, font){
this.msg = msg;
this.font = font;
}
function Circle(radius){
this.radius = radius;
}
Rectangle && Circle {
/* had no idea how to write the line above,
but basically for both the rectangle and the circle,
there is this code so I don't have to rewrite it for both.*/
this.position = function(x, y){
this.x = x;
this.y = y;
}
}
Yes, there is:
//creating class shape
function Shape(x,y){
this.x = x;
this.y = y;
};
//set position function
Shape.prototype.position = function(x,y){
this.x = x;
this.y = y;
};
function Rectangle(msg,font,x,y){
//calling object Shape
Shape.call(this,x,y);
this.msg = msg;
this.font = font;
}
//inheriting object Shape
Rectangle.prototype=Object.create(Shape.prototype);
function Circle(radius,x,y){
//calling object Shape
Shape.call(this,x,y);
this.radius = radius;
}
//inheriting object Shape
Circle.prototype=Object.create(Shape.prototype);
You can now call any function defined in Shape from a Rectangle or Circle object. Hope this helps.
Lately, on a number of js game making blogs and sites etc. I have seen a new convention for creating some kind of "instance". It looks like this:
var newCharacter = function (x, y) {
return {
x: x,
y: y,
width: 50,
height: 50,
update: function () {
//do update stuff here
}
};
};
var myCharacter = newCharacter(20, 30); //create "instance"
As opposed to the the traditional way:
var Character = function (x, y) {
this.x = x;
this.y = y;
this.width = 50;
this.height = 50;
this.update = function () {
//do update stuff here
};
};
var myCharacter = new Character(20, 30); //create intance
I was curious what the advantage of using the first way might be. Is it more efficent, semantic, or faster?
I've hit a wall trying to figure this out. I'm new to OO Javascript and trying to put together my first class/object. I'm trying to create a canvas loader and it's not working. I've narrowed down the error to the requestAnimationWindow portion inside my animate function in my clock class. I get a Object [object global] has no method 'animate' error. Here is my code.
HTML:
<div id="loader"><canvas id="showLoader" width="250" height="250"></canvas><div id="showTimer"><p id="elapsedTime">
<script>
var clockTest = new clock(document.getElementById("showLoader"), 0, 100);
clockTest.animate();
</script>
</p></div></div>
Javascript:
function clock(canvas, curPerc, endPrecent){
var showPerc = document.getElementById("elapsedTime");
this.canvas = document.getElementById("showLoader");
var context = this.canvas.getContext('2d');
var x = this.canvas.width / 2;
var y = this.canvas.height / 2;
var radius = 75;
this.curPerc = 0;
this.endPercent = 110;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
context.lineWidth = 10;
context.strokeStyle = '#ed3f36';
this.animate = function(current) {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.beginPath();
this.context.arc(x, y, radius, -(quart), ((circ) * current) - quart, false);
this.context.stroke();
this.curPerc++;
if(this.curPerc < this.endPercent) {
requestAnimationFrame(function () {
this.animate(curPerc / 100);
showPerc.innerHTML = this.curPerc + '%';
});
}
};
}
Any tips is appreciated. Thanks!
It is to do with the context of this in the anonymous function you pass to requestAnimationFrame, its not the this you think. Use a closure
i.e.
this.animate = function(current) {
var self = this; //<-- Create a reference to the this you want
self.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
/.. etc, etc..
if(self.curPerc < self.endPercent) {
requestAnimationFrame(function () {
self.animate(self.curPerc / 100); //<-- and use it here
showPerc.innerHTML = self.curPerc + '%'; //<-- and here
});
}
};
On a couple of other points, I would try to structure the object a bit better, you don't seem to be keeping reference to the properties correctly. The parameters you passed in , are not store on the object, and you are not storing the context correctly. Something like:
function clock(canvas, curPerc, endPrecent) {
var self = this;
// Set object properties here, i.e. from the parameters passed in
// Note also, anything that is a property (i.e. this. ) is public, can be accessed from otuside this object,
// whereas variable declared with var , are privte, can only be access within this object
self.canvas = canvas;
self.curPerc = curPerc;
self.endPercent = endPrecent;
self.context = self.canvas.getContext('2d'); //needs to be store like this, if you want to access below as this.context
self.context.lineWidth = 10;
self.context.strokeStyle = '#ed3f36';
//Private variables
var showPerc = document.getElementById("elapsedTime");
var x = self.canvas.width / 2;
var y = self.canvas.height / 2;
var radius = 75;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
//Methods
self.animate = function (current) {
self.context.clearRect(0, 0, self.canvas.width, self.canvas.height);
self.context.beginPath();
self.context.arc(x, y, radius, -(quart), ((circ) * current) - quart, false);
self.context.stroke();
self.curPerc++;
if (self.curPerc < self.endPercent) {
requestAnimationFrame(function () {
self.animate(curPerc / 100);
showPerc.innerHTML = self.curPerc + '%';
});
}
};
}
is starting to head in a better direction.
I ran into the same problem using three.js while calling requestAnimationFrame from inside an ES6 class method and the way I ended up solving it was by:
animate() {
requestAnimationFrame(() => this.animate());
this.render();
}