Blog

You are filtering on tag 'unity'. Remove Filters
RSS

Unity UI Gradient Shader v2

March 4th, 2021 (edited November 3rd, 2022)

Some time ago, I shared a Unity shader for coloring a Unity UI element with a 2D gradient. That post seems to get a lot of traffic, so I thought I should share my improved version as well.

The original shader uses the first UV channel to distribute the gradient color, which means it only works well with the Simple image type. The other Image types do funny things with that UV channel. To solve this, I created a special version of the Image component that also produces a second UV channel. The second channel is always evenly distributed over the entire image, regardless of the Image Type.

Get the new component and shader here on GitHib. My shader and modifications are free to use, but the Image source code is subject to the Unity Reference-Only License.

Comparison of the new and old gradient shaders.

Future work could add support for the Tiled and Filled image types as well.


Permalink

Ludum Dare 46 - The Wrecker

April 28th, 2020 (edited November 3rd, 2022)

It's been a few Ludum Dares since I participated, but last weekend I jumped in for the Ludum Dare 46 with audio designer Christian Camargo. This Dare saw a big spike in participation this time around, probably due to the stay-at-home orders in many places. The theme was "Keep it Alive". We worked in Unity with Wwise.

After starting, as always, by spamming a heap of ideas out on (virtual) paper, I started trying to mash two or more of the ideas together into a hybrid idea. "The Wrecker" was born from the union of a game about a factory poisoning a town, and my recent playthrough of Red Faction: Guerrilla and love for its granular destruction systems. We also grabbed two other elements from the doc we wanted to include: a time limit to push the player forward, and the idea that the player is also dying and needs to prioritize their own health against that of the town. That more than was enough to get started!

'The Wrecker' logo

What We Learned

Wwise

The decision to use Wwise for our audio had some pros and cons. Despite being relatively unfamiliar with it as the programmer of the team, I was able to hook in pretty easily by making a single call to posting a string-named event when audio cues should be played or stopped. Christian was able to do a lot of customization in Wwise, such as randomizing the sounds played and their parameters, that I didn't need to implement.

On the down side, however, Wwise did not play nicely with Unity's Collab source control, which only synchronizes the Unity project data and did not understand that there was also a Wwise project to synchronize. This effectively siloed off the audio work until we manually transfered the project, and I didn't hear most of the audio until a few hours before the deadline. Theoretically, Wwise is capable of building audio banks into the project that could be synced, but in the jam crunch we never got that working.

I also discovered late on Saturday (making an early test build to catch any build problems - I definitely recommend it) that WWise is not compatible with Unity WebGL builds, which are the optimal way to get Ludum Dare raters into the game quickly. Fortunately this does not appear to be affecting our ability to get ratings.

Physics Gameplay

Screenshot of the player smashing down some walls.

I chose to implement the game's progression through three different weapons, each capable of breaking more objects than the last, and only the final weapon was able to break through the doors into the factory. It's a pretty time-honored design. However, I opted to implement the destruction with Unity's breakable physics joint system, in which each joint has a specified Break Force.

This was a quick way to get everything up and running, but the result was a huge number of breakable joints scattered throughout the game's prefabs, many of which needed to be carefully balanced so they were only breakable by the appropriate weapon. Any change to physics parameters, such as the player's movement speed or weapon swing speed, could throw off everything. If I did this over, I would probably try to implement explicit, hard limitations on which objects can be broken by which weapons rather than relying on the implicit interactions of the physics engine to gate crit-path gameplay.

Vector Graphics

Adobe Illustrator artboard containing game graphics.

I used Adobe Illustrator to produce all the art for the game. This was very efficient, and I was able to turn out assets incredibly quickly and get back to coding. I made all the assets on two artboards (Game and UI), which made it quick and easy to ensure everything was at a consistent scale and stroke width. I used a simple visual language that I think contributes significantly to the game's clean look - collidable objects have a black stroke and background objects have none. Additionally, control prompts are always a key in a rounded white rectangle.


If you haven't check out the game yet, you can download it for free from this link, or watch a video right over here! If you are a Ludum Dare participant, you can rate the game at its Ludum Dare page.


Permalink

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

Sizing elements to fit their contents in Unity UI

April 10th, 2019 (edited November 3rd, 2022)

You have a UI widget that contains a number of different elements (text, images, etc). These are all children of a background element, which you want to automatically resize to encompass all of these children.

You read a bunch of forum threads and the documentation and it just won't work. Me too. I figured it out, though, so I'm documenting it here. This was written with reference to Unity 2018.3.3.

To demonstrate, I'll be creating a tooltip. I'm starting with a naive implementation - a few different widgets that are children of a VerticalLayoutGroup.

The results of the naive implemention. There are large gaps between the different elements and the background is the wrong size.

The first important concept to understand is the idea of "preferred" size. Preferred size is an internal value that some UI components have (specifically, ones that implement the ILayoutElement interface). It doesn't have anything to do with any of the values of the RectTransform on that element. Some examples of components that have a preferred size:

  • Image - The preferred size is the original size of the sprite being used.
  • Text - The preferred size is the size of the actual visible text.
  • Layout Groups - The preferred size is the size of the bounds encompassing all of the group's children.
  • Layout Element - Allows you manually set the preferred size for an object.

By default, Layout Groups look at the RectTransform size of their children when deciding how to position them, not the preferred size1. Remember that for Text components, the visual size of the rendered text is the preferred size, not the RectTransform size. So in order to include Text in a Layout Group and have it respond to the actual rendered text, we need to set the text's RectTransform size to match its preferred size.

There is a built-in component for this called ContentSizeFitter. When attached to a GameObject with a UI component on it, it can set the RectTransform size of that object to match the preferred size.

I'll attached ContentSizeFitters to each of the Text objects and set the Vertical Fit to "Preferred Size". You may have to disable and re-enable the VerticalLayoutGroup to get it to refresh. You'll also see a warning message on the ContentSizeFitter component about the parent layout group, which you can ignore for now (but see footnote 1).

The results of the second attempt. The contents are correctly positioned but the background is still the wrong size.

This is getting closer, but the background (an Image component on the Tooltip object) isn't resizing yet. Remember that the preferred size of the VerticalLayoutGroup is the size of the bounds encompassing all its children. So let's just add a ContentSizeFitter to the VerticalLayoutGroup object and set both fits to "Preferred Size".

The results of the third attempt. The contents are correctly positioned and the background is the correct size.

And that's pretty much it. You can use the Padding and Spacing properties on the Layout Group to clean it up.

1 Layout Groups have two checkboxes for "Child Controls Size", one for the Width and one for the Height. Checking these boxes causes the Layout Group to automatically perform the function of the ContentSizeFitter - changing the RectTransforms of the children to match their preferred size. This allows you achieve the same result without ContentSizeFitters, and it is apparently how Unity now intends it to be done; however, it affects ALL the children of the layout group. That includes the divider in this case, which we didn't want to resize. We could have fixed this by adding a LayoutElement component to the divider and setting its preferred height to 1.

One more thing

What if I wanted to introduce a second part of the background - a border? Ordinarily, I would make the border a child of the background and set the RectTransform Anchors to stretch both dimensions. But that won't work in this case because the background can no longer find the content items (Layout Groups only operate on their immediate children), so it won't grow to match them.

The results of attempting to add a border. The border is the correct size, but the background is not.

What we really want in this case is to pass the size up from a Layout Group on the border. I move the Layout Group and ContentSizeFitter from the background to the border. Now the border is sizing correctly, but I need to get the background to match it as well.

Remember that Layout Groups set their own preferred size to the size of the bounds encompassing all of the group's children. So we can achieve this by adding a Horizontal or Vertical Layout Group to the background (they will produce the same result with only one child), which sets the preferred size, and then adding a ContentSizeFitter to the background, resizing the RectTransform to match.

A tooltip with a correctly-sized border and background.
Permalink

Unity UI Gradient Shader

February 14th, 2019 (edited November 3rd, 2022)

EDIT: An updated version of this shader that supports Sliced images is available here.

I created a variant of Unity's default UI shader; instead of using a solid tint color across the entire element, it uses a four-color gradient. The source is MIT-licensed and available on GitHub.

Unity UI Gradient shader example image


Permalink


Previous Page
6 posts — page 1 of 2
Next Page