Jump to content
IGNORED

New game: Stealth Runner


Recommended Posts

While playing Farcry and Watchdogs on my Linux system, it occurred to me that we don't have any open-world, motion-captured, 3D rendered games for our beloved TI. Like back in the days, that meant I had to write one myself. So I started work on a new game, Stealth Runner. Given the constrained computational resources, and lacking a development team of a few hundred people, I did need to check my goals...

player.png.4706e5079363d6f86a5254baf2077d97.png

Stealth Runner is a third-person open-world action game with the following features:

  • Pre-rendered graphics.
  • Smooth motion-captured animation (30 fps).
  • Pixel-precise omni-directional scrolling.
  • Sound and speech.
  • Entirely written in TMS-9900 assembly code.

 

 

The game tries to push the envelope of what is achievable on the TI, with the help of freely available modern-day resources. The motion-captured models come from Mixamo. The rendering is done with Blender. The graphics, sound, and speech are processed with ImageMagick, ffmpeg, and my own Video Tools . The build process is tied together with python/bash/java scripts and programs . The game engine builds on my experiences with streaming graphics, sound, and speech in my Bad Apple demo, only this time interactively. The excellent xas99 assembler helped keeping the code readable with nifty features like macros, and extensive comments.

 

This is version 0.1. It mainly shows the game engine in a single, simple level. It has a few rough edges, e.g. the speech is occasionally garbled, for some unknown reason. The speech snippets are placeholders; I'm hoping to add epic humming choir vocals (Mozart's Lacrimosa?) and better grunts. The level is not very exciting yet.

 

You can find the source code and technical details and the compiled binaries on Github. The cartridge ROM is 1.5 MB -- I've only tested it in Mame. The source code notably contains include files that can be generally useful for writing assembly code for games.

 

The game engine is mainly driven by the internals of the CPU/VDP/sound/speech, but comments and ideas are welcome. Enjoy!

  • Like 36
  • Thanks 2
Link to comment
Share on other sites

Thanks everyone! 😃

 

3 hours ago, Asmusr said:

Very cool. The speech is garbled in my emulator (JS99er), so now I have something to fix. 🙂 

The speech is occasionally garbled in Mame too. I haven't figured it out yet -- let me know if you find something. The speech code checks that the synthesizer is not talking, then sends the SPEAK_EXTERNAL command and 16 bytes of speech. Then, theoretically 60 times per second, it checks if the speech buffer is low and sends 9 additional bytes of speech. This should even be sufficient in 50Hz PAL (unless the drawing code can't keep up with the vsync rate), so I should delve into it again.

  • Like 4
Link to comment
Share on other sites

3 hours ago, Eric Lafortune said:

The speech is occasionally garbled in Mame too. I haven't figured it out yet -- let me know if you find something. The speech code checks that the synthesizer is not talking, then sends the SPEAK_EXTERNAL command and 16 bytes of speech. Then, theoretically 60 times per second, it checks if the speech buffer is low and sends 9 additional bytes of speech. This should even be sufficient in 50Hz PAL (unless the drawing code can't keep up with the vsync rate), so I should delve into it again.

If you want to debug for yourself, patch src/devices/sound/tms5220.cpp this way. You will see that with the 50Hz consoles you actually run into buffer empty states.

 

--- a/src/devices/sound/tms5220.cpp
+++ b/src/devices/sound/tms5220.cpp
@@ -399,6 +399,7 @@ emulating the tms5220 in MCU code). Look for a 16-pin chip at U6 labeled
 #define LOG_RS_WS (1U << 15)
 
 //#define VERBOSE (LOG_GENERAL | LOG_DUMP_INPUT_DATA | LOG_FIFO | LOG_PARSE_FRAME_DUMP_HEX | LOG_FRAME_ERRORS | LOG_COMMAND_DUMP | LOG_COMMAND_VERBOSE | LOG_PIN_READS | LOG_GENERATION | LOG_GENERATION_VERBOSE | LOG_LATTICE | LOG_CLIP | LOG_IO_READY | LOG_RS_WS)
+#define VERBOSE (LOG_GENERAL)
 #include "logmacro.h"
 
 #define MAX_SAMPLE_CHUNK    512
@@ -708,6 +709,7 @@ void tms5220_device::update_fifo_status_and_ints()
                // generate an interrupt if necessary; if /BL was inactive and is now active, set int.
                if (!m_buffer_low)
                {
+                       LOG("Buffer low\n");
                        m_buffer_low = true;
                        set_interrupt_state(1);
                }
@@ -723,6 +725,7 @@ void tms5220_device::update_fifo_status_and_ints()
                // generate an interrupt if necessary; if /BE was inactive and is now active, set int.
                if (!m_buffer_empty)
                {
+                       LOG("Buffer empty\n");
                        m_buffer_empty = true;
                        set_interrupt_state(1);
                }
@@ -881,7 +884,7 @@ void tms5220_device::process(int16_t *buffer, unsigned int size)
        int i, bitout;
        int32_t this_sample;
 
-       LOG("process called with size of %d; IP=%d, PC=%d, subcycle=%d, m_SPEN=%d, m_TALK=%d, m_TALKD=%d\n", size, m_IP, m_PC, m_subcycle, m_SPEN, m_TALK, m_TALKD);
+       // LOG("process called with size of %d; IP=%d, PC=%d, subcycle=%d, m_SPEN=%d, m_TALK=%d, m_TALKD=%d\n", size, m_IP, m_PC, m_subcycle, m_SPEN, m_TALK, m_TALKD);
 
        /* loop until the buffer is full or we've stopped speaking */
        while (size > 0)

 

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

I want to point out what I think may be an underappreciated aspect of this game: the humming.  A while back, @retroclouds experimented with using the Speech Synthesizer for non-speech audio.  The fact that Eric uses it for humming is another extraordinary departure from how we traditionally use the SS.

  • Like 4
Link to comment
Share on other sites

7 hours ago, Artoj said:

Brilliant!!!!, I am highly impressed. (thinking.... i would love to pixel paint the scenery...mm.. would need more colours...maybe just a careful B/W render.....)  

Thanks! Better scenery would be nice! Adding some trees and shrubs as sprites might help.

 

The landscape of dots seems simple, but it was a big challenge.

  • The simplest approach would have been a rectangular grid of dots, with 1 dot per character. You can then render a screen with two characters: one with a dot and a blank one  Every frame, you can smoothly scroll the screen by updating the pattern of the character, and updating the necessary characters on the screen (along the edges of the landscape, depending on the scrolling direction).
  • I've opted for a diagonal grid of dots, with 2 dots per character. You can then render a screen with four characters: one with two dots, two with one dot, and a blank one. Every frame, you then need to update the patterns of three characters, and update a larger number of characters on the screen.

The amount of data to transfer increases as the patterns get more complex, and there's already a lot going on with the player avatar and the supersprites. It's one example where the game engine is driven by the internals of the VDP.

  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...