Sign in to follow this  

JavaScript Platformer Level Format

This topic is 810 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am writing a tile based platformer in JavaScript. I was thinking about storing the level data in a JSON file and then using XMLHttpRequest to grab the file and then load it using XMLHttpRequest.onreadystatechange like this:

TileMap.prototype.loadFile = function(pathToFile) {
	'use strict';
	var xhr = new XMLHttpRequest();
	var that = this;
	xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
                // parse json file and load tilemap stuff
                }
};

PlayState.prototype.init = function() {
	this.tileMap = new TileMap();
	this.tileMap.loadFile('level-1.json');
	this.camera = new Camera(0, 0, this.tileMap.mapLayers[0].width * 
							 this.tileMap.mapLayers[0].tileWidth, canvasWidth,
							 this.tileMap.mapLayers[0].height * 
							 this.tileMap.mapLayers[0].tileHeight, canvasHeight);
};

However, I'm not sure if this is the best way of going about this. Would other parts of my game be running while this is loading? Would it be better if I hardcoded the data into my game? What do you guys think?

Share this post


Link to post
Share on other sites

I don't see a problem with using this to grab the next level, one thing you could do if you don't want your game running in the background is display some kind of loading screen while the file is being retrieved, something like:

 

1. show a loading screen in the middle of the page or something (as a DOM element like a div or whatever)

2. send off XMLHttpRequest with an onready callback that goes parses the json and loads the level, and then hides the loading screen when it's done

3. resume running the game...

 

I don't think hardcoding the level in your game is a good idea. It's okay to prototype but having it on the server allows for plenty of interesting mechanics like letting users upload and share their levels, etc...

Share this post


Link to post
Share on other sites

How are you going to store the tilemap, by the way? I remember that when I used to mess with HTML games, I'd store the tilemap as a string (one character per tile), because otherwise the memory usage would blow up like crazy (it may not seem much given the size of RAM... but remember this is running on a browser on a rather loose scripting language and the data may have to be downloaded every time).

 

Mind you, that's just regarding the tilemap, object placement is a different issue.

 

EDIT: incidentally: http://sik.titandemo.de/supercat/supercat/level_map_1.js

And here thinking it'd look pretty horrible since this was a pre-HTML5 experiment, but that format actually looks... quite reasonable, all things said. And it's json too, to boot.

Edited by Sik_the_hedgehog

Share this post


Link to post
Share on other sites
Other things to consider are what other data you need may need to download. If you need to download a level and then also tilemap images, object definitions, sounds, etc. for the level then a single request for a JSON object may be a bad choice. Each individual XMLHttpRequest call has a lot of TCP+HTTP overhead so you want to make as few of those as possible.

You'll possibly want to grab datapack files, same as you might use in a native-code game. e.g., grab a ZIP file or the like and then unpack it in JS using something like zip.js.

Share this post


Link to post
Share on other sites

Thanks for the responses! I am loading the level like this:

TileMap.prototype.loadFile = function(pathToFile) {
	'use strict';
	var xhr = new XMLHttpRequest();
	var that = this;
	xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
			console.log('reading file in');
			
			var levelData = JSON.parse(xhr.responseText);
			
			var layers = levelData.layers;
			
                        // a test to see if it's loaded
			console.log(layers[0].height);
			
			Game.levelLoaded = true; 
		}
	};
	xhr.open('GET', pathToFile, true);
	xhr.send(null);
};

PlayState.prototype.init = function() {
	this.tileMap = new TileMap();
	this.tileMap.loadFile('platformer/res/maps/level-1.json');
	
	while(!Game.levelLoaded) {
		clear('#ffffff');
	}
	
	console.log('camera is being created');
	
	this.camera = new Camera(0, 0, this.tileMap.mapLayers[0].width * 
							 this.tileMap.mapLayers[0].tileWidth, canvasWidth,
							 this.tileMap.mapLayers[0].height * 
							 this.tileMap.mapLayers[0].tileHeight, canvasHeight);
};

However, whenever I try to load the PlayState which then loads the TileMap, the page freezes, and I get the option to stop the script from Firefox. It doesn't reach the 'console.log(layers[0].height)'. What's wrong here?

 

For the resources, I was just planning to grab those using something like this:

this.dotImg = new Image(this.dotHeight, this.dotWidth);
this.dotImg.src = '/platformer/res/grey_dot.png';

Is that a good way to load resources?

 

And thanks again for the help.

Share this post


Link to post
Share on other sites
You have a fundamental misunderstanding of how JavaScript works.

JavaScript is single-threaded unless you explicitly use something like WebWorkers. XMLHttpRequest is not run in the background; it's run "in between" event callbacks.

Your while loop on levelLoaded never returns, and hence never gives the browser a chance to pump the event queue, so the request never has a chance to complete. The result is a deadlock; three lip can't exit without the request finishing but the request can't finish until the loop ends.

Share this post


Link to post
Share on other sites

Sean is bang on there with your while loop, just hit a callback from the level loaded section where you set Game.levelLoaded = true.

i.e.

TileMap.prototype.loadFile = function(pathToFile, onLoad, onError) {

Share this post


Link to post
Share on other sites

Thanks for the all the replies! I got the level loading working using a callback, but I have another problem. I also set up a camera to help me render what's supposed to be on screen and what's not. However, when I call the callback to set up the camera, I get this error message: Uncaught TypeError: Cannot read property 'setProperties' of undefined. Here's my code:

function Camera(camX, camY, camWidth, camHeight) {
	this.positionX = camX;
	this.positionY = camY;
	this.xBounds = 0;
	this.yBounds = 0;
	this.width = camWidth;
	this.height = camHeight;
}

Camera.prototype.setBounds = function(camXBounds, camYBounds) {
	 this.xBounds = camXBounds;
	 this.yBounds = camYBounds;
};

TileMap.prototype.loadFile = function(pathToFile, callback) {
	'use strict';
	 var xhr = new XMLHttpRequest();
	 var that = this;
	 xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
			 // parse json and load tilemap ...
			 Game.levelLoaded = true;
			
			 callback();
			
		 }
	};
	 xhr.overrideMimeType("application/json");
	 xhr.open('GET', pathToFile, true);
	 xhr.send(null);
};

function PlayState() {
    'use strict';
    GameState.call();
	
	clear('#ffffff');
	this.camera = new Camera(0, 0, canvasWidth, canvasHeight);
	this.camera.setHeight(canvasHeight);
	this.camera.setWidth(canvasWidth);
	console.log(this.camera);
	
	this.tileMap = new TileMap();
	
	this.tileMap = new TileMap();
	this.tileMap.loadFile('/platformer/res/maps/level-1.json', this.continueLoadingLevel);
		
}

PlayState.prototype.continueLoadingLevel = function() {
        // this.camera is undefined
	this.camera.setBounds(this.tileMap.mapLayers[0].width, this.tileMap.mapLayers[0].height);
};

I don't know why it's undefined. It's defined in the PlayState constructor, and continueLoadingLevel() is a part of the PlayState prototype, so this shouldn't be happening. I also created this.camera with the new keyword. Why is it undefined?

Share this post


Link to post
Share on other sites

It is undefined because if a little-understood quirk of ECMA script; "this" is a function parameter, not an upvalue. The function reference this.continueLoadingLevel, used by the name callback in onreadystatechange, does not have any memory of what object "this" is.

 

The normal fix is to replace your call to loadFile with...

var thisobj = this;
this.tileMap.loadFile('/platformer/res/maps/level-1.json', function(aData) {
    thisobj.continueLoadingLevel(aData);
});

I added a parameter aData because you should pass the data from the XHR to the callback function, instead of storing it somewhere fixed to be picked up by the callback.

Edited by Wyrframe

Share this post


Link to post
Share on other sites

This topic is 810 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this