Skip to content

A Unity package for building scalable game architecture.

License

Notifications You must be signed in to change notification settings

buck-co/buck-basics

Repository files navigation

opt 03

Unity License Version

BUCK Basics

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.

Features

  • 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

Getting Started

Note

This package works with Unity 2021.3 and above.

Install the BUCK Basics Package

  1. Copy the git URL of this repository: https://github.com/buck-co/buck-basics.git
  2. In Unity, open the Package Manager from the menu by going to Window > Package Manager
  3. Click the plus icon in the upper left and choose Add package from git URL...
  4. Paste the git URL into the text field and click the Add button.

Basic Workflow

The BUCK Basics package provides several independent systems that can be used together or separately:

  1. For ScriptableObject Variables: Create variables via Assets > Create > BUCK > Variables, reference them in your scripts, and use GameEventListeners to react to changes
  2. For Extension Methods: Simply add using Buck; to access 150+ extension methods on Unity types
  3. For Object Pooling: Add an ObjectPooler component and configure your pooled prefabs
  4. For Conditions & Operations: Define logic in the Inspector without code using the provided classes

Included Samples

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.

Core Systems

ScriptableObject Variables & Events

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.

Creating Variables

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 Variables in Code

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();
    }
}

Variable References

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;
}

GameEvents & Listeners

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;
    }
}

Conditions & Operations

Define game logic visually in the Inspector without writing code.

Conditions

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: ==, !=, <, <=, >, >=

Operations

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

Extension Methods

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();

Object Pooling

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);
}

PoolerIdentifier

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);
}

Overflow Behaviors

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

Menus

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.

Core pieces

  • 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 MenuScreen pages (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.

MenuScreen and MenuSiblingGroup both inherit from MenuView, which provides a localizable TitleText and CanvasGroup-based visibility.

Quick start (single screen)

  1. Add a MenuController under your Canvas.
  2. Create a MenuScreen (add your UI and any VariableBinding components).
  3. Open it via button OnClick → MenuScreen.MenuNav_OpenThisMenu() or in code:
menuController.MenuNav_OpenMenu(myScreen);

Paged settings (sibling menus)

  1. 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.
  2. Under OptionsGroup, add a child for the MenuPager.
    • Assign an Items Root (recommend a HorizontalLayoutGroup) and an Underline RectTransform.
    • Optionally assign a TextMeshProUGUI prototype to style labels.
  3. 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.
  4. From your Pause/Options button, open the first page:
    • OnClick → OptionsGroup.OpenFirstPage() (or open a specific page).
  5. Hook your input to call MenuPager.NextPage() / PrevPage() to flip pages.

Behavior notes

  • 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: MenuSiblingGroup and MenuPager show only when the current top-of-stack MenuScreen belongs to that group.
  • Focus: MenuPager has no Selectables; the selection indicator continues to track the focused control on the active page.
  • Localization: TitleText on screens and groups supports LocalizedString when BUCK_BASICS_ENABLE_LOCALIZATION is defined.

Example hierarchy

Canvas
└── MenuController
    ├── MenuScreen - Pause
    ├── MenuSiblingGroup - Options (CanvasGroup, TitleText)
    │   └── MenuPager (Items Root + Underline)
    ├── MenuScreen - Options - General
    ├── MenuScreen - Options - Audio
    └── MenuScreen - Options - Video

Additional Utilities

Singleton Pattern

public class GameManager : Singleton<GameManager>
{
    // Automatically creates a persistent instance
    public void RestartLevel() { }
}

// Access from anywhere
GameManager.Instance.RestartLevel();

Soft Singleton

For singletons that should only exist for the duration of a currently loaded scene:

public class EnemyManager : SoftSingleton<EnemyManager>
{
    // Can be destroyed and recreated
}

Runtime Sets

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;

BaseScriptableObject & GUIDs

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.

Contributing

Found a bug or have a feature request? We'd love to hear from you!

Authors

See the full list of contributors.

Acknowledgments

License

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. 👋

About

A Unity package for building scalable game architecture.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages