anaseto / boohu

Break Out Of Hareka's Underground, a roguelike game.
https://codeberg.org/anaseto/boohu
ISC License
128 stars 8 forks source link

Some technical questions #28

Open Zireael07 opened 4 years ago

Zireael07 commented 4 years ago

1) What files exactly do I need to build WASM version locally? 2) What did you use to convert images to byte string? I am guessing it's base64, was it a go package or some external tool?

anaseto commented 4 years ago

Hi!

To build the wasm file, you just have to use GOOS=js GOARCH=wasm go build -o boohu.wasm --tags js, and then you have to load this file with html and js served statically with an http server (anyone one will do). The index.html file in boohu webpage shows how to do that (actually, currently, the one in Harmonist website is a litter better, using lazy loading but that only works if the server is properly configured to send a mimetype for wasm files), and which js files it loads. One of them is essential (there are two), the wasm_exec.js file: it comes with your Go compiler distribution, and may change somewhat with Go versions, so the one on boohu webpage may not be exactly the one you need, so pick the one for your Go version found under a misc/wasm folder. Both the loaded screenfull.min.js for fullscreen, and the stylesheet are optional and can be changed/removed if you adapt index.html.

For image stuff I use the image and base64 packages in the Go standard library.

BTW, if you just want a native tiles version, you can also try with the Tk backend.

anaseto commented 4 years ago

Actually, there's one more thing for images, in case you want to change a tile or something: they're generated with a short gen.pl script (found in tiles folder), which generates base64 encoded images and puts then in a generated images.go, using the tiles found in the same folder. So the images are integrated in the wasm file at the end.

Zireael07 commented 4 years ago

Thanks! For now, I am just reusing your images.go, for replacing them I probably could use go's base64 encoder, but that's later.

Right now, I attempted to make a basic renderer for WASM based on Boohu's. It compiles, but doesn't draw anything. I know the WASM code runs because the canvas size changes, but that's all. wasm-roguelike.zip

Once I get the renderer going, I'll probably be fine on my own, I've done the basic tutorial so many times ;)

anaseto commented 4 years ago

I had a look at your code: the issue comes from FlushCallback. Actually, it didn't draw the “log” as you did understand. I admit the name DrawLog is quite unintuitive, so my bad :-) It contains the “log” of frame drawing commands, I should have named it more explicitly DrawFrameLog or something like that. Index n contains the list of drawing commands called when Flush is called for the nth time (and only redraw parts of screen that changed). Because Boohu has replay functionality, all previous drawing commands are keeped around: if you don't want replay functionality you can keep only a list of drawing commands for the next frame.

anaseto commented 4 years ago

If you grep for DrawLog in Boohu code, you'll find that it's type is []drawFrame. In particular, you'll need the DrawLogFrame method.

Zireael07 commented 4 years ago

I figured that out moments before you posted.

Unfortunately, I have a weird problem with drawing a mouse highlight (I verified that the position is passed in from the mousemove callback, and I get an error in console from not being able to find tile... the TermInput along the way somehow isn't getting printed either)

wasm-roguelike.zip

anaseto commented 4 years ago

Not sure what's going one at a glance, but your print uses %s instead of %c (or %v, useful for debugging, and if it's not printable then print it as a number %d), I suppose that's why you don't get the letter for which a tile is not found. Knowing the rune, it should be easier to debug.

Zireael07 commented 4 years ago

The value is 32, which I expect corresponds to A. Maybe the problem is somewhere in the use of goroutines?

anaseto commented 4 years ago

32 is space. For non-alphabetic characters, the key for the tile is not letter-character and map LetterNames is used for them, so tile for ` the tile key isletter-space`.

BTW, you somehow got letter-spaced in TileMaps instead of letter-space.

Zireael07 commented 4 years ago

That fixed the weird issue. It looks like input isn't happening at all, since TermInput isn't getting printed...

anaseto commented 4 years ago

Seems you forgot to use PlayerEvent ?

Zireael07 commented 4 years ago

I wanted to skip that whole complex can of worms.

From what I can tell, the input channel is buffered and that ch.cap check is probably the source of the issue. What is the deal with that and why did you make that whole complex Event class?

anaseto commented 4 years ago

The uiInput type (TermInput in your code) together with PollEvent() serves several purposes.

1) It's a common interface for all backends inspired from PollEvent() in termbox and tcell terminal backends.

2) Even if you only target wasm, if you want keyboard, mouse and mouse motion events, you want to unify that in a single event queue so that when checking user input you just check a single channel.

3) It allows for non-blocking input thanks to the channel: if you press keys fast, they will not be dropped even if your program is still computing/drawing last action, or displaying an animation. The size of the channel is limited to 5 to avoid unwanted extra key presses in case the game is slow (for example because of some slow animation or whatever and the player was [unsafely] holding down a key and it got repeated too much).

anaseto commented 4 years ago

Ah, now I think I misread your question because you named PlayerEvent a function that calls PollEvent (which was your current problem, nothing to do with game Event class).

With respect to Event class (I suppose you mean game events, not UI ones), you can of course use other approaches for that, there are quite a few, which may be simpler or not depending of what features you want in your game. The goal was to have a common interface to anything that can happen in a given order at a given time. A single priority queue handles them, so that you can have monster turn, player turn, a programmed explosion, status end, fire/cloud progression or whatever scheduled using the same mechanism.

anaseto commented 4 years ago

So yeah, the original issue was you seemed to not make any calls to PollEvent.

Zireael07 commented 4 years ago

One step forward, one step back. Trying to call PollEvent in FlushCallback (aka Request Animation Frame) caused a deadlock. So I wrote a main loop and uncovered a weird problem with actually using game's methods, as well as: it seems to be blocking in spite of using channels! As in, I get input flooding my browser console, but nothing is drawn. wasm-roguelike.zip

anaseto commented 4 years ago

You put your eventLoop before clear/drawing methods, it seems. And your highlightPos drawings never get flushed either, I would say.

And in your previous version, I think the problem was newGame did not block, so you had an infinite loop of newGame (the goal of that loop is to allow to play two games in a row).

Maybe you can draw a diagram with main loop and goroutines control, and channel reading/writing points. It may help to debug such things.

Zireael07 commented 4 years ago

Thanks, putting pressAnyKey() at the end of newGame, and flushing the terminal fixed most issues, except the fact that I still can't move the main loop to game struct, it keeps telling me game has no methods or properties ...

anaseto commented 4 years ago

You defined your gameeventLoop as a function applied to type game instead of as a method for type game.

Zireael07 commented 4 years ago

Thanks for giving me something to check - I figured out the difference with the power of Google :)

I think this is out of the teething stage now, input and drawing work fine: wasm-roguelike.zip

anaseto commented 4 years ago

That's great to hear! :-)

Zireael07 commented 4 years ago

Currently halfway through part 2 of the tutorial, because I've got to figure out data structures - instead of imitating OOP with Go's structs, I'll probably use an ECS. But that's an aside.

I noticed you're using an extant JS library for fullscreen mode: https://github.com/anaseto/boohu/blob/9103191d9e1a4b337e81102e979dba0a46264d62/js.go#L346

Can this be done with any custom JS code/library? How does the js/syscall module know what to call/how do I figure out what's available in JS.Global() ? (BTW that's for future stuff, waay past the end of the tutorial ;) )

anaseto commented 4 years ago

You use any js library from Go (as long as it's loaded before in the html of course). I used screenful because portable fullscreen is hard. js.Global() contains all the identifiers/stuff that are available at toplevel in js, AFAIK. Then you can do anything you want to them using the js/syscall Go interface for calling js (actually probably based on wasm capabilities to call js). I haven't really see any big limitations in the js/syscall interface, though I'm by no means an expert of js stuff, but even stuff for passing data around beetween Go and js seemd enough to me.

About OOP, ECS (never tried for real) and other stuff, I cannot say much, as I always do a kind of procedural/OO/functional mix or whatever feels right at the moment :-)