3D Printing & Software Development Portfolio

I’m a maker and developer specializing in high-quality 3D printed parts and practical software solutions.
I combine technical precision with creative problem-solving to bring ideas to life both digitally and physically.

Hero Illustration

As a side project I have been working on a personal game for a while.
And someone told me it would be cool if I make a developer blog about it.
But the thing with a personal project like this is that it’s difficult to show it to others.
My way of programming in a project where im it’s only developer and no one will see the code,
well its not nice code to look at, missing comments, short or ugly method names and so on.
All the things to add to make it readable and nice to look at are gone.
Making it a really bad representation of how I would work in a team.


Each time I make a version of the game I make it as fast as possible to try out the concept, playability and most importantly is it fun? And with each version I make I always think that it’s a lot of fun to play, then I threw it all away because the code I made to make the demo of a game in such a fast way, is useless in production.

So this year I did it again, threw it all away and started again. This time I made the scope really small, a 1 single room level with 4 in-game days to play, as a sort of tutorial room.
But this time i would not make the code so that its not useable after,
why not try and make it so in that 1 little room is everything the player needs to know about how to play the game,
it will work for the player but also me as the developer, as i then made all the mechanics i would need and don’t have to add any new behaviors for the rest of the game, right?

Well… I got a working demo for the game that you can download HERE
But It really needs a lot more things before I can continue with the rest of it.

I have gotten feedback from a few play testers, a bug or 2, a idea here and there, but all of them asked me how to save the game so they can play it later without losing progress, and well the design of it was 1 life only, it still had to have to option to save the progress you made right?

Unity3D the engine I use, doesn’t have a good save system built in, and while there are assets to buy and use, I didn’t want to spend money on a project that’s not even a product anytime soon, so I’ll have to make something myself.


Save system

First some base classes that will contain the data.

public class WorldData
    {
        public string worldName = "";

        public Dictionary<string, DayData> days = 
                 new Dictionary<string, DayData>();

        public Dictionary<string, string> stats = 
                 new Dictionary<string, string>();

        public DateTime LastPlayed;
    }
    public class DayData
    {
        public Dictionary<string, EntryData> objects = 
                new Dictionary<string, EntryData>();

        public List<string> inventory = new List<string>();

        public Dictionary<string, string> stats = 
                new Dictionary<string, string>();
    }
    public class EntryData
    {
        public Dictionary<string, string> entries = 
                 new Dictionary<string,string>();
    }

A Dictionary works great for this as I don’t have to think about duplicates.

Ok lets add a method to save the data:
Here we also do the null checks as its the last layer of the save system.

public static TValue GetSet<TKey, TValue>(
   this IDictionary<TKey, TValue> dictionary, TKey key, TValue value, 
    GetSetType type = GetSetType.Auto)
    {
        TValue returnValue;
        if (dictionary == null)
            throw new ArgumentNullException(nameof(dictionary));
        if (key == null)
            throw new ArgumentNullException(nameof(key));

        if (type == GetSetType.Get)
        {
            if (dictionary.TryGetValue(key, out returnValue))
                return returnValue;
            else
                Debug.LogFormat("TryGetValue failed!", key, value);
            return value;
        }

        if (type == GetSetType.Set)
        {
            dictionary.Add(key, value);
            return value;
        }

        if (type == GetSetType.Overwrite)
        {
            dictionary[key] = value;
            return value;
        }

        if (type == GetSetType.Auto)
        {
            if (dictionary.TryGetValue(key, out returnValue))
                return returnValue;
            else
            {
                dictionary.Add(key, value);
                Debug.LogFormat("[MISSING] {0} SET TO {1}", key, value);
                return value;
            }
        }

        return value;
    }
        public static void SaveEntry(this WorldData world, string dayNumber,
              string entryId, string key, object value)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            var entry = day.objects.GetSet(entryId, new EntryData());
            var obj = entry.entries.GetSet(key, value.ToString(),
                          GetSetType.Overwrite);
        }

The GetSet sounds a bit odd but how it works is that if i save an entry i first have to find the day but it’s possible that the day i’m trying to save to doesn’t exist yet, in that case it will create a new day and returns that after adding it.
And saving the entry it uses GetSetType.Overwrite, meaning it will overwrite any existing data already found.

Now i just have to make it a bit easier to use

    public void Save(string id, string key, string value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, value);
    }

Ill be able to use it like this and I like how simple it is.

Save(uniqueID, "PlayerName", "Bob");
string playerName = LoadString(uniqueID, "PlayerName");

Using it with a Interface

Lets add a interface so I don’t forget that I have to implement the save/load methods for some classes.

    public interface ISaveObject
    {
        public void OnLoad();
        public void OnSave();
    }

And on my PlayerController lets add the option to save and load

    private void OnLoad()
    {
        this.transform.localPosition = 
          SS.instance.LoadVector3("FirstPersonMovement", "position");
        this.transform.localRotation = 
          SS.instance.LoadQuaternion("FirstPersonMovement", "rotation");
    }

    private void OnSave()
    {
        SS.instance.Save("FirstPersonMovement", "position",
            this.transform.localPosition);
        SS.instance.Save("FirstPersonMovement", "rotation",
            this.transform.localRotation);
    }

The scene loads with a delay so that the scene items first load in, after It’s sending out a OnLoad event, so we can listen to that from the start method.

    private void Start()
    {
        SS.instance.OnSave.AddListener(OnSave);
        SS.instance.OnSceneDataLoad.AddListener(OnLoad);
    }

Adding Extensions

Lets go back to the save system and add the missing methods to interact with the Dictionarys.

public static class SaveDataExtensions
    {
        public static bool HasEntry(this WorldData world, string dayNumber, string entryId)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            return day.objects.Has(entryId);
        }

        public static string LoadEntry(this WorldData world, string dayNumber, string entryId, string key)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            var entry = day.objects.GetSet(entryId, new EntryData());
            var obj = entry.entries.GetSet(key, null, GetSetType.Get);
            return obj;
        }

        public static DayData GetDayData(this WorldData world, string dayNumber)
        {
            return world.days.GetSet(dayNumber, new DayData());
        }

        public static void SaveEntry(this WorldData world, string dayNumber, string entryId, string key, object value)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            var entry = day.objects.GetSet(entryId, new EntryData());
            var obj = entry.entries.GetSet(key, value.ToString(), GetSetType.Overwrite);
        }

        public static void SaveGlobalStat(this GameData game, string key, string value)
        {
            game.stats.GetSet(key, value, GetSetType.Overwrite);
        }
        public static void SaveWorldStat(this WorldData world, string key, string value)
        {
            world.stats.GetSet(key, value, GetSetType.Overwrite);
        }
        public static void SaveDayStat(this WorldData world, string dayNumber, string key, string value)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            day.stats.GetSet(key, value, GetSetType.Overwrite);
        }
        public static void SaveInventory(this WorldData world, string dayNumber, string[] value)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            day.inventory = value.ToList();
        }

        public static string LoadGlobalStat(this GameData game, string key)
        {
            return game.stats.GetSet(key, null, GetSetType.Get);
        }
        public static string LoadWorldStat(this WorldData world, string key)
        {
            return world.stats.GetSet(key, null, GetSetType.Get);
        }
        public static string LoadDayStat(this WorldData world, string dayNumber, string key)
        {
            var day = world.days.GetSet(dayNumber, new DayData());
            return day.stats.GetSet(key, null, GetSetType.Get);
        }
    }

And while im add it i’ll add some more ways to save different data types, i can do the converting to string in here and not in the code that implements the save / load

    public void Save(string id, string key, string value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, value);
    }

    public void Save(string id, string key, float value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, value);
    }

    public void Save(string id, string key, bool value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, value);
    }

    public void Save(string id, string key, Vector3 value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, 
          new SimpleVector3(value));
    }

    public void Save(string id, string key, Quaternion value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, 
          new SimpleQuaternion(value));
    }

    public void Save(string id, string key, int value)
    {
        currentWorldData.SaveEntry(GetDayNumber(), id, key, value);
    }

    public void SaveGlobalStat(string key, string value)
    {
        currentGameData.SaveGlobalStat(key, value);
    }

    public void SaveWorldStat(string key, string value)
    {
        currentWorldData.SaveWorldStat(key, value);
    }

    public void SaveDayStat(string key, string value)
    {
        currentWorldData.SaveDayStat(GetDayNumber(), key, value);
    }

    public void SaveInventory(List<ObjectiveItem> items)
    {
        string[] listOfIds = items.Select(i => i.uniqueID.Value()).ToArray();
        currentWorldData.SaveInventory(GetDayNumber(), listOfIds);
    }

public string LoadString(string id, string key)
{
    return currentWorldData.LoadEntry(GetDayNumber(), id, key);
}

public bool LoadBoolean(string id, string key)
{
    string returnValue = currentWorldData.LoadEntry(GetDayNumber(), id, key);
    return returnValue.ToBool();
}

public int LoadInt(string id, string key)
{
    string returnValue = currentWorldData.LoadEntry(GetDayNumber(), id, key);
    return returnValue.ToInt();
}

public float LoadFloat(string id, string key)
{
    string returnValue = currentWorldData.LoadEntry(GetDayNumber(), id, key);
    return returnValue.ToFloat();
}

public Vector3 LoadVector3(string id, string key)
{
    SimpleVector3 simpleVector3 = 
      new SimpleVector3(currentWorldData.LoadEntry(GetDayNumber(), id, key));
    return simpleVector3.ToVector3();
}

public Quaternion LoadQuaternion(string id, string key)
{
    SimpleQuaternion simpleQuaternion = 
      new SimpleQuaternion(currentWorldData.LoadEntry(GetDayNumber(), id, key));
    return simpleQuaternion.ToQuaternion();
}

public string[] LoadInventory()
{
    DayData day = currentWorldData.GetDayData(GetDayNumber());
    string[] returnValue = day.inventory.ToArray();
    return returnValue;
}

public bool HasData(string id)
{
    return currentWorldData.HasEntry(GetDayNumber(), id);
}

public string LoadGlobalStat(string key)
{
    return currentGameData.LoadGlobalStat(key);
}

public string LoadWorldStat(string key)
{
    return currentWorldData.LoadWorldStat(key);
}

public string LoadDayStat(string key)
{
    return currentWorldData.LoadDayStat(GetDayNumber(), key);
}

Results

Lets see the end result:

{
  "worldName": "Developer world",
  "days": {
    "1": {
      "objects": {
        "FirstPersonMovement": {
          "entries": {
            "position": "19.651,0.03272599,19.447",
            "rotation": "0,-0.0436194,0, 0.9990482"
          }
        },
        "FirstPersonLook": {
          "entries": {
            "rotation": "-0.1158041,0,0, 0.9932721"
          }
        }
      },
      "inventory": [],
      "stats": {}
    }
  },
  "stats": {},
  "LastPlayed": "2024-02-22T21:50:05.5970249+01:00"
}

For what i wanted this is perfect and easy to use too.

Illustration

3D models

A placeholder description

Illustration

About me

A placeholder description

Illustration

Dev projects

A placeholder description

Got a project in mind? Let’s talk 3D printing, software, or hardware