BUCK Basics is BUCK's Unity package that provides a foundation for scalable game architecture using ScriptableObject-based systems, comprehensive extension methods, and essential utility classes. It helps developers build cleaner, more maintainable Unity projects by reducing hard-coded dependencies and providing battle-tested tools refined across multiple productions.
- ⚡ ScriptableObject Architecture: Variables and Events as assets that enable decoupled communication between game systems
- 🔗 150+ Extension Methods: Time-saving utilities for vectors, colors, collections, geometry, and more
- 🔢 Conditions & Operations: Visual scripting-like functionality for logic and math operations without writing code
- ♻️ Object Pooling: High-performance pooling system with multiple overflow behaviors
- 📚 Runtime Sets: Dynamic collections that automatically track active game objects
- 📜 Menus: Helper components for quickly creating menus using Unity's UI
- 🎲 Battle-Tested: Used in production on multiple shipped games including Let's! Revolution! and The Electric State: Kid Cosmo
Note
This package works with Unity 2021.3 and above.
- Copy the git URL of this repository:
https://github.com/buck-co/buck-basics.git - In Unity, open the Package Manager from the menu by going to
Window > Package Manager - Click the plus icon in the upper left and choose
Add package from git URL... - Paste the git URL into the text field and click the
Addbutton.
The BUCK Basics package provides several independent systems that can be used together or separately:
- For ScriptableObject Variables: Create variables via
Assets > Create > BUCK > Variables, reference them in your scripts, and use GameEventListeners to react to changes - For Extension Methods: Simply add
using Buck;to access 150+ extension methods on Unity types - For Object Pooling: Add an
ObjectPoolercomponent and configure your pooled prefabs - For Conditions & Operations: Define logic in the Inspector without code using the provided classes
This package includes a comprehensive sample project demonstrating RPG-like mechanics using Variables, Events, Conditions, and Operations. Install it from the Unity Package Manager by selecting the package and clicking Import in the Samples tab.
BUCK Basics provides a powerful architecture pattern where variables and events exist as ScriptableObject assets. This approach, pioneered by Ryan Hipple at Schell Games, enables truly decoupled game systems.
Tip
📺 Watch Ryan Hipple's Unite Austin 2017 talk to understand the inspiration and philosophy behind this architecture.
Right-click in the Project window and navigate to Create > BUCK > Variables to create any supported type:
- Primitives: Bool, Int, Float, Double, String
- Unity Types: Vector2/3/4, Vector2Int/3Int, Quaternion, Color
- References: GameObject, Material, Sprite, Texture2D
using Buck;
public class PlayerHealth : MonoBehaviour
{
[SerializeField] IntVariable m_health;
[SerializeField] IntVariable m_maxHealth;
void TakeDamage(int damage)
{
m_health.Value -= damage;
m_health.Raise(); // Notify listeners
if (m_health.Value <= 0)
HandleDeath();
}
}Use Reference types for maximum flexibility - they can be either a constant value or a ScriptableObject variable:
[SerializeField] FloatReference m_moveSpeed; // Can be constant OR variable
void Update()
{
transform.position += Vector3.forward * m_moveSpeed.Value * Time.deltaTime;
}Every Variable inherits from GameEvent, allowing you to listen for changes:
public class HealthBar : MonoBehaviour
{
[SerializeField] IntVariable m_playerHealth;
[SerializeField] Image m_fillImage;
void OnEnable()
{
// Listen for health changes
var listener = gameObject.AddComponent<GameEventListener>();
listener.Event = m_playerHealth;
listener.Response.AddListener(UpdateHealthBar);
}
void UpdateHealthBar()
{
m_fillImage.fillAmount = m_playerHealth.Value / 100f;
}
}Define game logic visually in the Inspector without writing code.
Create boolean logic that can be evaluated at runtime:
[SerializeField] Condition m_canAttack; // Configure in Inspector
[SerializeField] Condition[] m_winConditions;
void Update()
{
if (m_canAttack.PassCondition)
PerformAttack();
if (m_winConditions.PassConditions()) // All must be true
TriggerVictory();
}Supported comparisons: ==, !=, <, <=, >, >=
Execute mathematical operations on variables without code:
[SerializeField] IntOperation m_scoreOperation; // Configured to add 10 points
[SerializeField] BoolOperation m_togglePause;
void OnEnemyDefeated()
{
m_scoreOperation.Execute(); // Adds to score and raises event
}
void OnPausePressed()
{
m_togglePause.Execute(); // Toggles pause state
}Supported operations vary by type:
- Bool: Set To, Toggle
- Number: Set To, Add, Subtract, Multiply, Divide, Power (with optional rounding for integers)
- Vector: Set To, Add, Subtract, Scalar Multiply/Divide
Over 150 extension methods to make Unity development faster and cleaner:
using Buck;
// Collections
myList.Shuffle(); // Fisher-Yates shuffle
var random = myList.Random(); // Get random element
// Vectors
float distance = ExtensionMethods.ManhattanDistance(posA, posB);
Vector2Int gridPos = worldPosition.ToVector2Int();
// Colors
Color tinted = myColor.Tint(0.2f);
spriteRenderer.SetAlpha(0.5f);
// Geometry
bool visible = myRenderer.IsVisibleFrom(mainCamera);
Rect screenRect = myTransform.GetScreenRectangle();
// Math
float smooth = ExtensionMethods.Smootherstep(0, 1, t);
float angle = rotation.Angle360Positive(); // -10° becomes 350°
// Arrays
int[,] rotated = my2DArray.Rotate90();
my2DArray.Transpose();High-performance pooling with automatic overflow handling:
Tip
What is Object Pooling? Instead of constantly creating and destroying objects (which causes memory allocation and garbage collection), object pooling pre-creates objects and reuses them. This dramatically improves performance for frequently spawned objects like bullets, particles, or enemies.
public class BulletSpawner : MonoBehaviour
{
ObjectPooler m_pooler;
[SerializeField] PooledObject m_bulletPool = new PooledObject
{
m_prefab = bulletPrefab,
m_numberOfObjects = 50
};
void Start()
{
m_pooler = GetComponent<ObjectPooler>();
m_pooler.GenerateObjects(m_bulletPool);
}
void FireBullet()
{
GameObject bullet = m_pooler.Retrieve(firePoint.position, firePoint.rotation);
// Bullet is automatically activated and positioned
}
}
// In the bullet script
void OnCollisionEnter(Collision collision)
{
GetComponent<PoolerIdentifier>().m_pooler.Recycle(gameObject);
}Every pooled object automatically receives a PoolerIdentifier component that tracks its originating pool. This allows objects to be recycled from anywhere without needing direct references:
// Any script can recycle a pooled object
void OnTriggerEnter(Collider other)
{
var identifier = other.GetComponent<PoolerIdentifier>();
if (identifier != null)
identifier.m_pooler.Recycle(other.gameObject);
}When the pool runs out of objects:
- Recycle Oldest: Reuse the oldest active object (default)
- Double Size: Expand the pool (may cause GC spikes)
- Warn: Log a warning and return null
BUCK Basics includes a lightweight menu framework built on Unity UI that couples with ScriptableObject variables and supports stack-based navigation, sibling “pages,” and automatic value binding for standard controls.
- MenuController — Manages a stack of menus (push, back, sibling-replace).
- MenuScreen — A single screen of UI (show/hide, focus first
Selectable, variable bindings). - MenuSiblingGroup — Declares an ordered set of sibling
MenuScreenpages (e.g., Settings tabs). Shows/hides automatically whenever one of its pages is on top of the stack. - MenuPager — A non-interactive “bumper bar” UI: renders a horizontal list of page titles using TextMeshProUGUI and an underline that tracks the selected page.
MenuScreenandMenuSiblingGroupboth inherit fromMenuView, which provides a localizableTitleTextand CanvasGroup-based visibility.
- Add a MenuController under your Canvas.
- Create a MenuScreen (add your UI and any
VariableBindingcomponents). - Open it via button OnClick →
MenuScreen.MenuNav_OpenThisMenu()or in code:
menuController.MenuNav_OpenMenu(myScreen);- Create an empty GameObject (e.g.,
OptionsGroup) and add MenuSiblingGroup (+CanvasGroup).- Optionally set the group
TitleText. - Add your pages (e.g.,
Options – General,Options – Audio,Options – Video) to the Pages list in order.
- Optionally set the group
- Under
OptionsGroup, add a child for the MenuPager.- Assign an Items Root (recommend a
HorizontalLayoutGroup) and an UnderlineRectTransform. - Optionally assign a TextMeshProUGUI prototype to style labels.
- Assign an Items Root (recommend a
- Create one MenuScreen per page and design their UI normally. Pages may be siblings of the group or children—membership is defined by the group’s Pages list.
- From your Pause/Options button, open the first page:
- OnClick →
OptionsGroup.OpenFirstPage()(or open a specific page).
- OnClick →
- Hook your input to call
MenuPager.NextPage()/PrevPage()to flip pages.
- Stack semantics: Opening a page pushes it on the stack; sibling navigation replaces the top entry, so Back still returns to the parent (e.g., Pause).
- Auto visibility:
MenuSiblingGroupandMenuPagershow only when the current top-of-stackMenuScreenbelongs to that group. - Focus:
MenuPagerhas noSelectables; the selection indicator continues to track the focused control on the active page. - Localization:
TitleTexton screens and groups supportsLocalizedStringwhenBUCK_BASICS_ENABLE_LOCALIZATIONis defined.
Canvas
└── MenuController
├── MenuScreen - Pause
├── MenuSiblingGroup - Options (CanvasGroup, TitleText)
│ └── MenuPager (Items Root + Underline)
├── MenuScreen - Options - General
├── MenuScreen - Options - Audio
└── MenuScreen - Options - Video
public class GameManager : Singleton<GameManager>
{
// Automatically creates a persistent instance
public void RestartLevel() { }
}
// Access from anywhere
GameManager.Instance.RestartLevel();For singletons that should only exist for the duration of a currently loaded scene:
public class EnemyManager : SoftSingleton<EnemyManager>
{
// Can be destroyed and recreated
}Automatically track collections of objects:
[CreateAssetMenu(menuName = "BUCK/Runtime Sets/Enemy Set")]
public class EnemyRuntimeSet : RuntimeSet<Enemy> { }
// Enemies register themselves
void OnEnable() => m_enemySet.Add(this);
void OnDisable() => m_enemySet.Remove(this);
// Query active enemies from anywhere
int enemyCount = m_enemySet.Items.Count;All BUCK Basics ScriptableObjects inherit from BaseScriptableObject, which provides persistent GUIDs (globally unique identifiers) for each asset:
// Find specific ScriptableObjects by their GUID
var myVariable = BaseScriptableObject.FindByGuid<IntVariable>(guid, "Assets/Variables");
// GUIDs persist across renames and moves
bool isSameAsset = variableA.Guid == variableB.Guid;
// Useful for save systems or detecting duplicates
List<Guid> collectedItems = new List<Guid>();
if (!collectedItems.Contains(item.Guid))
collectedItems.Add(item.Guid);This GUID system ensures your references remain stable even when assets are renamed or reorganized, making it invaluable for save systems, inventory management, and asset tracking.
Found a bug or have a feature request? We'd love to hear from you!
- Open an issue for problems or suggestions
- Create a pull request if you'd like to contribute code
- Check our contribution guidelines before submitting
- Nick Pettit - nickpettit
- Ian Sundstrom - iwsundstrom
See the full list of contributors.
- Ryan Hipple's Unite Austin 2017 talk for the foundational architecture patterns
MIT License - Copyright (c) 2025 BUCK Design LLC buck-co
BUCK is a global creative company that brings brands, stories, and experiences to life through art, design, and technology. If you're a Game Developer or Creative Technologist or want to get involved with our work, reach out and say hi via Github, Instagram or our Careers page. 👋
