Blog

You are filtering on tag 'periodic deliveries'. Remove Filters
RSS

Periodic Deliveries Postmortem - Time-Saving Tricks

December 9th, 2019 (edited November 3rd, 2022)

I'm releasing a Unity 2018 game called Periodic Deliveries! Development of any game is fraught with little speedbump tasks that slowly soak up dev time and make it a little less fun, too. Here are a few tricks I did to knock out some of those bumps in my process that you can easily implement in your Unity projects, too.

Use the MenuItem attribute to create developer cheats

In a larger game with a larger team, you might implement a developer console you can activate with a key press and type commands into. That will scale well if you have a lot of commands, and it will work in standalone builds. But if your game is small, this is a quick and effective approach. Quick implementation is important because it will encourage you to add cheats more often, and those will save you time as development goes on.

Screenshot of a dropdown menu containing cheat commands

I put them all in a static class called CheatCommands. Don't forget to add a validation function, which will disable the option when it doesn't make sense and improve your own experience.

public static class CheatCommands
{
	[MenuItem("Cheats/Infinity Money", true)]
	public static bool ValidateInfinityMoney()
	{
		// Disables the item in Edit Mode and in the Main Menu
		return InGameManager.Instance.PlayerCompany != null;
	}

	[MenuItem("Cheats/Infinity Money")]
	public static void InfinityMoney()
	{
		InGameManager.Instance.PlayerCompany.AddMoney(PeriodicInt.PositiveInfinity);
	}
	
	...
}

Make custom property drawers for structs that show up in the Inspector

Using Serializable structs for common data patterns can help you avoid writing similar code multiple times. But you'll have to expand a foldout every time you want to edit this data in the Inspector. If you can write a custom property drawer quickly, you can save yourself a lot of clicks over the entire development of your project.

Here is a base class for a common property drawer I used several times. You can inherit from it to create a custom property drawer for any struct that represents a "quantity" of a "thing" (like a stack of items in a loot container, or an ingredient requirement in a recipe).

using UnityEditor;
using UnityEngine;

// Author: Brian MacIntosh (The Periodic Group)
// MIT License

/// <summary>
/// Base class that can be extended to quickly create a property drawer for a structure containing
/// data like "quantity" of "thing".
/// </summary>
public abstract class ItemQuantityPropertyDrawer : PropertyDrawer
{
	/// <summary>
	/// Name of the quantity/stack size property.
	/// </summary>
	public abstract string QuantityPropName { get; }

	/// <summary>
	/// Name of the item/object property.
	/// </summary>
	public abstract string ItemPropName { get; }

	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
		EditorGUI.BeginProperty(position, label, property);

		position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

		int indent = EditorGUI.indentLevel;
		EditorGUI.indentLevel = 0;

		Rect quantityPosition = position;
		quantityPosition.width = 40f;
		Rect resourcePosition = position;
		resourcePosition.xMin += quantityPosition.width + 4f;
		EditorGUI.PropertyField(quantityPosition, property.FindPropertyRelative(QuantityPropName), GUIContent.none);
		EditorGUI.PropertyField(resourcePosition, property.FindPropertyRelative(ItemPropName), GUIContent.none);

		EditorGUI.indentLevel = indent;

		EditorGUI.EndProperty();
	}
}

Alternatively, get it from Github.

Here's an example use:

//RecipeComponent.cs

[Serializable]
public class RecipeComponent
{
	[Tooltip("The resource.")]
	public ResourceData Resource;

	[Tooltip("The quantity of the resource.")]
	public int Quantity = 1;

	public RecipeComponent(ResourceData resource, int quantity)
	{
		Resource = resource;
		Quantity = quantity;
	}

	public override string ToString()
	{
		return Quantity.ToString() + " " + Resource.name;
	}
}
//RecipeComponentPropertyDrawer.cs

using UnityEditor;

[CustomPropertyDrawer(typeof(RecipeComponent))]
public class RecipeComponentPropertyDrawer : ItemQuantityPropertyDrawer
{
	public override string ItemPropName
	{
		get { return "Resource"; }
	}

	public override string QuantityPropName
	{
		get { return "Quantity"; }
	}
}

Separate your code files into 'Game' and 'SDK' folders

Put any code files that are specific to this game in the 'Game' folder. Put any code files that might be useful on your future games in the 'SDK' folder. The idea here is to create a folder that you can literally copy and paste into your next project (or you can use a git submodule if you jump back and forth between a lot of projects).

You need to follow one rule to make this work: code in the 'SDK' folder can never reference code in the 'Game' folder. If this happens, either (1) rework your SDK code so the Game code can insert its own behavior - for example, provide an event it can subscribe to, or virtual methods it can override - or (2) put that code in the Game folder.

My SDK folder contains things like:

  • Static classes full of utility functions (MathUtility, ListUtility, etc).
  • The abstract property drawer above (in an Editor subfolder).
  • My generic input context system.
  • Generic helper components, like PositionAtMouse, MirrorColor, GameObjectPool...
  • etc.

If do this, you will write better, reusable code and save yourself time debugging this project and implementing these functions on the next.

Use a GlobalData ScriptableObject

ScriptableObjects are a great tool for organizing data. I like creating a scriptable object for each item, ability, and other such thing in my game to hold all the data associated with it. On Periodic Deliveries, I also created a monolithic 'GlobalData' object. This object holds all those global configuration options that usually end up throughout the project on various manager components and code constants. Putting them in one place meant I never had to spend time tracking down where so particular value was. The scriptable object also diffs much better in source control than a component in a scene, in case you need to look back through the history.

The GlobalData object can be held on a global singleton component somewhere, or maybe loaded with the new Addressable Asset system (I haven't played with it yet).

Screenshot of the Global Data object inspector

I hope some of these tips can make your development a little faster and more fun. If you're into space, simulation, or management games, don't forget to check out Periodic Deliveries on Steam!


Permalink

Periodic Deliveries - Steam Release!

December 7th, 2019 (edited March 4th, 2023)

Way back in Ludum Dare 30 Justin Britch and I made a game called Space Transport Tycoon (which you can still play in your browser for free. Four years later, we still remember it fondly as the the game we spent the most time actually playing after making it. This year, we finally resurrected it as Periodic Deliveries and we're publishing it on December 17th on Steam!

Check out this before-and-after:

Space Transport Tycoon screenshot

We've obviously upgraded the visual quality, thanks to our real artist, John Lewis. The game still features the same core mechanic - configuring trade routes between planets to move certain goods from where they're produced to where they're needed - but we changed several peripheral mechanics. In the game jam, I wanted to create a game with multiple views shown simultaneously. That's how we got the planetary view at the bottom-right, where you could relocate factories to tiles that had more optimal conditions, based on the type of factory. This ended up being one of those features that had a lot of cool stuff in the implementation but didn't work in practice - it was just a quick chore to do once on each new planet. We removed it to focus on the main view.

One small change had a big impact: we prohibited trade routes from crossing each other. In Space Transport Tycoon, you'd often build a crisscrossing web of routes that wasn't terribly satisfying. The new restriction requires the player to use certain planets as hubs and create arterial intermediate routes, which we think is an important and cool part of the shipping fantasy.

Periodic Deliveries screenshot

The UI also got a big rework. The old UI was "planet-centric" - that is, you selected a trade route and were presented with a list of every planet on that route, with controls to configure the behavior at each planet. This allows the player to picture everything that happens at a particular planet little more easily, but it requires extra mental steps to think about what happens to a particular good across the entire route - the later being much more useful to the player than the former. The list can also get arbitrarily long, which is kind of a UI nightmare. Periodic Deliveries now uses a goods-centric UI, where the player sees a list of goods that are involved with the route, and configures which planets import or export that good.

That's just a few of the larger changes we made for Periodic Deliveries. If it sounds like something you'd like, try out Space Transport Tycoon right now and wishlist Periodic Deliveries below!

This post has been updated: the explanation of planet-centric vs. goods-centric UI was backwards.


Permalink


Previous Page
2 posts — page 1 of 1
Next Page