I'm following a tutorial on how to make a javascript game, but i'm stuck on the return part. Why are is there { }, and what is the init: init for? Any help would be appreciated. Thanks.
var JS_SNAKE = {};
JS_SNAKE.game = (function () {
var ctx;
var xPosition = 0;
var yPosition = 0;
var frameLength = 500; //new frame every 0.5 seconds
function init() {
$('body').append('<canvas id="jsSnake">');
var $canvas = $('#jsSnake');
$canvas.attr('width', 100);
$canvas.attr('height', 100);
var canvas = $canvas[0];
ctx = canvas.getContext('2d');
gameLoop();
}
function gameLoop() {
xPosition += 2;
yPosition += 4;
ctx.clearRect(0, 0, 100, 100); //clear the canvas
ctx.fillStyle = '#fe57a1';
ctx.fillRect(xPosition, yPosition, 30, 50); //a moving rect
setTimeout(gameLoop, frameLength); //do it all again
}
return {
init: init
};
})();
$(document).ready(function () {
JS_SNAKE.game.init();
});
The {} is an object literal in JavaScript. The statement
return {
init: init
}
returns an object with one property. That property's key is init and value is whatever value the variable named init has (in this case, a function).
In case that syntax is confusing, this is equivalent and might be clearer:
JS_SNAKE.game = (function () {
// snip...
function performInitialization() {
// snip...
}
// snip ...
return {
init: performInitialization
};
})();
That is something that is called a module pattern - where you enclose your "class" (or represent it, if you will) with an anonymous function.
The function returns the JS object that can be used to access "class" methods and variables, but only those that are exposed (public) - such as init.
{} is object literal - it is used here to declare an empty JS_SNAKE object - which will serve as a namespace for the following "class" declaration.
Related
I'm trying to create a simple drawing application using ReactJS and Flux. However, I came across a bug [Uncaught RangeError: Maximum call stack size exceeded] (it's more specifically a ReactJS problem) I've been trying figure out for a while now. It occurs when I'm trying to do setState() in a component. Below is part of my code to better illustrate my problem.
DrawingCanvas.react.js
var React = require('react');
var MouseInteractionsMixin = require('../utils/MouseInteractionsMixin');
var StrokeActionCreators = require('../actions/StrokeActionCreators');
var StrokeStore = require('../stores/StrokeStore');
function getStateFromStores() {
return {
currentStroke: StrokeStore.getCurrentStroke()
};
}
var DrawingCanvas = React.createClass({
mixins: [MouseInteractionsMixin],
styles: {
canvas: {
border: '1px solid black'
}
},
getInitialState: function() {
return getStateFromStores();
},
componentWillMount: function() {
StrokeStore.addChangeListener(this._update);
},
componentDidMount: function() {
this._onChange();
},
componentDidUpdate: function() {
this._onChange();
},
_onChange: function() {
if(this.state.dragging) {
StrokeActionCreators.beginStroke(this.state.mouse.point);
} else {
if(this.state.mouse.event == 'mouseup') {
StrokeActionCreators.endStroke(this.state.currentStroke);
}
}
},
_update: function() {
this.setState(getStateFromStores()); // where the problem occurs
context = this.getDOMNode().getContext('2d');
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
this._draw(context);
},
_draw: function(context) {
context.strokeStyle = '#000000';
context.lineJoin = 'round';
context.lineWidth = 5;
// Draw current stroke (incompleted)
for(var index = 1; index < this.state.currentStroke.points.length; index++) {
context.beginPath();
context.moveTo(this.state.currentStroke.points[index - 1].x, this.state.currentStroke.points[index - 1].y);
context.lineTo(this.state.currentStroke.points[index].x, this.state.currentStroke.points[index].y);
context.closePath();
context.stroke();
}
/*// Draw the other completed strokes
for(var strokeIndex = 0; strokeIndex < this.state.strokes.length; strokeIndex++) {
var stroke = this.state.strokes[strokeIndex];
for(var currentPointIndex = 1; currentPointIndex < stroke.points.length; currentPointIndex++) {
var previousPointIndex = currentPointIndex - 1;
context.beginPath();
context.moveTo(stroke.points[previousPointIndex].x, stroke.points[previousPointIndex].y);
context.lineTo(stroke.points[currentPointIndex].x, stroke.points[currentPointIndex].y);
context.closePath();
context.stroke();
}
}*/
},
render: function() {
return (
<canvas style={this.styles.canvas} width={this.props.width} height={this.props.height} />
);
}
});
module.exports = DrawingCanvas;
Basically, what I'm trying to do is trigger an action when the mouse is clicked and starts moving on the canvas, which sends the mouse position data to the StrokeStore. The StrokeStore stores all the mouse position data, so that eventually the DrawingCanvas component requests for the data (hence the getStateFromStores() function) and draws on the canvas, but when I try to set the state in the _update() function and try drawing on the canvas, there would be a large amount of mouse position data coming in (even after moving the mouse a tiny bit then stopping it) and an error "Uncaught RangeError: Maximum call stack size exceeded" would come up.
I've tried not storing the mouse position data as a state of the component, but rather separate variables outside the component which I use inside the component, and it works perfectly fine, so I think it's a problem with the setState().
I'd really like to get it working as states (or any other way so that I can contain the data in the stores).
EDIT:
StrokeStore.js
var AppDispatcher = require('../dispatchers/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var ActionTypes = AppConstants.ActionTypes;
var CHANGE_EVENT = 'change';
var _currentStroke = {
// Points array contains point objects with x and y variables
points: []
};
// Strokes array contains stroke objects with similar structure to currentStroke
var _strokes = [];
function _addPointToCurrentStroke(point) {
_currentStroke.points.push(point);
}
function _addStrokeToStrokes(stroke) {
_strokes.push(stroke);
}
function _clearCurrentStroke() {
_currentStroke = {
points: []
};
}
var StrokeStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* #param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCurrentStroke: function() {
return _currentStroke;
},
getStrokes: function() {
return _strokes;
}
});
AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.BEGIN_STROKE:
_addPointToCurrentStroke(action.point);
break;
case ActionTypes.END_STROKE:
_addStrokeToStrokes(action.stroke);
_clearCurrentStroke();
break;
default:
}
StrokeStore.emitChange();
});
module.exports = StrokeStore;
Any help is appreciated, thanks!
I am trying to draw an array of images on canvas at random x,y but it gives me an infinite loop.... here is my code
var fruits = ["fruit1.png", "fruit2.png", "fruit3.png", "fruit4.png"];
var monsterReady1 = true;
var draw = function() {
for (var i = 0; i < fruits.length; i++) {
monsterImage1 = new Image();
monsterImage1.onload = function () {
monster1.x = (Math.random() * (canvas.width - 100));
monster1.y = (Math.random() * (canvas.height - 100));
ctx.drawImage(this, monster1.x, monster1.y);
};
monsterImage1.src = fruits[i];
}
};
var render = function() {
if (monsterReady1) {
draw();
}
var main = function () {
update();
render();
requestAnimationFrame(main);
};
You have recursive in main() function. This is normal behaviour of requestAnimationFrame(). It is normal to call that function infinite to draw canvas each frame.
There is also recursion when render() executes. You don't need to call render again and again. Pass out render call from render() body
var render = function(){
if (monsterReady1) {
draw();
}
var main = function () {
update();
render(); // THIS is error. You should not call render again
requestAnimationFrame(main); // This will call main function infinite loop. Expected.
}
};
//render(); // Better to call it here
By the way in code you provide there is a syntax error. You missed one closing bracket
Although I have used Javascript extensively in the past, I have never used classes and objects in my programs. This is also the first for me using the HTML5 canvas element with an extra Javascript library. The library I'm using is EaselJS.
Short and sweet, I'm trying to make a square move with keyboard input, using object-oriented programming. I've already looked over sample game files, but I've never been able to properly get one to work.
The following is my classes script:
/*global createjs*/
// Shorthand createjs.Shape Variable
var Shape = createjs.Shape;
// Main Square Class
function square(name) {
this.name = name;
this.vX = 0;
this.vY = 0;
this.canMove = false;
this.vX = this.vY = 0;
this.canMove = false;
this.body = new Shape();
this.body.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100);
}
And below is my main script:
/*global createjs, document, window, square, alert*/
// Canvas and Stage Variables
var c = document.getElementById("c");
var stage = new createjs.Stage("c");
// Shorthand Create.js Variables
var Ticker = createjs.Ticker;
// Important Keycodes
var keycode_w = 87;
var keycode_a = 65;
var keycode_s = 83;
var keycode_d = 68;
var keycode_left = 37;
var keycode_right = 39;
var keycode_up = 38;
var keycode_down = 40;
var keycode_space = 32;
// Handle Key Down
window.onkeydown = handleKeyDown;
var lfHeld = false;
var rtHeld = false;
// Create Protagonist
var protagonist = new square("Mr. Blue");
// Set Up Ticker
Ticker.setFPS(60);
Ticker.addEventListener("tick", stage);
if (!Ticker.hasEventListener("tick")) {
Ticker.addEventListener("tick", tick);
}
// Init Function, Prepare Protagonist Placement
function init() {
protagonist.x = c.width / 2;
protagonist.y = c.height / 2;
stage.addChild(protagonist);
}
// Ticker Test
function tick() {
if (lfHeld) {
alert("test");
}
}
// Handle Key Down Function
function handleKeyDown(event) {
switch(event.keyCode) {
case keycode_a:
case keycode_left: lfHeld = true; return false;
case keycode_d:
case keycode_right: rtHeld = true; return false;
}
}
This is the error I get in the Developer Tools of Chrome:
Uncaught TypeError: undefined is not a function
easeljs-0.7.0.min.js:13
In case you're wondering, the order of my script tags is the EaselJS CDN, followed by my class, followed by the main script file.
I would really like closure on this question. Thank you in advance.
I figured it out. I was adding the entire protagonist instance to the stage. I've fixed by adding the protagonist.body to the stage.
I have no idea why this code does not loop as it should. My mind is blown and hopefully someone can give me a hand. This is my first attempt into the HTML5 and JavaScript world and my first StackOverflow post. My background is in java so that should explain the quirks in my code. By the way, if you run the code the canvas and balls will show up, just not move.
First off, here is the HTML5
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>ChainReaction5</title>
<script type="text/javascript" src="chain_reaction.js"></script>
</head>
<body>
<body onLoad="init();">
<canvas id="myCanvas" width="500" height="400">
Your browser dosen't support the HTML5 canvas.</canvas><br />
</body>
</html>
Secondly here is the js
//gobal vars
var context;
var box;
var balls;
var defaultBallX=240;
var defaultBallY=190;
var defaultBallRad=6;
var defaultBallV=5;
var defaultNumBalls=10;
//box class
function Box() {
var boxx=20;
var boxy=20;
var boxWidth=460;
var boxHeight=360;
this.getX = function() {return boxx;}
this.getY = function() {return boxy;}
this.getWidth = function() {return boxWidth;}
this.getHeight = function() {return boxHeight;}
this.getBalls = function() {return ball;}
this.paintMe = function() {
context.fillStyle = "black";
context.strokeRect(boxx, boxy, boxWidth, boxHeight);
}
}
/* Box Class
* this class is sloppy but more memory efficent
*/
function Ball(x, y, radius, vx, vy, color) {
this.x=x;
this.y=y;
this.radius=radius;
this.vx=vx;
this.vy=vy;
this.color=color;
this.paintMe = function() {
context.beginPath();
context.arc(this.x, this.y, radius, 0, 2*Math.PI, true);
context.fillStyle = this.color;
context.fill();
}
}
Array.prototype.appendBalls = new function(array) {}
Array.prototype.clearBalls = new function() {}
Array.prototype.appendBalls = function(array) {
for (var i = 0; i < array.length; i++) {
balls.push(array[i]);
}
}
Array.prototype.clearBalls = function() {
balls = new Array();
}
// begin program
function init() {
context = document.getElementById("myCanvas").getContext("2d");
box = new Box();
balls = new Array();
balls.appendBalls(createBalls(box, defaultNumBalls));
setInterval(moveBall(balls, box), 100);
}
function createBalls(box, numBalls) {
var locBalls = new Array(numBalls);
for (var i = 0; i < numBalls; i++) {
var randx = randp(50, 400)
var randy = randp(50, 300);
var randr = Math.random()*defaultBallRad+1;
var randvx = randv();
var randvy = randv();
var randc = randColor();
locBalls[i] = new Ball(randx, randy, randr, randvx, randvy, randc);
}
return locBalls;
function randv() {
var neg = 1;
if (Math.random()>.5) neg = -neg;
return Math.random()*defaultBallV*neg;
}
function randp(low, hight) {
if (low < 0) low = 0;
var p = -1;
while (p > hight || p < low) {
p = Math.random()*hight;
}
return p;
}
function randColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.round(Math.random() * 15)];
}
return color;
}
}
function moveBall(balls, box) {
clear(this.box);
this.box.paintMe();
for (var i = 0; i < this.balls.length; i++) {
moveAndCheck(this.balls[i], this.box);
}
}
function moveAndCheck(b, box) {
if ((b.x+b.vx+b.radius-1)>(this.box.boxWidth+this.box.boxx) || b.x+b.vx-b.radius<this.box.boxx+1) {
b.vx = -b.vx;
}
if ((b.y+b.vy+b.radius-1)>(this.box.boxHeight+this.box.boxy) || b.y+b.vy-b.radius<this.box.boxy+1) {
b.vy = -b.vy;
}
b.x += b.vx;
b.y += b.vy;
b.paintMe();
}
function clear(box) {
context.clearRect(this.box.boxx, this.box.boxy,
this.box.boxWidth, this.box.boxHeight);
}
The first time I tried running it I got the following in the Firebug console:
useless setInterval call (missing quotes around argument?)
[Break On This Error] setInterval(moveBall(balls, box), 100);
Putting quotes around 'moveBalls(balls, box)' animates things.
Incidentally, you can use prototype inhertiance to make your function a bit more efficient, the methods in Box are given to each instance. To have them inherit the methods, put them on the constructor's prototype:
Box.prototype = {
getX: function() {return this.boxx;},
getY: function() {return this.boxy;},
getWidth: function() {return this.boxWidth;},
getHeight: function() {return this.boxHeight;},
getBalls: function() {return this.ball;},
paintMe: function() {
context.fillStyle = "black";
context.strokeRect(this.boxx, this.boxy, this.boxWidth, this.boxHeight);
}
};
Note that in javascript, a function's this keyword is set by the call, it is not set by how you declare the function (though you can use ES5 bind, but that is not widely supported yet).
Some other hints:
In the Box constructor you are making local variables but you really want to assign them to the new Box instance, so use this instead of var:
function Box() {
this.boxx=20;
this.boxy=20;
this.boxWidth=460;
this.boxHeight=360;
}
In the clearBox function, you are using this when it is not set in the call, so it references window. Just get rid of it, you pass box to the function so reference it directly:
function clear(box) {
context.clearRect(box.boxx, box.boxy,
box.boxWidth, box.boxHeight);
}
Same applies to the moveBall and moveAndCheck functions, just get rid of this (I think you should do some research on how this is handled in javascript, there are many articles on it, it's quite different to Java).
Now the balls will bounce around nicely inside the box.
I want to thank the people who contributed to my question and it has been helpful in resolving the issue and the answer I selected was current in a way, but it fixed the problem for a different reason.
Incorrect:
Putting quotes around 'moveBalls(balls, box)' animates things.
What actually fixes the problem is removing the arguments and parentheses from the call to the moveball function. I discovered this when rewriting other parts of my code as the poster suggested.
So for future notice to other people with a similar problem if you need to remove the arguments and use a wrapper function or global variables.
have an object of a class Abon and then i want this object to move around the page.
a = new Abon();
a.init();
a.move();
the method move() contains:
function abon_move () {
var x = this.x;
var y = this.y;
var direction_x = Math.random()*5 - 5;
var direction_y = Math.random()*5 - 5;
var x_new = x + direction_x * this.movement_rate;
var y_new = y + direction_y * this.movement_rate;
console.log(x_new+" "+y_new)
$(".abonent."+this.id).animate( {
left:+x_new,
top:+y_new
}, 'slow', "linear", function() { this.move() });
}
All i want is that the method move (represented as function abon_move()) repeated again and again, after the animate stops. But the problem is that this.move() shown in callback has no connection to my object, because this in that place points to the HTML element, selected by jQuery.
UPD:
function Abon(id) {
...
this.move = abon_move;
...
}
Abon.prototype.move = abon_move;
And the actual method is the same, but with no callback in animate
then i try doing the following:
setInterval( a[0].move , 300); //doesn't work - says `this` members are undefined
setInterval( a[0].move() , 300); //works only one time and stops
Thank you for any help!
Try this :
function abon_move () {
var x = this.x;
var y = this.y;
var class = this;
...
}
Then, inside your jQuery animate, your can refer to your class using the variable class
Wrap the function abon_move() in a setTimeout call, as such: setTimeout(abon_move, 300); so it will run every 300 ms.