melonjs / melonJS

a fresh, modern & lightweight HTML5 game engine
https://melonjs.org
MIT License
5.86k stars 644 forks source link

Question: Parallax TMX tile layer #734

Closed gcornut closed 3 years ago

gcornut commented 9 years ago

Hello everyone,

First I'd like to say that melonJS is really awesome. I've used it a long time ago to make a little platformer game in a few days and I found it really easy to use with great examples. Now I'm developing a "mini" clone of Teeworlds in melonJS with WebRTC connections (just for fun, not sure it really bring anything better than the original game).

So working on that project I was wondering if it is possible to do parallax effect on a TMX tile layer? This layer would in the background but it would really be great if it could be tiled and not a static image. Also, I've seen that I could do parallax effect on TMX image layer but can I move this layer with X and Y coordinates?

parasyte commented 9 years ago

In short, parallax scrolling tile layers is not supported.

The long answer is:

It's probably something we will want to explore, especially as WebVR (#728) gains momentum. Each tile layer in the map creates a me.TMXLayer object, which has it own position vector (initialized to <0,0>) Here's the implementation for 2.1.3: https://github.com/melonjs/melonJS/blob/2.1.3/src/level/TMXLayer.js#L343

It extends me.Renderable, which is where the position vector is created. If you can get a reference to this object at runtime, you can adjust its position in response to viewport scrolling:

// Get a reference to the tile layer named "background" when a new map is loaded
me.event.subscribe(me.event.LEVEL_LOADED, function () {
    this.bgTileLayer = me.game.world.getChildByName("background")[0];
}.bind(this));

// Subscribe to the viewport change event; fires when the viewport scrolls
me.event.subscribe(me.event.VIEWPORT_ONCHANGE, function (pos) {
    // Copy the viewport position into the tile layer position, then divide by 2...
    if (this.bgTileLayer) {
        this.bgTileLayer.pos.copy(pos).scale(0.5, 0.5);
    }
}.bind(this));

This is a simplified example, but demonstrates tile layer parallax scrolling (the layer scrolls half as fast as the viewport). Plug in different scaling values to fit your needs. A unit vector works best, so the values should be in range [0.0..1.0]. Also missing is the anchorPoint, but you can implement that yourself if necessary.

gcornut commented 9 years ago

Thank you very much, I managed to do what I wanted on the map. The code is a bit hacked but it works :).

Here is a link to the game (still in development) if you want to see the result: http://git.io/twjs The source code repo: https://github.com/Soaring-Outliers/teeworlds.js

Thanks for everything

parasyte commented 9 years ago

@gcornut That looks pretty cool!

My MBP started running hot after playing around in the level for a bit. I thought that was kind of unusual, so tried enabling the WebGL renderer: http://soaring-outliers.github.io/teeworlds.js/#webgl

It throws an exception on load because the texture cache overflows. :confounded: I didn't do any further investigation. Assuming the game is currently unoptimized.

It took a moment to spot it, but the dark brown layer (cave walls) is scrolling with a tiny bit of parallax. I guess you have plans to randomly generate maps? Thanks for sharing that!

gcornut commented 9 years ago

Indeed the game is not optimized, I didn't tried WebGL yet but I will check into that.

For the cave wall I decided to use "preRender"; It's easier that way, I can do parallax just like with "ImageLayer". I'm not sure I will keep this parallax effect for this layer though because as you saw it's not very visible and a bit heavy at runtime. I didn't thought of randomly generate maps but that could be a great idea.

If you have any advice for optimization let me know, I'm new to game development and I don't do a lot of JavaScript. Also, for fun, I decided to use Ecmascript 6 with browserify and babelify. Maybe the transformation to ES5 doesn't help runtime performance :/

parasyte commented 9 years ago

Got it! All makes sense.

Have a look at the difference between the scrolling implementation for me.ImageLayer on the 2.1.3 release and what's currently in master (3.0.0 WIP). There have been significant changes to reduce the number of texture atlas regions that need to be created for WebGL. Specifically, every time the source coordinate arguments to renderer.drawImage change, it creates a new texture region. The new scrolling code in 3.0.0 keeps the source coordinates static, and adjusts the destination coordinates using a bunch of math tricks. That's one tip for optimizing.

I didn't see a whole lot of images being loaded, but another good way to make it faster is to pack all of your sprites into a single image with TexturePacker or ShoeBox. Using prerender will take up one slot in the texture cache for every layer that is prerendered. The texture cache is really small, on most devices it has 16 - 24 slots. Also, each image should have a power-of-two size, like 256x256, 512x512, or 1024x1024...

Some other ideas you can use on the JavaScript side are documented in the FAQ: https://github.com/melonjs/melonJS/wiki/Frequently-Asked-Questions#optimization

I haven't experimented with Babel, so I cannot comment on runtime performance of the code it outputs.

gcornut commented 9 years ago

Thanks for all these resources and advice that's very helpful :smiley: