A roguelike sprite randomiser with Shiny

kenney
r
shiny
shinylive
Author
Published

December 15, 2024

A web app titled 'roguelike sprite builder'. A 'reroll' button is pressed several times, which causes a 16-by-16 pixel character sprite to be regenerated with random hair, shirt, weapons, etc. There are additional buttons to download the image. The art is attributed to Kenney.

tl;dr

I made a quick Shiny app that generates randomised characters for your next roguelike game, made with parts designed by Kenney.

Standing on the shoulders of Kenney

Kenney makes free (CC0) assets for videogames, like the 16×16-pixel sprites I used in a previous post, where you could move a little character move around a procedural forest glade while being chased by a mouse.

Kenney also has a roguelike characters pack where you can create your own characters by mixing and matching bodies, clothes and weapons. I’ve had a stab at making a toy roguelike in R before, but the ‘art’ and interface were purely composed of text.

With a long-term goal of creating a ‘real’ roguelike game (maybe in R?), I made a small app to combine sprite parts randomly to create new characters. It’s a pretty simple implementation for now, but I’m recording its current state in case I never come back to it.

An ‘applike’

The app is made with Shiny, deployed for the browser with {shinylive} and served with GitHub Pages. The source for the app is on GitHub. I’ve embedded it below, but you can also visit it in a standalone window. It might take a moment to load.

Hit the reroll button to randomise the sprite parts. You can also download as PNG at 16×16 and 1024×1024 pixels or as an R-specific RDS file containing a nativeRaster representation of the sprite.

Going rogue

The basic approach was to:

  1. ‘Cut out’ and save each individual sprite part from a single ‘spritesheet’.
  2. Select sprite parts at random given some weighting (bodies are always selected, but weapons aren’t).
  3. Store the part names in a reactiveValues() object so they can be called by the draw method (to display the sprite onscreen) and via the download options.
  4. Use image_mosaic() from the {magick package} to print sprite parts on top of each other and store this as a nativeRaster object.
  5. Read the nativeRaster and use {grid} functions to draw it.

Why not just draw the image_mosaic() object? Two reasons: I specifically want to download the nativeRaster version of the sprite (for later use in R) and because nativeRaster can then be used to produce an image at any size. The nativeRaster object is just a matrix of colour information, where each cell is a pixel of the image. That means you can re-plot it any size you want.

Of course, I could use {nara} by Mike (coolbutuseless) to ‘blit’ sprite parts to a single object, but I had some trouble using the package in a {shinylive} context as its not on CRAN and the GitHub repo doesn’t have any releases with the necessary WebAssembly binaries (yet?).

Note

Mike points out that {nara} is on R-universe, which builds WASM binaries for use by WebR applications including {shinylive}. Totally forgot about this! Will give it a go.

Expanding the inventory

As ever, there were some learning points, like how:

  • I used ignoreNULL = FALSE in shiny::bindEvent() on the step that chooses sprite parts, so that a sprite is created on startup without the need for the user to click the reroll button
  • there’s a {shinylive} deployment GitHub Action that can save the step of having to shinylive::export() your app whenever you make changes
  • the Chrome browser appears to have a problem downloading the files when the buttons are clicked, so there is a workaround at time of writing that’s used in one of the examples on the {shinylive} website

Even something this simple can be a learning experience.

Permadeath

Obviously this app is more of a proof-of-concept (as ever on this blog). It’s not particularly special or all that useful outside of my needs.

Next steps might involve letting the user select parts manually, styling and theming the app and giving the sprites a transparent background.

You can add a pull request to the GitHub repo if this interests you. But beware, adventurer, I can’t promise my code is less labyrinthine than a classic Rogue dungeon.

Environment

Session info
Last rendered: 2024-12-24 08:57:34 GMT
R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.2    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.2       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       fontawesome_0.5.2 evaluate_1.0.1   

Reuse

CC BY-NC-SA 4.0