Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1395992 Posts in 67324 Topics- by 60456 Members - Latest Member: Mersy

October 19, 2021, 04:23:13 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsSpider Game
Pages: [1]
Print
Author Topic: Spider Game  (Read 1297 times)
bayersglassey
Level 0
***



View Profile
« on: December 13, 2020, 11:58:20 PM »

Posts in this devlog:

Hey hey!
I've been making games since I was a kid, originally in QBASIC on DOS, these days in C on Linux.

In university we didn't have a CS department but we just barely had a math department, so I did that.
I was terrible at university, but great at obsessively studying random stuff on my own time.
Among other things, I got into algebraic geometry, and did a lot of "research" (sketching in notebooks) on tilings of the plane using squares and triangles.



Ummm, I recommend this book? Your mileage may vary.
Tilings and Patterns, by Grünbaum and Shephard.

Eventually I thought to myself "what if you had like, a screen, right?.. but where the pixels were squares and triangles".
And so I wrote various demos to that effect.


Unrelatedly, over the years I occasionally sketched out ideas for a game taking place on a grid of triangles, where your character's movement uses a finite state machine.
(You can also say "your character's movement is like the original Prince of Persia games", although I never played those... the characters in Another World / Out of This World moved this way as well.)



Eventually, the squares-and-triangles geometry stuff and the triangular-grid-game idea came together into a game demo.
And the main character looks like a spider robot thing, because it's super hard to draw anything when your "pixels" are squares and triangles.
So in casual conversation the demo always ended up being called "the spider game", and the name has become official.








It has a litle website where you can play it online!
You will likely want to have your browser in fullscreen mode (F11 in Chrome) so the up/down arrow keys don't scroll.
I... should probably look into improving this situation somehow. Tongue

The source is up on Github.

It's written in C + SDL2 with basically no other dependencies. Emscripten is used to compile it for the browser.
I'm on Linux, so I don't have a Windows build, unfortunately. :/ If there is any interest in this, perhaps I'll try to figure out how to do that. I would be happy to know if anyone else is able to build it. Smiley

There is a fair amount of stuff to find in the game, although there's no particular goal.
It certainly doesn't hold your hand... although I've tried to give you a safe place to start from, and exciting places to go as you master the controls and solve puzzles.
There is now a tutorial which you can complete, and I'm now working on tying the rest of the game map together with puzzles and NPCs and things, and an endgame. But for now, after the tutorial you're just basically free to explore.
« Last Edit: October 11, 2021, 07:43:08 PM by bayersglassey » Logged
Rogod
Level 2
**



View Profile WWW
« Reply #1 on: December 14, 2020, 01:51:50 AM »

Quote
because it's super hard to draw anything when your "pixels" are squares and triangles.
Never stopped me Wink

No but seriously, this does look like a unique concept.
Put some music to it and it could be the next geometry dash :D

I am always interested to see where other people take these sorts of tech-demo concepts and turn them into a game somehow (the more involved and imaginative, the better; I'm anticipating a full open world, multiplayer co-op, randomly generated dungeons, a full inventory system for powerups and cosmetics, other creatures that live in the tessellation world and a Flatland-esque backstory Grin )
Logged

marcgfx
Level 7
**


if you don't comment, who will?


View Profile WWW
« Reply #2 on: December 14, 2020, 03:22:40 PM »

This looks pretty insane/cool! It was kind of fun to move around, but also pretty confusing. I think you might need to add the hand holding you mentioned was missing and build up the complexity slowly.
Logged

bayersglassey
Level 0
***



View Profile
« Reply #3 on: December 14, 2020, 04:23:28 PM »

Hmm, confusing in what way? Just the flipping upside down and running on the ceiling, or something else?
Or to put it another way, would you have kept playing a bit longer if you hadn't gotten flipped upside down on the very first screen?
Logged
michaelplzno
Level 10
*****



View Profile WWW
« Reply #4 on: December 16, 2020, 02:12:15 PM »

Its an interesting concept, but yeah, the controls were awkward at best. When your character is upside down the buttons don't flip so you can press left and move right, press up and jump down. Also the goal wasn't clear: I was sort of chasing the other character that was there at the beginning for a while and then I gave up on that b/c there was very little feedback.

Keep it up!
Logged

bayersglassey
Level 0
***



View Profile
« Reply #5 on: December 22, 2020, 11:44:28 PM »

Thanks to all who gave feedback!

It sounded like more than anything else, what was needed was a tutorial. So I've added one.
If you'd like to play it, the demo is updated: http://depths.bayersglassey.com/main.html

First, you learn left and right.
(I'm thinking of renaming the game "Crawl Space" because every domain like "spidergame.com" is apparently taken by filthy squatters, but I was able to get crawlspacegame.com...)


Then, you learn about savepoints.


Then, you learn about the fact that you will be flipped upside down, you stick to walls, there's no gravity, etc.


Then, you learn how to be forced to jump.


Then, you learn how to jump intentionally.


Then... a minor puzzle to make sure you "get" everything so far.


You end up travelling all the way back along the underside of the map.


...until somehow you end up here. Another puzzle, but a bit more open ended, so you can actually play around a bit.


Another puzzle, why not.


Finally you learn how to crawl.


End of tutorial!


...after which, you're thrown into the game proper.


...the controls were awkward at best. When your character is upside down the buttons don't flip so you can press left and move right, press up and jump down.

I thought a bit about whether it would be possible to flip the controls when the player is flipped, and how that might work.

Currently, you can travel around "curves" by holding left or right.
For instance, given a "smooth" shape like these connected hexagons, you can walk around them forever by holding left or right:

  + - +       + - +
 /     \     /     \
+       + - +       +
 \     /     \     /
  + - +       + - +


So, when you're standing on the bottom of something, pressing right makes you move left.
Simply put: pressing right makes you move clockwise, left makes you move counter-clockwise.

Let's say we wanted to change the controls so that when standing on the bottom of something, pressing left made you move left.
I can kind of imagine how that would work; you would need to decide what happens when holding right/left and travelling around a "curve" though: do your controls flip when player rotates?.. so in order to go around something indefinitely, you'd need to hold right for a bit, then switch to holding left as you flipped upside down, then switch back, etc?

There are various ways it could work, but some of them would require a serious rework of the current system.
It uses a mini-language to specify the controls, animations, and map collision detection... for instance, this file contains the following rule which causes you to take a step forward when holding "forwards" (left/right relative to player's orientation), if there is ground there to stand on and nothing blocking you:

    if:
        key: isdown f
        coll: all yes
            ;; ( )  + - +
        coll: all no
            ;;       \*/*
            ;;        + -
            ;;       /*\*
            ;; ( )
    then:
        move: 1 0
        goto: step
        delay: 2


Changing all that stuff for all the different creature types (who all use this same system, with AI players using a virtual keyboard for their "keypresses") would be really rough Cry
But it might be possible to fiddle with the C code which decides e.g. whether "key: isdown f" is true. Hmmmm.

Ultimately I'm hoping that with e.g. the tutorial, I can get players comfortable with the controls as they are...
« Last Edit: October 07, 2021, 11:19:26 PM by bayersglassey » Logged
bayersglassey
Level 0
***



View Profile
« Reply #6 on: December 22, 2020, 11:50:21 PM »

Quote
because it's super hard to draw anything when your "pixels" are squares and triangles.
Never stopped me Wink

Ooooh, those rotating planets though! Addicted

I am always interested to see where other people take these sorts of tech-demo concepts and turn them into a game somehow (the more involved and imaginative, the better; I'm anticipating a full open world, multiplayer co-op, randomly generated dungeons, a full inventory system for powerups and cosmetics, other creatures that live in the tessellation world and a Flatland-esque backstory Grin )

There's already 2-player coop and theoretically more :D
There is 1 room with little islands whose positions are randomly... displaced slightly each time you play. It's actually pretty fun because I've mastered everything else (having written all the maps), so having to actually look and judge jumps is nice. But that's pretty far from a "randomly-generated dungeon". It would actually be cool to try a simple first step towards that, like "implement a Rogue-style algorithm generating some rooms connected by hallways" in this engine. Hmmmmm.

Logged
marcgfx
Level 7
**


if you don't comment, who will?


View Profile WWW
« Reply #7 on: December 23, 2020, 01:39:46 AM »

Gave it another shot and I really like your tutorial. I nearly gave up on the crouching level, I think you should also inform about bridging gaps while standing. I only went back, when I saw that your post showed it had an end of tutorial after that screen.



After the tutorial I felt kind of lost in this possibly open world? I ended up in a green wavy area and I find the visuals very distracting. I think the best course of action would be for you to continue the game as you have now made the tutorial. Level by level giving the player a sense of progression.

I really do not mind the way the controls work, it's the only logical way for me. A little like controlling a remote controlled car.
Logged

bayersglassey
Level 0
***



View Profile
« Reply #8 on: December 24, 2020, 12:35:08 AM »

I nearly gave up on the crouching level, I think you should also inform about bridging gaps while standing.

Ahhhh yup. I take a lot of the mechanics for granted, but yeah knowing how your character behaves around little gaps is pretty crucial.

So I replaced the useless little puzzle right before the crouching room with a veritable playground of small gaps:



After the tutorial I felt kind of lost in this possibly open world? I ended up in a green wavy area and I find the visuals very distracting. I think the best course of action would be for you to continue the game as you have now made the tutorial. Level by level giving the player a sense of progression.

Yeah, the map's a patchwork of odds and ends.
It grew organically as I had ideas for puzzles and features.
I do want to keep it fairly open, but it clearly needs an overarching goal to motivate people to explore it.
Hmm...
Logged
marcgfx
Level 7
**


if you don't comment, who will?


View Profile WWW
« Reply #9 on: December 24, 2020, 04:02:32 AM »

if it really is something to explore it should be fun to traverse the same areas again. but if it's just repeating puzzles, I think that could be tedious. you could maybe add new ways to get through areas you have already managed, but I do believe making it linear will help you game.
Logged

terrarray
Level 0
**


View Profile
« Reply #10 on: March 27, 2021, 04:08:32 AM »

Sort of reminds me of convays game of life for some reason. Keep it up!
Logged
bayersglassey
Level 0
***



View Profile
« Reply #11 on: October 06, 2021, 11:43:17 PM »

I have, I believe, finished the tutorial!
It hopefully teaches you how to move, how to save, how to "spit" (basically shoot), how to unlock things by collecting purple things (coins?) and hitting switches, and what can kill you.

It doesn't teach you about swimming or turning into different types of spider and enemy, because I think those can be learned when you happen across them in-game...

I also tried to add an "explanation" for why you are shown text during the tutorial, but not in the rest of the game. There are eyeballs which talk to you, and they "release" you into the game at the end of the tutorial. What does it all mean? Who knows, but I'm hoping to achieve some kind of "story" giving people a reason to play beyond the tutorial. Maybe the eyes want to you bring back the Amulet of Yendor or whatever?

Some screenshots:











Try it out in your browser! http://depths.bayersglassey.com/


Interestingly, Emscripten seems to generate WASM these days, and Chrome is willing to run it too, as long as I tell nginx to serve up .wasm files with "Content-Type: application/wasm":
Code:
   # in nginx.conf...

    types {
        application/wasm wasm;
    }

It runs well on my laptop, although it makes the fan blow like crazy. I think it's because I'm calling SDL_Delay inside each step of my main loop, which when compiled with Emscripten, is being run using browser's requestAnimationFrame, which probably means... I'm delaying the browser in the middle of it trying to render. On, like, every vsync. O_o Should probably fix that tomorrow.
Edit: fixed!
« Last Edit: October 07, 2021, 08:26:32 AM by bayersglassey » Logged
bayersglassey
Level 0
***



View Profile
« Reply #12 on: October 07, 2021, 10:48:35 PM »

Let's try writing an actual devlog post.


The graphics engine, a linear algebra refresher, and writing parsers instead of GUI editors

The graphics are made out of tiny geometric shapes, each of which can be exactly one colour, much like pixels.
However, they come in various shapes -- currently square, triangle, and diamond -- and are positioned in a 4-dimensional space based on angles of 30 degrees, as opposed to pixels' 2d space based on angles of 90 degrees.
In the graphics engine, the tiny shapes are referred to as "prismels" (because they're like pixels, but can be arbitrary... prisms?.. no, polygons. Hmmmmm. It was many many years ago that I first started thinking about all this, so I guess I have no idea why I went with the term "prismels").

Anyway, here is a motivating example... you play as this spider-looking thing:


And here is a text representation of the "prismels" which make it up:

               -+---+-
              / |   | \
            +-  |   |  -+
            | \ |   | / |
           /   -+---+-   \
          |     |   |     |
          +-   / \ / \   -+---+-
          | \ |   |   | / |   | \
          |  -+---+---+-  |   |  -+
          | / |   |   | \ |   | /
          +-   \ / \ /   -+---+-
          |     |   |     |
           \   -+---+-   /
            | / |   | \ |
            +-  |   |  -+
              \ |   | /
               -+---+-
             -+       +-
            / |       | \
          +-  |       |  -+
          | \ |       | / |
         /   -+       +-   \
        |     |       |     |
        +-   /         \   -+
        | \ |           | / |
        |  -+           +-  |
        | /               \ |
        +-                 -+


They are all squares and triangles. There is a third type of prismel, the diamond, which gets used less frequently than the others. Here are all 3 shown together:

   +---+     +
   |   |     |
   |   |    / \       -+---+
   |   |   |   |     /   /
   +---+   +---+   +---+-



Earlier I said they are positioned in a "4 dimensional" space. What did that mean, though?
It means that the coordinates of these shapes can be expressed as 4-dimensional vectors (a, b, c, d).
Here are the 4 unit vectors I use:

         D  C

         + +
         | |   B
         |/ -+
         | /
         O---+ A

  O = (0, 0, 0, 0) <- The origin
  A = (1, 0, 0, 0)
  B = (0, 1, 0, 0)
  C = (0, 0, 1, 0)
  D = (0, 0, 0, 1)


In case that's not too clear, here are some examples of other vectors:

   2A = (2, 0, 0, 0):

      O---+---+


   -A = (-1, 0, 0, 0):

  +---O


   2B = (0, 2, 0, 0):

             -+
            /
         -+-
        /
      O-


   C - A = (-1, 0, 1, 0):

    +
    |
     \
      |
      O


   ...you can see why that should be C - A, if you put C and -A end-to-end:

     -A

    +---+
        |
       /  C
      |
      O



Why, you might ask, use these 4 dimensions instead of the usual X and Y?
The answer is that basic trigonometry will tell you that if the bottom-left point of the following triangle is the origin (0, 0), then the (x, y) coordinates for its topmost point are, if I recall correctly, (1/2, 1 + sqrt(3)):

      +
      |
     / \
    |   |
    +---+


...and long story short, if you want to represent such coordinates as *integers* (which I do), you actually end up with 4 "dimensions" anyway! The general form just becomes, if I recall correctly, ((a + b * sqrt(3)) / 2, (c + d * sqrt(3)) / 2). You see? Four variables: a, b, c, d. But we've got gross sqrt(3) stuff everywhere, whereas the 4d space I prefer to use has some nice properties when it comes to rotation, as you will now see.


Now, one thing you might notice is that A, B, C, and D can be generated by rotating A by multiples of 30 degrees.
If it helps you visualize this, here they are shown one after the other:

    A: 0 degrees:

         O---+


    B: 30 degrees:

            -+
           /
         O-


    C: 60 degrees:

           +
           |
          /
         |
         O


    D: 90 degrees:

         +
         |
         |
         |
         O


Let's say the function R rotates its argument by 30 degrees.
And note that 30 * 12 = 360 (that is, applying R 12 times gets you back to where you started: R(R(R(R(R(R(R(R(R(R(R(R(x)))))))))))) = x for any x).
Then we have:

  R(A)      = B
  R(B)      = C
  R(C)      = D
  R(D)      = C - A   <- See the visualization of C - A earlier to see how it must be R(D)!
  R(C - A)  = D - B
  R(D - B)  = -A
  R(-A)     = -B
  R(-B)     = -C
  R(-C)     = -D
  R(-D)     = -C + A
  R(-C + A) = -D + B
  R(-D + B)  = A      <- 12 rotations got us back where we started


Also, we can do something cool from linear algebra: we can define vector multiplication, and say that R is a vector instead of a function.
So for any vector x, R * x = R(x).
Now we need to pick a unit vector, let's say that's A.
So A represents the identity transformation: A * x = x
Now since B is A rotated by 30 degrees, we have A * R = B, but since A is the identity, that means R = B.
We can keep going with that:

  R^0  = A
  R^1  = B
  R^2  = C
  R^3  = D
  ...
  R^12 = A  <- 12 rotations of 60 degrees is a 360 degree rotation... i.e. no change


And by the way, 2A represents a "stretch" of size 2. That is, 2A * x stretches x to twice its size.
So I can use vectors to stretch and rotate the graphics.
The graphics engine can also define "mappers" which stretch & rotate all coordinates in an image, and replace each of its prismels with a shape composed of many prismels -- and this whole transformation can be applied repeatedly, basically generating fractals.
For instance, here is a transformation called "curvy" which... well, you'll see:

    "curvy":
        unit: 2 4 0 -2           <---- this is the vector it multiplies the coordinates by
        entries:
            : "vert" -> "vert"
            : "edge" -> "edge"
            : "sq"   -> "curvy_sq"     <---- this says it replaces prismel "sq" with shape "curvy_sq"
            : "tri"  -> "curvy_tri"
            : "dia"  -> "curvy_dia"


...this is what "curvy_sq", "curvy_tri", and "curvy_dia" look like:


...so what happens if we repeatedly apply the "curvy" transformation to some other shape?..

Original:


Transformed with "curvy" a couple of times:


Cool eh?!?!
Anyway, that's enough linear algebra and fractals for now. Apologies?.. or you're welcome?.. but let's get back to games...


So how is all this used by the game engine?
In fact, I "wrote" most of the graphics by hand using 4d coordinates.
Let's see how the spider image is defined. Here it is again for reference:


...and here is how it was defined using the game's graphics language. Note, lines beginning with '#' are comments. This is all taken more or less directly from the game's current code:

    "_head_sixth":
        #      _ +
        #    +    \
        #   / \  _ +
        # (+)- + _ |
        #          +
        prismels:
            : "tri" (0 0 0 0)  0 f eval: 1 + 8 + 1
            : "tri" (1 0 0 0) 11 f eval: 1 + 8 + 2
            : "sq"  (1 0 0 0)  1 f eval: 1 + 8 + 1

    "_head":
        shapes:
            : "_head_sixth" (0 0 0 0)  0 f
            : "_head_sixth" (0 0 0 0)  2 f
            : "_head_sixth" (0 0 0 0)  4 f
            : "_head_sixth" (0 0 0 0)  6 f
            : "_head_sixth" (0 0 0 0)  8 f
            : "_head_sixth" (0 0 0 0) 10 f

    "eye":
        prismels:
            : "tri" (0 0 0 0)  0 f eval: 1 + 8 + 4
            : "tri" (0 0 0 0)  2 f eval: 1 + 8 + 4
            : "tri" (0 0 0 0)  4 f eval: 1 + 8 + 4
            : "tri" (0 0 0 0)  6 f eval: 1 + 8 + 4
            : "tri" (0 0 0 0)  8 f eval: 1 + 8 + 4
            : "tri" (0 0 0 0) 10 f eval: 1 + 8 + 4

    "nose":
        prismels:
            : "sq"  (0 0 0 0) 0 f eval: 1 + 8 + 1
            : "tri" (1 0 0 0) 1 f eval: 1 + 8 + 2

    "head":
        shapes:
            : "_head" (0 0 0  0) 0 f
            : "eye"   (0 0 0  0) 0 f
            : "nose"  (1 1 0 -1) 0 f

    # Back leg
    "bleg":
        prismels:
            : "tri" (  0 -1  0  0) 11 f eval: 1 + 8 + 5
            : "sq"  (  0 -1 -1  0) 11 f eval: 1 + 8 + 3
            : "tri" (  0 -1 -1 -1)  1 f eval: 1 + 8 + 3

    # Front leg
    "fleg":
        shapes:
            # For historical reasons, "bleg" and "fleg" look exactly the same,
            # just "fleg" is usually rotated 180 degrees wherever its used...
            # However, they're separate in case we ever want to tweak one.
            : "bleg" (0 0 0 0) 0 f

    "stand":
        shapes:
            : "head"  ( 0  1  3  1)  0 f
            : "bleg"  ( 0  1  1  1)  0 f
            : "fleg"  ( 2  1  1  1)  6 t


...and just to be clear, that text is parsed by the game engine and used to generate the spider image.

You can maybe see the 4d coordinates in there, e.g. "head"  ( 0  1  3  1)  0 f is saying that the shape called "head" should be rendered at B + 3C + D, and rotated by R^0, and not flipped vertically ("f" is for "false"). The "eval" bits are specifying colours, e.g. eval: 1 + 8 + 2 is light green. (It's a classic 4-bit palette, RGBI where the "I" is for "intensity", e.g. the "light" in "light green". See also: Wikipedia article. And the + 1 is because 0 is reserved for the "transparent colour".)

In any case, somehow after typing up enough of that stuff, we end up with a game which looks like this:


You might argue this does not seem like a particularly efficient way to generate graphics.
And I would agree, which is why I (recently, after already typing up 90% of the graphics the other way) wrote a parser which can understand "text representations" of prismels, like this:

    "_coin_beast_bleg_step1":
        hexpicture:
            ;;                    +
            ;;                   | |
            ;;                   |5|
            ;;                  +---+
            ;;                   | | |
            ;;                   |5|5|
            ;;                    +---+
            ;;                    |   |
            ;;                    |   |
            ;;                    |D  |
            ;;                    +---+
            ;;                    |   |
            ;;                    |   |
            ;;                    |F  |
            ;;                    +---*


...that shape is called "_coin_beast_bleg_step1", because it's the back leg of a "coin beast" during frame 1 of its "stepping" animation, which looks something like this:



Okay, but there are some pieces missing here. How do the animations work?.. also, as you may (or may not) be able to tell from the gameplay clip above, the map's tiles actually form a triangular grid -- the "prismels" and 4d coordinates and whatnot are only used for rendering the sprites, not for the "physics".

First of all, the animations are done by specifying how many frames of animation a "shape" (image) has, and then optionally specifying for which frames its prismels or sub-shapes are visible. For instance, here is the "crawl_step" shape, in which the spider takes a step while crouching:

    "crawl_step":
        animation: cycle 3
        shapes:
            : "crawl_head" (-1  0  2  1)  1 f  0 (0 1)
            : "crawl_head" ( 0  0  2  1)  1 f  0 (1 1)
            : "crawl_head" ( 0  1  2  0)  1 f  0 (2 1)
            : "bleg"  (-1  1  1  1)  0 f
            : "fleg"  ( 2  1  1  2)  7 t  0 (0 1)
            : "fleg"  ( 3  1  1  2)  6 t  0 (1 1)
            : "fleg"  ( 3  1  1  1)  6 t  0 (2 1)


...so, "crawl_head" (-1  0  2  1)  1 f  0 (0 1) says to render the "crawl_head" shape at some 4d coordinate, with a rotation of R^1, not flipped (the "f" is for "false", remember), and finally "0 (0 1)" means don't offset "crawl_head"'s animation by any frames (the "0"), and only make it visible for 1 frame, starting on frame 0 (the "(0 1)").

Look, I enjoy writing parsers more than I enjoy writing GUIs, okay? I'm not saying this was the best way to achieve all this.

Okay, now how is the map defined?
Well, the bottom-left part of the map in this screenshot:


...is defined like this:

                                                    + - + - +   + - +
                                                   /* * *\   \*/
                                          + - + - + - +   + - +
                                         /   /* * *\   \* * */
                    + - + - +   + - + - +   + - +   + - +   +
                   /   /             \           \* * * * */*
      + - + - + - +   +               + - + - +   +   + - + -
     /                 \                       \   \*/  */*\*
    +   + -             +                       +   +
   /   /               /                         \   \
  +       + - + - + - +                           +   +
   \     /                                       /   /
    + - +               .   + - +       + - + - +   +
                        S  /     \     /             \
      .      (+)- + - + - +       + - +     - + -     +
             /                   /     \             /*
            + - +               +       + - + - + - +
           /     \             /                  * * *
          +       + - + - + - +
           \
            +
           /
          +
         /
        +
      */
      .


...that file is called "start.fus", and another file, "worldmap.fus", glues together such maps like this:

submaps:
    :
        file: "data/maps/demo/start.fus"
        pos: (0 0)
        camera: (6 4)
        mapper: ("quadruple")
        palette: "data/maps/demo/pals/start.fus"
    :
        file: "data/maps/demo/start2.fus"
        pos: (7 11)
        camera: (5 4)
        mapper: ("quadruple")
        palette: "data/maps/demo/pals/start.fus"
        submaps:
            :
                file: "data/maps/demo/start3.fus"
                pos: (2 15)
                camera: (5 -5)
                mapper: ("triple")
                tileset: "data/maps/demo/tilesets/shiny.fus"
                submaps:

                    ...ETC...



Terrifying.

And in fact, there is one last major type of file (and associated mini-language), which defines how a character moves and is controlled -- how its animations are glued together, how it responds to keypresses and collisions, and even the logic used by its AI.

Here is a snippet from the file describing the spider's movement:

collmsgs: "touch"
on "crush": goto: dead

collmap "stand":
    ;;   .   .
    ;;   *\*/*
    ;; . - + - .
    ;;   */*\*
    ;;  (.)  .

collmap "crawl":
    ;;     .
    ;;     *
    ;;  (.)  .

stand:
    rgraph: "stand"
    hitbox: collmap("stand")

    # Can't stand on nothing
    if:
        coll: any no
            ;;  (+)- +
    then:
        move: 1 0
        goto immediate: start_jump

    # Crawl
    if:
        key: isdown d
    then:
        goto delay: crawling


    # Forced jump
    if:
        key: isdown f
        coll: all no
            ;;       \*/*
            ;;        + -
            ;;       /*\*
            ;; ( )
    then:
        move: 1 0
        goto immediate: start_jump


    ...ETC...


...so we've got conditionals and "goto"s which send you to other animations, and side-effects like "move: 1 0" which moves you 1 space to the right.
On the one hand, it's a pretty gross example of "not invented here" syndrome (why not embed Lua or something?), but on the other hand, it lets us add syntax and features which might be difficult to express in another language.

For instance, I can express hitboxes as some kind of literal. Here is part of a conditional saying "if the following hitbox, at our sprite's location, would not collide with the map". (Note, the "( )" represents the sprite's location, which for the spider is its back foot):

        coll: all no
            ;;       \*/*
            ;;        + -
            ;;       /*\*
            ;; ( )


For reference, a single triangular map tile, including its 3 points, 3 edges, and 1 triangular face looks like this:

      +
     /*\
    + - +


...the same map tile, not including 2 of its points, 1 of its edges, or its face, looks like:

     .
    /
   + - .


(The "." indicate positions where you could put an "+", that is, a point. The "." are optional, they just make it easier to visualize the triangular grid.)


Hooray, an info dump! I'd say this has been a successful devlog entry. If we can cover enough nitty gritty, maybe I can start writing out my thoughts about how to bring this game project to a close, and actually tie together the features and map I have so far into something which people can play from start to finish, and enjoy.
« Last Edit: October 08, 2021, 12:09:31 AM by bayersglassey » Logged
a-k-
Level 1
*


View Profile
« Reply #13 on: October 08, 2021, 08:46:56 PM »

That was an interesting read! In terms of gameplay, I think it would be nice if tiles changed their color slightly but permanently once you step on them (assuming that e.g. all red tiles are reachable, otherwise that might be confusing).
Logged

bayersglassey
Level 0
***



View Profile
« Reply #14 on: October 09, 2021, 07:09:09 AM »

That's interesting... would the idea be to help the player see where they had been in the world as a whole, sort of a roundabout automapping feature? Or is the idea more to help the player solve individual areas, e.g. so they can remember which places they've tried to jump from in order to get across a gap, kind of thing?

FWIW, there is actually a minimap which gets automatically filled in as you travel. Hold Tab to see it. I'm not sure if I want to keep it in, or have it be unlockable, or what.

Here's an example of a certain area, with its minimap shown below:
Logged
a-k-
Level 1
*


View Profile
« Reply #15 on: October 10, 2021, 10:18:08 AM »

Quote
to help the player solve individual areas, e.g. so they can remember which places they've tried to jump from in order to get across a gap
Exactly this. Also for purely aesthetic reasons, to give players more feedback (even if there's no goal of 100% exploration).
Logged

bayersglassey
Level 0
***



View Profile
« Reply #16 on: October 11, 2021, 07:18:33 PM »

Hmmm, I've definitely been interested in adding tile-editing functionality in general. I don't think I want "edges change colour when you step on them" to be a feature present in every area, but it would definitely be a neat effect to use here and there. And once the groundwork for changing tiles is in place, there are lots of different ways it could be used.

As for whether it's technically possible... currently the map is pasted together from "submaps" which are composed of tiles, and each submap gets rendered as a bitmap (well, 24 bitmaps because all maps allow for a hardcoded 24 frames of looped animation), which is cached. So the engine doesn't currently support changing a submap's tiles. But an easy solution would presumably be to simply re-render a submap when its tiles are changed. Things get a bit more difficult if we want to *save* changes to every submap's tiles, but that would be doable as well (probably as a new data structure in the save data, only included for submaps whose tiles have changed).

Maybe I'll look into this when I try to fix the graphics engine's current "cache every bitmap you render" approach. Basically the graphics are built up as tree structures, whose branch nodes are called "shapes" and leaf nodes are called "prismels" (see the long post above), and in order to render top-level "shapes" the engine needs to render all the intermediate ones down to the prismels, and right now it's caching all those intermediate bitmaps forever, whereas it should probably throw them away and just keep the top-level ones (which are used as e.g. sprites and tiles).
So when I have a look at that, I'll also take a peek at allowing submaps' tiles to be modified...

Thanks for the idea. Smiley
« Last Edit: October 11, 2021, 08:44:22 PM by bayersglassey » Logged
bayersglassey
Level 0
***



View Profile
« Reply #17 on: October 11, 2021, 07:42:06 PM »

I finally figured out how to make saved games persist when running in the browser!
Emscripten doesn't make this easy.
The basic idea makes a lot of sense: Emscripten gives you a virtual filesystem implemented in Javascript, so that C calls to fopen etc work as expected. And you can tell the Emscripten compiler to bake source directories into the Javascript it builds, e.g. if game assets are in data/, then you can say emcc --preload-file data, and then calls to fopen("data/myfile") will work.
However, the default virtual filesystem is 100% in-memory, so all changes to it disappear when you leave the page.
So, Emscripten lets you add persistent storage in a very unix-y way, by letting you "mount" other filesystems, including one ("IDBFS") which uses HTML5's IndexedDB API under the hood.

Long story short, the game now stores savegame files in a subdirectory called "./saves", and when I compile with Emscripten, I tell it to mount an IDBFS filesystem on that directory:
Code:
// This is a little .js file I tell emcc to include with its --post-js option.
Module.preRun.push(function() {
    FS.mkdir("saves");
    FS.mount(IDBFS, {}, "saves");
});


So far so good, now stuff stored in that directory will be magically saved between page refreshes, right??!?!?!
Heck no! To load everything from IndexedDB at the start of the game, and to save everything back when player touches a save point, I need to manually call (from C) a Javascript function, FS.syncfs.
And because modern Javascript does everything asynchronously by default, you have to pass that function a Javascript callback.
So when the game (which is written in C) starts, it calls the following Javascript function (written in a .c file using Emscripten's EM_JS macro):
Code:
EM_JS(void, emccdemo_initial_syncfs, (), {
    console.log("Starting initial syncfs...");
    FS.syncfs(true, function(err){
        console.log("Initial syncfs finished.");
        if(err){
            console.error("There was an error in FS.syncfs.", err);
            if(!window._syncfs_broke)alert(
                "There was an error in FS.syncfs. " +
                "Saved games may not persist between page refreshes! " +
                "See the console for error details.");
            window._syncfs_broke = true;
        }
        ccall('emccdemo_start', 'v');
    });
})

...notice the ccall('emccdemo_start', 'v'), which is Javascript code calling a C function, "emccdemo_start".
That function mostly just does this:
Code:
emscripten_set_main_loop_arg(&emccdemo_step, app, 0, true);

...that is, it calls a C function provided by Emscripten, "emscripten_set_main_loop_arg", which causes the Javascript-based C interpreter at runtime to abandon the current callstack and instead start calling "emccdemo_step" (in this case) every time the browser's requestAnimationFrame thing fires. (You can also ask it to use the more old-fashioned setInterval.)

Anyway, long story short, my C "main" function now contains the following:
Code:
#ifdef __EMSCRIPTEN__

/* Load save files from IndexedDB */
emccdemo_initial_syncfs();

/* Now kill the current call stack!!!
Why? Because emccdemo_initial_syncfs has kicked off an
async JS operation which, when it completes, will set
current thread's "main loop" to emccdemo_step.
So the current call to main() never returns.
Hooray, it's callback hell with C *and* Javascript! */
emscripten_exit_with_live_runtime();

#endif

It's all pretty ridiculous.
But... it works! Try it out:
http://depths.bayersglassey.com/
(And click "play in your browser")

I should mention, much of this would not have been necessary had this Emscripten issue not been marked "wontfix"...

Next... I need to add a menu to the game, so you can choose whether to start a new game or load an old one, etc. As things stand, in order to start a new game, you would need to clear your browser history or something. WTF
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic