Alexander Lövberg

Game Programmer

Rest A-shored


GitHub
  • Roles: Programmer
  • Time: 4 weeks
  • Engine: Unreal Engine 4
  • Language: C++
  • Genre: Management Survival
  • Team Size: 12 (3 programmers)
  • Data: 2018


A group of teenagers are stranded on an island and have no idea how to survive. Its your job to try and guide these defiant people and help them survive until they find a way to get home again.
Astrid, go to tent.
Name:
Astrid
Action:
Go To
Object:
Tent

Beatrice, pick up fishing rod.
Name:
Beatrice
Action:
Pick Up
Object:
Fishing Rod

Beatrice, give fishing rod to Astrid.
Name:
Beatrice
Action:
Give
Object:
Fishing Rod
2nd Object:
Astrid
Units are registered as objects as well to let other units interact with them.

Parsing Commands

  • Speech to Text
  • Splits the commnd into smaller parts
  • Tries to find a Name, the interactable Object and what to use the Object on.

We used an external "speech to text"-plugin called Sphinx. The output from Sphinx were the input used in the parsing system. The system tries to find out if the words are a "Name", "Action" or "Object".

  • If the name is in the beginning of the sentence the unit corresponding to that name is selected (registered as a listener to the command system).
  • The next thing it looks for is an action like "pick up", "eat" or "hug".
  • The last part of the command is what to do the Action to so it tries to identify an Object.


    void ATTCommandSystem::StringToCommand(FString text)
    {
        // Exit function since there's no command listeners
        if (listeners.Num() < 1 || !listeners[0])
            return;

        // Remove the spaces in the beginning of the string
        text.TrimStartInline();

        // Check if the first word is a name (Special check on the first word)
        TArray<UTTCommandListenerComponent*> commandListeners;

        // Check if the word is everyone
        FString listenerName = "everyone";
        if (text.StartsWith(listenerName))
        {
            // Add all the listeners
            for (UTTCommandListenerComponent* listener : listeners)
            {
                commandListeners.Add(listener);
            }
        }
        // Or check which listeners the player tried to select
        else
        {
            int listenersNum = listeners.Num();
            for (int i = 0; i < listenersNum; i++)
            {
                text.TrimStartInline();

                FString listenerName = listeners[i]->ListenerTag.ToString();
                bool wordsMatched = text.RemoveFromStart(listenerName);
                if (wordsMatched)
                {
                    if(!commandListeners.Contains(listeners[i]))
                        commandListeners.Add(listeners[i]);
        
                    i = -1;
                }
            }
        }

        // Select the listeners if any has been found
        if(commandListeners.Num() > 0)
            SelectListeners(commandListeners);

        int verbsNum = verbs.Num();
        int interactablesNum = interactableNames.Num();

        while(text.Len() > 2)
        {
            bool wordsMatched = false;

            // loop through all the words
            for (int i = 0; i < verbsNum + interactablesNum; i++)
            {
                // Clean up the text
                text.TrimStartInline();

                // Flag to check if within verbs arrays range
                bool withinVerbsRange = i < verbsNum;
                // Calculate the correct index to use depending on array
                int correctIndex = withinVerbsRange ? i : i - verbsNum;
                // Get the word we want to compare
                FString wordToCompare = withinVerbsRange ? verbs[correctIndex] : interactableNames[correctIndex];

                wordsMatched = text.RemoveFromStart(wordToCompare) ;
                if (wordsMatched)
                {
                    // add the command as well
                    FCommandQueueStruct newCommand;
                    newCommand.type = withinVerbsRange ? CT_Verb : CT_Object;
                    newCommand.text = wordToCompare;

                    commandQueue.Add(newCommand);

                    break;
                }
            }

            // Remove the first word since it's not recognized
            if (!wordsMatched)
            {
                // Remove first word
                int spaceIndex;
                text.FindChar(' ', spaceIndex);

                if (spaceIndex < 0)
                    text.Empty();
                else
                    text.RemoveAt(0, spaceIndex + 1);
            }
        }

        PrintQueue(&commandQueue);
        SendCommandToUnit();
    }
        

Command System

  • Component base

When the system have recognized the whole command it is sent out to the listener. The listener have different events that the desginers of the game could use to create the desired action in game.


    void ATTCommandSystem::StringToCommand(FString text)
    {
        // Exit function since there's no command listeners
        if (listeners.Num() < 1 || !listeners[0])
            return;

        // Remove the spaces in the beginning of the string
        text.TrimStartInline();

        // Check if the first word is a name (Special check on the first word)
        TArray<UTTCommandListenerComponent*> commandListeners;

        // Check if the word is everyone
        FString listenerName = "everyone";
        if (text.StartsWith(listenerName))
        {
            // Add all the listeners
            for (UTTCommandListenerComponent* listener : listeners)
            {
                commandListeners.Add(listener);
            }
        }
        // Or check which listeners the player tried to select
        else
        {
            int listenersNum = listeners.Num();
            for (int i = 0; i < listenersNum; i++)
            {
                text.TrimStartInline();

                FString listenerName = listeners[i]->ListenerTag.ToString();
                bool wordsMatched = text.RemoveFromStart(listenerName);
                if (wordsMatched)
                {
                    if(!commandListeners.Contains(listeners[i]))
                        commandListeners.Add(listeners[i]);
        
                    i = -1;
                }
            }
        }

        // Select the listeners if any has been found
        if(commandListeners.Num() > 0)
            SelectListeners(commandListeners);

        int verbsNum = verbs.Num();
        int interactablesNum = interactableNames.Num();

        while(text.Len() > 2)
        {
            bool wordsMatched = false;

            // loop through all the words
            for (int i = 0; i < verbsNum + interactablesNum; i++)
            {
                // Clean up the text
                text.TrimStartInline();

                // Flag to check if within verbs arrays range
                bool withinVerbsRange = i < verbsNum;
                // Calculate the correct index to use depending on array
                int correctIndex = withinVerbsRange ? i : i - verbsNum;
                // Get the word we want to compare
                FString wordToCompare = withinVerbsRange ? verbs[correctIndex] : interactableNames[correctIndex];

                wordsMatched = text.RemoveFromStart(wordToCompare) ;
                if (wordsMatched)
                {
                    // add the command as well
                    FCommandQueueStruct newCommand;
                    newCommand.type = withinVerbsRange ? CT_Verb : CT_Object;
                    newCommand.text = wordToCompare;

                    commandQueue.Add(newCommand);

                    break;
                }
            }

            // Remove the first word since it's not recognized
            if (!wordsMatched)
            {
                // Remove first word
                int spaceIndex;
                text.FindChar(' ', spaceIndex);

                if (spaceIndex < 0)
                    text.Empty();
                else
                    text.RemoveAt(0, spaceIndex + 1);
            }
        }

        PrintQueue(&commandQueue);
        SendCommandToUnit();
    }
        



Final Thoughts

The greatest hurdle during this project was to find the fun. The lesser used input method did in the end damage the game play by making it harder to control and less responsive. In the end we accepted this as a failed game, but we also recognized that this was still a fun experiment. Even though the game weren’t as fun as we hoped, some players found the game enjoyable in other ways. By just trying commands and see what happens did sometimes have a comical effect.

If I was to create another similar game I would focus more on developing the idea and figuring out beforehand how the input enhances the game play, and not just include it and see it become a gimmick.

Intro Cinematic