i've just implemented movement for my main character sprite on my JavaScript/HTML5 game and all is well except for when i load existing users saved data.
In my main JavaScript file depending on whether the user clicked the new game button or load game button either a new game is loaded or the users data is loaded from a DB via PHP and Ajax.
If the user clicks new game it runs the code below and the player moves fine:
else{
//set beginning params
//Change screens
ui.new_game();
layer = scene.load("level_"+1);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if (layer.hasOwnProperty("properties") && layer.properties.hasOwnProperty("collision"))
{
scene.collisionLayer(layer);
}
});
gameGUI.set_level(1);
gameGUI.set_location(20,20);
gameGUI.init_inv();
player.init_Player(20,20);
player.draw();
last_update = Date.now();
}
BUT if the player decides to load their previous game settings the code below is run and instead of the sprite moving fluidly in the canvas, when a key is pressed e.i right arrow key, the sprite will disappear, then flicker somewhere on the right side of the screen then disappear again.
function game_settings(state){
if(state == "load"){
ui.load_game();
//do ajax call to load user last save
var dataString = {"user_data": "true"};
$.ajax({
type:"POST",
url:"PHP/class.ajax.php",
data: dataString,
dataType: 'JSON',
async:false,
success: function(success) {
player_details_init(success);
},
error: function(){
alert("ERROR in User data");
}
});
layer = scene.load("level_"+level);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if (layer.hasOwnProperty("properties") && layer.properties.hasOwnProperty("collision"))
{
scene.collisionLayer(layer);
}
});
player.init_Player(location_X,location_Y);
player.draw();
last_update = Date.now();
}
I know the Ajax is whats causing the problem because when i comment it out the Sprite moves like it should do, the only problem is i don't know why the Ajax is causing this weird behavior?
I have uploaded the current version of the game to HERE, so you can witness the weird behavior if you so wish.
To log in use
guest - username
guest - password
Thanks for your time
EDIT
Ok i have narrowed down the problem even further to the variables location_X, location_Y. Whne i stick in a hardcoded number say 20, 20 in the playr.init call the movement works fine, but when i use location_X, location_Y like you see in the example the problem still occurs as above.
I retrieve location_X and location_Y from a function called when the Ajax returns success called player_details_init.
Here is that function:
function player_details_init(success){
$.each(success, function(key,value){
if(level == null){
level = value.level;
}
if(location_X == null){
location_X = value.location_X;
}
if(location_Y == null){
location_Y = value.location_Y;
}
//items.push([value.game_item_id, value.quantity]);
gameGUI.set_level(level);
gameGUI.set_location(location_X,location_Y);
gameGUI.init_inv();
});
}
SO my guess is it is something to do with this function although when i do a console.log the location returns fine and the sprite does appear where it should it just doesnt move correctly
From the symptoms you describe, it looks like location_X and location_Y are strings instead of numbers. In this situation, computations will not work as you expect (addition will result in concatenation, for instance).
I would suggest you try applying parseInt() to these values (and possibly others as well) when reading your JSON payload.
Related
I've been running into an issue while trying to update a script for a client. I'm still somewhat new to the industry, so I wanted to make sure I'm not missing anything.
I am attempting to edit a web page which displays a number of PDFs in canvas views. The issue arises when a user tries to switch which PDF they're viewing. The function that retrieves the PDFs has a setTimeout in place to get the content to reload every minute, as the PDFs themselves are updated with new data every 5 minutes and the users need to be able to monitor the data as it comes in. However, when switching from one PDF canvas to the next, I found that the PDF doesn't update. The only PDF that reloads and updates is the one whose canvas has been open. This is what I'm trying to change. I want the PDF display to update not only every minute, but also whenever a user switches which PDF they're viewing.
What this told me is that I need to add something that loads the PDFs again when switching canvas views. First, I tried taking the function that updates the PDFs and calling it whenever the user switches the canvas view. This seemed to work, but the issue ended up changing. The page would begin to reload more and more often the more times you would change canvas views - I'd assume because the setTimeout within the function had then been called more than once.
My second attempt was to make a new function that simply didn't have the setTimeout code and call that instead. I must have done something wrong however, because after calling that function in the code, none of the PDFs wanted to load at all.
I'll paste the code for the functions I need to work with below. Any help is appreciated, especially if I'm just missing something obvious. If you need any other details, let me know. Thank you to anybody who offers help.
function getReport(reportName, frameId) {
var params = {
report: reportName
};
$.post('/getReport.php', params, function(data){
if (data == "") {
window.location.href="/login.php";
return;
}
if (useBrowserViewer) {
$("#pdfFrame" + frameId).attr("data", "data:application/pdf;base64," + data);
} else {
displayPdf(data, document.getElementById("canvas" + frameId), 1);
if (frameId == "4") {
displayPdf(data, document.getElementById("canvas4Page2"), 2);
}
}
setTimeout(function() {
getReport(reportName, frameId);
}, 60*1000);
}).fail(function() {
console.log("Report not returned.");
setTimeout(function() {
getReport(reportName, frameId);
}, 60*1000);
});
}
function showReport(frameIdNum) {
if (useBrowserViewer) {
$(".pdfFrame").hide();
$("#pdfFrame" + frameIdNum).show();
} else {
$(".pdfCanvas").hide();
document.getElementById("canvas" + frameIdNum).style.display = 'block';
if (frameIdNum == "4") {
document.getElementById("canvas4Page2").style.display = 'block';
}
}
$(".link").removeClass("active");
$("#link" + frameIdNum).addClass("active");
}
I have a POST request that pulls data from a server, according to parameters that are adjustable by the user through number inputs. Simply listening to the change event is not enough, because I want the data to refresh while using the mousewheel to change the input value.
Calling my refresh function with something like that is clearly not optimal:
$('input').on('mousewheel', function(){
refresh_data();
});
There would be a lot of requests, and because they are asynchronous, I can never know for sure if the last request to be completed will be the last one I send.
What I currently use is a setInterval to watch an object containing intervals (rounded timestamps) where a refresh has been requested. Inside the mousewheel listener, I add the next interval to the object, as a property/key, to avoid duplicates.
Something like that:
var i = 100;
var obj = [];
$('input').on('mousewheel', function(){
// add next interval to obj;
obj[Math.ceil(Date.now()/i)*i] = true;
});
var check = setInterval(function(){
// refresh if current interval is in obj
var t = Math.floor(Date.now()/i)*i;
if(obj[t]){
delete obj[t]; // remove from obj
refresh_data();
}
}, i);
I tested this code, on my machine, with i=50, and the object is always empty, which is what we want, but with i=30, as soon as I go too fast with the wheel, I see some old intervals accumulating in the object. This is caused by the setInterval is skipping beats, so whatever value I choose for i, some user with less CPU could "skip the wrong beat", and end up finishing his mouse wheel movement seeing a result that does not match the parameters value.
I feel like maybe i'm making this more complicated than it has to be, so i'm asking:
What would be the best way to pull data from server onmousewheel, considering:
1: I want to constantly refresh the data as the user rolls the wheel over an input field.
2: I want to be 100% sure the presented data after the wheel movement is always accurate.
Try using the document offsetheight like the below code. This will work when user scrolls down and reaches the end of scroll. The behavior is kind of like a recycler view of android.
window.onscroll = function(ev) {
if ((window.innerHeight + window.pageYOffset ) >=
document.body.offsetHeight) {
alert("you're at the bottom of the page");
}
};
I'm using a setTimeout function that calls itself in a loop in order to create the look of a spinner. Right now I'm just using a spinner image from Google and rotating the image within a setTimeout function and a global variable for the number of degrees to rotate. It looks like this:
var rotate = 0;
var rotateOn = false;
function rotateSpinner() {
if (rotateOn) {
setTimeout(function() {
rotate +=5
$('.spinner').css("transform", "rotate(" + rotate + "deg)");
rotateSpinner();
}, 30)
}
}
I call the spinner like this, which corresponds with a sending a socket.emit to the server:
socket.emit('someEmit', xyz, abc)
rotateOn = true;
rotateSpinner();
$('.spinner').show();
And when the response is received from the server, I reverse the code above, which should hide and then stop the spinner.
socket.on('someResponse', function() {
$('.spinner').hide();
rotateOn = false;
someOtherFunction();
})
I'm using this to fill the gap between the emit to the server and the response in several areas, however, in one of these instances, I have to go through a fairly large number of loops to build the page on the server response and it's causing the spinner to stop for a few seconds before the content actually shows up. It doesn't seem to matter how I write it out (including moving the statement to turn rotateOn to false to the end, adding a setTimeout function that doesn't turn rotateOn to false until well after the content should be done loading, etc.) - I still get the pause. Is there anything I can do to counteract this?
I am using OpenLayers to connect to a home-grown server, and unlike professional grade servers like Google or Cloudmade that box will actually take a while to calculate the result for a specific tile. And as it is a mathematical function I am plotting, there is no big chance to accelerate the server or even pre-render the tiles.
My initial trials with Leaflet quickly came to the conclusion that Leaflet actually leaves all of the reloading and load-error handling to the browser, while OpenLayers at least has an event that is fired when the tile server does return with an error code.
The idea I am following was to basically start rendering a tile when it was requested and fire an HTTP 503 immediately, relying on the client to try again.
To try again, I implemented a simple layer like this:
var myLayer = new OpenLayers.Layer.OSM.MYLayer("mine", {
'transparent':"true",
'format':"image/png",
'isBaseLayer':false});
myLayer.events.register("tileerror", myLayer, function (param) {
// Try again:
var targetURL = param.tile.layer.getURL(param.tile.bounds);
var tile = param.tile;
tile.timeout = tile.hasOwnProperty("timeout") ? tile.timeout * 2 : 1000;
setTimeout(function (tileToLoad, url) {
if (tileToLoad.url === url) {
tileToLoad.clear();
tileToLoad.url = url;
tileToLoad.initImage();
}
}.bind(undefined, tile, targetURL), tile.timeout);
});
I figured out the code required to reload a tile from the source of OpenLayers, but maybe there is a cleaner way to accomplish this.
My problem is: The tiles themselves are reused, as are the divs in the DOM, so the reload procedure might actually try to reload a tile into a DIV that long as been successfully reused, e.g. because the user scrolled to someplace else where the server was able to provide data quickly.
The question I guess boils down to - is there an official way to use the tileerror event to simply try to reloading, or at least a simpler way in the API to trigger a reload? I spent quite a while in the source of OpenLayers itself but couldn't shed light on why it is still going wrong (the test for tileToLoad.url == url didn't really do it).
Thanks for your help!
Ok, after some more trial and error I found that I could actually add an eventListener to my Layer class, which will do what I want - try to reload the tile again after a certain wait. The trick was the consecutive call of setImgSrc() for cleanup and to draw with the true parameter, which effectively is an (undocumented) force flag. Thanks to the code!
OpenLayers.Layer.OSM.MyLayer= OpenLayers.Class(OpenLayers.Layer.OSM, {
initialize:function (name, options) {
var url = [
"xxxx"
];
options = OpenLayers.Util.extend({
"tileOptions":{
eventListeners:{
'loaderror':function (evt) {
// Later reload
window.setTimeout(function () {
console.log("Drawing ", this);
this.setImgSrc();
this.draw(true);
}.bind(this), 3000); // e.g. after 3 seconds
}
}
}
}, options);
var newArguments = [name, url, options];
OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
},
CLASS_NAME:"OpenLayers.Layer.OSM.MyLayer"
});
You should have a look at the following resources:
http://dev.openlayers.org/docs/files/OpenLayers/Util-js.html#Util.IMAGE_RELOAD_ATTEMPTS
http://dev.openlayers.org/apidocs/files/OpenLayers/Tile-js.html
http://dev.openlayers.org/docs/files/OpenLayers/Tile/Image-js.html
I have a TreeView with some nodes inside of it. Whenever the user clicks on a node, some code runs:
function LoadFloorPlan(componentID) {
var floorPlanImage = new Image();
//Will only fire if component has a floorPlan, else no image returned.
$(floorPlanImage).load(function () {
//Resize the stage now that that we know the image's dimensions.
//Don't use img's width because a small stage may truncate the messageLayer.
planViewStage.setSize($('#tabs-6').width(), floorPlanImage.height);
PaintFloorPlan(floorPlanImage);
GetDiagrams(componentID);
});
$.ajax({
url: '../PlanView/RenderFloorPlanImage',
data: { ComponentID: componentID },
datatype: 'json',
success: function (imageSource) {
//TODO: This isn't correct for all browsers.
if (imageSource !== "") {
//TODO: When the component's node stores information about itself, check it for a 'HasFloorPlan' attribute.
floorPlanImage.src = '../PlanView/RenderFloorPlanImage?ComponentID=' + componentID;
}
else {
WriteMessage("The selected component has no associated floor plan.");
}
}
});
}
I am experiencing timing issues with the above code. If the user selects a new node in the TreeView after the ajax request has fired off I display incorrect data momentarily. This issue is exacerbated if the newly clicked node does not overwrite the old nodes image.
I don't really want to wipe the canvas in multiple places to handle this scenario. Instead, I would like to kill my ajax request such that the load event never fires.
Does this sound like a reasonable approach to my problem? Advice? I am still learning how to interact with asynchronous events properly.
Either way, your canvas will be "momentarily" invalid. The question is, do you want to freeze the user's browser (which a synchronous call will do), or implement a "please wait" method of your own (which is more work).
Synchronous calls are frowned upon because locking up the browser is generally considered discourteous (at best). The only time I ever use it is for quick prototyping against a local server.
I am considering this as a solution. It works well for my purposes and I don't see any dangerous edge cases.
var requestCount = 0;
function LoadFloorPlan(componentID) {
requestCount++;
var floorPlanImage = new Image();
//Will only fire if component has a floorPlan, else no image returned.
$(floorPlanImage).load(function () {
requestCount--;
if (requestCount == 0) {
//Resize the stage now that that we know the image's dimensions.
//Don't use img's width because a small stage may truncate the messageLayer.
planViewStage.setSize($('#tabs-6').width(), floorPlanImage.height);
PaintFloorPlan(floorPlanImage);
GetDiagrams(componentID);
}
});
$.ajax({
url: '../PlanView/RenderFloorPlanImage',
data: { ComponentID: componentID },
datatype: 'json',
success: function (imageSource) {
//TODO: This isn't correct for all browsers.
if (imageSource !== "") {
//TODO: When the component's node stores information about itself, check it for a 'HasFloorPlan' attribute.
floorPlanImage.src = '../PlanView/RenderFloorPlanImage?ComponentID=' + componentID;
}
else {
WriteMessage("The selected component has no associated floor plan.");
requestCount--;
}
},
error: function () {
WriteMessage("There was an error rendering the floor plan.");
requestCount--;
}
});
}