I'm trying to make a snake game with HTML5 using the object literal pattern, but I can't seem to draw an image on the canvas. I've researched a lot and found for instance that I can't set the image src while the object is being created (so I've put that in a function). The image seems to be available as it shows up in the console and can be appended to the body. What am I missing please?
(function ($) {
$.fn.preload = function() { // http://stackoverflow.com/a/476681
this.each(function(){
$('<img/>')[0].src = this;
});
}
})(jQuery);
$(document).ready(function() {
$(['./images/grass-500x500.png']).preload(); // I've tried with and without this function (defined elsewhere)
// Object literal pattern
var game = {
// Background image
background: new Image(),
setBGSource: function() {
this.background.src = 'images/grass-500x500.png';
},
// Canvas details
canvas: $("#canvas")[0],
ctx: canvas.getContext("2d"),
WIDTH: $("#canvas").width(),
HEIGHT: $("#canvas").height(),
// Game details
CELL_WIDTH: 10,
direction: null,
food: null,
score: null,
snakeArray: [],
init: function() {
this.direction = "right";
this.createSnake();
this.setBGSource();
this.draw();
},
createSnake: function() {
var length = 5; // Initial length of snake
for (var i = length - 1; i >= 0; i--) {
this.snakeArray.push({
x: i,
y: 1 // y : 0 - initial position of snake in cell units
});
}
},
// Create food item
createFood: function() {
this.food = {
x: Math.round(Math.random() * (WIDTH - CELL_WIDTH) / CELL_WIDTH),
y: Math.round(Math.random() * (HEIGHT - CELL_WIDTH) / CELL_WIDTH)
} // Position of food with x and and y between 0 and e.g 44
},
// Drawing function
draw: function() {
// Repaint the background on each frame
console.log(this.background); // displays <img src="images/grass-500x500.png">
this.ctx.drawImage(this.background, 0, 0); // WHY DOESN'T THIS WORK PLEASE?
$('body').append(this.background); // appends image, no problem
}
};
game.init();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>
The problem here is that the image is being preloaded asynchronously while at the same time you call game.init() and thus game.draw(), which expects the image being loaded already.
Thus, unless the image is in your browser's cache, the asynchronous preloading might not have finished at the time of game.draw().
You need to wait until preloading has finished before calling game.init(). JavaScript offers some good tools in dealing with asynchronous execution, e.g. callbacks, promises etc.
Have a look here: https://gamedev.stackexchange.com/questions/24102/resource-loader-for-an-html5-and-javascript-game
Related
I am working with Processing.js (version 1.4.8).
I have 5 white points, which coordinates I chose specifically. The black dot marks the center of the sketch! I want to be able to translate and scale my sketch. ALSO, I want it to occupy the whole window.
var mapWidth, mapHeight, canvas, pjs, centerX, centerY;
var points = [[100, 100], [300, 100], [100, 300], [300, 300], [200, 200]];
var setSize = function() {
mapWidth = $(window).outerWidth();
mapHeight = $(window).outerHeight();
if (pjs) {
pjs.size(mapWidth, mapHeight);
}
};
var clear = function() {
pjs.background(200);
};
var drawPoint = function(coordinates) {
var radius = 30;
pjs.ellipse(coordinates[0], coordinates[1], radius, radius);
};
var drawPoints = function() {
pjs.fill(255);
points.map(function(point) {
drawPoint(point);
});
};
var calculateCenter = function() {
centerX = Math.floor(mapWidth / 2);
centerY = Math.floor(mapHeight / 2);
};
var drawCenter = function() {
calculateCenter();
var radius = 10;
pjs.fill(0);
pjs.ellipse(centerX, centerY, radius, radius);
console.log("center", centerX, centerY);
};
var move = function() {
pjs.translate(200, 300);
redraw();
};
var zoomIn = function() {
pjs.scale(2, 2);
redraw();
};
var draw = function() {
clear();
drawPoints();
drawCenter();
};
var redraw = function() {
clear();
draw();
};
var addEvent = function(object, type, callback) {
if (object == null || typeof object == "undefined") return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
$(function() {
canvas = document.getElementById("map");
setSize();
var pjsRun = function(processingjs) {
pjs = processingjs;
pjs.setup = function() {
pjs.size(mapWidth, mapHeight);
draw();
};
};
var p = new Processing(canvas, pjsRun);
addEvent(window, "resize", function(event) {
setSize();
redraw();
});
});
Until here, everything is fine, as you can see in this CodePen.
I want to be able to resize the window AND keep the transformations (translations, scales, ...) that I had already performed.
Please, open the CodePen and try to reproduce this weird behaviour:
1) Perform one (or two) transformation(s) using the top-right buttons
The map is translated by 200 to the right and 300 downwards.
Everything OK by now...
But the problem arises now.
2) Resize the window
The five points are again where they were before the "translate" operation.
So... Again... Is there a way to resize without losing all the transformations that had been performed?
Thanks
Like you've discovered, it appears as though calling the size() function resets the transformation matrix. The short answer to your question is that you need to keep track of the transformations, and then apply them whenever you draw something.
The longer answer to your question is that you're using Processing.js a little bit differently than people typically use it. You've left out the draw() function (note that your draw() function is not the draw() function that's automatically called 60 times per second) and are trying to code event handlers yourself. This disconnect is why you're having issues.
If I were you, I'd start with a more basic sketch that starts out using Processing's built-in draw() function. Write code that draws the scene every frame. Make sure you set the translation and scale every frame. Here's an example:
var draw = function() {
scale(scaleX, scaleY);
translate(translateX, translateY);
background(200);
fill(255);
points.map(function(point) {
ellipse(coordinates[0], coordinates[1], 30, 30);
});
fill(0);
ellipse(width/2, height/2, 10, 10);
};
Then setup event listeners that change the values of scaleX and scaleY as well as translateX and translateY. Let Processing handle the rest.
I want to draw onto a canvas multi times with the same image, in other words i want to apply code that will when i click on the canvas area produces an image and when i click again somewhere else it produces another image and so on.
<canvas></canvas>
But when trying to draw again it instead replaces the previous image that was drawn and then draws the new image. instead of keeping the previous drawn image on the canvas.
function drawAll(){
context.drawImage(imageObj, 0, 0, imageObj.width, imageObj.height,0, 0, 700, 618);
if(coordinates.length > 0){
coordinates.map((coord, key) =>{
context.beginPath();
context.moveTo(coord.startX, coord.startY);
context.lineTo(coord.toX,coord.toY);
context.strokeStyle="rgb(226, 104, 36)";
context.lineWidth=2;
context.stroke();
});
}
if(entry){
context.drawImage(imageObjBall, entry.posX-8, entry.posY-8, 16, 16);
}
}
EDIT: Hey sorry forgot to insert this bit down here
function handleClick(e){
if(!isDrawing && prize_id != null){
var pos = getMousePos(canvas, e);
mouseX = pos.x;
mouseY = pos.y;
$x_id=`#cor_x_${prize_id}`;
$y_id=`#cor_y_${prize_id}`;
context.clearRect(0, 0, 700, 618);
context.drawImage(imageObjBall, mouseX-8, mouseY-8, 16, 16);
entry={posX:mouseX,posY:mouseY};
$($x_id).text(mouseX);
$($y_id).text(mouseY);
drawAll();
}
}
NOTE: This is in laravel 5, there is more code outside of this but i
felt this was the necessary snipped needed for you to be able to help
me but if you need more please ask me to edit some more code into this
post
To draw multiple images onto the canvas using a single image object, create the image object via new Image() and set the src property accordingly.
Alternatively, you can also write an <img src=""> tag in the HTML code and get a reference to it via document.querySelector.
There is one important thing to consider: Before you call drawImage, you have to make sure the image is loaded, even when there are no load times (eg. localhost or data-url).
In my example, I solved this by not starting the animation immediately. Instead, the requestAnimationFrame loop is started in the image onload event.
var R = Math.random
var a = document.getElementById("a")
var W = a.width = 400
var H = a.height = 300
var c = a.getContext("2d")
var img = new Image()
img.src = 'https://assets-cdn.github.com/images/icons/emoji/unicode/1f60d.png'
function loop() {
var rndX = (R()*W)|0
var rndY = (R()*H)|0
c.drawImage(img, rndX, rndY)
requestAnimationFrame(loop)
}
img.onload=function() {
loop()
}
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<canvas id="a"></canvas>
Here is such a code snippet:
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create });
var emitter;
function preload() {
game.load.image('wasp', 'assets/glass.png');
game.load.image('glass', 'assets/glass.png');
game.load.image('water', 'assets/blue-raster-floor.png');
}
function create() {
game.physics.startSystem(Phaser.Physics.ARCADE);
game.add.tileSprite(0, 344, 800, 256, 'water');
emitter = game.add.emitter(game.world.centerX, 200);
emitter.makeParticles('glass');
emitter.setXSpeed(-200, 200);
emitter.setYSpeed(-150, -250);
emitter.bringToTop = true;
emitter.setAlpha(0.1, 1, 500);
emitter.setScale(-2, 2, 1, 1, 3000, Phaser.Easing.Sinusoidal.InOut, true);
emitter.gravity = 300;
emitter.start(false, 5000, 700, 50);
game.time.events.add(3000, destroyEmitter, this);
}
function tweens(cash) {
var bugs;
var index = 0;
var data;
var pos = [];
var tween;
var tweenData = { x: 0, y: 0 };
tween = game.make.tween(tweenData).to( { x: 100, y: 400 }, 2000, "Sine.easeInOut");
tween.yoyo(true);
data = tween.generateData(60);
bugs = game.add.group();
pos.push(new Phaser.Point(32, 0));
pos.push(new Phaser.Point(300, 100));
pos.push(new Phaser.Point(600, 70));
bugs.create(pos[0].x, pos[0].y, 'wasp');
bugs.create(pos[1].x, pos[1].y, 'wasp');
bugs.create(pos[2].x, pos[2].y, 'wasp');
tween.onUpdateCallback(function () {
bugs.getAt(0).x = pos[0].x + data[index].x;
bugs.getAt(0).y = pos[0].y + data[index].y;
bugs.getAt(1).x = pos[1].x + (data[index].x / 2);
bugs.getAt(1).y = pos[1].y + data[index].y;
// Inverse one of the values
bugs.getAt(2).x = pos[2].x - data[index].x;
bugs.getAt(2).y = pos[2].y + data[index].y;
index++;
if (index === data.length)
{
index = 0;
}
});
tween.start();
}
function destroyEmitter() {
console.log(emitter);
emitter.destroy();
tweens();
}
As you can see, I have made the particle-animation. Such steps need to be taken:
Particle-animation should be cached in the form of a set of shots (textures)
Particle-animation should be deleted. I have already done it (by means of ‘destroy‘)
Instead of the particle animation sprite animation should be realized by means of the function tweens using received textures
and passing these textures as the argument of the function tweens
Any refractoring is welcome.
In Phaser, the emitter particles are of the relatively simple DisplayObject class which do not support animations like the Phaser.Sprite does. Btw I don't know if using tweens is the best way to animate particles, because I suspect it will be heavy on CPU usage, using Sprite animations on the other hand is a bit more "light weight".
But either way, you could create a custom particle class which contains the code for your particle animation (using tweens, animations, timers, whatever) and then set that custom class as the emitter.particleClass, see a code example in link below:
http://codetuto.com/2016/02/phaser-animated-particles/
I am attempting to write a Darkroom.JS plugin that will transform white space in images to transparency.
I have used this answer (solely canvas based) to write this code:
(function() {
'use strict';
var Transparency = Darkroom.Transformation.extend({
applyTransformation: function(canvas, image, next) {
console.log(canvas);
console.log(image);
var context = canvas.getContext("2d");
var upperContext = $('.upper-canvas').get(0).getContext("2d");
var imageData = context.getImageData(0, 0, image.getWidth(), image.getHeight());
//var upperImageData = upperContext.createImageData(image.getWidth(), image.getHeight());
console.log("apply transformation called");
for(var i = 0, n = imageData.data.length; i < n; i +=4){
var r = imageData.data[i],
g = imageData.data[i+1],
b = imageData.data[i+2];
if(r >= 230 && g >= 230 && b >= 230){
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 1;
}
};
context.putImageData(imageData, 0, 0);
upperContext.putImageData(imageData, 0, 0);
//canvas.renderAll();
next();
}
});
Darkroom.plugins['transparency'] = Darkroom.Plugin.extend({
defaults: {
clearWhiteSpace: function() {
this.darkroom.applyTransformation(
new Transparency()
);
}
},
initialize: function InitializeDarkroomTransparencyPlugin() {
var buttonGroup = this.darkroom.toolbar.createButtonGroup();
this.destroyButton = buttonGroup.createButton({
image: 'wand' //Magic Wand by John O'Shea from the Noun Project
});
this.destroyButton.addEventListener('click', this.options.clearWhiteSpace.bind(this));
},
});
})();
(I should also note I based the structure of the plugin off of the existing rotate plugin)
The code does get called, and I do not currently have it in the code (for performance reasons) but a log statement indicated that the if block where the pixel editing is done also gets called.
To verify, I presently have the pixels set to fully opacity and black (instead of transparent so that I can see the effects of editing).
Also, I noticed that Darkroom.JS seems to generate two canvas objects, an upper canvas and lower canvas. The object passed to the transform function is the "lower canvas" object, so I even tried using jQuery to grab the "upper" one and set the image data on that, to no avail.
What am I missing?
I was focusing my search for an answer far too much on Darkroom.JS.
Darkroom.JS is just a layer on top of Fabric.JS, and this answer holds the key:
fabric js or imagick remove white from image
I actually used the second answer and it works perfectly:
So there is a filter in Fabric.js that does just that.
http://fabricjs.com/docs/fabric.Image.filters.RemoveWhite.html
var filter = new fabric.Image.filters.RemoveWhite({ threshold: 40,
distance: 140 }); image.filters.push(filter);
image.applyFilters(canvas.renderAll.bind(canvas));
Here is my completed code (with some extraneous details removed to simplify):
fabric.Image.fromURL(imgData.URL, function(logoImg){
canvas.add(logoImg);
var threshold = 40;
var whitespace = function(){
var filter = new fabric.Image.filters.RemoveWhite({
threshold: threshold,
distance: 140
});
threshold+=20;
logoImg.filters.push(filter);
logoImg.applyFilters(canvas.renderAll.bind(canvas));
};
});
I am trying to follow this tutorial here https://www.smashingmagazine.com/2012/10/design-your-own-mobile-game/ and I am stuck on the second part. (2. A Blank Canvas)
I am not sure where to put the POP.Draw object. Does it go inside of the var POP{} brackets where the other objects are created? I've tried keeping it inside, outside, and in the init function which I don't think makes sense. The purpose is to create methods within the new Draw object so they can be called later to create pictures in the canvas.
Here is my current code. It is the same as the one in the link:
var POP = {
//setting up initial values
WIDTH: 320,
HEIGHT: 480,
// we'll set the rest of these
//in the init function
RATIO: null,
currentWidth: null,
currentHeight: null,
canvas: null,
ctx: null,
init: function() {
//the proportion of width to height
POP.RATIO = POP.WIDTH / POP.HEIGHT;
//these will change when the screen is resized
POP.currentWidth = POP.WIDTH;
POP.currentHeight = POP.HEIGHT;
//this is our canvas element
POP.canvas = document.getElementsByTagName('canvas')[0];
//setting this is important
//otherwise the browser will
//default to 320x200
POP.canvas.width = POP.WIDTH;
POP.canvas.width = POP.HEIGHT;
//the canvas context enables us to
//interact with the canvas api
POP.ctx = POP.canvas.getContext('2d');
//we need to sniff out Android and iOS
// so that we can hide the address bar in
// our resize function
POP.ua = navigator.userAgent.toLowerCase();
POP.android = POP.ua.indexOf('android') > -1 ? true : false;
POP.ios = (POP.ua.indexOf('iphone') > -1 || POP.ua.indexOf('ipad') > -1) ? true : false;
//we're ready to resize
POP.resize();
POP.Draw.clear();
POP.Draw.rect(120, 120, 150, 150, 'green');
POP.Draw.circle(100, 100, 50, 'rgba(225,0,0,0.5)');
POP.Draw.text('Hello WOrld', 100, 100, 10, "#000");
},
resize: function() {
POP.currentHeight = window.innerHeight;
//resize the width in proportion to the new height
POP.currentWidth = POP.currentHeight * POP.RATIO;
//this will create some extra space on the page
//allowing us to scroll past the address bar thus hiding it
if (POP.android || POP.ios) {
document.body.style.height = (window.innerHeight + 50) + 'px';
}
//set the new canvas style width and height note:
//our canvas is still 320 x 400 but we're essentially scaling it with css
POP.canvas.style.width = POP.currentWidth + 'px';
POP.canvas.style.height = POP.currentHeight + 'px';
//we use a timeout here because some mobile browsers
//don't fire if there is not a short delay
window.selfTimeout(function() {
window.scrollTo(0, 1);
})
//this will create some extra space on the page
//enabling us to scroll past the address bar
//thus hiding it
if (POP.android || POP.ios) {
document.body.style.height = (window.innerHeight + 50) + 'px';
}
}
};
window.addEventListener('load', POP.init, false);
window.addEventListener('resize', POP.resize, false);
//abstracts various canvas operations into standalone functions
POP.Draw = {
clear: function() {
POP.ctx.clearRect(0, 0, POP.WIDTH, POP.HEIGHT);
},
rect: function(x, y, w, h, col) {
POP.ctx.fillStyle = col;
POP.ctx.fillRect(x, y, w, h);
},
circle: function(x, y, r, col) {
POP.ctx.fillStyle = col;
POP.ctx.beginPath();
POP.ctx.arc(x + 5, y + 5, r, 0, Math.PI * 2, true);
POP.ctx.closePath();
POP.ctx.fill();
},
text: function(string, x, y, size, col) {
POP.ctx.font = 'bold' + size + 'px Monospace';
POP.ctx.fillStyle = col;
POP.ctx.fillText(string, x, y);
}
};
SOLVED
I didn't realize but the completed code is on the webpage. I downloaded it and looked at the example for answers.
I solved the issue by placing the POP.Draw.clear, POP.Draw.rect methods before calling the POP.resize() method. I'm not really sure why the order matters, but it does.