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.
Related
I am trying to make a sine wave with createBuffer(). Like following code.
var TWOPI = Math.PI * 2;
var Sinewave = function(freq, amp, phase){
var self = this;
this.src;
this.init = function(){
var osc = new self.Osc(phase);
var buffer = ac.createBuffer(1, osc.samplerate, osc.samplerate);
var buffering = buffer.getChannelData(0);
for(i = 0; i < buffering.length; i++){
buffering[i] = amp * this.sinetick(osc, freq);
}
this.src = ac.createBufferSource();
this.src.buffer = buffer;
this.src.loop = true;
this.src.connect(contect.destination);
}
this.Osc = function(_phase){
this.samplerate = context.sampleRate;
this.twopiovsr = TWOPI/this.samplerate;
this.curfreq = 0;
this.curphase = _phase;
this.incr = 0.0;
}
this.sinetick = function(osc, freq){
var val = Math.sin(osc.curphase);
if(osc.curfreq != freq){
osc.curfreq = freq;
osc.incr = osc.twopiovsr * freq;
}
osc.curphase += osc.incr;
if(osc.curphase >= TWOPI){
osc.curphase -= TWOPI;
}
if(osc.curphase < 0.0){
osc.curphase += TWOPI;
}
return val;
}
this.play = function(duration){
this.src.start(0);
this.src.stop(duration);
}
}
This only contains function for sine wave but I've succeeded to implement other shapes as well.
Reason for this in the first place was to set phase offset, if there's a better option please give me a notice.
So, When I look at the output signal through oscilloscope they look like how they should be. However when I try to modulate frequency of a regular createOscillator node with the signal created by code above, output is not pretty. When regular createOscillators nodes fm themselves, waves on the oscilloscope changes horizontally, only frequencies, but with the signal generated with the code above gives changes in both horizontal and vertical giving clips
what could be the problem? Thanks
And the problem was
var buffer = ac.createBuffer(1, osc.samplerate, osc.samplerate);
which should be
var buffer = ac.createBuffer(1, osc.samplerate/2, osc.samplerate);
Thanks guys
edit: SOLVED
I'm a highschool student in Japan trying to learn how to program.
I recently viewed https://vimeo.com/105955605 this video, and decided I could use the beginning section to start building pong in javascript.
I'm pretty much a complete novice with programming and/or javascript and I still have a long way to go.
I got Player1 (left paddle) to work on its own, so I figured I could just copy paste, mess with a couple things, and make Player2. However, now Player2 moves when I press w/s, but Player1 no longer moves.
I've tried creating 2 separate keyboarder() functions, using this.keyboarder from Player1 in player2 (Player2.keyboarder = Player1.keyboarder() ), and declaring/calling keyboarder() before doing anything else.
HTML:
<html>
<head>
<meta charset="UTF-8">
<title>Pong</title>
<link type="text/css" rel="stylesheet" href="css/stylesheet.css">
</head>
<body>
<canvas id="screen" width="310" height="210"></canvas>
<script src="js/pong.js"></script>
</body>
</html>
JavaScript:
;(function(){
//Main game function
//tells objects in bodies array to update.
//stores gameSize pulled from canvasId
var Game = function(canvasId){
var canvas = document.getElementById(canvasId);
var screen = canvas.getContext('2d');
var gameSize = {x: canvas.width, y: canvas.height};
var self = this;
//bodies array
this.bodies = [new Player1(this, gameSize), new Player2(this, gameSize)];
//update function
var tick = function(){
self.update();
self.draw(screen,gameSize);
requestAnimationFrame(tick);
};
tick();
};
//constructer for game() function. tells bodies to update, and draw
Game.prototype = {
update: function(){
for(var i =0 ; i < this.bodies.length; i++){
this.bodies[i].update();
}
},
draw:function(screen,gameSize){
screen.clearRect(0,0,gameSize.x,gameSize.y);
for(var i =0 ; i < this.bodies.length; i++){
drawRect(screen, this.bodies[i]);
}
}
};
//P1 object, declares size and start position of P1
var Player1= function(game, gameSize){
this.size = {x:30,y:gameSize.y / 3};
this.game = game;
this.gameSize = gameSize;
this.center = {x: 0, y:gameSize.y/2};
this.keyboarder = new Keyboarder();
requestAnimationFrame(this.update);
};
//constructor for P1, updates position based on keyboard input
Player1.prototype = {
update:function(){
if (this.keyboarder.isDown(this.keyboarder.KEYS.DOWN) && this.center.y < (5*this.gameSize.y / 6)){
this.center.y += 4;
}else if(this.keyboarder.isDown(this.keyboarder.KEYS.UP) && this.center.y > this.size.y /2 ){
this.center.y -= 4;
}
}
};
//P2, same as P1 aside from position
var Player2= function(game, gameSize){
this.size = {x:30,y:gameSize.y / 3};
this.game = game;
this.gameSize = gameSize;
this.center = {x: gameSize.x, y:gameSize.y/2};
this.keyboarder = new Keyboarder();
requestAnimationFrame(this.update);
};
//constructor for P2, same as P1
Player2.prototype = {
update:function(){
if (this.keyboarder.isDown(this.keyboarder.KEYS.S) && this.center.y < (5*this.gameSize.y / 6)){
this.center.y += 4;
}else if(this.keyboarder.isDown(this.keyboarder.KEYS.W) && this.center.y > this.size.y /2 ){
this.center.y -= 4;
}
}
};
//Draw function, draws object
var drawRect = function(screen, body){
screen.fillRect(body.center.x - body.size.x /2,
body.center.y - body.size.y /2, body.size.x,body.size.y);
};
//Keyboard input function
//reads if keys are being pressed and takes the event code
//isDown() returns boolean of key down = true, key up = false
var Keyboarder = function(
){
var keyState = {};
window.onkeydown = function(e){
keyState[e.keyCode] = true;
};
window.onkeyup = function(e){
keyState[e.keyCode] = false;
};
this.KEYS = {DOWN: 37, UP:39,W:87 , S: 83};
this.isDown = function(keyCode){
return keyState[keyCode] === true;
};
};
//calls game() function when the page has loaded.
window.onload = function(){
new Game("screen")
};
})();
Sorry if this is bad protocol for using stackoverflow, I'm also new to asking questions here.
The problem is you have two Keyboarder instances, and they're both binding to the key events by assigning a handler directly to them - this will overwrite any other handlers. There's two ways to fix it:
1: Don't assign directly, instead use addEventListener, eg:
window.addEventListener('keydown', function(e){
keyState[e.keyCode] = true;
});
2: Use the same keyboarder instance for both players:
var kb = new Keyboarder();
this.bodies = [new Player1(this, gameSize, kb), new Player2(this, gameSize, kb)];
and in your player object assign it to this 3rd parameter. Even if you go this route, I would still advise on using addEventListener as well simply to ensure that the events can be bound to in multiple places if needed.
On another point, you should also be able to refactor your players into a single function and create two instances of it with different parameters, but that sort of thing is better dealt with over on Code Review.
When you create two Keyboarder instances, one collides with the other, because of this:
window.onkeydown = function(e){
keyState[e.keyCode] = true;
};
window.onkeyup = function(e){
keyState[e.keyCode] = false;
};
When you create one Keyboarder, the event listeners the previous one attached are overriden. Try something like this:
window.addEventListener('keydown', function(e){
keyState[e.keyCode] = true;
});
window.addEventListener('keyup', function(e){
keyState[e.keyCode] = false;
});
This ensures there are listeners for every instance of the Keyboarder.
Hello stackoverflow community!
First I must say that I dont have much experience with constructors.
So. What I am trying to do, is to animate a parachutist to fly from top to bottom of the screen.
I thought I could use a constructor to set up a parachutist:
var parachute = function() {
this.height = 35;
this.width = 30;
this.speed = 50;
this.xPos = Math.round(Math.random() * (window.width - this.width));
this.animate = function() {
this.img = new Image();
this.yPos = 0;
this.img.onload = function() {
ctxPara.globalCompositeOperation = 'copy';
ctxPara.translate(0, this.yPos);
ctxPara.drawImage(this.img, this.xPos, 0);
};
this.img.src = 'para.png';
this.yPos++;
};
};
This constructor is used in a function called 'fly':
var fly = function() {
var newParachute = new parachute();
setInterval(newParachute.animate, newParachute.speed);
};
And this 'fly' function is triggered when the window loads:
window.onload = function() {
var canvasBg = document.getElementById('canvasBg');
// I splitt the Background and the parachutists in two canvas elements
// handling the problem (to erase content and draw new content) with
// the canvas animation.
var canvasPara = document.getElementById('canvasPara');
ctxPara = canvasPara.getContext('2d');
canvasPara.width = window.width;
canvasPara.height = window.height;
canvasBg.width = window.width;
canvasBg.height = window.height;
fly();
clouds(); // background is loading here
};
What you should see, is a Parachutist flying down the screen. But unfortunately you don't...
Now, after that Long text. (Iam very sorry that it is so long :-( ) My question is: Do you know what I am doing wrong? Is my constuctor correct? Is, what i am trying to do, supposed to be written like this? Any advices or suggestions for a succesfull opportunity? (I hope my english isn't that terrible I think it is :-) )
Oh i forgot to mention the error. It's a TypeMissMatchError.
That means 'this.img' is not an img element at this line:
ctxPara.drawImage(this.img, this.xPos, 0);
Now, I followed the example of markE.
Instead of showing me a parachutist. It shows me an error in this line: ctxPara.drawImage(this.img, this.xPos, this.yPos);
var fly = function () {
var newParachute = new parachute();
newParachute.img.load.call(newParachute);
setInterval(newParachute.animate.call(newParachute), newParachute.speed);
};
var parachute = function () {
this.height = 35;
this.width = 30;
this.speed = 25;
this.xPos = Math.round(Math.random() * (window.innerWidth - this.width));
this.img = new Image();
this.yPos = 0;
this.img.isLoaded = false;
this.img.load = function () {
this.img.isLoaded = true;
};
this.img.src = 'parachute.png';
this.animate = function () {
if (this.img.isLoaded) {
ctxPara.clearRect(0, 0, canvasPara.width, canvasPara.height);
ctxPara.drawImage(this.img, this.xPos, this.yPos); // ERROR: 'Unknown Error'.
this.yPos++;
console.log('animating');
}
};
};
I am stuck again. But now i don't even know the reason... Please help!?
Demo: http://jsfiddle.net/m1erickson/ym55y/
A couple of issues:
(1) To get the window width you can use:
window.innerWidth
(2) setInterval calls newParachute.animate.
setInterval(newParachute.animate, newParachute.speed);
But this inside animate the window object--not the Parachute object.
To give the correct this to animate you can use the call method like this:
var newParachute = new parachute();
setInterval(function(){newParachute.animate.call(newParachute);}, newParachute.speed);
(3) You need to deal with clearing previously drawn images or they will still show on your canvas.
How can I make the snow clear after a certain time. I've tried using variables and the calling a timeout which switches on to false and stops the makesnow() function but that doesn't seem to clear the page at all.
<script language="javascript">
ns6 = document.getElementById;
ns = document.layers;
ie = document.all;
/*******************[AccessCSS]***********************************/
function accessCSS(layerID) { //
if(ns6){ return document.getElementById(layerID).style;} //
else if(ie){ return document.all[layerID].style; } //
else if(ns){ return document.layers[layerID]; } //
}/***********************************************************/
/**************************[move Layer]*************************************/
function move(layer,x,y) { accessCSS(layer).left=x; accessCSS(layer).top = y; }
function browserBredde() {
if (window.innerWidth) return window.innerWidth;
else if (document.body.clientWidth) return document.body.clientWidth;
else return 1024;
}
function browserHoyde() {
if (window.innerHeight) return window.innerHeight;
else if (document.body.clientHeight) return document.body.clientHeight;
else return 800;
}
function makeDiv(objName,parentDiv,w,h,content,x,y,overfl,positionType)
{
// positionType could be 'absolute' or 'relative'
if (parentDiv==null) parentDiv='body';
var oDiv = document.createElement ("DIV");
oDiv.id = objName;
if (w) oDiv.style.width = w;
if (h) oDiv.style.height= h;
if (content) oDiv.innerHTML=content;
if (positionType==null) positionType="absolute";
oDiv.style.position = positionType;
if (x) oDiv.style.left=x; else oDiv.style.left=-2000;
if (y) oDiv.style.top=y; else oDiv.style.top=-2000;
if (overfl) oDiv.style.overflow=overfl; else oDiv.style.overflow="hidden";
eval(' document.'+parentDiv+'.appendChild (oDiv); ');
delete oDiv;
}
var snowC=0;
var x = new Array();
var y = new Array();
var speed = new Array();
var t=0;
var cC = new Array();
var ra = new Array();
function makeSnow() {
x[snowC] = Math.round(Math.random()*(browserBredde()-60));
y[snowC] = 10;
makeDiv("snow"+snowC,"body",32,32,'<img src="http://i693.photobucket.com/albums/vv296/KIBBLESGRAMMY/CAT/Orange-tabby-cat-icon.gif">');
speed[snowC] = Math.round(Math.random()*8)+1;
cC[snowC]=Math.random()*10;
ra[snowC] = Math.random()*7;
snowC++;
}
function moveSnow() {
var r = Math.round(Math.random()*100);
if (r>70 && snowC<20) makeSnow();
for (t=0;t<snowC;t++) {
y[t]+=speed[t];move("snow"+t,x[t],y[t]);
if (y[t]>browserHoyde()-50) {y[t] = 10;x[t] = Math.round(Math.random()*(browserBredde()-60));}
cC[t]+=0.01;
x[t]+=Math.cos(cC[t]*ra[t]);
}
setTimeout('moveSnow()',20);
}
moveSnow();
</script>
makeSnow just adds the snowflakes. Stopping that, as you say, does not clear anything. moveSnow handles the animation, and calls itself at a timeout. If instead of setting a timeout for the next moveSnow each time, you set it up to run in an interval just once, you would have an easier time stopping it.
window.snowAnimation = window.setInterval(moveSnow, 20);
If you add a css class to your snow flakes, it would be easier to target them for deletion.
oDiv.className = 'snowflake';
Then your clear function could look something like:
function clearSnow() {
window.clearTimeout(window.snowAnimation);
var flakes = document.getElementsByTagName('snowflake');
for(var i = 0, l = flakes.length; i < l; i++) {
document.body.removeChild(flakes[i]);
}
}
Timeout doesnt help, it helps you only to stop creating new snowdivs, however if you see makeDiv is the one which creates new divs on to the body, if you clear / display:none the divs which got created on makeDiv i hope it will clear all the divs on the screen.
You need to remove the divs that were created. It might be easier if you give them all some sort of class, like ".snowflake" as you create them (in makeDiv), then start removing them from the dom.
You will have to clear the elements created after the time you wanna stop snow falling.
Following code snippet will help you to clear the elements
if(count < 500){
setTimeout('moveSnow()',20);
}else{
var i = 0;
var elem = document.getElementById('snow'+i);
do{
elem.parentNode.removeChild(elem);
elem = document.getElementById('snow'+ ++i);
}while(elem != null)
}
count++;
you have to create a global variable count.
I am newbie to javascript objects and I have a problem. I am trying to make an image gallery but I keep getting an error that this.current, this.size & this.initial are undefined, and therefore, the script cannot work. Please help me resolve this error. the following is the full script.
function gallery()
{
this.image = new Array(10);
this.initial = 1;
this.current = 0;
this.size = 10;
this.frame_height = 400;
this.frame_width = 600;
this.initialize=function()
{
if(document.images)
{
var count = 1;
for(count=1;count<=this.size;count++)
{
this.image[count] = new Image();
this.image[count].src = 'images/'+count+'.jpg';
}
}
divImg.id = "divImg";
divImg.setAttribute("Width",this.frame_width);
divImg.setAttribute("align","center");
divImg.setAttribute("margin","0px auto");
divBtn.id = "divBtn";
divBtn.setAttribute("align","center");
divBtn.setAttribute("backgroung-color","Black");
divBtn.setAttribute("color","White");
divBtn.style.margin = "0px auto";
divBtn.className ="btn";
pictureFrame.src = this.image[this.initial].src;
pictureFrame.setAttribute('Width',this.frame_width);
pictureFrame.setAttribute('Height',this.frame_height);
pictureFrame.name = 'img';
btnNext.innerHTML='NEXT';
btnPrevious.innerHTML='PREVIOUS';
btnLast.innerHTML='LAST';
btnFirst.innerHTML='FIRST';
btnFirst.onclick=this.first;
btnLast.onclick=this.last;
btnPrevious.onclick=this.previous;
btnNext.onclick=this.next;
myForm.appendChild(pictureFrame);
divImg.appendChild(myForm);
divBtn.appendChild(btnFirst);
divBtn.appendChild(btnPrevious);
divBtn.appendChild(btnNext);
divBtn.appendChild(btnLast);
pageBody.appendChild(divImg);
pageBody.appendChild(divBtn);
headerTag.appendChild(pageBody);
}
this.next=function()
{
alert(this.size);
alert(this.current);
if (this.current < this.size)
{
this.current +=1;
pictureFrame.src = this.image[this.current].src;
}
else
{
alert("This is the last image");
}
}
this.previous=function()
{
alert(this.current);
alert(this.initial);
if (this.current > this.initial)
{
this.current = this.current-1;
pictureFrame.src = this.image[this.current].src;
}
else
{
alert("This is the first image");
}
}
this.first=function()
{
this.current=this.initial;
pictureFrame.src = this.image[this.current].src;
}
this.last=function()
{
alert(this.size);
this.current=this.size;
pictureFrame.src = this.image[this.current].src;
}
};
var divImg= document.createElement('div');
var divBtn = document.createElement('div');
var btnFirst= document.createElement('button');
var btnNext= document.createElement('button');
var btnPrevious= document.createElement('button');
var btnLast= document.createElement('button');
var divTop = document.createElement('div');
var headerTag = document.getElementsByTagName('html')[0];
var pageBody = document.createElement('body');
var myForm=document.createElement("form");
var pictureFrame = document.createElement('img');
var pics=new gallery();
window.onload=pics.initialize();
You're expierencing an out of scope failure which is very common to people who are new to ECMAscript.
Every function has it's own execution context and each context in ECMA-/Javascript has its own this context variable. To avoid this, the most common way is to store a reference to the "outer" this context-variable in a local variable:
function gallery()
{
this.image = new Array(10);
this.initial = 1;
this.current = 0;
this.size = 10;
this.frame_height = 400;
this.frame_width = 600;
var self = this;
//...
this.initialize=function()
{
if(document.images)
{
var count = 1;
for(count=1;count<=self.size;count++)
{
self.image[count] = new Image();
self.image[count].src = 'images/'+count+'.jpg';
}
}
// ...
This will work in like every Javascript environment. In the meantime, there are "better" (other) ways to avoid this problem in ECMA. For instance, ECMAscript Edition 5 introduces the .bind() method which let you "bind" the reference from the this context variable to an object of your choice. Lots of javascript frameworks offer a pretty similar way of binding this to an object, even Javascript itself lets you do it with a little effort.
What jAndy said is correct, when the window object calls pics.initialise, this refers to window (this refers to the caller of the function, unless the function is anonymous).
However there is a simpler solution that you may prefer:
Instead of
var pics = new gallery();
window.onload = pics.initialize;
You can do:
var pics = new gallery();
window.onload = function() {
pics.initialize();
};
Because it is wrapped in an anonymous function, this will refer to the gallery instance, instead of window.
jAndy's suggestions are definitely more robust, but may be a bit difficult for someone still grappling with Javascript.