Description

Refill your roguelike is a casual roguelike platformer with a focus on user generated content. Users can drop down to the underground mall to fight off the creatures created by a 3D printer gone rogue. Or support the printer by designing and submitting creatures to it’s database.

 

 

info

Platform: PC
Engine: Unity
Year: 2018
team Size: 4

RESPONSIBILTIes

- Steam Workshop integration
- Creature back end
- Custom Tools
- Technical Support

 

 

LEARNING ACHIEVEMENTS

Steam workshop integration

Since we set out on creating a game with user-generated content at its core, we quickly decided to build it on the existing infrastructure of the Steam workshop. I was tasked with researching, designing and implementing the systems we would need to let users create creatures, and download random creatures from the steam workshop. This was the first time I used the Steamworks SDK and the first time working with a service over which I’ve got very little control directly in-game.

there are now almost 800 creatures on the workshop, click here to browse them for yourself!

Small team

Going back from 34 people in my last project to 4, of which I was the only programmer, was a nice challenge again. I Needed to quickly learn to be able to clearly explain what I was doing, and also help the designers when they were having trouble with major bugs, without them losing track of what’s going on. I tried to avoid implemented a fix without them understanding how it worked, so that they would be able to fix it themselves the next time.

Custom Tools

Over the project, I also developed two Unity integrations to help improve our workflow: a simple Perforce integration into unity and a part setup tool, which would walk one of the designers through the process of adding a new Creature Part to the game. The first tool was fully working, but since it offered the same functionality as the Perforce visual client, and the integration wasn’t bug-free it was quickly dropped by the rest of the team. The Part setup on the other hand has been used to drastically turn down the amount of time it took to add a new Creature part to the game, by automatically generating and linking certain references for the developer, so that he/she only has to focus on what is different from part to part, and not about the basic data. we went from needing about 2 weeks to fully implement 10 parts to just 2 days.

 

 

Code snippet

The following code snippet shows the Saving function used when creating the save file which will be ready for uploading to the Steam Workshop.

 
public IEnumerator Save(string Name, string tag, bool upload = false, bool update = false, PublishedFileId_t id = new PublishedFileId_t())
    {
        
        GameObject[] FoundParts = GameObject.FindGameObjectsWithTag("Part");
        List<src_PartData> SavedParts = new List<src_PartData>();

        int bodyID = -1;
        for (int i = 0; i < FoundParts.Length; i++)
        {
            if (!FoundParts[i].GetComponent<src_Part>().isAttached && FoundParts[i].GetComponent<src_Part>().group != src_PartGroup.Body)
            {
                print(FoundParts[i].GetComponent<src_Part>().group + " not connected");
                print("not all parts are attached, couldn't save");
                yield break;
            }
            if (FoundParts[i].GetComponent<src_Part>().group == src_PartGroup.Body) bodyID = i;
        }

        if (bodyID == -1)
        {
            print("no body found");
            yield break;
        }
        
        // create creature Data
        src_CreatureData Data = new src_CreatureData();
        Data.name = Name;
        Data.version = "0.0.6";

        // Save Creature Color
        Material BodyMaterial = FoundParts[bodyID].GetComponent<SpriteMeshInstance>().sharedMaterial;

        Data.ColorOne = BodyMaterial.GetColor("_ColorOne");
        Data.ColorTwo = BodyMaterial.GetColor("_ColorTwo");
        Data.ColorThree = BodyMaterial.GetColor("_ColorThree");

        //when using the Rainbow color, save it as Clear. note that the Rainbow color is an Easter egg
        if (FoundParts[bodyID].GetComponent<src_Part>().Cactive)
        {
            src_Part part = FoundParts[bodyID].GetComponent<src_Part>();
            if (part.Ca) Data.ColorOne = Color.clear;
            if (part.Cb) Data.ColorTwo = Color.clear;
            if (part.Cc) Data.ColorThree = Color.clear;
        }

        // wait until the end of the frame before taking a thumbnail
        yield return new WaitForEndOfFrame();

        Bounds boundingbox = GetMaxBounds(FoundParts);
        CreateThumbnail(boundingbox, Data.name);

        Vector3 offsetFromOrigin = FoundParts[bodyID].GetComponent<src_Part>().bones[0].transform.position;

        //saving every part
        for (int i = 0; i < FoundParts.Length; i++)
        {
            src_PartData partData = new src_PartData();
            partData.IKTarget = new List<Vector2>();

            partData.partType = FoundParts[i].GetComponent<src_Part>().part;
            partData.pos = FoundParts[i].transform.position -= offsetFromOrigin;

            if (FoundParts[i].GetComponent<src_Part>().moveChildren)
            {
                partData.rot = FoundParts[i].GetComponent<src_Part>().backBone.transform.rotation;
                partData.scale = FoundParts[i].GetComponent<src_Part>().transform.localScale;
            }
            else
            {
                partData.rot = FoundParts[i].transform.rotation;
                partData.scale = FoundParts[i].GetComponent<src_Part>().transform.localScale;
            }


            // if this part has a Creature_Legs script, we also need to save the IKS
            Creature_Legs CreatureLegsWithIks = FoundParts[i].GetComponent<Creature_Legs>();
            if (CreatureLegsWithIks != null)
            {
                for (int j = 0; j < CreatureLegsWithIks.iks.Length; j++)
                {
                    partData.IKTarget.Add(CreatureLegsWithIks.iks[j].transform.position);
                }
            }
            SavedParts.Add(partData);
        }


        Data.parts = SavedParts;

        // Create the Json File
        string temp = JsonUtility.ToJson(Data, true);
        System.IO.File.WriteAllText(getFilePath(Data.name) + "/" + Data.name + ".json", temp);

        // find the Steam Workshop Tag if the user specified one
        if (tag != "") tag = "[TAG]" + tag;

        // if the user wanted to upload the creature, start uploading
        if (upload) GameObject.FindGameObjectWithTag("uploader").GetComponent<creatureSubmitter>().startUploading(Data.name, update, id, tag);

        yield return null;
    }