Populating the ship

In the very first drafts of the game story, back before any work had begun on the actual game itself, the protagonist accidentally kills all of his friends in a depressurization event at the beginning of the game. I now think this was a terrible way to get a cheap laugh. I may change my mind on this once more, but I think having living relationships with friends on the ship is important in developing an emotionally affecting story. They can also reflect upon the protagonist through their dialogue, filling in his backstory and shedding light on his character.

The protagonist has a name now, by the way: Lieutenant Roark. Roark is also the name of the motion capture actor and the creative consultant for this game.

Today I set to work creating some secondary characters for the ship, including the main villain for the story. I decided to name them all after obscure nautical terminology: Captain Purchase, Chef Hawser, Ensigns Forecastle and Taffrail, etc. The exception is the villain, Professor Iapetus (another moon of Saturn).

I realized that the NPC system I had hacked together earlier was pretty functionally minimal, so I had to expand it quite a bit to get something worth looking at. First was the problem of formatting text to the screen, since I didn’t want to have to insert \n characters all throughout my dialogue by trial and error. Feel free to use the following algorithm I whipped up, which automatically wraps the text to however wide you want and makes sure it’s all on the screen.

Vector2 displayPosition = GetBaseDisplayPosition();

// split the string with newlines to get the width right
StringBuilder sb = new StringBuilder();
string text = GetConversation()[_conversationLineNum];
int left = 0, right = text.Length - 1;
while ( left < right ) {
    string substring = text.Substring(left, right - left + 1);
    float width = Font.MeasureString(substring).X;
    while ( width > MaxDialogWidth ) {
        // look for a space to break the text up
        for ( int i = right; i > left; i-- ) {
            if ( text[i] == ' ' ) {
                right = i - 1;
        substring = text.Substring(left, right - left + 1);
        width = Font.MeasureString(substring).X;
    left = right + 2;
    right = text.Length - 1;

Vector2 stringSize = Font.MeasureString(sb);
displayPosition -= stringSize / 2;

// If we're still drawing off of the screen, nudge the draw boundary
int screenWidth = spriteBatch.GraphicsDevice.Viewport.Bounds.Width;
float cameraScreenCenter = GetCameraCenter();
float leftMargin = cameraScreenCenter - screenWidth / 2 + HorizontalMargin;
float rightMargin = cameraScreenCenter + screenWidth / 2 - HorizontalMargin;
if ( displayPosition.X < leftMargin ) {
    displayPosition.X = leftMargin;
} else if ( displayPosition.X + stringSize.X > rightMargin ) {
    displayPosition.X = rightMargin - stringSize.X;

// Finally, draw the text shadowed. This involves drawing the text twice, darker then lighter.
Color shadow = Color.Lerp(_color, Color.Black, .5f);
spriteBatch.DrawString(Font, sb, displayPosition + new Vector2(3), shadow);
spriteBatch.DrawString(Font, sb, displayPosition, _color);

I also noticed that the text was a bit hard to read against some of the background, so I added a drop-shadow effect to it (the last lines of code above). It definitely looks a lot nicer, although I might eventually place the text on a dark backdrop during the conversation to give it even more contrast.

Just noticed I made a typo in this dialogue.

Finally, I needed to animate the characters to bring them to life. They all walk back and forth in a predefined box, randomly stopping, starting, and changing direction every now and then. It looks reasonably convincing, if I do say so myself!

Tomorrow I need to draw the scenery for the engine room and correct the lingering camera issues, then I can move on to scripting the climactic shock of the ship’s crash to the surface of Enceladus and the beginning of the game proper.

Leave a Reply

Your email address will not be published. Required fields are marked *