Statistical Patterns

 

download pixbet apk

Joyrider: Run-time Level Graphics Generation Kasper Mol · Follow Published in Poki · 11

min read · Nov 11, 2024 -- Listen Share

Gameplay from a Joyrider level

Hi all! Next to

my job as a Tech Lead at Poki, I like to spend some of my free time chasing my passion:

game development. I’ve been at Poki for a long time, and next to the awesome team, a

big reason for that is because I believe in web as a great destination for games.

So

far I’ve created two games with my one-man studio kokos.games: Supernova and the

freshly released Joyrider.

This post is a deep-dive into how levels (and especially the

graphics) are created for Joyrider, but before we get there, a little bit of

context:

One project leads to another

Supernova, my first project, released in early

2024

Supernova: small size, simple experience

Good conversion to play, or an as small

as possible loading drop-off, is one of the main pillars of creating a successful web

game. This is achieved by multiple things, the most important one being a small initial

file-size.

My goal when creating Supernova was centered around this idea. Basically, I

wanted to create the smallest possible game, while still providing a polished (though

simple) experience.

I would say that was a success, by using Three.js and relying on

mostly procedural graphics, the game ended up at around 200kb. And I’m quite happy with

how far I managed to push the geometric psychedelic style within this budget. The users

seem happy too, the game has a great rating of 4.5 out of 5.

Joyrider: more goals, more

better (😅)

For Joyrider, my goals were not as straightforward. The project actually

started out as an exploration of building my own ECS framework with RxJS. Originally I

intended to make a simple game again so I could focus on this framework, but somewhere

along the way I realised that I wanted to make a game that forced me to explore more

sides of the game development process such as 2D art, level design, sound design and

others.

My first prototype in the RxJS-based ECS framework

In the end I landed on the

idea of creating a bike trials game, very much inspired by a classic series: RedLynx

Trials, which I played and loved in high-school. My aim was for it to be a slightly

more casual-friendly experience while still keeping the trials vibe of controlling your

character’s weight alongside the bike’s acceleration.

The project’s scope ended up a

lot bigger than I initially intended, but since these games are personal projects, I

don’t have any deadlines to work with. As long as I enjoyed the process, I’d get there

eventually. The project took about two to three times longer to complete compared to

Supernova, and was much more challenging at times, but eventually I got it done. Don’t

be afraid to challenge yourself!

Early prototype of the game’s bike and player

During

the entire process of designing and developing the game, the conversion to play was

always in the back of my mind. This started with the choice of tech by creating my own

framework (as opposed to using an existing engine with more overhead), with PixiJS for

graphics, Planck.js (a Box2D implementation in JS) for physics and howler.js for

audio.

Run-time level graphics generation

It was important to me for Joyrider to have

some basic sense of world-building. I really wanted to have a Super Mario Bros 3 style

overworld through which the game levels and shops were accessed, and the game would

need to support various biomes. I wanted to avoid having to download too many sprites

for all these biomes and levels and keep the file-size as small as possible, but I

still wanted to have great control over the level geometry as this would be a physics

game where small changes in geometry could have a big impact.

The solution? Designing

levels with polygonal geometry, and drawing the graphics at run-time. I realised that

this could prove to be a challenge especially for mobile devices, but I had faith and

started the work.

Comparison of in-editor vs. in-game level in the final build

Step 1:

Designing the levels

Before I got to worry about generating the graphics, I first

needed level geometry. Initially I intended to have my own level editor, but quickly

decided to drop that idea due to the insane scope it would add to an already big

project. While I was researching techniques of building good physics simulations, I

landed on the amazing Youtube channel of iforce2d, who not only does some insane things

with Box2D, but has also built his own editor called R.U.B.E. (Really Useful Box2D

Editor).

The editor is super powerful, fairly priced, and the creator provides a ton of

tutorials on his Youtube channel. I ended up using only a fraction of the features the

tool provides, but it gave me a great foundation to build my levels. I started with a

trial and quickly decided to purchase and use the editor for the project, and I’m still

very happy with it.

A screenshot of Joyrider’s first level in R.U.B.E.

A Joyrider level

in R.U.B.E. consists of the following:

Static bodies which contain the main geometry

for the level Dynamic/kinematic bodies and joints for things like rope bridges or

moving platforms Static sensors that get translated to things like kill, finish and

camera volumes Image objects which get translated into simple or complex entities in

the game engine, for things like warning signs and checkpoints

Once a level is

complete, it is directly exported from the editor to a json file. This file contains

all the information necessary for a Box2D engine to run the simulation. On top of that,

the custom attributes I’ve specified in the editor allow the passing of more

information to the game engine about things like ground material or camera volume

configuration.

Custom attributes on a body

Step 2: Getting the level ready for the

engine

Read along with the raw export of the first level here. The file that gets

exported from R.U.B.E. has a lot of info, but it’s not fully ready yet.

Translating to

game coordinates and reducing decimals

First I need to translate the coordinates to

game world space. This is a simple piece of logic that just inverts every Y coordinate,

and multiplies all X and Y coordinates by our WORLD_SPACE constant (which is

100).

While I’m doing this, I also round up all coordinates as there’s really no need

for 10 decimal places of detail. This reduces the size of the level file, leading to

savings in download time, and has the added benefit of making the merging of polygons a

bit easier in the next step.

Re-merging polygons

A limitation of Box2D is that it does

not support concave polygons. A great feature of R.U.B.E. is that it handles this issue

elegantly, and upon exporting automatically splits up your concave polygons into

multiple convex polygons. However, this makes it hard to create nice-looking graphics

on top, as those require one continuous polygon.

A concave polygon, split by R.U.B.E.

into two convex ones as you can tell from the separator in the center.

To solve this, I

have to do a pass on the exported data to re-merge the polygons where possible within a

body. To achieve this, I implemented Turf: “a JavaScript library for spatial analysis”.

This library has loads of features, but I only used one: @turf/union.

My implementation

basically comes down to brute-forcing all the polygon pairs until I’ve tried

everything. It’s not the fastest, but since this is all part of a build step it doesn’t

need to be fast, it just needs to work. You can see the relevant code here.

During this

step I also hash the resulting polygons, and give all of them a unique identifier based

on this hash. This helps later on by not having to generate graphics multiple times for

identical polygons.

In the end there’s two sets of polygons per body. One for Box2D to

handle the physics, and one for the generation of sprites.

Result

You can view the

resulting level file after these changes here. The final minified level json weighs in

at 38KB before getting Brotli compressed on the Poki game CDN. I decided to ship the

levels as part of the main game bundle as they are very suitable for compression and

thus barely impact the game’s total file-size.

Step 3: Generating the world

Now that

all the level data is prepared and ready for the engine, it’s time to start worrying

about drawing the world.

Drawing the sprites

Drawing the sprites happens as soon as

somebody selects a level.

The moment that levels are generated

As a last thing before

drawing the sprites, I check the capabilities of the user’s device. Two things are

important:

Does the device support workers?

Hopefully yes! This allows doing the sprite

generation off the main thread, and helps performance greatly. But if not, I just fall

back to doing this on the main thread. Are we dealing with a mobile device, and more

specifically, an iOS device?

For mobile devices I make the general assumption that

there’s less power to work with. For these devices, I lower the resolution of the to-be

generated sprite to 50%. For iOS, I lower it even further to 25%, as those devices were

especially prone to crashing. Lower resolution means smaller sprites means a lower

memory footprint, means happy devices.

Now we’re all set!

The drawing logic itself is

quite straightforward. I first create an OffScreenCanvas or Canvas Element (depending

on whether we’re in a worker) at the size of the polygon. Then I use the Canvas API to

draw this polygon onto the 2D context. The sprites are nothing more than a nice filled

pattern surrounded by a couple of strokes.

The ground patterns used

One ground polygon

in-editor and after generation

Easy does it! However there is one more thing to deal

with, on most mobile GPU’s, the maximum size of an image is reasonably low. To be safe,

I assume we can have no
download pixbet apk
larger than 2048x2048. Although most of the level

polygons are below this limit, not all of them are.

To fix this, I split up the image

into chunks of 2048x2048. I extract these chunks by using context.getImageData. Then I

use createImageBitmap (polyfilled as not all browsers support it) to turn this

ImageData back into a format that can be used directly in PixiJS.

An example of what

these chunks look like (for illustrative purposes I’ve set the chunk size to

256x256)

All these chunks are sent back over to the main thread, alongside the

necessary data for stitching them back together.

The resulting
download pixbet apk
are saved to

cache, keyed on the hash that was generated during the build step. In some cases same

polygon is re-used for multiple bodies and it would be a waste to do this process again

when I can just re-use the same sprite. This also improves run-time performance as the

GPU can re-use the same image as well.

Loading everything into the game

Finally, the

game entities are generated. All the chunks are combined together into a single PixiJS

container so it looks like a whole again. Next to the polygons, there’s also some other

entities to generate (such as checkpoints).

The entities are split into two types,

static and dynamic. I do this so I can destroy and re-generate the dynamic entities

once a player crashes / restarts, while keeping the static ones alive during the entire

level duration.

And that’s it, we now have a level :)

{nl}

blaze cassino download

apostas on line da sportsnetloteria da pascoa

criar roleta digitaljogo da bet365