I've been attempting to program a start up section for my space invaders game. However, when the body loads one of my objects (the startbtn variable) is not appearing on the canvas.
Bellow is the code to my game
<html>
<head>
<title>Space Invaders</title>
</head>
<body onload="startGame()">
<script type="text/javascript">
var startbtn;
function startGame(){
startbtn = new compenent(50, 20, "blue", 120, 10);
myGameArea.start();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function(){
this.canvas.width = 420;
this.canvas.height = 220;
ctx = this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas,
document.body.childNodes[0]);
ctx.font="35px Verdena";
ctx.fillText("Welcome to Space Invaders",10,50);
},
clear : function(){
this.context.clearRect(0, 0, this.canvas.width ,
this.canvas.height);
}
}
function compenent(width, height, color, x, y){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function updateGame(){
myGameArea.clear();
startbtn.update();
}
</script>
</body>
</html>
If someone could help me, that would be great.
The main problem is that updateGame() is never called. You can add it to the startGame() function to run after the canvas is set up. Having done this, the blue box shows but not the text. The reason is because myGameArea.clear(); clears the text that was drawn in the myGameArea.start function. Removing myGameArea.clear(); results in:
var startbtn;
function startGame() {
startbtn = new compenent(50, 20, "blue", 120, 10);
myGameArea.start();
updateGame();
}
var myGameArea = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 420;
this.canvas.height = 220;
ctx = this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas,
document.body.childNodes[0]);
ctx.font = "35px Verdena";
ctx.fillText("Welcome to Space Invaders", 10, 50);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width,
this.canvas.height);
}
}
function compenent(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function updateGame() {
//myGameArea.clear();
startbtn.update();
}
startGame(); // moved from body.onload
However, at this point, it may be clear that there are some potential design obstacles ahead in making a full-featured space invaders game. The w3schools tutorial you're using is attempting a basic entity-component system, but this might be confusing for starters, so I'd encourage you to poke around at other tutorials for context if nothing else.
As your example is moving towards, animations have basic parts:
initialization routine (create objects and set initial positions for everything)
update routine (reposition objects, calculate collisions and damage)
rendering routine (draw the frame)
The init runs one time, or in some cases whenever an animation (or game level) needs resetting. The update and rendering routines run many times a second and together constitute the main animation loop (each one executes once per frame, so they're essentially parts of the same "animation loop" function):
----------------
| |
v |
init -----> update -----> render
This requires that each routine be responsible for their task and nothing else. Every call to render should clear the screen, then draw each and every visible object. The init function shouldn't have any rendering code, for example.
Also worth mentioning: everything being drawn is an object (or entity). W3schools' tutorial gets you started on this path, but how are you going to distinguish a button component from a spaceInvader component? You might just make them separate objects/classes entirely for starters.
The next level of abstraction is a state, each state with its own init --> update --> render functions:
---------------------------------------- ----------------------------------------
| menu state | | game state |
| | | |
| ---------------- | | ---------------- |
| | | | | | | |
| v | | | v | |
| init -----> update -----> render | | init -----> update -----> render |
---------------------------------------- ----------------------------------------
What does all of this mean for your example? You're attempting to set up a menu state with some text and a button to begin the game. Managing a bunch of game states can get pretty complex, so graphics libraries like Phaser offer built-in state management systems. You can design one if you wish or postpone the problem and use a variable or boolean to determine which state you're in. For starters, I recommend working in a single state, then adding more when necessary. In other words, consider simplifying or skipping the menu state in favor of the game state until you're ready to work with states.
Another potentially challenging aspect of doing animations is handling mouse events. How do you know when your button was clicked on? At the least, you'll need to use an event listener and some math. Libraries can do it for you.
Either way, getting your loop going in JS will require requestAnimationFrame or (less likely/recommended) setInterval. Check out some basic examples from the MDN web docs.
I hope this helps offer a path forward and highlights some areas you may want to look into before going too much further with your current approach.
Related
I am trying to draw a kitten in an HTML5 canvas through a class constructor using TypeScript but I am confused on how to achieve the task. I have commented the code to show what I have attempted to do based on the behavior that I expected vs what actually works. Thank you very much for your timer and advice.
module Game {
export class Test {
width: number;
height: number;
cellWidth: number;
cellHeight: number;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
constructor() {
this.width = 28;
this.height = 31;
this.cellWidth = 20;
this.cellHeight = 20;
this.canvas = <HTMLCanvasElement> document.getElementById("game_canvas");
this.context = this.canvas.getContext("2d");
this.canvas.width = this.width * this.cellWidth;
this.canvas.height = this.height * this.cellHeight;
this.context.fillStyle = "blue";
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
/*
I was assuming that by accessing the this.context property
I would have direct access to the canvas and I will be able to use
drawImage to draw the kitten on it; however, that approach
produces no kitten in the canvas.
Only by using the .onload method it works.
I am using the () => notation so that the this inside the block
is referring to the class.
I have seen many JavasScript files in which images are simple drawn
through:
context.drawImage(image, 0, 0);
They are not embedded in .onload
I have tried to Google information but I cannot pinpoint what is
happening.
*/
}
}
}
As per my comments here is my answer: quite simply because you are declaring a new Image() and setting the src, your drawImage call will no doubt be in advance of the src being loaded... if you were to use a previously loaded image (e.g. from the DOM) then the creation of a new image and load would not be required
setting the src triggers the load - doing it in another class still subjects you to the wait time for load and you cannot be sure - I would say using onload is bulletproof and essential if you are the loader of the images you are using - the only alternative methods are when the images are already loaded into the DOM (or preloaded elsewhere), you may find the canvas examples you have seen are designed to be initiated onload of the images concerned
From your code:
let kitten = new Image();
kitten.src = 'img/kitten.png';
// When trying to draw a kitten in the canvas,
// this will work:
kitten.onload = () => {
this.context.drawImage(kitten, 0, 0);
};
// but this work won't:
//this.context.drawImage(kitten, 0, 0);
Using onload is the defacto way you get an image that you can draw on a canvas. A microoptimization would be to keep a dictionary of loaded images so you get to reuse them if drawing multiple kittens.
I need some direction here as I'm not clear on what I'm doing wrong. All I'm trying to do is a load a bitmap in the center of the canvas but it's not showing up. My file path is correct and I don't see what I might have coded incorrectly, where am I going wrong?
var canvas, stage, centerX, centerY;
function init() {
'use strict';
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
centerX = canvas.width / 2;
centerY = canvas.height / 2;
var ship = new createjs.Bitmap("../images/millenium.png"),
shipCenterX = ship.width / 2,
shipCenterY = ship.height / 2;
ship.x = centerX;
ship.y = centerY;
ship.regX = shipCenterX;
ship.regY = shipCenterY;
stage.addChild(ship);
stage.update();
}
The way this library appears to handle drawing to the canvas is by calling stage.update() which they recommend attaching to their "tick" event (e.g. http://www.createjs.com/docs/easeljs/classes/Stage.html)
Basically, we need to keep continually redrawing the canvas, and createjs gives us a method to do that, like so:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
stage.update();
}
However, since you haven't made your stage globally accessible, I tweaked your init function slightly so that we can access stage by returning it at the end of the function. Thus you can set stage in the global scope to the result of the function:
var stage = init();
And handleTick will use that stage by default. However if you're thinking of reusing your objects outside of your init() function, you may want to consider passing them to the init function or keeping their initial data structure outside of the init function to make them easier to access.
https://jsfiddle.net/jpgw1oka/
And make sure you are loading the CreateJS library: https://code.createjs.com/createjs-2015.11.26.min.js.
Remove the 'use strict'.
And make sure you are declaring your variables and function calls inside a window.onload event listener.
E.g.
var canvas, stage;
function init() {
canvas = document.getElementById("easel");
stage = new createjs.Stage(canvas);
var ship = new createjs.Bitmap("../images/millenium.png");
ship.x = Math.floor(stage.canvas.width * 0.5);
ship.y = Math.florr(stage.canvas.height * 0.5);
ship.regX = Math.floor(ship.image.width * 0.5);
ship.regY = Math.floor(ship.image.height * 0.5);
stage.addChild(ship);
stage.update();
}
My code is
canvas.clipTo = function (ctx) {
ctx.beginPath();
for (var i = 0; i < totalPrintArea; i++) {
ctx.save();
ctx.fillStyle = 'rgba(51,51,51,0)';
ctx.rect(clipLft[i], clipTp[i], clipW[i], clipH[i], 'rgba(51,51,51,1)', clipRtn[i]);
ctx.stroke();
ctx.restore();
}
ctx.closePath();
ctx.clip();
canvas.calcOffset();
};
canvas.renderAll();
I am taking values from the red dotted box and apply to clip where multiple masks are generating.
My issue is its taking all properties but not rotation for all.
I want to rotate all the rectangles.
I just get some code to change the rotation for the clip like ctx.rotate(50); but will not work as I want to make all rotate with their own values
Please guide me for the same.
On the original fabricJS github project I saw the comment: https://github.com/kangax/fabric.js/issues/932#issuecomment-27223912
and decided that I need to prevent making ctx.beginPath all the time:
canvas.clipTo = function(ctx) {
var skip = false;
// Workaround to make possible
// making clipTo with
// fabric.Group
var oldBeginPath = ctx.beginPath;
ctx.beginPath = function() {
if (!skip) {
oldBeginPath.apply(this, arguments);
skip = true;
setTimeout(function() {
skip = false;
}, 0);
}
}
group.render(ctx)
};
You can see my workaround to the problem described:
https://jsfiddle.net/freelast/6o0o07p7/
The workaround is not perfect, but hope it will help somebody.
I have tried using the Andrey's answer, but althouth there some interesting points, it didn't work.
If you try to clip the canvas to a single object (e.g. a circle or a rectangle), you can simply do this:
canvas.clipTo = function(ctx) {
shape.render(ctx); //shape is a circle, for instance
}
However, as explained by Kienz and butch2k in the aforementioned comment on GitHub, the problem is that you cannot use this solution with groups. In particular, if you use the following snippet:
canvas.clipTo = function(ctx) {
group.render(ctx);
}
you will only see one object of the group to be used for clipping.
The issue is due to the render method, which calls the ctx.beginPath() and ctx.closePath() for each object in the group. And because only the last couple of beginPath-closePath calls will affect the clipping, you need some workaround.
So in my solution, I have temporarily redefined the ctx.closePath and ctx.beginPath methods (after storing them in other two temporary variables, named oldBeginPath and oldClosePath) so that they do nothing. Then I call oldBeginPath at the beginning, and after rendering all the objects in the group I call the oldClosePath.
And now, here is the (working) snippet:
canvas.clipTo = function(ctx) {
var oldBeginPath = ctx.beginPath;
var oldClosePath = ctx.closePath;
ctx.beginPath = function() {}
ctx.closePath = function() {}
oldBeginPath.apply(ctx);
group.forEachObject(function(shape){
shape.render(ctx);
});
oldClosePath.apply(ctx);
ctx.beginPath = oldBeginPath;
ctx.closePath = oldClosePath;
};
Hope this will save someone's spare time in the future.
I am trying to use InfoVis / JIT to render a force directed graph visualizing a network.
I am a newbie to both java script and JIT.
I have created my own custom node types using following code in my js file, which lets me display my image on the node.
$jit.ForceDirected.Plot.NodeTypes.implement({
'icon1': {
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.drawImage(img, pos.x, pos.y);
};
},
'contains': function(node,pos){
var npos = node.pos.getc(true);
dim = node.getData('dim');
return this.nodeHelper.circle.contains(npos, pos, dim);
//return this.nodeHelper.square.contains(npos, pos, dim);
}
}
I am assigning this custom node type to the node using "$type": "icon1" in the json data object. I do get image on the node, but the problem is that I am not able to hide it when required. I am able to hide the in-built node types like circle,square etc. using following code.
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
But the same code does not work for custom nodes.
Hence I tried to temporarily change the type of node to the built-in "circle" type, hid it and then re-setted the type of node to its original i.e. my custom node, icon1.
function hideNode( ){
var typeOfNode = node.getData('type');
node.setData( 'type','circle');
node.setData('alpha', 0);
node.eachAdjacency(function(adj) {
adj.setData('alpha', 0);
});
fd.fx.animate({
modes: ['node-property:alpha',
'edge-property:alpha'],
duration: 2000
});
node.setData('type',typeOfNode );
}
I think this should work but the custom image comes back in a while on the canvas.
If I don't reset the type of node to its original i.e. in the above code and comment out the following statement and call hide function, then the node gets hidden.
node.setData('type',typeOfNode );
I am not able to figure out how by only setting a node's type to some custom type, the node is being rendered. Any help with this question will be appreciated.
I need to re-set the node's type to its original because I want the node to be restored when required by calling unhide function. If I don't reset node's type to the original then it would be rendered as a circle when restored.
I have gone through the API and the google group for JIT but couldn't find an answer.
Can anyone help?
Here's a look at a snippet from the Plot's plotNode function:
var alpha = node.getData('alpha'),
ctx = canvas.getCtx();
ctx.save();
ctx.globalAlpha = alpha;
// snip
this.nodeTypes[f].render.call(this, node, canvas, animating);
ctx.restore();
As you can see, the node's alpha value is applied to the canvas immediately before the node's render function is called. After rendering the node, the canvas is restored to the previous state.
The issue here is that your custom node's render function does not render the node synchronously, and the canvas state is getting restored prior to the call to drawImage. So, you can do one of two things:
1) Preload and cache your image (preferred approach, as this will also prevent image flickering and help with performance):
// preload image
var magnifyImg = new Image();
magnifyImg.src = 'magnify.png';
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var pos = node.pos.getc(true);
ctx.drawImage(magnifyImg, pos.x, pos.y);
}
or 2) save the canvas state, reapply the alpha, and then restore the canvas state after drawing the image in your onload handler:
// 'icon1' node render function:
'render': function(node, canvas){
var ctx = canvas.getCtx();
var img = new Image();
img.src='magnify.png';
var pos = node.pos.getc(true);
img.onload = function() {
ctx.save(); // save current canvas state
ctx.globalAlpha = node.getData('alpha'); // apply node alpha
ctx.drawImage(img, pos.x, pos.y); // draw image
ctx.restore(); // revert to previous canvas state
};
}
With all the buzz of HTML5 I've began by investigating the Canvas's capabilities along with interaction from Javascript. Unfortunately things haven't been going well due to idiosyncrasies of Javascript and its OO model.
For instance, I figured I could create a wrapper class for my canvas object and effectively box all appropriate methods and properties into it making the development side of things much easier. Unfortunately I'm struggling with the way the mouse handlers are working. In my case, I have the 'DrawArea' class that adds three mouse handlers for drawing rectangles and a 'Draw' routine titled 'Invalidate'. When the mouse events are fired (mouseMove and mouseUp methods), they fail claiming that the 'Invalidate' function is invalid - almost like it is out of context of the method it is being called within. Code below.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
<script type="text/javascript">
// Top level variables
var dWrap;
// Point Class
function Point( xPos , yPos ){
this.X = xPos;
this.Y = yPos;
}
// Create wrapper class for the draw area
function DrawArea( da ){
this.SrcArea = da;
// Add mouse handlers
this.SrcArea.addEventListener('mousedown', this.mouseDown, false);
this.SrcArea.addEventListener('mousemove', this.mouseMove, false);
this.SrcArea.addEventListener('mouseup', this.mouseUp, false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
// Properities
DrawArea.prototype.ProposedStartPos = undefined;
DrawArea.prototype.ProposedEndPos = undefined;
DrawArea.prototype.IsDrawing = false;
// Mouse Events
// Handles the mouse down event for new objects
DrawArea.prototype.mouseDown = function(m) {
// Flag as drawing
this.IsDrawing = true;
// Record the start position
this.ProposedStartPos = new Point(m.layerX, m.layerY);
}
// Handles mouse movement when creating a proposed object
DrawArea.prototype.mouseMove = function(m) {
if (this.IsDrawing) {
// Set the current end position
this.ProposedEndPos = new Point(m.layerX, m.layerY);
// NOTE: this call doesn't work!
this.Invalidate();
}
}
// Handles the completion of a proposed object
DrawArea.prototype.mouseUp = function(m) {
if (this.IsDrawing) {
// Set the final end position
if (m.type != 'mouseout') this.ProposedEndPos = new Point(m.layerX, m.layerY);
// NOTE: this call doesn't work!
this.Invalidate();
}
}
// Redraws the source object
DrawArea.prototype.Invalidate = function() {
// Obtain
if (this.SrcArea.getContext) {
var context = this.SrcArea.getContext('2d');
// Clean up
context.clearRect(0, 0, this.SrcArea.width, this.SrcArea.height);
context.save();
// Draw the background
context.strokeStyle = "#000000";
context.fillStyle = "#AAAFFF";
context.beginPath();
context.rect(0, 0, this.SrcArea.width, this.SrcArea.height);
context.closePath();
context.stroke();
context.fill();
// Are we drawing any proposed items
if (this.IsDrawing) {
context.strokeStyle = this.ProposedColorStroke;
context.fillStyle = this.ProposedColorFill;
context.beginPath();
context.rect(this.ProposedStartPos.X, this.ProposedStartPos.Y, this.ProposedEndPos.X - this.ProposedStartPos.X, this.ProposedEndPos.Y - this.ProposedStartPos.Y);
context.closePath();
context.stroke();
context.fill();
}
}
// Flush
context.restore();
}
// Initialise the wrapper class
$(document).ready(function() {
// Obtain the canvas and set
var cWrap = $('#cDrawArea')[0];
dWrap = new DrawArea( cWrap );
});
Html code...
<body>
<div id="DrawContainer">
<canvas id="cDrawArea" width="800" height="600"></canvas>
</div>
</body>
What am I missing here and is this a particular efficient and smart way of handling complex objects that will require a lot of behind the scenes code?
This is a common misunderstanding. JavaScript doesn't have classes, and it doesn't have methods. It has functions. Unlike some other languages (Java, C#, C++), this is determined entirely by how a function is called, not where a function is defined. (This is incredibly powerful, but surprising to someone coming from class-based languages.) So this line of code:
this.SrcArea.addEventListener('mousedown', this.mouseDown, false);
...does hook up the function referenced by the mouseDown property, but does nothing to ensure that when that function is called, this is the value you expect.
If you're really using an ECMAScript5-compliant browser (there are some that have canvas but are not completely ES5-compliant), you can use the new Function#bind feature, but again note that this is only about two years old:
// Create wrapper class for the draw area
function DrawArea( da ){
this.SrcArea = da;
// Add mouse handlers using ECMAScript5's new `Function#bind`
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
this.SrcArea.addEventListener('mousemove', this.mouseMove.bind(this), false);
this.SrcArea.addEventListener('mouseup', this.mouseUp.bind(this), false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
Alternately, you can do pretty much the same thing yourself using closures:
// Create wrapper class for the draw area
function DrawArea( da ){
var self = this; // Set up a variable referencing the instance
this.SrcArea = da;
// Add mouse handlers - these are closures over the context of this
// call to the constructor, and have access to the `self` variable
// above. They just relay the call to the functions on the prototype,
// but in a way that ensures that `this` is what you expect.
this.SrcArea.addEventListener('mousedown', function(event) {
return self.mouseDown(event);
}, false);
this.SrcArea.addEventListener('mousemove', function(event) {
return self.mouseMove(event);
}, false);
this.SrcArea.addEventListener('mouseup', function(event) {
return self.mouseUp(event);
}, false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
More reading:
Mythical methods
You must remember this
Closures are not complicated
Try:
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
this.SrcArea.addEventListener('mousemove', this.mouseMove.bind(this), false);
this.SrcArea.addEventListener('mouseup', this.mouseUp.bind(this), false);
The "bind()" method on the Function prototype (should be in any browser with <canvas> I think) returns a function that will force the this value to be the parameter you pass, in this case your wrapper object instance.
If you don't do something like that, then the handler won't have the this you expect.
The this is not the DrawArea instance in the handler, but the element itself.
You should bind (freeze) the this value with bind. This is the easiest, but is not available in all browsers. There is a shim available, though.
// guarantee the 'this' value inside handler
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
http://jsfiddle.net/KdnZC/