Another FPS style 3D demo

(James Howard) #1


I have been experimenting with doing a first person demo on the Arduboy. This uses similar techniques to my Wolfenduino project on the Gamebuino.


Some notable features:

  • Runs mostly at 30 FPS on device
  • Textures are ‘vector based’ rather than imaged based. I think this actually works quite well for the small black and white screen
  • Fake camera tilt for shaky cam movement / turning
  • The sprite scaling method adds outline shading to the edges of the sprite after scaling
  • A very simple particle system for effects

Prebuilt HEX available to download here
Code is a bit messy but available here
Play in ProjectABE here

(Jean Charles Lebeau) #2

Beautifull, maybe a little too much roll effect atm but i think that it’s too show how fine it’s works. The graphical result is really beautiful. (And please continue your Gamebuino works from the Gamebuino on the META too). The black and white works on Arduboy is well contrasted, outline are clean. You’ll be able to complete the Wolfenboy soon i think with the advance of your motor if you have already the levels done on Gamebuino to insert in your motor. The final game will be amazing, i think

1 Like
(Matt) #3

This is fantastic! I hope you work it into a full game.

1 Like
(Miloslav Číž) #4

Absolutely beautiful :open_mouth: I have two questions:

  • Couldn’t the fake tilt be made into real tilt? Instead of just offsetting the points in vertical direction (that seems to be what you’re doing) rotate them around the screen center. It would require sin and cos though, but maybe some clever precomputation would help. Or do up-down movement of the camera instead (like e.g. in Doom).
  • Would you mind adding a license so that someone can expand on it and make it a game? Or are you planning to expand it further yourself?
1 Like

looks great. Are you going to scale the black lines too?

(Jean Charles Lebeau) #6

Yes up and down to simulate walking could be great and seems possible when i see the fluidity of the moves.

(Cody) #7

This is amazing it looks so smooth!!!

1 Like
(Kevin) #8

WOW! How have I never seen this! This is incredible performance on the raycasting and I love the way it tilts when you turn. This is so good!!

1 Like
(James Howard) #9

Just realised that I never replied to any of the questions!

@Jean-Charles_Lebeau : I don’t have any plans to do any projects for the Gamebuino META as I don’t actually own one. If you want to port any of my games or demos then feel free to. I’m happy to answer any questions that you have about the code

Thanks! It would take a bit of work to make a real tilt due to the way the rendering is set up. I think for the slight rotation amounts this works ok though. Could do a camera bob up/down movement instead of the tilt.
I have added an MIT license to the demo so it is free to be copied, picked apart, built upon. I don’t have any plans right now to expand this to a full game

I think that the current vector style / line drawing works really nicely on the low resolution display and scaling the lines would probably spoil this.

Thanks Kevin! Technically it isn’t ray casting which is why it was possible to do the vector style, which was the aim of this demo. Performance was really important too. It does get a big sluggish if sprites take up too much of the screen though.

1 Like
(Kevin) #10

Being too close to the sprites has always been a problem for this type of engine. The one I worked on a long time ago would pretty much freeze the system if you got too close. It’s kind of a weird bug isn’t it? I guess it has to do with how it is drawn but I wonder if there is a way to fix it.

(James Howard) #11

It’s because the closer the sprite, the larger it is, and therefore needs more CPU cycles to render it out. In my implementation I use a look up table to speed up the process of calculating the scaling to avoid any expensive mathematical operations (e.g. divide).

It could probably be sped up without the outline drawing effect. Also not sure if the calls to PutPixel() are getting inlined or not by the compiler. Could probably be faster still if written in hand tweaked ASM!

1 Like
(Pharap) #12

While having a skim of it I decided to start adapting it to see what space savings I could squeeze out of it.


Sketch uses 12532 bytes (43%) of program storage space. Maximum is 28672 bytes.
Global variables use 1643 bytes (64%) of dynamic memory, leaving 917 bytes for local variables. Maximum is 2560 bytes.


Sketch uses 12150 bytes (42%) of program storage space. Maximum is 28672 bytes.
Global variables use 1642 bytes (64%) of dynamic memory, leaving 918 bytes for local variables. Maximum is 2560 bytes.

And as far as I’m aware it’s still just as fast and doesn’t have any bugs.

I doubt I’ll do any more to it, but you never know.


Nice! How do you plan to turn it into a full game?

(Kevin) #14

@Pharap ill get you a Jamba Juice if you can fix the close up sprite bug

1 Like
(Pharap) #15

What is the bug?

The sprite disappearing?
(I assumed that was because the player had just walked through it.)

Or the warning LED lighting up?

The easiest way to tell is to put __attribute__((noinline))/[[gnu::noinline]] and __attribute__((always_inline))/[[gnu::always_inline]] on PutPixel.

If noinline increases the code size and always_inline has no effect, then it’s being inlined.
If always_inline reduces the code size and noinline has no effect, then it’s not being inlined.

If both give different results, mysterious things are happening.

I think the bigger problem is that drawing pixel-by-pixel is slow.
If you could find a way to draw column by column then you’d save a lot of shifting and masking.

In fact, I think even having a 1 byte buffer for the column and writing into that before copying it to the screen would save some processing because you could cache the row offset after calculating it (i.e a single multiply instead of 8) and possibly save some other calculations.

For example, instead of needing to do bit = 1 << (y & 7); (or the equivalent),
you’d be able to maintain a single mask variable (e.g. uint8_t mask = 1),
and then just do mask <<= 1 once per iteration.

(Kevin) #16

The fps drops dramatically when you get too close to sprites.

(James Howard) #17

Interesting idea! I considered just inspecting the disassembly to see what the compiler has done but haven’t really had time to dedicate

Also a good idea! This would definitely help speed things up

1 Like
(Erwin) #18

This is f*cking awesome!

(Pharap) #19

I can never remember where objdump.exe is.
(I really need to find it, rename it and put it on my %PATH% or something.)
Even when I can find it, the assembly dump is always massive.

There’s no rush as far as I’m concerned.

If you do get back to it at any point,
have a look at my fork and decide if you like any of the changes.
I can make a PR or you can commit them yourself.
(But if you commit the changes yourself,
I’d appreaciate it if you could make me a co-author.)

(James Howard) #20

Had a quick look at your fork and it looks like you’ve been busy! Looks like most of your commits are style changes but also a few optimisations too. One interesting difference in DrawWallSegment() was your change from this:

uint8_t u1 = pgm_read_byte(texPtr++);
uint8_t v1 = pgm_read_byte(texPtr++);
uint8_t u2 = pgm_read_byte(texPtr++);
uint8_t v2 = pgm_read_byte(texPtr++);

to this:

uint8_t u1 = pgm_read_byte(&texture[index + 0]);
uint8_t v1 = pgm_read_byte(&texture[index + 1]);
uint8_t u2 = pgm_read_byte(&texture[index + 2]);
uint8_t v2 = pgm_read_byte(&texture[index + 3]);

What was surprising was that the generated code from your code uses less instructions. I expected the compiler to be smart enough with my code to pack each line into a single AVR instruction, e.g.

lpm r15, Z+ ; load from program memory to register and increment pointer by one
lpm r16, Z+
lpm r17, Z+
lom r18, Z+

but unfortunately it wasn’t as clever as I hoped and actually used far more instructions than your version!

I had a go at rewriting the sprite scaling code and got it much faster but I had to drop the outline effect which is a bit of a shame. I can commit it later if you’re interested at taking a look

1 Like