This is called a Flags enumeration.
You mark it with the System.Flags attribute, and give it a backing type that's some kind of integer:
[System.Flags]
public enum Characteristics : int {
None = 0x00,
Heavy = 0x01,
Hot = 0x02,
Shiny = 0x04,
Squishy = 0x08,
Loud = 0x10,
Large = 0x20
}
You can then stack multiple flags in a single variable with an OR operator:
var myCharacteristics = Characteristics.Loud | Characteristics.Squishy;
or stack on more later with myCharacteristics |= Characteristics.Shiny;
You can check for the presence of a particular flag with an AND operator:
if((myCharacteristics & Characteristics.Loud) != Characteristics.None)
Debug.Log("I am loud");
or for any of a group of flags
var conspicuous = Characteristics.Shiny | Characteristics.Large | Characteristics.Loud;
if((myCharacteristics & conspicuous) != Characteristics.None)
Debug.Log("I am conspicuous");
To edit this properly in the Unity inspector, you can define a custom PropertDrawer like the one shown here (modified below to work with the new EnumFlagsField instead of EnumMaskField) and mark your variable with an attribute:
[EnumFlags]
public Characteristics myCharacteristics;

Here are the scripts to make this work:
EnumFlagsAttribute - place in your Assets folder.
using UnityEngine;
public class EnumFlagsAttribute : PropertyAttribute {
public string enumName;
public EnumFlagsAttribute() { }
public EnumFlagsAttribute(string name) {
enumName = name;
}
}
EnumFlagsDrawer - place in an Assets sub-folder called "Editor" so it's not compiled into the game:
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
public class EnumFlagsDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EnumFlagsAttribute flagSettings = (EnumFlagsAttribute)attribute;
Enum targetEnum = GetBaseProperty<Enum>(property);
string propName = flagSettings.enumName;
if (string.IsNullOrEmpty(propName))
propName = property.name;
EditorGUI.BeginProperty(position, label, property);
var enumNew = EditorGUI.EnumFlagsField(position, propName, targetEnum);
property.intValue = (int)Convert.ChangeType(enumNew, targetEnum.GetType());
EditorGUI.EndProperty();
}
static T GetBaseProperty<T>(SerializedProperty prop) {
// Separate the steps it takes to get to this property
string[] separatedPaths = prop.propertyPath.Split('.');
// Go down to the root of this serialized property
System.Object reflectionTarget = prop.serializedObject.targetObject as object;
// Walk down the path to get the target object
foreach (var path in separatedPaths) {
FieldInfo fieldInfo = reflectionTarget.GetType().GetField(path);
reflectionTarget = fieldInfo.GetValue(reflectionTarget);
}
return (T)reflectionTarget;
}
}
Edit: based on a link from the article Galandil shared, here's a different way to draw the flags in the inspector. I souped up the linked version to support different enum backing types, multi-object editing, and multiple rows in a single enum. It will also skip over "None" or compound flags. Still experimental, but it seems to work.

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
public class EnumFlagsAttributeDrawer : PropertyDrawer {
[System.Flags]
enum TriBool {
Unset = 0,
False = 1,
True = 2,
Both = 3
}
struct Entry {
public string label;
public int mask;
public TriBool currentValue;
}
List<SerializedProperty> _properties;
List<Entry> _entries;
int _rowCount;
int _columnCount;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (_properties == null)
Initialize(property);
return _rowCount * EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
label = EditorGUI.BeginProperty(position, label, property);
Rect labelRect = new Rect(
position.x,
position.y,
EditorGUIUtility.labelWidth,
position.height);
EditorGUI.LabelField(labelRect, label);
float buttonStride = (position.width - labelRect.width) / _columnCount;
float buttonWidth = buttonStride * 1.0f;
Rect buttonRect = new Rect(
labelRect.max.x,
labelRect.y,
buttonWidth,
EditorGUIUtility.singleLineHeight);
int column = 0;
GUIStyle mixedButton = new GUIStyle(GUI.skin.button);
mixedButton.normal.textColor = Color.grey;
for(int i = 0; i < _entries.Count; i++) {
//Debug.Log(_entries[i].label + " : " + _entries[i].currentValue);
var entry = _entries[i];
EditorGUI.BeginChangeCheck();
bool pressed = GUI.Toggle(
buttonRect,
entry.currentValue == TriBool.True,
entry.label,
entry.currentValue == TriBool.Both ? mixedButton : GUI.skin.button);
if (EditorGUI.EndChangeCheck()) {
if (pressed) {
foreach (var prop in _properties)
prop.intValue |= entry.mask;
entry.currentValue = TriBool.True;
} else {
foreach (var prop in _properties)
prop.intValue &= ~entry.mask;
entry.currentValue = TriBool.False;
}
_entries[i] = entry;
}
buttonRect.x += buttonStride;
if(++column >= _columnCount) {
column = 0;
buttonRect.x = labelRect.max.x;
buttonRect.y += buttonRect.height;
}
}
foreach (var prop in _properties)
prop.serializedObject.ApplyModifiedProperties();
EditorGUI.EndProperty();
EditorGUI.showMixedValue = false;
}
void Initialize(SerializedProperty property) {
var allTargetObjects = property.serializedObject.targetObjects;
_properties = new List<SerializedProperty>(allTargetObjects.Length);
foreach (var targetObject in allTargetObjects) {
SerializedObject iteratedObject = new SerializedObject(targetObject);
SerializedProperty iteratedProperty = iteratedObject.FindProperty(property.propertyPath);
if (iteratedProperty != null)
_properties.Add(iteratedProperty);
}
var parentType = property.serializedObject.targetObject.GetType();
var fieldInfo = parentType.GetField(property.propertyPath);
var enumType = fieldInfo.FieldType;
var trueNames = System.Enum.GetNames(enumType);
var typedValues = GetTypedValues(property, enumType);
var display = property.enumDisplayNames;
var names = property.enumNames;
_entries = new List<Entry>();
for (int i = 0; i < names.Length; i++) {
int sortedIndex = System.Array.IndexOf(trueNames, names[i]);
int value = typedValues[sortedIndex];
int bitCount = 0;
for (int temp = value; (temp != 0 && bitCount <= 1); temp >>= 1)
bitCount += temp & 1;
//Debug.Log(names[i] + ": " + value + " ~ " + bitCount);
if (bitCount != 1)
continue;
TriBool consensus = TriBool.Unset;
foreach (var prop in _properties) {
if ((prop.intValue & value) == 0)
consensus |= TriBool.False;
else
consensus |= TriBool.True;
}
_entries.Add(new Entry { label = display[i], mask = value, currentValue = consensus });
}
_rowCount = Mathf.CeilToInt(_entries.Count / 4f);
_columnCount = Mathf.Min(_entries.Count, (Mathf.CeilToInt(_entries.Count / 3f) == _rowCount) ? 3 : 4);
}
int[] GetTypedValues(SerializedProperty property, System.Type enumType) {
var values = System.Enum.GetValues(enumType);
var underlying = System.Enum.GetUnderlyingType(enumType);
if (underlying == typeof(int))
return ConvertFrom<int>(values);
else if (underlying == typeof(uint))
return ConvertFrom<uint>(values);
else if (underlying == typeof(short))
return ConvertFrom<short>(values);
else if (underlying == typeof(ushort))
return ConvertFrom<ushort>(values);
else if (underlying == typeof(sbyte))
return ConvertFrom<sbyte>(values);
else if (underlying == typeof(byte))
return ConvertFrom<byte>(values);
else
throw new System.InvalidCastException("Cannot use enum backing types other than byte, sbyte, ushort, short, uint, or int.");
}
int[] ConvertFrom<T>(System.Array untyped) where T : System.IConvertible {
var typedValues = new int[untyped.Length];
for (int i = 0; i < typedValues.Length; i++)
typedValues[i] = System.Convert.ToInt32((T)untyped.GetValue(i));
return typedValues;
}
}