DSR Log #5

Posted by Mega on May 21, 2016, 9:26 a.m.

Welcome to the first in what is probably going to be a moderately long string of relatively boring logs, as I hammer my C++ code into shape. :P

I actually got very little done this week (For me), because like clockwork my days went like this:

> Wake up

> At 9AM, start coding

> At 9:22AM get phone-call from boss

> Arrive back home at 6PM and wonder what just happened

> Code a bit then get too tired to do much.

As a result of this, I've not really had time to tweak the prototype or work on art this week.

I'm pretty happy that the shop is getting so much work though, means things are becoming sustainable and quickly.

On the other hand, I'm probably going to have to start sacrificing more sleep to get stuff done; I've got another project I'm working on for local business - an extension of my inventory management tool that is being built on top of Electron.

People in town are willing to pay me money for a polished version of that, so I'll probably make a bit of cash off that if I can pull it together in between repairing their computers.

And some good news: Intel finally payed out the prize money for the competition this game won. I've got a shopping list that needs clearing (Including items like extra winter clothing, Dark Souls 3 and my Driver's License).

Now, onto the meat of the blog. It's a bit dry and hard to chew this time, but still represents a good deal of progress:

Some time last year, probably in November, I started writing a little renderer while bored. I wanted to try representing 3D objects in a purposefully low-res setting, to emulate the style a lot of 3D DOS games used, or N64/PS1 titles.

Here's a screenshot of the renderer in action:

The render code has a few useful features, but most importantly: It can handle any desired "native" render mode and scale them correctly to the current desktop resolution while preserving the aspect ratio and maximizing screen usage at the same time.

To render the scene at a lower resolution, I do a two-pass render. First into a texture, then render the texture scaled correctly to the display.

The way the renderer works is that it can operate in either Legacy OpenGL mode or in 2.0+ mode, where it uses GLSL to do most of the legwork.

Performance is pretty much the same as rendering a scene normally; copying the framebuffer to memory and drawing it doesn't really take much out of a system, old or new.

The advantage to allowing for old GL and new GL is that the game works effectly on basically anything with a graphics card from 2000 and up (Tested on an old PC I have in the house that has an Nvidia RIVA TNT2 with 16MB of RAM in it).

The broad process of doing this is:

> Create a blank texture to use

> On each draw frame:

- Render scene as normal, setting viewport and projection to "native" sizes

- Copy framebuffer to texture

- Calculate scaling and positioning of resulting viewport

- Clear framebuffer

- Draw scaled and positioned texture

This results in consistently "perfect" pixels on pretty much any display, with the only exception being if the monitor resolution is lower than the resolution you're using in game (In which case the renderer will lower the scale to fit. Looks a bit worse, since information is lost, but works in a pinch).

As mentioned in my previous blog, I've chosen to use rapidxml so I can load tilemaps from various editors. Most of the good 2D tile editors I've bothered using can export to an XML-style format, and I happen to be using Ogmo, which saves to XML by default.

I did a quick test of how I might load and render the tiledata, and managed to get a test level up on screen using some quick-'n-dirty tile-rendering code (Hard coded and using GL_QUADS).

Everything checked out, so I've stashed that code for later (Want to clean it up properly before committing to using it).

Another minor feature I decided to add quickly while testing was the ability to parse and check command line options. Adding debug switches to a game is a really good way of sectioning off certain bits of code during runtime or jumping into a level/feature that you want to test without having to hard-code it into the gameloop.

This is mostly a system-level design feature, something I decided to add because I'm trying to minimize the game's resource-footprint for no reason other than that it's the kind of thing I do I guess :P

This is basically a case of me pre-allocating my own Heap to use for resources. Instead of allocating a giant monolithic chunk of memory and hoping it'll be enough, I'm assigning blocks of contiguous memory to related blocks of textures/sounds/whatever.

Essentially, each game state has a block assigned to it that contains any unique resources it needs for it to function. The top-level game's data structure contains any common resources that can be used anywhere (Things like bitmap fonts, UI, UI sounds, player sprite, etc), the forest level will probably load the forest tilesets, enemy sprites, etc.

The usual reason for doing this in an engine is to help prevent alloc fragmentation:

If you're frequently grabbing and releasing varying fragments of memory, you're often left with small gaps that can't fit the new stuff you might try to load, so the OS just hands you a few extra pages of memory; so even if you're only using 1% of the new chunk of memory, you've just committed it and the OS won't take it back 'til you're done.

A lot of older game engines did this, and while it's not as immediately useful in my exact situation, it's the kind of thing I felt like doing :P

It might also have uses in the future where I'm potentially working with larger resources; I want to eventually revisit making an FPS, something like a PS1 title: Models, textures, terrain, BSP data, etc. Those can end up taking up quite a large bit of memory, especially once you've got them "unpacked" into usable data.

Heck, I won't underestimate the scope of this game, I might end up needing to use a load-transition between some areas if things get big enough.

I guess that's actually pretty decent for the 2-3 hours I actually got to spend on things.

A lot of this code is actually coming from other places. As I mentioned before, I have a ridiculously huge development folder full of project fragments. Most of them have some useful code I can reuse or recycle, saving a ton of development time.

Some of the things I'm working on integrating soon:

AngelMod

An AngelScript framework I wrote for some version of Exile or other. Basically provides interfaces for linking script to native code.

I may or may not even use this, but having the option to potentially implement the game's logic and gameplay in script is very appealing to me.

SFGME

The third iteration of my GME wrapper. If I'm going to use NES-style music and sound effects, I may as well use them via an emulator like this one :P

MPK3

Back when I made Exile, I made an Asset Packer called MPK2 that basically just stored a flat file table as ASCII with starting offsets and lengths, then sequentially dumped each file in order.

I got a bit more sophisticated last year with a few extra features: zlib integration, virtual paths, editing existing PAKs and selective loading (E.G: Load entire /data/textures/level2 folder contents into contiguous block of memory).

This basically allows me to obfuscate my resource files a bit and reduce final filesize.

MXMath

Math library I started back when I made Exile. Started out as a messy Vector implementation, now has cleaner Vectors, basic Collision functions (Line, AABB, Circle, 2D Raycast) and a bunch of interpolation functions.

Also an implementation of Fixed Point numbers in case I ever want to port my games to systems without a Floating Point Unit…

Portions of a few engines

Most didn't even have names. I basically just went over a bunch of my old engines, found a few decent implementations of the gameloop and hybridized something out of them.

The Gameloop is already fully implemented, and consists of three major parts: The "Engine" (Part that runs each cycle of the loop), the State and the Renderer.

The Game State is the part that contains the current "stack" for the game. It holds resources used by whatever situation is currently playing out, executes entity code, dispatches events to listeners and a bunch of other stuff.

Entities are stored as pointers, and are stored in an associative array of singly-link lists.

Each list contains only entities of one specific type (As reported by the entity's data); this is done to make it easy to iterate over specific kinds of entities for situations like collision checking, deletion, etc.

The system I'm working on currently for things like collision is based on the Listener pattern. The GameState is basically the Listener. Entire types of Entities can be bound to specific events, along with parameters.

So, for instance, the Player entity can register to listen for all Collision events that come from any Pickup type.

The loop tightens up nicely when you're only considering things that can collide with each other instead of the naive double-dispatch system I implemented in Exile (Basically everything is looped through twice and checked to see if they're colliding).

Of course, the Listener system isn't just for that kind of thing. Generic messages can also be sent and triggered between various entities; sounds, music, transitions and other things can be triggered by code condition and propagated to anything that wants to be notified about that event…

Next week, I'll hopefully be showing a partially working game (I've got the player partially implemented, just need to get my tile renderer polished up and I'll actually be able to do something with it).

Comments

Jani_Nykanen 7 years, 11 months ago

Hopefully you'll share some portions of your code someday. It really interests me to see C++ written by other people (I assume your code is much more professional than mine, but that wouldn't be a surprise since I usually focus on art and ignore the quality of the code).

I noticed you mentioned AngelScript. It really surprises me someone is actually using it. I once considered using it by myself, but I changed my mind when I realized how bad a documentation it had.

Jani_Nykanen 7 years, 11 months ago

@SpectreNectar

Now I understand why it's recommend to use comments in code… You have different naming habits, but otherwise the code (or coding style) is not so far from my code as I had thought.

Jani_Nykanen 7 years, 11 months ago

Everywhere. It would help understanding what that code does, instead of "running" the code in your head and imagining the effects.

But I don't think I'm the right person to tell other people to add comments since I don't do that by myself, either…

Mega 7 years, 11 months ago

Quote:
Hopefully you'll share some portions of your code someday. It really interests me to see C++ written by other people (I assume your code is much more professional than mine, but that wouldn't be a surprise since I usually focus on art and ignore the quality of the code)
I'll probably end up putting most of the engine-specific code up on GitHub at some point or other, and will probably paste snippets most weeks :P

Quote:
I noticed you mentioned AngelScript. It really surprises me someone is actually using it. I once considered using it by myself, but I changed my mind when I realized how bad a documentation it had.
It's got an API reference, and I've come to learn that if you can even find one of those, you've got good documentation :P

Besides, most of the functionality comes from the binds you do to your own engine; you're kinda creating your own Standard Library that happens to deal mostly with game related stuff.

Quote:
You have winters in Afrika? :0
South Africa is far enough South that where I live on the coast we hit the single digits throughout most of Winter and even get into the sub-zero temperatures on occasion; it was raining hail a couple of years back. There was enough of the stuff on the ground that my younger sisters built a "hail-man" out of it :P

The big mountain range nearby is usually capped with snow too.

Quote:
Deprecated since version 3.0 so eew!
¯\_(ツ)_/¯

Quote:
Just felt like sharing it might interest Mega as well idk…
Yup, interest grabbed. That's a decent batching system.

I'm probably going to be working on optimizing my draw cycle once I've actually got a few full scenes rendering, though frankly I'm not yet convinced that at the complexity-level I'm working at that it'll be worth the work.

Even if I wanted to do some complex shading (Say applying a shader to the scene for 2D lighting or shadows), I'd just make use of the framebuffer I'm keeping around and composite it in layers (So Render-To-Texture->Calculate and Render Shadowmap->Calculate and Render Lightmap->Mix final result).

Nopykon 7 years, 11 months ago

Aw man, that looks like PS1! :D

Quote: Render Core

- Render scene as normal, setting viewport and projection to "native" sizes

- Copy framebuffer to texture

- Calculate scaling and positioning of resulting viewport

- Clear framebuffer

- Draw scaled and positioned texture

Yep, that's how to do it. In games with the crt-effect, I draw the (framebuffer->texture) over a model of a screen instead of just a screen covering quad. Then the screen model again with a 3x3 crt rgb pattern texture and multiply blend mode. Sometimes one more pass to make it brighter. Tbh, thats a few to many full-screen blends for something that could be achieved with no blending and a single draw call with glsl, but you know, if your computer is too slow to run that, you're probably on a crt anyways and can disable the effect. xD

I think quads make sense when they are 90% of the primitives that you draw. They also sample a better center point (and are fewer ofc) than triangles, for depth-sorting. I still agree with them being removed from newer standards, but why are they ew when working with old gl? GL_QUADS aren't deprecated unless you're using a standard they are deprecated in, right? By using old gl at all, the risk is that future drivers stop supporting your game. But if you would need to, it isn't hard to rewrite a renderer from legacy to modern unless you somehow have mixed all your gl-stuff with the rest of the game code.

Nopykon 7 years, 11 months ago

Ah, yes. those are valid points that I'm aware of. :) I'm arguing that treating faces as quads in the engine, before sending the stuff of to GL (whether you or the driver turn it into triangles), can be more efficient (for the CPU) and elegant (if your entire world are quads). But now we're talking niche low-poly retro style 3D games intended to work on a computer from 1998. Probably don't do dis for anything else.

Mega 7 years, 10 months ago

Just a note here instead of making a new blog about it: No log this week, between work being hectic, Dark Souls 3 being distracting as hell and the sudden onset of a bad cold yesterday, I've hardly had the project open this entire week.

Not to say that I didn't get anything done. Basic player movement is in (Including dashing). It's bugging out a bit because I 'ported' it at something like 3 in the morning while unable to sleep, so for some reason that I still can't figure out, I can dash around perfectly, and walk normally so long as I hold a direction key after the dash… but try move from a standstill without the dash? Nothing happens.

The above movement also includes all the correct animations (Sans animation dash trail); things are looking pretty good for about 2 hours of on-off work this week.