I am trying to recursively call a function until the game has ended, however, during the execution "this" gets reset to 'window', which causes the following error: "cannot read property 'step' of undefined."
I have tried rewriting the offending line as
this.game.bind(this).step();
as well as
this.game.step.bind(this);
Neither are working. For context, step is just a function that calls two helpers in Game that move objects and look for collisions, when I used those instead of step, the error remained.
/* globals key */
const Game = require('./game.js');
let speed = 1;
function GameView(ctx) {
this.ctx = ctx;
this.game = new Game();
}
GameView.prototype.start = function (callback) {
// let that = this
this.bindKeyHandlers();
this.animate(0);
};
GameView.prototype.animate = function(time) {
speed += 1;
if (speed >= 605) {
speed = 0;
};
this.game.step();
this.game.draw(this.ctx, speed);
if(!this.game.lose()){
requestAnimationFrame(this.animate());
}
else {
this.ctx.fillStyle = "white";
this.ctx.font = "italic "+24+"pt Arial ";
this.ctx.fillText(`Game Over \n Press Enter to restart`, 100,200 );
key('enter', ()=>{
this.game = new Game();
this.start();
});
}
};
GameView.prototype.bindKeyHandlers = function() {
key('d', () => {
this.game.ship.power([2, 0]);
});
key('a', () => {
this.game.ship.power([-2, 0]);
});
key('s', () => {
this.game.ship.power([0, 2]);
});
key('w', () => {
this.game.ship.power([0, -2]);
});
key('l', () => {
this.game.ship.fireBullet();
});
};
module.exports = GameView;
Your problem is that this of the animate function does not refer to the context you wish it to. So your offending line (or lack thereof) is completely elsewhere.
Function.bind returns a function and you need to this new bound functions somewhere and the perfect place for this is the constructor. Here's an example:
function GameView(ctx) {
this.ctx = ctx;
this.game = new Game();
this.animate = this.animate.bind(this)
}
See this jsfiddle for more, remember to open your developer console to witness the error from calling notBound
Hope this helps!
Related
I'm working on web application(c#) with angularjs (client side) project.
we've requirement to show pdf on webpage with next/previous button options.
for that We'd used pdf.js (By Mozilla Foundation), and we've implemented code as per given example.
we have created one angular directive to things works correctly.
Everything works fine while it is loading page first time, now come to point:
After loading new source URL it is throwing below error:
pdf.js?v=1641729671:12170 Uncaught (in promise) Error: Cannot use the same canvas during multiple render() operations. Use different canvas or ensure previous operations were cancelled or completed.
at InternalRenderTask.initializeGraphics (pdf.js?v=1641729671:12170)
at pdf.js?v=1641729671:10666
I've tried below things from my side:
Tried to render pdf by re-linking directive.
Tried to render all pdf by page cleanup function but still it causes an issue.
Tried to render pdf pages by canceling pages in between but it's not working.
Tried to clear canvas and context and reinitialized it but no trick.
Tried to create dynamic canvases by different different IDS but it didn't work.
Tried to assign class instead of Id of pdf viewer but it didn't work.
I did research on google but I didn't get any working solution :(
now let me post my client side code for better understanding.
below is my directive code:
angular.module('angularPDFView', [])
.directive('pdfviewer', ['$parse', function ($parse) {
var canvas = null;
var instance_id = null;
var context = null;
return {
restrict: "E",
template: '<canvas></canvas>',
scope: {
onPageLoad: '&',
loadProgress: '&',
src: '#',
id: '='
},
controller: ['$scope', function ($scope) {
$scope.pageNum = 1;
$scope.pdfDoc = null;
$scope.scale = 1.0;
$scope.rotation = 0;
$scope.pageNumPending = null;
$scope.renderTask;
$scope.path = null;
$scope.getRandomSpan = function () {
return Math.floor(Math.random() * 1000000000);
};
$scope.loadPDF = function (path) {
$scope.path = path;
var randNum = $scope.getRandomSpan();
pdfjsLib.GlobalWorkerOptions.workerSrc = '/Content/js/pdf.worker.js?v=' + randNum;
console.log('loadPDF ', path);
$scope.scale = 1.0;
$scope.rotation = 0;
var loadingTask = pdfjsLib.getDocument(path);
loadingTask.promise.then(function (_pdfDoc) {
$scope.pdfDoc = _pdfDoc;
$scope.renderPage($scope.pageNum);
});
};
$scope.renderPage = function (num) {
pageRendering = true;
// Using promise to fetch the page
$scope.pdfDoc.getPage(num).then(function (page) {
if ($scope.renderTask) {
try {
$scope.renderTask.cancel();
} catch (e) {
//
}
}
var viewport = page.getViewport({ scale: $scope.scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport,
continueCallback: function (cont) {
cont();
}
};
$scope.renderTask = page.render(renderContext);
// Wait for rendering to finish
try {
$scope.renderTask.promise.then(function () {
pageRendering = false;
if ($scope.pageNumPending !== null) {
// New page rendering is pending
$scope.renderPage($scope.pageNumPending);
$scope.pageNumPending = null;
}
$scope.$apply(function () {
$scope.onPageLoad({
page: $scope.pageNum,
total: $scope.pdfDoc.numPages
});
});
});
} catch (e) {
//
}
});
// Update page counters
$scope.pageNum = num;
};
$scope.queueRenderPage = function (num) {
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
}
if ($scope.pageRendering) {
$scope.pageNumPending = num;
} else {
$scope.renderPage(num);
}
};
$scope.$on('pdfviewer.prevPage', function () {
if ($scope.pageNum <= 1) {
return;
}
$scope.pageNum--;
$scope.queueRenderPage($scope.pageNum);
});
/**
* Displays next page.
*/
$scope.$on('pdfviewer.nextPage', function () {
if ($scope.pageNum >= $scope.pdfDoc.numPages) {
return;
}
$scope.pageNum++;
$scope.queueRenderPage($scope.pageNum);
});
$scope.$on('pdfviewer.gotoPage', function (evt, id, page) {
if (id !== instance_id) {
return;
}
if ($scope.pdfDoc === null) {
$scope.pageNum = page;
$scope.loadPDF($scope.path);
} else {
if (page >= 1 && page <= $scope.pdfDoc.numPages) {
$scope.pageNum = page;
$scope.renderPage($scope.pageNum);
}
}
});
}],
link: function (scope, iElement, iAttr) {
canvas = iElement.find('canvas')[0];
context = canvas.getContext('2d');
instance_id = iAttr.id;
iAttr.$observe('src', function (v) {
console.log('src attribute changed, new value is', v);
if (v !== undefined && v !== null && v !== '') {
scope.pageNum = 1;
scope.loadPDF(scope.src);
}
});
}
};
}])
.service("angularPDFViewerService", ['$rootScope', function ($rootScope) {
var svc = {};
svc.nextPage = function () {
$rootScope.$broadcast('pdfviewer.nextPage');
};
svc.prevPage = function () {
$rootScope.$broadcast('pdfviewer.prevPage');
};
svc.Instance = function (id) {
var instance_id = id;
return {
prevPage: function () {
$rootScope.$broadcast('pdfviewer.prevPage');
},
nextPage: function () {
$rootScope.$broadcast('pdfviewer.nextPage');
},
gotoPage: function (page) {
$rootScope.$broadcast('pdfviewer.gotoPage', instance_id, page);
}
};
};
return svc;
}]);
..
How can I cancel render operation or Is there any way to use different canvas to load PDF ?
Any help would be appreciated.
Thanks in advance.!
Use a "render in progress" flag:
var renderInProgress;
var renderTask;
if (renderInProgress) {
//ensure previous operations were cancelled or completed.
.then(function() {
renderTask = doRender();
});
} else {
renderTask = doRender();
};
function doRender() {
renderInProgress = true;
var renderOp = page.render(renderContext);
renderOp.promise = renderOp.promise.then(function () {
//Do whatever
}).finally(function() {
renderInProgress = false;
});
return renderOp;
}
After two days of efforts finally I've fixed pdf load issue, console error is still there but functionality is working correctly now :)
I'm posting answer that might help someone, here are little steps I did:
Visit: https://cdnjs.com/libraries/pdf.js
I've downloaded most recent version of PDF.js and Pdf-worker.js from above mentioned link.
And just replaced my old reference libraries with newer one and that's it.
It works like charm in my angular application now!!!
i've wrote a snippet which should start counting a number from 1 to 1000 or pre defined.
Because of needing the script mulitple times i thought it would be a great idea to write it as an object and use it mulitiple times. But i get an error:
Uncaught ReferenceError: root is not defined
(anonymous function)
What did i wrong?
var time = function() {
var root = this;
var i=0;
root.max = 1000;
root.elm = false;
root.runTime = function() {
if(root.elm != false) {
if (i < root.max) {
i++;
root.elm.text(i);
} else {
clearInterval(root.interval);
}
}
this.interval = setInterval('root.runtTime()', 5);
};
};
if($(document).ready(function() {
var countUp= new time();
countUp.max = 1526;
countUp.elm = $("#elm");
countUp.runTime();
});
This is because of the following line:
this.interval = setInterval('root.runtTime()', 5);
Because it's a string it has to be evaluated as a global object.
Change to the following to ensure the same scope:
this.interval = setInterval(root.runtTime, 5);
Also there's a typo (runtTime should be runTime), so change to the following:
this.interval = setInterval(root.runTime, 5);
Finally you're using setInterval which will repeatedly call root.runTime every 5ms. Change to setTimeout if you wish to call this recursively:
this.interval = setTimeout(root.runTime, 5);
Alternatively set up the interval outside of your runTime function:
root.runTime = function() {
if(root.elm != false) {
if (i < root.max) {
i++;
root.elm.text(i);
} else {
clearInterval(root.interval);
}
}
};
this.interval = setInterval(root.runTime, 5);
Also you don't need the if statement around document.ready. This is a callback function which is triggered when the DOM has loaded, and therefore doesn't require an if statement.
$(document).ready(function() {
var countUp= new time();
countUp.max = 1526;
countUp.elm = $("#elm");
countUp.runTime();
});
I'm having a problem where I need to reset the game.input.onDown event in the same way as Phaser.Key.reset. It seems like this is doable with the Phaser.Pointer.reset method. Unfortunately, there seems to be a bunch of different potential pointer objects in the Phaser.Input class, and I'm not sure which one I need to use for game.input.onDown, which says that it gets "dispatched each time a pointer is pressed down". I'm just not really sure which pointer I need to use. Can anyone shed some light on this?
I guess I effectively need to do something like this:
this.game.input.whicheverPointer.reset();
EDIT:
Here is an example that mimics the the problem I'm having.
Here is the source code for it:
var game = new Phaser.Game(800, 600, Phaser.CANVAS, "game", {preload: preload, create: create, update: update});
var dude;
var block;
var spacebar;
var gameOverText;
var gameOverCounter = 0;
var gameOverBool = false;
function preload () {
game.load.image("block", "assets/block.png");
game.load.image("dude", "assets/phaser-dude.png");
}
function create () {
dude = game.add.sprite(373, 760, "dude");
block = game.add.sprite(0, 505, "block");
game.physics.arcade.enable(dude);
game.physics.arcade.enable(block);
dude.body.collideWorldBounds = true;
dude.body.gravity.y = 200;
block.body.collideWorldBounds = true;
block.body.immovable = true;
block.body.velocity.x = 100;
block.body.bounce.x = 1;
spacebar = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
var jumpOrTryAgain = function () {
if (gameOverBool === false) {
dude.body.velocity.y = -250;
// If you open up your JS / error console, you'll see that
// this message gets printed an extra time each reset,
// when you click. The spacebar doesn't have this problem
// because of the `spacebar.reset()` call below.
console.log("down");
} else {
dude.destroy();
block.destroy();
gameOverText.destroy();
// Here, I can reset the spacebar, but I'm not sure what to do
// for the click / touch event, which keeps getting the jumpOrTryAgain
// callback added onto it.
spacebar.reset();
gameOverBool = false;
gameOverCounter = 0;
create();
}
};
game.input.onDown.add(jumpOrTryAgain);
spacebar.onDown.add(jumpOrTryAgain);
}
function update () {
function gameOver () {
if (gameOverCounter === 0) {
gameOverBool = true;
dude.body.velocity.y = 0;
dude.body.velocity.x = 0;
block.body.velocity.x = 0;
gameOverText = game.add.text(300, 200, "Game Over!", {fontSize: "16px", fill: "white"});
gameOverCounter += 1;
}
}
game.physics.arcade.collide(dude, block, gameOver);
}
As you can read in my comments above, the problem is that the click / touch event keeps getting the jumpOrTryAgain callback added onto itself as create gets recursively called when you reset the game. I need a way to reset the click / touch event, similar to spacebar.reset() as seen above.
game.input.onDown gets triggered from both mouse and touch input. If you need to reset its callback after every call, you can just use game.input.onDown.addOnce() (instead of game.input.onDown.add()) so that the callback is removed automatically after the first run.
All I had to do was implement a simple counter keeping the spacebar and pointer from getting re-assigned, like so:
var game = new Phaser.Game(800, 600, Phaser.CANVAS, "game", {preload: preload, create: create, update: update});
var dude;
var block;
var spacebar;
var gameOverText;
var gameOverCounter = 0;
var gameOverBool = false;
var downCounter = 0;
function preload () {
game.load.image("block", "assets/block.png");
game.load.image("dude", "assets/phaser-dude.png");
}
function create () {
dude = game.add.sprite(373, 760, "dude");
block = game.add.sprite(0, 505, "block");
game.physics.arcade.enable(dude);
game.physics.arcade.enable(block);
dude.body.collideWorldBounds = true;
dude.body.gravity.y = 200;
block.body.collideWorldBounds = true;
block.body.immovable = true;
block.body.velocity.x = 100;
block.body.bounce.x = 1;
var jumpOrTryAgain = function () {
if (gameOverBool === false) {
dude.body.velocity.y = -250;
console.log("down");
} else {
dude.destroy();
block.destroy();
gameOverText.destroy();
gameOverBool = false;
gameOverCounter = 0;
create();
}
};
if (downCounter === 0) {
spacebar = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
game.input.onDown.add(jumpOrTryAgain);
spacebar.onDown.add(jumpOrTryAgain);
downCounter += 1;
}
}
function update () {
function gameOver () {
if (gameOverCounter === 0) {
gameOverBool = true;
dude.body.velocity.y = 0;
dude.body.velocity.x = 0;
block.body.velocity.x = 0;
gameOverText = game.add.text(300, 200, "Game Over!", {fontSize: "16px", fill: "white"});
gameOverCounter += 1;
}
}
game.physics.arcade.collide(dude, block, gameOver);
}
I'm trying to run the following code, but get an error in the gameLoop function saying: JavaScript runtime error: Object doesn't support property or method 'update'.
I'm a beginning JavaScript programmer. Can you spot what is wrong with this code?
function Core(context) {
this.context = context;
this.fps = 500;
this.sprite = new Sprite();
}
Core.prototype.run = function() {
setInterval(this.gameLoop, this.fps); // <<<< PROBLEM
}
Core.prototype.gameLoop = function () {
this.update();
this.draw();
}
Core.prototype.update = function () {
this.sprite.x += 50;
this.sprite.y += 50;
}
Core.prototype.draw = function () {
this.context.clearRect(0, 0, 300, 300);
this.context.fillRect(this.sprite.x, this.sprite.y, 50, 50);
this.context.fillText('x: ' + this.sprite.x + ' y: ' + this.sprite.y, 10, 250);
}
In JavaScript, this is defined entirely by how a function is called, not where or how it's defined. The problem is that setInterval doesn't call your code with the correct this value. To fix:
function Core(context) {
var self = this;
this.context = context;
this.fps = 500;
this.sprite = new Sprite();
this.boundGameLoop = function() {
self.gameLoop();
};
}
Core.prototype.run = function() {
setInterval(this.boundGameLoop, this.fps);
}
On JavaScript engines that have ES5 features (or if you're using an ES5 "shim"), you can change Core to:
function Core(context) {
this.context = context;
this.fps = 500;
this.sprite = new Sprite();
this.boundGameLoop = this.gameLoop.bind(this);
}
More reading:
Mythical methods
You must remember this
Function#bind in the ES5 spec
Side note: Your code relies on the horror that is Automatic Semicolon Insertion. (All of your function assignments — Core.prototype.run = function() { ... }) need semicolons after the closing }.)
What you need is .bind.
setInterval(this.gameLoop.bind(this), this.fps)
Try reshuffling your code so that you declare update before you call it, e.g.
Core.prototype.update = function () {
this.sprite.x += 50;
this.sprite.y += 50;
}
Core.prototype.gameLoop = function () {
this.update();
this.draw();
}
var focus = true;
function z() {
this.t = 0;
this.p = function (t) {
if (focus == true) {
this.t = t;
alert(this.t);
}
}
}
var zp = new z();
setTimeout('zp.p(0)', 100);
window.setInterval('zp.p(1)', 2000);
var ftimer = setTimeout('focus=false', 2000);
document.addEventListener('mousemove', function (e) {
clearTimeout(ftimer);
focus = true;
ftimer = setTimeout('focus=false', 2000);
}, false);
I have this code. but for some reason it only alerts twice even with continuous mouse movements. I have been working at this and investigating in firebug and focus is true when I am moving my mouse. I have been trying to figure out what is going on.. even if I do this:
function z() {
this.t = 0;
this.p = function (t) {
if (focus == true) {
this.t = t;
}
alert(this.t);
}
}
It still only alerts twice.
I have tried using a looping setTimeout function too but that doesnt work either. It is driving me crazy.
Good that you found your bug.
I would write your code using a bit more functions-as-first-class-objects and less eval logic:
var focus = true;
function z() {
var that = this; // we won't always have the correct value of "this"
this.focus = true;
this.t = 0;
this.p = function (t) {
if (that.focus == true) {
that.t = t;
alert(t);
}
}
this.fade = ( function(obj){ return function(){ obj.focus = false; } } )(this); // Using self-invokation
}
var zp = new z();
setTimeout(zp.p, 100, 0);
window.setInterval(zp.p, 2000, 1);
var ftimer = setTimeout(zp.fade, 2000);
document.addEventListener('mousemove', function (e) {
clearTimeout(ftimer);
zp.focus = true;
ftimer = setTimeout(zp.fade, 2000);
}, false);
I used a self-invoking function on line 10: ( function(obj){ return function(){...} })(this);, and set this to a different variable.
I found out that it was just related to firefox and that firefox choked on a line of code so that is why it wouldn't run. So I fixed that and now everything is all good.