Catacombs of the damned! (Formerly Another FPS style 3D demo)

This is impressive. Fluid and works nicely. I love the screen shake effect. Can almost hear the fireball sound without the sound😉

And procedural generation is always great.

Thanks!

I just realised that ProjectABE doesn’t support the Arduboy2Base::generateRandomSeed() functionality so in the emulator you always get the same level. If you run on device you’ll get a different level each time you start. Maybe something @FManga can fix?

1 Like

I thought you probably weren’t,
but it’s good to be aware of that issue anyway.

Looks good.

At the moment it looks like more of a rock than a fireball.
Or maybe a dragonball.

In which case it wouldn’t support Arduboy2Base::initRandomSeed() at any point in Arduboy2’s history, since generateRandomSeed() is actually just the former contents of initRandomSeed() adapted to return the seed instead of feeding it to random().

Most likely the problem is with reading ADC.
Arduboy2 is relying on it producing random noise,
and I’m guessing in ProjectABE it’s currently just producing 0 because it’s an unconnected pin.

This is so incredible!!! It looks like it could soon be more than a demo! :smiley: How much space do you have left?

Noticed a small bug where if you stand inside one of the skeletons (just past where it disappears) it will occasionally glitch and draw some squares in the upper left or upper right of the screen.

If your looking to add some entropy without an ADC, you can make the seeed based on the milliseconds since the user presses the first button from a menu screen.

2 Likes

Arduboy2Base::generateRandomSeed(), and thus Arduboy2Base::initRandomSeed(), uses a combination of both the random noise on unconnected Pin A4 and the time since the system started. Therefore if you wait for a button press before calling either, you will still get a different seed even if the value read from the pin is always the same.

@jhhoward I was just reading the Game Engine Black Book about Wolfenstein 3D…

void  scaleTextureTo2(void* src , void* dst) {
	dst [0] = src [16];
	dst [1] = src [48];
}

void  scaleTextureTo4(void* src , void* dst) {
	dst [0] = src [0];
	dst [1] = src [16];
	dst [2] = src [32];
	dst [3] = src [63];
}

Seems familiar :P

(Side note: I have no idea how this would actually have compiled. Is a void * dereferenceable in C?)

Although the reason this works for Wolfenstein3D is more complicated than it may seem at first.

Mix of old C compilers being what they were and some language features:

  • sizeof(void) is 1
  • You can ignore the return value of a function.
  • And you could compile & call a function with a return value declared but that didn’t have any return statement in it. It would still work. (with whatever garbage was in the CPU’s AL/AX register as the return value if you tried to use it)

My guess is the compiler internally aliased the void type to the char type (or unsigned char) to save on memory since it pretty much worked just the same. They forgot to special-case disable dereferencing on it.

I just saw that both the wolfenstein 3D and Doom books are both available as free PDFs now:
http://fabiensanglard.net/gebb/index.html
I have hard copies of both and I would definitely recommend them! Lots of interesting tricks used for the old PC hardware.

I assumed that this was the case. Maybe the emulator can sample random noise when trying to read an unconnected pin?

Maybe! There is still plenty of flash and RAM left available.

Good idea! Alternatively just keep calling the random function each frame until a button press would work too.

I remember making floating pins and/or the ADC return random values at one point, and I think it was visible in the theremin app someone made. Did that break?

Edit: Found it… looks like it still works:
ptheremin.hex (37.7 KB)

Yes, it seems to. Running the following simple sketch in the emulator returns a different value each time it’s run as a new game or the reset button is pressed.

#include <Arduboy2.h>

Arduboy2 arduboy;

void setup() {
  arduboy.begin();
  arduboy.initRandomSeed();
  arduboy.println(random(2147483647L));
  arduboy.display();
}

void loop() {
  arduboy.idle();
}

Yes, that’s where I got my copy from.

They’re interesting, but most are very hardware specific,
and some I wouldn’t advise attempting if you can avoid it.

That would work to a degree, but you’re effectively just shifting the same pseudo-random sequence along a few entries which means that after a while people will start running into the same levels if they take a similar amount of time to skip past the title screen.


Pre-ANSI C is full of weird things.

Apparently GCC will still allow this for C if you pass the right flag.

In modern C (C99, 6.5.3.4.1):

The sizeof operator shall not be applied to an expression that has function type or an incomplete type

And (6.2.5.19):

The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

I’m not sure what you mean by ‘ignore’.

That’s worrying, and it makes me really glad that modern compilers have come such a long way.

That’s plausible. Upsetting, but plausible.

Well, they had less than 640KB to work with and 64KB data segments. :smiley:
I probably would have done the same.

I got a table of reciprocals in Starduino that I read out of bound for 0 and 1 on purpose (uint8_t stepping = (reciprocals-2)[height] ) because 0 renders nothing and 1 only renders one scanline so it doesn’t matter if I add a garbage number at the end of the for-loop in those cases.

You save wherever you can. (and not having an MMU to worry about can be fun)

1 Like

I’d sooner look for micro-savings elsewhere than invoke undefined behaviour.

There’s almost always savings to be made somewhere.

Looking more like a game…
Arduboy3D.hex (65.9 KB)

Sprites based on Kenney’s 1 bit pack

9 Likes

Just wow! This is incredible, gameplay wise, performance wise, graphics… This already looks like a fully functional game… very impressive.

2 Likes

I am super impressed :grin::raised_hands:

1 Like

WOW indeed!!!

1 Like

This. Is. Incredible.

I know I’ve said it before but I think the dithering is quite heavy on the floor and ceiling, maybe consider using a different or lighter pattern?

Inspiration:

Just for you Kevin: a build where the A + B buttons cycle through different dither patterns for the floor and ceiling!
Arduboy3D-dither-select.hex (67.2 KB)

Which combination do you think works best? Post a screenshot!

I found the ladder.

Presumably level 2 isn’t implemented yet and I’m viewing junk data? :P

Level2

Anyway, personally I like this permutation:

Permutation

And here’s a tiny .gif to show it in action:

ArduboyRecording

Whatever combination is used, I think the ceiling should be dark, the floor should be light and the patterns should have a small to medium frequency of dots.
Higher dot frequencies are too ‘busy’, and make it hard to process.

It’s a shame there’s not enough processing power left over to correct for perspective so the floors could be drawn as actual floor tiles instead of just drawn as a flat pattern.