Show HN: A PSX/DOS style 3D game written in Rust with a custom software renderer
1 year ago (totenarctanz.itch.io)
So, after years of abandoning Rust after the hello world stage, I finally decided to do something substantial. It started with simple line rendering, but I liked how it was progressing so I figured I could make a reasonably complete PSX style renderer and a game with it.
My only dependency is SDL2; I treat it as my "platform", so it handles windowing, input and audio. This means my Cargo.toml is as simple as:
[dependencies.sdl2] version = "0.35" default-features = false features = ["mixer"]
this pulls around 6-7 other dependencies.
I am doing actual true color 3D rendering (with Z buffer, transforming, lighting and rasterizing each triangle and so on, no special techniques or raycasting), the framebuffer is 320x180 (widescreen 320x240). SDL handles the hardware-accelerated final scaling to the display resolution (if available, for example in VMs it's sometimes not so it's pure software). I do my own physics, quaternion/matrix/vector math, TGA and OBJ loading.
Performance: I have not spent a lot of time on this really, but I am kind of satisfied: FPS ranges from [200-500] on a 2011 i5 Thinkpad to [70-80] on a 2005 Pentium laptop (this could barely run rustc...I had to jump through some hoops to make it work on 32 bit Linux), to [40-50] on a RaspberryPi 3B+. I don't have more modern hardware to test.
All of this is single threaded, no SIMD, no inline asm. Also, implementing interlaced rendering provided a +50% perf boost (and a nice effect).
The Pentium laptop has an ATI (yes) chip which is, maybe not surprisingly, supported perfectly by SDL.
Regarding Rust: I've barely touched the language. I am using it more as a "C with vec!s, borrow checker, pattern matching, error propagation, and traits". I love the syntax of the subset that I use; it's crystal clear, readable, ergonomic. Things like matches/ifs returning values are extremely useful for concise and productive code. However, pro/idiomatic code that I see around, looks unreadable to me. I've written all of the code from scratch on my own terms, so this was not a problem, but still... In any case, the ecosystem and tooling are amazing. All in all, an amazing development experience. I am a bit afraid to switch back to C++ for my next project.
Also, rustup/cargo made things a walk in the park while creating a deployment script that automates the whole process: after a commit, it scans source files for used assets and packages only those, copies dependencies (DLLs for Win), sets up build dependencies depending on the target, builds all 3 targets (Win10_64, Linux32, Linux64), bundles everything into separate zips and uploads them to my local server. I am doing this from a 64bit Lubuntu 18.04 virtual machine.
You can try the game and read all info about it on the linked itch.io page: https://totenarctanz.itch.io/a-scavenging-trip
All assets (audio/images/fonts) where also made by me for this project (you could guess from the low quality).
Development tools: Geany (on Linux), notepad++ (on Windows), both vanilla with no plugins, Blender, Gimp, REAPER.
Didnt you think of making it open-source on github?
The game-side code, not really (I am ashamed of reading it myself). The engine-side is a bit less shameful, but I'd rather not as I mentioned elsewhere, especially on github. I may change my mind though.
That's a shame, i like how you go about using Rust to have crystal clear structure while sidestepping the unreadable side that comes from using all of its nuances.
I'd kinda like a look. My Code is dependency free where possible (up to the graphics library for making the pixels go on screen) too - and everything else is a clean pipeline. Soooo i can only speak for myself, but if you have some parts that don't make you cringe much but proud - please share!
It's less about implementation of functionality, im mostly curious about your style! :)
1 reply →
> I do my own physics, quaternion/matrix/vector math, TGA and OBJ loading.
> FPS ranges from […] [70-80] on a 2005 Pentium laptop
> I am using [Rust] more as a "C
Great minds think alike! Please, I invite you to submit your work for approval here:
https://xcancel.com/tsoding/status/1960511663788188095
https://xcancel.com/tsoding/status/1964636951358894337
Nice discussions! I've seen many of these projects lately around related communities. Happy to see a trend around this, as I really think there is actual merit in the topic. My project was more of a random timesink than something that pushes the matter forward though.
Do you mean directly e-mail/contact said person?
Tweet
I love custom game engines. Looks fantastic! Will you be sharing the source?
Thanks! For several reasons, most probably and regrettably no, for now at least.
More than happy to talk about any specific part however (e.g. how scenes are handled, the code itself, or how particular features are implemented or optimized).
You talked about using Rust as a better C so I just wanted to ask, do you define any enums with payloads? (also called "sum types" or "tagged unions" in other languages) (edit: also called "algebraic tyeps" and there's an article about it in the front page, though this is a slight misnomer)
Things like
Also, how is your usage of Option? (one such enum)
I think this plus pattern matching is the foundation of Rust's superpowers. It's also very old tech and absolutely not Rust's invention, present in languages like OCaml and SML. Hence the early Rust slogan, "technology from the past, come to save the future from itself"
1 reply →
Could you talk more about the subset of rust you settled on?
You said you didn't explicitly use simd, but did you do anything to help the optimizer autovectorize like float chunking
10 replies →
nice, but i find it very hard to play. acceleration is either not enough and you are pulled in by the planet, or it is to much and you are getting away so fast that you can't counter steer. shouldn't gravity take care of that? if i am in orbit, then speeding up along my trajectory should slowly increase the orbit, and slowing down should decrease it. but speeding up takes me immediately out of the orbit as if the planet had no gravity.
Thanks for trying it out. It's a regular inverse square law, no tricks. The numbers (masses, distance) determine the final acceleration but not the actual trend of the curve.
I've become too familiar with it over testing to notice unintuitive behaviour, but I think I understand what feels off: in real world units, the gradual region you describe is very wide, and feels linear. This would make for very boring gameplay (imagine spending minutes to reach the planet). You need to keep the playable area [radiusForce0, radiusForceMax] small. So you will either map that small [r0, rmax] into real world [F0, Fmax], which means the force will be almost constant across, or "compress" the [F0, Fmax] curve so that you can fit both [zero outer space gravity, strong surface gravity] into that [r0, rmax].
That's what happens here, I probably tweaked the values for the second case. It's kind of an accelerated version of reality and the margins feel very tight, and you have to "buy into" that reality.
For example, Master difficulty in Mission 1 may seem impossible, but if you try to be gentle and find a balanced orbit, you can complete it with miniscule fluctuations in distance and minimal input.
Just rambling though, I never really actually designed or balanced the game.
maybe some kind of indicator to show which level of acceleration/speed is best would help.
the unintuitive behavior is that it is very difficult to find that balance. if i am to slow i crash into the planet, if i am to fast i leave orbit with no chance to get back in time. being gentle always results in being to slow. in other words there is no gentle way to reach the balance. and if i don't know where that balance is, i don't know what to aim for.
you may argue that finding that balance is the goal of the game, but then i guess the game is not for me. i lose interest if i have to try 10 times and still can't figure out how to do it right.
1 reply →
If you embed all the assets in the binary, the download would be an instantly playable single file..!
It would be cool indeed, I played around with !include_str and co. and works as expected. It wouldn't be possible for the user to modify the assets though, and to save the settings file.
Very cool project, can't play because on mac but looks like cool approach
I have considered getting a Mac in the past (even a very old Air) to make and, most importantly test, Mac builds, but I never got to it and cross-compiling to Mac seems like a pain, if at all possible. If you have a VM laying around though, it should work.
A good thing about the approach is that if it compiles, it should work exactly the same everywhere and with predictable/linear performance, no matter the environment or driver situation.
I believe you can build to mac targets on github though
3 replies →
It works perfectly cool through wine. Also i think adding mac builds wouldnt be hard given its simple dependencies.
Yeah it should be just a simple cargo build. But for the same reason it should be playable through any emulator/translation/VM layer as you mentioned.