DSR Log #6

Posted by Mega on June 4, 2016, 7:40 a.m.

This week: Animation, Movement, Float Truncation and Entity Spawns

I spent a good week and a bit finishing Dark Souls 3. I have no regrets.

Not that I spent the entire time playing the game, even I get burnt out on Dark Souls if I'm spending 4 consecutive hours getting Rekt by a boss (Dancer).

This does, of course, mean that progress was slowed for the past two weeks, but not entirely halted.

So without further interruptions…

Sprites, Animation and Movement

The 'big feature' I tackled over the past two weeks was getting the player onto the screen and moving in the same way the GM version had it.

End Result (Click for full size):

Looks a bit jerky in GIF form, but I can assure that it feels exactly as it should (I should get into the habit of recording WEBMs instead, they retain most information including the full framerate).

The movement code was more or less directly ported from the Game Maker project, then polished up to make better use of the C++ language; things like x/y pairs (force_x, force_y) have been paired into t_point structs to clean things up a bit, for instance, and the motion calculation has been pushed off into a few general purpose motion calculation functions that are now part of the broad engine instead of exclusive to the player.

One annoying little bug I created was that, at first, walking wasn't working and only the dash would work… but if a key was held at the end of the dash, the player would continue walking as normal.

This happened at some point after midnight last Thursday, so I only discovered the cause of the bug in the morning.

Basically, Truncation. Consider this piece of code:

C++
// Float Truncation Example  
   
 float floatyNumber = 2.9F;  
 int intyNumber = (int)floatyNumber;

My midnight logic was thinking, as is the case in some other languages, that the float would round to the nearest integer and that the result would be a 3 stored in the resulting int.

This is, of course, stupid thinking. C++ adopts the fastest route to stuffing the float into an int, and just ignores anything trailing the decimal point.

The reason this came about in my code is that, for whatever reason, I decided that all Entity types would store their X/Y positions as ints; it made some sense considering that, at all times, I wanted my sprites to be rendered precisely on pixel boundaries.

Again, stupid thinking. For fine motion control, you definitely want to allow for any object to store its position as a floating point or fixed point value, just so you can pull things off like proper acceleration and friction at low speeds.

The solution to this entire situation was to just store everything as float, and make the renderer round the x/y values as it needed them. Everything is fine now.

Sprites and Animation

Getting player motion to work was one problem that needed solving, but having a still frame floating around the screen wasn't very amusing.

I spent a bit of time making a decent animated Sprite module for the engine. It supports a few basic features I'm likely to use:

> Sprite flipping (Vertical and Horizontal)

> Reverse animation and automatic Stop on Begin/End of animation

> Callbacks for animation stop/end

> Sprite blending (Coloration and Transparency)

I haven't got much of a fancy system for actually putting these on the screen; I'm basically using a backbuffer for everything, rendering to that, then swapping it out to the active framebuffer as needed.

I'm wanting to get the framebuffer update onto a separate thread to untie game ticks from render ticks, but that's for "later" in the project. My current tasks involve basic game features, and I want to reach parity with my GM project by month's end.

At this point, I can pretty much render everything as it appears in the GM version. Later I might add some shader effects for a few things like Magic and particles.

Entity Spawns

This is one of those major engine features I tend to forget until it's too late; then I end up getting stuck trying to shoehorn something into the engine that deals with whatever level format I'm using.

In this case, I'm using Ogmo to edit my levels; Ogmo allows for a specified Entity layer, with custom named Entities (And icons for them to help with editing).

These get stored in the resulting XML like so:

xml
<DebugPlayerStart id="0" x="144" y="80" />

The name of the node (DebugPlayerStart) is the name I'm using for Entity spawns. The id data is ignored, x and y are passed to the resultant object.

The way I'm dealing with this is via a Registry system utilizing lambdas.

The idea is that the Entity Manager contains an associative Map of string keys and functions that return an Entity pointer.

Entities can then be registered in one of the static initialization functions I have in the engine; the entire process kind of looks like this:

c++
// Entity Manager Registration Function

void registerSpawn(std::string name, std::function<Entity* ()> spawner);

// In initialization function for spawns:
EntityManager::registerSpawn("DebugPlayerStart", []() {
    return new ENTPlayer();
});

// In level spawn code:
Entity* ent = entity_manager.spawn(node.name);
if(ent) {
    ent->setPosition(node.x, node.y);
}

That's a bit of a simplification, but it's generally the same as the end result; there are a couple of extra things the spawn system does:

> If a registration already exists, the new one overrides the old one.

> If the spawn() function can't find an entity, it fails gracefully and logs a fault to the Log (But continues regardless)

> The level spawner in my working code currently passes the entire XML node and its attributes to the lambda function. This allows for custom attribute pulling for created entities (Some entities might have options for extra attributes, like "health", etc.

The way I'm designing this entire framework is to allow, eventually, for things to be modified easily via external scripting; so I'm trying to create a broad API of sorts for dealing with entities, their creation/destruction and their logic.

Level Loader/Renderer

My level loader is currently a bit of a mess; I'm experimenting with a couple different ways of handling the entire process, and that means chaos for a while until I finalize things.

Basically, I'm trying a few different methods for rendering the entire level. The first, and the old one I always used to use, is basically occlusion. In an orthographic 2D scenario that basically means rendering only what is within the screen boundary (With a bit of padding to compensate for movement).

The second method I'm messing with is based on the old way of doing things back in the days of VGA programming: Keep the tile background in the framebuffer and only render tiles on the edges as they're needed. Mostly tried this because of the novelty, but it does make a pretty big performance difference (No need to loop through (ViewWidth/TileWidth)*(ViewHeight/TileHeight) number of tiles per frame).

The final method I'm contemplating and measuring is basically compositing all static tile layers into a single buffer, then basically just copying a "window" out of that buffer into the working buffer.

My buffers are in VRAM wherever possible, and my current 'problem' is basically to slice up the entire tilebuffer into manageable slices (Basically 512x512 24-bit planes).

So far performance is basically a few miles ahead of the other two methods because copies from one or several VRAM locations to another are pretty damned fast.

Even in the case that we end up on the GL Software Renderer, it still shapes up better than my old dumb system (Which I tested on my old rubbish laptop).

I knew I'd eventually get caught up in doing weird low level engine stuff like this if I moved to C++, but I'm having too much fun to care!

Conclusion

I'll be spending my next week working on finishing up the level loader and renderer (And tidying up the way I'm using the rapidxml library), get level collision maps working, and see about getting the basic UI elements working (Health and Stamina, specifically).

The week after that I'm probably going to start re-building my state-based AI to fit C++, and getting some A* pathfinding going. So maybe a playable demo in a few weeks?

PS: I'm also posting these at my other blog: http://64mega.space

That way I actually have stuff to put there.

Comments

Nopykon 7 years, 9 months ago

Hehe, gotta be careful not to get carried away with low level stuff.

It makes more sense anyways to store entity-positions in floats rather than ints, if the rest of your game is floaty. Then doing the casting at the end when you actually need ints for drawing, alt. just flooring them, after culling and whatnot. Casting to ints seemingly randomly in the code could get messy. It's also pretty expensive to convert float to int in volumes. In one case in one of my older engines, I speeded up the physics significantly by changing tile/triangle storage from short to float, because every time needed the a triangle for collision or such, that resulted in 9 short ->float conversions, I kept it in shorts to begin with as an untested cache optimization, that proved to just slow things down and make things ugly. Shorts could probably be made to work faster, and simd and stuff, but you know, "premature optimization…" and so on.

Nice website. I look forward to the next post.

Mega 7 years, 9 months ago

Heh, the low level stuff is a bit of a problem sometimes. So fun to tinker with.

I'm staying focused though, sticking to my todo list on my Gitlab setup; I'm basically spending a couple of hours each Monday going over the list, working out what gameplay feature I need to implement next, create a milestone for that, then add 'issues' to it; if there's anything that requires a low-level feature or something, I do it as quickly and cleanly as possible… with the exception of things like the tile renderer above, but that's a case of design-phase optimization; the fewer bottlenecks the better.

Nopykon 7 years, 9 months ago

Ah, don't take my comment the wrong way. I often think people are too quick on the gun when repeating Knuth's words (POITROAE). One should have a plan for performance when designing a system, especially when making a c++ game.

Also, on stack overflow, it's fucking annoying to have to scroll trough 10 comments of "POITROAE, u newb, i'll have you know i graduated mit…," before reaching an answer to the actual question (even if the original asker was indeed POing). Nah, that doesn't happen often lately tbh. Guess they are downvoted.

Mega 7 years, 9 months ago

Heh, don't worry, I didn't take it the wrong way. I usually just follow the concept of "Optimization by Design". Generally speaking, you receive higher gains from a system that's designed to be fast over optimizing an existing system.

On the other hand, if you've made something and you can see a definitive area in your code that you know you can improve… branch, checkout, try it out, test it, and use it if you find it works better.

The advantage to doing this is that you're not only speeding up your game/app/whatever, you're streamlining your development process (Next time you encounter this type of code you'll end up jumping straight to the optimized solution without requiring the intermediate steps).

F1ak3r 7 years, 9 months ago

Nice site. Not sure how I feel about all the slide-down panels on every page load (and also your games page images are 404ing) but otherwise it looks good. Also I didn't know .space was a GTLD.

Ogmo looks pretty cool; did not know about that. Definitely something to look into.

Mega 7 years, 9 months ago

Yeah, I wasn't much liking that either. It's basically a hodgepodge of Blogger templates I pulled bits and pieces from to make a semi-coherent whole, and a lot of those templates are just messy.

Culprit for the slide was a bunch of jQuery slideUp/Down calls. Just hunted them down and removed them from the template.

I was also surprised by .space. It was going pretty cheap as well, $10 renewals (Or somewhere around there, could have been $8, had a lot of choices).

Like you and flashback mentioned in the other comment thread, a VPS seems like a good deal better than shared hosting. I'm probably going to give it a shot sometime later this month, make a mini blog/portfolio site in Node.js.

EDIT: Grabbed the $5 plan, really easy to set everything up. Set up my keys and everything. Even dug up a promo-code I'd gotten from some newsletter or other from last month, got $10 credit… so basically 2 months of free use for the single droplet I've activated.