[DEVLOG] Unnamed Entry

Log Entry #1

I am still not decided about which type of Punk my entry will be based on.
In the mean time, I’ve forked the P-Type project and started drawing characters to see if I find something worthy of pursuing.

I had already drawn Diesel/Bagel/Musical/Cyber Punk characters before the jam started:
splash2

Yesterday I played around with some ideas on pencil, but couldn’t come up with anything I liked:

And today I went for… ClownPunk:
Clown

At this rate my final entry will be a gallery-full of punk portraits. -_-

9 Likes

Log Entry #2

One week later and I haven’t been able to advance as much as I’d like.
It has mostly been a lot of planning. Here’s what has been decided so far:

  • Cyberpunk theme. Story involving a mil-tech company that acquires a girls orphanage.
  • Tilemaps will be loaded from the SD card to RAM
  • The tileset (images of each tile) will stay in Flash
  • Character customization… maybe.
  • Code snippets in the TMX that gets called when the player steps on certain tiles.

I’ll try alternating my devlog posts between art and code. The previous post was all pictures, this one is going to be all code.

For this to work I’ll have to modify the TilemapConverter. The array of images will continue to be generated as it is now, but tile maps and metadata will now go into a single binary file.

Since I’ll be using RAM for tile maps, I’ll need to plan this carefully. FemtoIDE’s ELF file viewer is going to be useful for keeping an eye on how much space is being used, but there’s a hitch: When a variable is declared inline, the symbol’s type in the ELF is V… which could be either in flash or in RAM.

Before I can start, I need to know how much free RAM I actually have, so I need to update the ELF viewer to look at the address instead of the type.


Now that that’s done, time to reserve chunks of RAM.

  • A 64x64 map will occupy about 8Kb of RAM.
  • 8Kb for state data
  • 7Kb for miscellaneous static data (palette, screenbuf, file handles, etc)
  • 2Kb for stack

By rough estimate, there’s going to be over 10Kb of free RAM.
I’ll be using it with:

  • Sound streaming takes up a lot of RAM, so I’ll disable PokittoLib’s sound and I’ll port FemtoLib’s sound code from Java to C++. I’ll dedicate RAM2 to sound, but maybe not all of it will be needed. This jam is also going to be three months long, right?

  • Triggers on the TMX. That code has to go somewhere and it can’t be flash. I’ll load it in RAM1 when the player steps on that tile. A simple script could check if the player has a key and unlocks a door, for example. Or trigger some dialog. Or teleport to another point in the map. Or teleport to another map entirely. Or add an item to the player’s inventory.

  • A 32x32 sprite takes up 1024 bytes, so 8 sprites can fit into the remaining 8Kb.
    Coincidentally, an RPG party is 4 people. Just enough RAM for 4 people vs 4 enemies.
    In theory, since the sprites would be in RAM, it’s possible to do character customization.


Time to setup a custom TilemapConverter.

I want to be able to put triggers anywhere on a map, using Tiled. To do this, I’ll put the trigger in a script property of an object.

The converter will read that property, wrap it in boilerplate and pass it to GCC.
Turns out GCC can accept code from STDIN (gcc -x c++ -), so I don’t have to use a temporary file. Nifty.

By using hashes instead of strings for everything, I avoid any string comparisons and, more importantly, save space.


Once GCC compiles the code, it’s time to run the linker and get an actual executable.

The linker (LD) has no idea where the code is supposed to run. It also doesn’t know where RAM is, so it doesn’t know where to put variables. I need everything to fit in RAM1 and I’ll make LD put it there using a custom linker script.

The gist of it: It declares that all data and code go into the same region, located at 0x20000000 and at most 0x800 bytes long. It also tells the linker which function is the main entry point (otherwise LD would see that the function isn’t called and it would get removed).

The resulting elf file can then be converted into a bin using objcopy as per usual. The code from the previous screenshot results in an 86-byte bin. Looks like 2Kb is going to be more than enough for each “tile program”.


Now that all the maps are packed into an external file, loading one of them with File is really straight-forward:

        file.seek(mapOffset);
        file >> mapWidth >> mapHeight;
        file.read(tiles, mapWidth * mapHeight);
        file.read(events, mapWidth * mapHeight);

As for the trigger, running it is also simple:

        file.seek(pos);
        file.read(RAM1, length);
        auto trigger = reinterpret_cast<void (*)(const Parasite *)>(RAM1+1);
        trigger(&parasite);

And here’s the result of today’s work:
image

4 Likes

Log Entry #3

I haven’t been able to do much during the week, but yesterday I made a simple text layer that uses the built-in Pokitto fonts. It’s much more limited than TASUI, but it serves my needs. Primarily, I need to be able to display text with the Pokitto rotated sideways. I want parts of my game to be played with the screen in portrait mode.

Enough talking about code, today is supposed to be an artsy post.


Plot

I thought a bit about the story this week. No idea how much of it will actually make it into the game.

Our protagonist lives in a small apartment in a large complex with multiple floors. Every morning the protagonist needs to find the way from the apartment to work: a restaurant, present in each floor, where traditional microwave-cooked meals are served.


Graphics

Drawing only a head in 16x16 is alright, but I’m definitely not happy with the full-body, even at 16x32.
Sprite-0001

Also tested with some quick heads at 64x64:
Sprite-0003

So I still haven’t found a satisfactory way to show full-body characters.

CyberPunk requires greebles. Detailed, visually noisy environments. Bits of electronics sticking to people’s faces. Circuit traces, holograms, LEDs, neon… even if it doesn’t make sense. Thus my dilemma: How do you get even a suggestion of this much detail in 220x176???

Time to find some references. It doesn’t help that the world is obsessed with Cyberpunk 2077 right now.

Google: cyberpunk pixelart -2077

  • Jason Tammemagi has a bunch of Cyberpunk-themed art. The environments are excellent. Character’s faces are minimalistic, but they are still expressive due to subtle animation and body language.

  • Slynyrd’s twitter and his blog has a bunch of pixelart tutorials and references to other artists:

  • Murder. A point-and-click game. Characters are less cartoony. Faces are even more minimalistic.

I’ve been seeing this style where the faces are so small they don’t even try to draw any of the features. It looks alright, but not something I’d like to do. Let’s look at games with actual faces instead.

  • River City Girls. Not only do they have faces, but the sprites are about 64 pixels tall. I can work with that. Maybe. Or maybe I can simply not have full-body sprites at all… :thinking:


Level Design

Still experimenting with techniques, but the general idea is to draw some random squares (rooms) then interconnect them. 64x64 tiles gives plenty of space for a nice maze.

Then I need to add plenty of doors. Once a player opens a door, it stays open. Opening doors might feel annoying, but having lots of them helps the player know where they’ve been before. I then place enemies, props and items. Arg, I need to draw props and items! I hadn’t even thought about them yet, but the rooms are going to feel very same-y if they’re empty.

Once the first map is complete, making a few hundred more is going to be a piece of cake.
This jam ends on May 15, 2021, right?

7 Likes

This is going to be interesting :thinking:

1 Like

You might find this old thing useful. I made this random dungeon generator a long time ago using SDL 1.2 (before 2 was even conceived). If run without any command line arguments it’ll print out the usage. Then it’s just pressing enter repeatedly to generate random dungeons. I’m sure you could figure out how to use this to your advantage if you like.
DungeonGenerator.zip (12.3 KB)
It’s licensed as MIT

You don’t have to use it though, but I thought it might come in handy for rapid map creation since it seems to fit perfectly with what you’re doing.

3 Likes

Log Entry #4

It has been a tough week for finding time to work on my entry, but I did manage to do some engine coding on the weekend.

Here’s the current state of the game:

  • The Map.js script compiles all the C++ code in the maps and creates one big binary file containing all of these resources (maps, “scripts”, and images).
    – Instead of file names, each resource is indexed by the hash of the file name. The Map.js script ensures there are no unwanted collisions and merges any duplicated data.

  • In the game, the ResourceManager makes it easy to get the required resource:
    File *bacon = resourceManager["bacon.png"_hash];
    – I am using the File lib, but since it doesn’t work with some cards I made an SDFS-backed version of File as an alternative. It performs terribly.

  • A MemoryManager caches resources in RAM using the LRU algorithm.
    – This allows me to store an indefinite amount sprites in the SD. It will pull them in when they’re needed and flush them out when the sprite hasn’t been used after a while and something else needs to be loaded instead.
    – This cache is organized into blocks of 256 bytes (one 16x16 sprite). A sprite can use more than one block.
    – I tested by streaming a few animated 64x64 sprites at the same time and it worked pretty well. You can’t tell when a cache miss happens.

  • Each map script gets an empty 4-block resource so it can store data in run-time. If a player opens a door, the door script has somewhere to store that information. This data is written back to the SD card automatically when the cache blocks are needed for something else.

  • As for the maps themselves, I had a try with the ones made by @tuxinator2009’s random map generator, but I’ll have to make them manually. I want each map to have its own look and feel, and using a generator isn’t going to do. No long/forking corridors. No recognizable patterns. While generated maps are unique, they all feel the same. Like grains of sand on the beach. Human brains don’t have good RNGs built-in, so I’ll still run the generator for ideas, though. Thanks, @tuxinator2009!

  • For the map tiles, I managed to find some on OpenGameArt that served as a base, and have been modding them as-needed.

  • I still haven’t drawn any sprites. I’ve been using megupets as placeholders but might keep them since they’re CC-0.
    pet1

5 Likes

Personally I’ve always preferred hand crafted maps vs randomly generated ones since it adds a huge artistic touch to it. That’s partly why most all of my map generators don’t try to generate a 100% ready to use final result, instead they give a great starting point (one that you can generate 100’s really quickly until you find one you really like) and then make some adjustments from there.

I’ve actually got several experimental map generators that usually follow the same principal of creating a nice, organic, starting point that can be manipulated and fine tuned from there.

1 Like

Log Entry #5

Yesterday I implemented support for 1BPP sprites in TAS, which I plan to use for visual-novel-style fullscreen bitmap cutscenes.
heads

Since they’re sprites, they can be used with recoloring, transparency, mirroring and/or flipping.

Also, I wrote a Simulator-compatible version of File.

Today I wrote an audio lib capable of streaming music from SD using File, playing sound effects with a configurable amount of channels, and consuming only 1Kb of RAM. The entire IRQ handler is in handwritten assembly. I suspected that rewriting the handler in assembly wouldn’t gain much and, now that it’s done, it’s confirmed. The SD card is a major bottleneck. At least it takes up less flash space. Maybe we’d notice a difference with tracker-based music playing samples in flash? :thinking:

As I’d mentioned before, to get a better idea of what size my characters can be on screen, and if their sprites can be streamed quickly enough, I’ve been doing some tests using placeholders:

Here I draw two animated sprites and do Z-sorting by checking which one is lower:

The red 0/1 on the left of the screen indicates how many cache-misses occurred in that frame. The LRU algorithm does a pretty good job of getting rid of stale data, keeping the framerate high and disk activity low.

7 Likes

Log Entry #6

Today I worked on code.
I got this, plugged it into that with this other thing and got this result (113.7 KB), and I fixed this emulator bug that has been bothering me for ages.

Now, if you’ll excuse me, I must call my mother.

10 Likes