Blog

You are filtering on tag 'tricks'. 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

A Different Approach to Pip-Based Bars

November 23rd, 2017 (edited November 3rd, 2022)

I've been checking out a number of different systems in Factorio while experimenting with making a factory-building game. While looking through the game's assets, I noticed an interesting health bar graphic that revealed Factorio's rather neat way of handling pip-based health bars. A pip-based bar uses filled or unfilled pips to indicate progress or health, rather than a continuous line:

Factorio health bar screenshot

The most obvious way to implement this element would be to use a number of different sprite instances, and have each of them flip between a "green" and a "grey" image as appropriate. This can be done in one draw call, if the two states are on one image, and one quad per pip. Here's the asset you would need to implement this:

Two health bar pips, one green, one grey

So I was initially confused when I saw that Factorio's health bar asset actually looked like this:

Factorio health bar with lots of green pips followed by lots of grey pips

Can you figure out how this asset might be used? My guess is that the bar is drawn as a single quad that's mapped to an appropriate part of this image using its UV coordinates. For example, if you wanted a 7-pip bar with 5 pips filled, you could map your UVs like so:

Factorio health bar asset with UV mapping illustration

This is more efficient because you only have to draw one quad. More significantly, it's simpler to implement because you only need a single sprite or entity in your engine, rather than having to create several and manage their positions. Your asset just needs to have twice as many pips as the maximum pip count you want to use.


Permalink


Previous Page
2 posts — page 1 of 1
Next Page