ChronoSquad: Malden

Discover the Hidden Histories of Malden in a mobile AR adventure

Released 25. September 2021. 4 min read.
DevelopmentProject ManagementUI/UXProgramming

Text Narrative

The Game

ChronoSquad: Malden is a mobile AR pervasive game which uses the enviroment and the app technology to overlay archival images and scenes into the city of Malden. It tells the story of Malden through the "scans" that connect thhe modern day city with the past and a text narrative of the ChronoSquad themselves.

The game is being developed currently, with 5 updates planned in different neighborhoods.

Text Narrative

The Team

The team of Paidia Stuidos included a director, artist, writer/reseearcher, designer, and a producer.

My Role

I was the developer for the entire project and project manager for the production of the first episode.

As the developer I was in charge of creating all the app systems. The game is based around the AR system which uses the vuforia engine to overaly ontop of targets found in the enviroment. Scanning a target unlocks new targets found on the map, advances the story, and allows the player to view the overlay.

I collaberated directly with our artist to to create overlay scenes that use archival photos in a 3D enviroment.

Overlay

Being a mobile game, UI was a focus on the project. My role in the UI included implmentation of assets, design and iteration, animation, and of course making the UI functional for the app.

The backend is built around missions which faciliate progression and specfic rewards for completing a scan.

Code Blocks

Mission Core System

This is the core system of the game's progression. Each mission has restrictions and rewards which are used here to give content to the game. The most common restriction is the AR restriction, although a dial for number puzzles is also avaliable and has it's part in the mission system.

    //Return bool: Is the mission complete based on restrictions
    public bool IsMissionComplete() {
        if (this.isCompleted) {
            return true;
        }
        if (restrictions.Length == 0) {
            return true;
        }
        bool completed = true;
        for (int idx = 0; idx < restrictions.Length; idx++) {
            completed = completed && restrictions[idx].IsRestrictionMet();
        }
        return completed;
    }

    //Returns bool: Runs rewards based on mission 
        completeness and sends completeness to MissionController")]
    public bool CheckCompletiton()
    {

        if (this.isCompleted || this.IsMissionComplete() || Input.GetKeyDown(KeyCode.A))
        {

            this.isCompleted = true;
            foreach (var reward in rewards)
            {
                reward.GetReward();
            }

            foreach (var nextMission in nextMissions)
            {
                if (nextMission != null)
                {
                    nextMission.gameObject.SetActive(true);
                    GetComponentInParent<MissionController>().AddMission(nextMission);
                }
            }
            if (dialMisson)
            {
                dialCanvas.Exit();
            }
            GetComponentInParent<MissionController>().RemoveMission(this);
            GetComponentInParent<MissionController>().SetAsPreviousMission(this);
            useGPS = false;
            return true;
        }
        return false;
    }

Dialouge System

This dialouge system uses ink as the database and API for obtaining each line at which point it applies custom character to each line for style and keeps a stable UI through queueing each set of dialouge.

public class Dialouge : MonoBehaviour
{
    // Ink Asset
    [SerializeField]
    private TextAsset inkJson;
    // Story variable
    public Story story;
    
    [Header("Game Objects")]
    public ButtonManager buttonManager;
    [SerializeField]
    private GameObject textPrefab;
    [SerializeField]
    private GameObject contentField;
    [SerializeField]
    private GameObject dialougeBox;

    //Character struct for use in the dictonary
    [SerializeField]
    public Character[] characters;

    //Queue for stories
    private List<string> storyQueue = new List<string>();

    // Character dictonary to style each segment
    private Dictionary<string,Character> characterDict = new Dictionary<string,Character>();

    private void OnEnable()
    {
        DialougeReward.StoryOn += QueueStory;
        AllMissionsReward.StoryOnSpecial += QueueStory;
    }

    private void OnDisable()
    {
        DialougeReward.StoryOn -= QueueStory;
        AllMissionsReward.StoryOnSpecial -= QueueStory;
    }

    //Initlize character dictonary
    private void Awake()
    {
        foreach (Character ch in characters)
        {
            characterDict.Add(ch.name, ch);
        }
    }

    //Puts stories in the queue for the next time the dialouge screen is viewed
    public void QueueStory(string branch)
    {
        storyQueue.Add(branch);
    }

    //Starts a story from an ink branch
    public void StartStory(bool withInput)
    {
        story = new Story(inkJson.text);
        StartCoroutine(RunDialougeBlock(withInput));
    }

    //Runs dialouge branch creating dialouge segments at touch
    private IEnumerator RunDialougeBlock(bool withInput)
    {
        foreach (var branch in storyQueue)
        {
            story.ChoosePathString(branch);

            while (story.canContinue)
            {
                string text = story.Continue();
                text = text.Trim();
                string[] pair = text.Split(':');
                GameObject message = Instantiate(textPrefab, contentField.transform);
                message.GetComponent<DialougeSegment>()
                    .SetText(pair[0], pair[1].Trim(), characterDict[pair[0]]);

                if (withInput)
                {
                    yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
                    yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
                }

            }
            
            if (withInput)
            {
                yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
                yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
            }
        }
        storyQueue.Clear();
        yield return new WaitForSeconds(.1f);
        buttonManager.SetButtonsInteraction(true);
    }
}

// Struct to make each unique character
[Serializable]
public struct Character
{
    public Sprite icon;
    public string name;
    public Sprite colorSprite;
    public Color color;
}
#Unity#Photoshop#Inkle
© 2022 Elijah Cobb
Made with💖