Always good to see clever optimisations and that profiler is a great idea!
Good Job Smile . Will the Enemy Sprites stay in one position or eventually move?
The enemy tanks will eventually move around. I'll probably implement the early level tanks that just wander around randomly first, but eventually, I hope to also add the tanks from the later levels that are actually somewhat intelligent.

Right now, I'm trying to give them some actual graphics, rather than just numbered squares, which will make it more obvious which direction they are facing when moving. However, convimg isn't cooperating - I tried updating it so that I won't have to rewrite my config file again in the future, and now my tilesets don't work.
I've gotten enemy tank rendering working:

I'm using a dynamic palette, where each tank color gets 5 unique colors, plus 2 shared colors for the wood tread. This means that I only have to store one copy of the enemy tank sprite (per rotation) in the program, and I can generate the rest later. I also set up a system to automatically trim the sprites and store the amount trimmed by to use as an offset. Combined with sprite mirroring and compression, this means that not much of the program actually stores sprite data.

The tanks look a bit off to me - I think that that's because the color that I'm replacing for the dynamic palette is really saturated, which means that there's not much contrast between the different sides of the tank. I don't really feel like fixing that right now, as I don't have the palette generation automated for the other tank colors, and I don't want to re-enter 50 different RGB colors.

Hopefully, the tanks will look a bit better when they're moving around and rotating, as our brains are generally pretty good at working out motion from even blurry videos.

The game is still running at 31 FPS in the screenshot, even with 7 tanks. Of that, 14.8 ms is spent on rendering tanks, 10.2 ms is spent on AI, and 3.8 ms is spent on processing collisions. This is pretty good, as my target is 30 FPS and I still have plenty of things to optimize to give time for movement AI.

Next, I think I'll work on enemy motion, so that the game is actually playable, then finish up the graphics.
After that, I've been thinking about how to implement multiplayer, both in a 2-player co-op mode like in the original game, and an online multiplayer PvP mode using a similar architecture to TI-Trek.

Also, do you guys think that a series of short videos explaining how the game is implemented would be interesting?
Looks great man! I particularly like the palette strategy and storage tricks you're using to keep the size down.

I'd be interested in dev videos for sure.
My last GitHub release is really old, and someone messaged me on Discord about it, so I've decided to make a new release so that you can try it out without having to install the LLVM toolchain, which takes ages. It includes the rather buggy AI.

Here's the download:

Minor progress update: I added an aim indicator, then realized that the precision on physics objects is so bad that the shell's trajectory is several degrees off from the aim indicator.
I've made a few graphical improvements - namely, the border around the map, and the kill and life counters.

I may add some more variation to the border, as is present in the original game, but that's a low priority for now.
And it turns out the issue with the aim indicator wasn't with precision; I just forgot to skew the angle to match the perspective. That's been corrected as well

Here's the download, if you want to give it a try yourself:

So apparently, the original game uses a different set of levels depending on the aspect ratio. My reference images and video are all from the 16:9 version, but the CE's screen is 4:3. That explains why I was having so much trouble getting everything to fit on-screen.

So, I guess I can either leave everything as-is or re-enter all of the levels and re-scale all of my graphics to match the larger block size. That's going to take a while, but hopefully, it will mean that more of the screen will be used by stuff actually relevant to gameplay.
I've switched everything over to the correct 4:3 levels and sprites.
I've also added rendering for shells:

I plan to enable clipping to stop the banner at the bottom from bugging out. The other visual bugs are also on my to-do list and will be fixed prior to release.

Here's the download, yada yada.
Wow it's coming along so fast now, it's playable!!!
I fixed the super broken enemy tank rendering:

I added more different colors, fixed the bug where some colors were transparent, and the bug where the rest were all actually the same color. I'm unsure how I previously thought that the code was working.
I also set up a Python script to automatically generate the dynamic palette, which took me forever to do by hand last time.

I've also fixed the UI, though I forgot to show it in the screenshot.
Another progress update:
I've switched everything to C++ using the LLVM branch of the toolchain - there isn't a significant reason for this. I thought there might be a slight readability benefit, but mostly I wanted to mess around with C++ since I haven't used it much before.
C++ is definitely a bit wonky on the CE - I had to modify the fileioc header slightly to get it to work, and the standard library is basically nonexistent, so I used tinystl at Adriweb's suggestion. Jacobly also added definitions for the new and delete operators to the toolchain, since they wouldn't work otherwise. I've also run into a few bizarre issues, such as the _vtable pointer being off by one only when I don't have a debug print in the relevant constructor.

Rather than just changing the file extension, I also changed most of the objects to be classes. Previously, I had tank_t, shell_t, and mine_t as structs containing a phys_body_t struct, and then had several functions that acted on pointers to phys_body_t. I've replaced this with Tank, Shell, and Mine classes, that inherit from a PhysicsBody class and implement functions like process and render.

I also changed how objects are stored in memory. Previously, I malloc'd an array of tank_t structs, and had each tank struct contain an array of the maximum number of shells that any tank type could fire. I now dynamically allocate each object when it's created, and keep a vector of pointers to PhysicsBody instances. Rather than having the tank keep track of which shells belong to it, I have each shell keep track of which tank it belongs to. This likely slows down object creation, but it makes it simpler to iterate through every active physics body. It also means that a custom level could theoretically allow you to shoot hundreds of shells, rather being limited to a particular maximum number of shells at compile time.

I also keep the vector of active objects sorted by y-depth. This has a few benefits - firstly, it allows me to render the graphics from back to front, rather than in an arbitrary order, which makes sure that you can't see something through an object that's supposed to be in front of it. Secondly, it also makes collision detection more efficient when there are a large number of physics bodies at once. Previously, I had to check each alive object against every other object, so doubling the number of objects would always quadruple the amount of time it takes to process collisions. With y-sorting, I can now stop checking for collisions with a particular object as soon as I encounter an object that's too far away to collide on the y-axis, and know that all objects after that will also not collide. This means I can get a best-case time complexity of O(n).

I made a few minor changes to the program before the switch to C++, which I did not think were significant enough to warrant an update in the thread.

Firstly, I replaced the depthmap-based obscuration system I was using (which didn't even work and just had a hackfix preventing the ground from obscuring objects) with a heightmap-based system that's a bit cleaner. I'm still not satisfied with it, as currently, it redraws tiles rather than clipping the sprite, and also runs twice, once for the base of the tank and once for the turret. As a result, drawing a single tank sprite can result in the same screen location being blitted up to 8 times per frame. Ideally, this would be cut down to 4 - two for partial redraw, one for the base, and one for the turret.

I also added a noclip hack. If you can find it, use at your own risk, as if you go out of bounds stuff is likely to crash.

Finally, I updated the placeholder explosion. The new one might be in the game for a while, as explosion animations take up a lot of space.

I'm not sure what I'm going to work on next - mine graphics are probably the highest priority, but the program I use to get 3D models is refusing to run after I accidentally reset my Wine install somehow.
The aspect of the game most interesting to me right now is probably a particle system - I have an idea as to how I would implement a "transparent" smoke trail behind shells, as well as the tank tracks and X's that get left behind.
I also need to work on the menus some, but I find measuring and laying out buttons somewhat boring, so that's probably going to be left until the end.

Here's a screenshot I took a few commits ago - I don't think anything has visibly changed since then, aside from the heightmap system.

Finally, as usual, if you want to check it out for yourself, here's the 8xp download and the GitHub repository for this version (v0.7-alpha).
I welcome any feedback - I have very little C++ experience, so I'd appreciate any complaints about how to improve code quality.
WHOA! This game is looking awesome! It's so close to the real deal and looks really fun to play.

I can appreciate how much has gone into the switch to C++, I've seen you on IRC working on getting everything working, great job! Smile.
This is absolutely fantastic! I will definitely be downloading this!
I ported Tanks (and the sizeable portion of the toolchain libraries it depends on) to CEmscripten.
You can give the live demo a try from your web browser, no ROM required.

Currently, several things are broken with the web port but not in the real program - I haven't implemented triangle or circle drawing functions, so circles render as squares and triangles don't render at all. The mouse support is also a bit wonky, which I think is related to screenspace conversion. Other than that, it's mostly working fine.
Spent another weekend working on this, here's the result:

What's new:
    Mines now have actual graphics instead of placeholders.
    The mission start screen now has tanks in the background, like the original game.
    The mission start screen shows a tank icon for the number of lives.
    The results screen shows tank icons with the correct colors for each tank type.

The tank icons all have shadows, and I'm generating those at runtime so that I don't have to store a separate shadow sprite.

I'm also reusing the enemy repalettization code for the results screen, so there's only a single tank sprite stored for that.

I also did some bugfixing, including a few crashes, as well as refactoring stuff and updating it to the latest toolchain version.

As I was trying to implement mines' graphics, I accidentally set the wrong model, which was too funny not to share:
I've spent the past few days making some optimizations. The game was running too slow in some later levels with more tanks (below 30 FPS, literally unplayable).

I initially tried optimizing the parts of the game called most often - the inner loops for raycasting and drawing tiles over tanks. However, this barely had any impact on speed, which initially puzzled me. Eventually, I was figured out that the O(1) portions of these functions were taking far more time than the parts that are called in the loop, as they contained a division.

I did some testing and found that the compiler's division routine takes about 4000 clock cycles regardless of the size of the inputs. Most of the divisions that I do (aside from divisions by a power of two) will always return a value that's less than 20. So, I tried using a repeated-subtraction division routine instead. I found that when the result is 20, it's around five times more efficient and that repeated-subtraction is always more efficient if the result is less than about 150.

This change increased the first level's speed from a maximum FPS of 46 to a maximum FPS of 75 (which is higher than the CE can even display in this mode). I still cap the FPS at 30 to make the physics run at a constant speed, however.

I also fixed a bug that let tanks clip through walls when pushed by other tanks.
In the process, I forgot to reset the velocity of immobile tanks to 0 and accidentally created one of those sliding block puzzles:

I'd include a longer screenshot, but I ended up pushing a tank through a block so hard that it broke my CEmu install

I'm down to three blocking issues that absolutely need to be fixed before launch, plus seven things that I could technically launch without but really should do first. Here's my current to-do list:

  Stationary missile tank AI
  Mine explosions
  Fix mine range

  Fix AI movement
  Pause menu
  Optimize tank aiming AI
  Get numerical values for speeds
  Aim indicator dot sprite
  Extra life screen
  Partial redraw overflow protection

  Refactor AI stuff
  Win screen / scores
  Mission Failed screen
  Help banner
  Mission Cleared / Destroyed text
  In-game banner
   - Counter displays the wrong thing
   - The text is nowhere near where it actually is
   - Tank icon is missing
  Continuous keypad scanning
  Move aim indicator to correct position
  Fix graphical clipping on shells
  Tread marks

  Consider player's future position when aiming
  Start text
  Level editor
  Tank death animations
  Reduce the startup loading time

  Floor texture
  Xes for dead tanks
   - Smoke when shells are fired
   - Smoke trails behind shells
   - Flames behind high-speed shells
   - Circle things when shells ricochet
   - Explosions for mines and tanks
  Fade effect on mission start
  In-game banner floats out
  Pause button indicator on first level

EDIT: I uploaded a prerelease that includes these plus a few more changes.
Since the last progress update, I added tread marks and graphics for mine explosions:

I also added a screen that's displayed when you get an extra life every five levels, changed the color of the aim indicator, and fixed a few bugs with mines.

Though not shown in this screenshot, I also added a pause menu and an error screen, removed level information from the main game (in favor of keeping it all in a level pack appvar, though I might revert that later), fixed a few freezes and crashes, and slightly tweaked how the AI behaves (though I still plan on rewriting it soon).

I also redid how sprites for enemy tanks are stored - previously, I kept a single copy of each enemy tank sprite in the program data, and then created a repalettized copy of each rotation for each enemy type at the start of each level. This worked fine until you got into some of the later levels with many tank types, at which point the game ran out of memory. Now, I use a cache system, that keeps 50 total sprites. If the game tries to use a sprite that's not in the cache, it removes whichever sprite was used the longest time ago, and generates the requested sprite at runtime by repalettizing and/or mirroring an existing sprite.

I also made changes to how killing objects works. Previously, every object had a kill() method that would immediately remove it from the objects list. This was a bit of a pain when iterating over the object vector, as the indexes of things would shift around while you're iterating over them. Now, all objects start out as active, and objects that are marked as inactive get removed at the end of each frame. This also makes more sense on a conceptual level, as objects usually stick around for a while after they're killed to display a death animation.

As you can see from the screenshot, I still have a ton of visual (and non-visual) issues to deal with, some of which will require changes to the graphics system to fix.

There's also a rather annoying crash that I've been trying to fix for the past few days to no avail - it happens while free() is called while removing sprites from the screen after a particular tank on level 19 fires three shells at the same time. It seems like it's a heap corruption bug, but I have absolutely no idea what's causing it. I'm considering just patching CEmu to automatically set read watchpoints on all of the heap's internal state whenever malloc is called.

No outtakes this time, most of the recent issues have just been boring NMIs.
The tracks looks awesome! How are you handling them?
The entire system is admittedly a bit of a hack on several levels.

I created a tread mark model in Blender using the same camera perspective as the tank, consisting of just two marks, one on each side of the tank. I then rendered it into one sprite per rotation of the tank base. I can then periodically display this directly under the tank to create a continuous path of tread marks. This is a bit of a hack, as logically, it would make sense if three different rows of marks were being placed, but this is how the original game did it, too, so I don't consider it that big of a deal.

I use the tank's velocity to determine how often to draw the tread mark - each tank has a distance counter, and in each frame, the absolute values of the X and Y velocity get added to it. Whenever the distance counter gets over a certain amount, I subtract that amount from it and then display the tread marks. This is kinda a hack in two ways - firstly, the velocity check takes place during the rendering phase, not the physics phase, so if a tank were to be rendered twice for some reason, it would display tread marks twice as often. I can't just add the velocity check to the physics phase because the tank's final velocity isn't known at that point.

Secondly, I'm adding the X and Y velocities instead of taking the vector magnitude, as that's relatively slow. This results in the tread marks being closer together the closer a tank is traveling to 45 degrees. I could try adding an approximate correction based on the direction the base is facing, but I don't think anyone is really going to notice.

Because I'm using partial redraw and gfx_SwapDraw, I also need to make sure that the tread marks get applied to both buffers, and I'm doing this in about the laziest way that actually works. Each time a tank displays tread marks on the buffer, I store the position and sprite it uses in the tank instance. The next time it gets rendered, I just redisplay the sprite at that position.
commandblockguy wrote:
I use the tank's velocity to determine how often to draw the tread mark - each tank has a distance counter, and in each frame, the absolute values of the X and Y velocity get added to it. Whenever the distance counter gets over a certain amount, I subtract that amount from it and then display the tread marks. This is kinda a hack in two ways - firstly, the velocity check takes place during the rendering phase, not the physics phase, so if a tank were to be rendered twice for some reason, it would display tread marks twice as often. I can't just add the velocity check to the physics phase because the tank's final velocity isn't known at that point.

Secondly, I'm adding the X and Y velocities instead of taking the vector magnitude, as that's relatively slow.

If distance moved per frame is relatively constant, you can probably use a system something like the following (definitely slower, but avoids doing a square root for distance):

  • Per tank, keep track of x and y (separately) moved since the last tread mark was placed
  • Each frame, check if the sum of the squares of the x and y counters is over some value. If so, place a tread mark
  • Reset x and y movement counters every time a tread mark is placed
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 3 of 4
» All times are UTC - 5 Hours
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum