Learning Design Patterns with Unity - Harrison Ferrone - E-Book

Learning Design Patterns with Unity E-Book

Harrison Ferrone

0,0
32,39 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.

Mehr erfahren.
Beschreibung

Struggling to write maintainable and clean code for your Unity games? Look no further! Learning Design Patterns with Unity empowers you to harness the fullest potential of popular design patterns while building exciting Unity projects. Through hands-on game development, you'll master creational patterns like Prototype to efficiently spawn enemies and delve into behavioral patterns like Observer to create reactive game mechanics. As you progress, you'll also identify the negative impacts of bad architectural decisions and understand how to overcome them with simple but effective practices.

By the end of this Unity 2023 book, the way you develop Unity games will change. You'll emerge not just as a more skilled Unity developer, but as a well-rounded software engineer equipped with industry-leading design patterns.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB
MOBI

Seitenzahl: 685

Veröffentlichungsjahr: 2024

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Contents

Preface

Who this book is for

What this book covers

To get the most out of this book

Get in touch

Priming the System

A word about software architecture

What are design patterns?

The Gang of Four

Pattern categories

Creational patterns

Behavioral patterns

Structural patterns

Why use design patterns?

When to use design patterns

Common pitfalls

About the example projects

Client code

Old vs new input system

Art assets

Summary

Further reading

Managing Access with the Singleton Pattern

Technical requirements

Breaking down the pattern

Diagramming the pattern

Pros and cons

Updating a MonoBehavior into a persistent singleton

Persisting the singleton between scenes

Testing for duplicate managers

Creating a generic singleton

Adding thread safety to the generic singleton

Thread locking during lazy instantiation

Creating singletons as ScriptableObjects

Summary

Further reading

Spawning Enemies with the Prototype Pattern

Technical requirements

Breaking down the Prototype pattern

Diagraming the pattern

Pros and cons

Implementing shallow and deep object copying

Adding a prototype interface

Making shallow object copies

Making deep object copies

Adding a prototype factory

Cloning prefabs

Creating a generic prototype component

Summary

Further reading

Join our community on Discord

Creating Items with the Factory Method Pattern

Technical requirements

Breaking down the Factory Method pattern

Diagramming the pattern

Pros and cons

Declaring our inventory products

Adding a product interface

Creating concrete products

Working with different factory class variations

Adding an abstract factory class

Building a concrete factory

Creating a concrete factory class

Building a parameterized factory

Creating a parameterized factory class

Scaling factories with reflection and LINQ

Adding GameObjects to the mix

Updating the Item class

Updating the Abstract Creator class

Updating the concrete factory

Updating the reflection factory

Extending products and factories with Scriptable Objects

Summary

Further reading

Join our community on Discord

Building a Crafting System with the Abstract Factory Pattern

Technical requirements

Breaking down the Abstract Factory pattern

Diagramming the pattern

Pros and cons

Creating related but independent products

Scripting product interfaces

Adding concrete products

Creating abstract and Concrete Factory classes

Writing a client class using only interfaces

Creating related and dependent products

Writing dependent product abstract classes

Creating concrete products

Adding abstract and Concrete Factory classes

Updating the client

Optional factory variations

Parameterized factories

Creating a factory of factories

Summary

Further reading

Join our community on Discord

Assembling Support Characters with the Builder Pattern

Technical requirements

Breaking down the Builder pattern

Diagramming the pattern

Pros and cons

Creating a base ally and builder interface

Scripting the product class

Declaring a common builder interface

Adding concrete builders

Using a director class

Integrating GameObjects

Transitioning to a Fluent Builder structure

Summary

Further reading

Managing Performance and Memory with Object Pooling

Technical requirements

Breaking down the Object Pool pattern

Diagramming the pattern

Pros and cons

Writing an Object Pool class

Creating objects and filling the pool

Retrieving pooled objects

Releasing pooled objects

Resetting pooled objects

Object Pooling customizations

Queues over lists

Thread-safe pools

Managing different pooled objects

Leveraging Unity’s ObjectPool class

Summary

Further reading

Join our community on Discord

Binding Actions with the Command Pattern

Technical requirements

Breaking down the Command pattern

Diagramming the pattern

Reusable versus single-use commands

Pros and cons

Building a basic Command structure

Creating reusable commands

Adding a receiver

Adding an invoker class

Using an input listener

Updating the client

Creating coupled commands

Implementing an undo/redo system

Stacking commands

Adding an undo feature

Adding a redo feature

Confirming and clearing commands

Summary

Further reading

Join our community on Discord

Decoupling Systems with the Observer Pattern

Technical requirements

Breaking down the Observer pattern

Diagramming the pattern

Pros and cons

Creating a basic Observer structure

Writing the abstract base objects

Making the UI an observer

Subscribing observers and publishing events

Pattern strategies and optimizations

Choosing a communication strategy

Listening for different state changes

Cleaning up hanging resources

Using C# event types

Delegates and events

Updating to Action types

UnityEvents and the Inspector

Adding Unity events

Adding UnityEvent parameters

Persistent versus non-persistent events

The final boss – drag-and-drop system

Writing a ScriptableObject event

Creating listeners

Creating ScriptableObject event assets

Invoking the SOEvent

Connecting the system

Performance considerations

Picking the right implementation

Summary

Further reading

Join our community on Discord

Controlling Behavior with the State Pattern

Technical requirements

Breaking down the State pattern

A little automata theory

Diagramming the pattern

Pros and cons

Creating a turn-based battle system

Defining abstract and base states

Creating a state machine

Protecting transitioning states

Subclassing state machines

Creating concrete states

Base state

Setup state

Player state

Enemy state

Ending state

Putting it all together

Initializing concrete states

Updating the client

Using a hierarchical state machine

Adapting for concurrent state machines

Unrelated concurrent states

Related concurrent states

Storing state history

Pushing new states

Reverting to previous states

Summary

Further reading

Adding Features with the Visitor Pattern

Technical requirements

Breaking down the Visitor pattern

Diagramming the pattern

Pros and cons

Creating a save system

Structuring the interfaces

Adding a concrete visitors

Adding concrete elements

Adding an object structure

Working with composite elements

Accounting for selective visitation

Summary

Further reading

Join our community on Discord

Swapping Algorithms with the Strategy Pattern

Technical requirements

Breaking down the Strategy pattern

Diagramming the pattern

Pros and cons

Creating a sorting system

Defining a strategy interface

Adding concrete strategies

Creating a context

Optimizing your strategies

Adding interchangeable strategies

Using default strategies

Strategies the Unity way

Upgrading to ScriptableObjects

Summary

Further reading

Join our community on Discord

Making Monsters with the Type Object Pattern

Technical requirements

Breaking down the Type Object pattern

Diagramming the pattern

Pros and cons

Creating monsters

Adding a Type Object

Configuring monsters

Optimization, inheritance, and behavior

Controlling allocation and initialization

Adding parent type objects

Sharing behavior isn’t easy

Creating ScriptableObject monsters

Summary

Further reading

Taking Data Snapshots with the Memento Pattern

Technical requirements

Breaking down the Memento pattern

Diagramming the pattern

Pros and cons

Creating and restoring data snapshots

Adding the memento class

Setting up the caretaker

Updating the originator

Wiring up the client

Working with MonoBehaviours

Memento pattern variations

Storing memento history

Bundling originator and caretaker

Managers and ScriptableObject snapshots

Persisting data

Summary

Further reading

Join our community on Discord

Dynamic Upgrades with the Decorator Pattern

Technical requirements

Breaking down the Decorator pattern

Diagramming the pattern

Pros and cons

Building a simple Decorator

Adding a Component interface

Creating a concrete Component

Using a base Decorator

Building a concrete Decorator

Chaining multiple Decorators

Adding customized behaviors

Using ScriptableObject Decorators

Static vs dynamic decoration in action

Summary

Further reading

Join our community on Discord

Converting Incompatible Classes with the Adapter Pattern

Technical requirements

Breaking down the Adapter pattern

Diagramming the pattern

Pros and cons

Building a controller adapter

Defining a target

Adding the class adapter

Creating an object adapter class

Mapping properties in separate hierarchies

Summary

Further reading

Simplifying Subsystems with the Façade Pattern

Technical requirements

Breaking down the Façade pattern

Diagramming the pattern

Pros and cons

Building a subsystem Façade

Defining subsystem objects

Initializing subsystems

Adding a public Façade method

Adding multiple Façade methods

Upgrading Façades

Summary

Further reading

Join our community on Discord

Generating Terrains with the Flyweight Pattern

Technical requirements

Breaking down the Flyweight pattern

Diagramming the pattern

Pros and cons

Creating shareable terrains

Adding a Flyweight interface

Extracting extrinsic state

Building a concrete Flyweight

Adding a Flyweight factory

Working with unshared Flyweights

Upgrading to ScriptableObjects

Flyweight or Type Object?

Summary

Further reading

Global Access with the Service Locator Pattern

Technical requirements

Breaking down the Service Locator pattern

Diagramming the pattern

Pros and cons

Creating services and contracts

Logging and saving contracts

Planning for missing services

Building a Service Locator class

The Null Object pattern

Adding a generic solution

Extending locator functionality

Initializing services

Explicitly registering services

Unregistering services

A word on scope

Singleton Service Locators

Grouping services

A service for every level

Summary

Further reading

Join our community on Discord

The Road Ahead

SOLID principles and you

The patterns left out (but not forgotten)

Resourceful resources

Getting involved

Summary

Other Books You May Enjoy

Index

Landmarks

Cover

Index

Learning Design Patterns with Unity

Learn the secret of popular design patterns while building fun, efficient games in Unity 2023 and C#

Harrison Ferrone

Learning Design Patterns with Unity

Copyright © 2024 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Senior Publishing Product Manager: Larissa Pinto

Acquisition Editor – Peer Reviews: Gaurav Gavas

Project Editor: Meenakshi Vijay

Content Development Editor: Tanya D’cruz

Copy Editor: Safis Editing

Technical Editor: Tejas Mhasvekar

Proofreader: Safis Editing

Indexer: Pratik Shirodkar

Presentation Designer: Rajesh Shirsath

Developer Relations Marketing Executive: Sohini Ghosh

First published: May 2024

Production reference: 2080724

Published by Packt Publishing Ltd.

Grosvenor House

11 St Paul’s Square

Birmingham

B3 1RB, UK.

ISBN 978-1-80512-028-5

www.packt.com

Contributors

About the author

Harrison Ferrone was born in Chicago, Illinois, and was raised all over the U.S. He’s worked at Microsoft, PricewaterhouseCoopers, and a handful of small start-ups, but most days you can find him creating instructional content for LinkedIn Learning or working on new projects. He holds various fancy-looking pieces of paper from the University of Colorado Boulder and Columbia College Chicago. Despite being a proud alumnus, these are stored in a basement somewhere. After a few years as a full-time iOS and Unity developer, he fell into a teaching career and never looked back. Throughout all this, he’s bought many books, been owned by several cats, worked abroad, and continually wondered why Neuromancer isn’t on more course syllabi.

Completing this book wouldn’t have been possible without the loving support of Kelsey, my wife and partner in crime on this journey.

About the reviewers

Charles Haché, also known as Oz, is a seasoned game development teacher with extensive experience at both the high school and university levels. As a technical mentor for graduates and individuals seeking to upskill, he specializes in Unity, C# OOP, architecture, design patterns, VR, AR, instructional design, and leadership. In addition to his teaching roles, Oz has contributed to various game development projects and has been instrumental in shaping curricula that bridge the gap between academic theory and practical application. He has worked with several esteemed organizations, including the University of Victoria as a Unity Certified Instructor, Mastered Studios Inc. as a technical mentor, and Pearson as a subject mattter expert in Unity. His expertise has been sought after for technical reviews and consultations on several notable publications within the game development community.

In working on this book, I would like to extend my heartfelt gratitude to my wife, Kari, for all the support she has given me over the past 12 years. I look forward to sitting on a porch with her, holding hands, now that this book is finished.

Luiz Henrique Bueno, also known as Rick Good, began his career in Brazil, focusing on smart home and IoT projects. This laid the groundwork for his expertise in software architecture and design patterns, leading to proficiency across iOS, macOS, Windows, and XR platforms. By 2016, he had become one of Brazil’s first Unity Certified Developers, showcasing his depth in game development. After moving to the USA in 2017, he founded Toodoo Studio, where he successfully launched several game titles, including Global Destruction VR, Blondie on the Road, Thomas’ Tales, and Food Chase, using Unity and Unreal Engine. His contributions extend to authoring and technically reviewing Unity books and creating educational content on Unity and Unreal development. His technical skills span from C++ and C# to Swift, React, and Node.js. He has ventured into XR applications for Apple Vision Pro and Meta Oculus devices, continuing to push the boundaries of interactive technology. For a closer look at his projects and achievements, visit his portfolio at https://rickgood.me.

Join our community on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://packt.link/gamedevelopment_packt

Share your thoughts

Once you’ve read Learning Design Patterns with Unity, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.

Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.

Download a free PDF copy of this book

Thanks for purchasing this book!

Do you like to read on the go but are unable to carry your print books everywhere?

Is your eBook purchase not compatible with the device of your choice?

Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily.

Follow these simple steps to get the benefits:

Scan the QR code or visit the link below:

https://packt.link/free-ebook/9781805120285

Submit your proof of purchase.That’s it! We’ll send your free PDF and other benefits to your email directly.

1

Priming the System

As programmers, our job is to design, build, and integrate software features into cohesive products that are useful and hopefully bring joy to our users. And somewhere in all of that, the software has to work. Our job can be anything from building a login feature or networking layer to a subscription and payment portal. In games, we’re thinking of moving a playable character, building an adaptive AI enemy, or adding a random level generator. The big idea that should pop out at you is that these elements don’t exist in a vacuum. In fact, they tend to wither and die of boredom if they’re not connected to each other.

When you think of the code powering your applications and games, you’re thinking about a collection of systems built around specific features or mechanics, but more importantly, around specific problems that come with making those features work as expected. If I could manage it, I’d have the word systems jump off the page and do a little dance to get your attention, because that’s what this entire book is about: games (and applications) are made up of systems – the more these systems grow and interact with each other, the more complicated things get.

Systems may not be anything new to you, and they’re certainly not new to the programming industry, but there are problems that you’ll repeatedly run into that are directly related to systems running and interacting. And that’s really what I want to drive home before we get started: design patterns provide reusable solutions to problems we face in our everyday software lives and offer a common vocabulary for understanding and talking about best practices.

Now the real work starts – how do we identify the parts of our code that could benefit from a design pattern? How does all this relate to software architecture? How can I adapt these patterns for game development in Unity?

These are big questions, which is why we’re spending this first chapter getting a holistic feel for the following topics:

Software architecture vs software designDesign patterns and the categories they belong to (creational, structural, and behavioral) Why use design patterns at all?When to use design patternsCommon pitfallsStarter projects for this book

By the end of the chapter, you’ll have the necessary foundation to dive into design pattern implementations and a big-picture view of the flexibility, reusability, and structure they can add to your projects. But first, let’s make sure you know what to expect from this book and that you’re coming to the table with the prerequisites for getting the most out of the experience.

At the time of writing, all chapter examples and project files were built using Unity 2023.1.5f1. Unity is the visual tool we’re using to make the learning process more inviting, but the bones of the design pattern content are still 100% applicable to C# projects. The main differences that come into play between Unity, C#, and design patterns are how objects are created (new classes vs MonoBehaviours) and assembled (inheritance vs GameObjects and Components). Don’t worry, we’ll cover these variations in each pattern. Learning how each design pattern works as a system doesn’t depend on a specific platform or language – it’s all problem-solving skills!

A word about software architecture

Software architecture is a big topic and shouldn’t be confused with software design even though they both deal with “patterns.” When we talk about architectural patterns, we’re talking about big-picture solutions to problems that are typically focused on how multiple components work together at the application level, as well as conventions, best practices, and principles that guide the overall development process. When we talk about software design patterns, we’re talking about problems that crop up when you’re building those internal components. Figure 1.1 provides a simplified example of the key difference we’re talking about: Process A is made up of multiple internal components (the software design portion) while managing and organizing how these internal components fit together (the architectural portion).

Figure 1.1: Example diagram of software architecture vs design

Clear as mud? OK, let’s take a more relatable example: if we think about building a house, the architectural patterns are focused on how the entire house fits together and how the component parts (electrical, plumbing, insulation, etc.) come together into a functioning structure you can live in. Think LEGO (because that always helps me): architectural patterns deal with problems affecting the overall structure you’re building, while design patterns focus on the individual LEGO blocks that make up the final structure. You ideally need both types of solutions in a great application or game, but they solve fundamentally different problems, which is why I bring this distinction up so early in our journey. We’ll be focused on the software design aspect of things, but you can (and should) continue your journey into architectural waters after or in conjunction with this book.

In your everyday life, when you open Twitter (or whatever app you go to first thing in the morning), you’re involved in what’s called the Client-Server architectural pattern – the client (the app on your phone) consumes information from a server somewhere in a basement and displays it to you in a way you can understand. But it’s not concerned with how the app remembers your login credentials or uses face recognition to log you in. Those are software design problems because they focus on the internal features of the application.

Before we get to the why, when, and how of design patterns, we need to agree on a basic understanding of what good software design is to see the benefits of design patterns. Books on software design are everywhere. Amazon thinks you want them, and your co-workers probably have a few gathering dust on their desks right now. Hell, your computer monitor might be sitting on a few of them at this very moment. This isn’t one of those books.

I like to think of software design as a tool that might make things harder in the moment but easier in the long run, like going to the gym. It’s a pain to use the Stairmaster every day, but being healthy has far-reaching benefits throughout a lifetime. The same is true of approaching a project with an eye to software design. With that in mind, let’s set up a simple definition for ourselves as a kind of guiding light for what we mean by good software design:

A codebase that is flexible, maintainable, and reusable is the product of good software design.

This is easier said than done no matter how well-intentioned you or your team start off developing your game. Decoupling your code can speed up your development process, but it adds overhead and maintenance time. On the other hand, interdependent code can be costly when you want to make changes, big or small. It’s all about balance, even if you can’t have everything you want all the time. Fast, good, or cheap – you can only ever have two.

With a working definition of good software design, we can move on to talking about what design patterns are, how they’re categorized, and how they fit into Unity projects.

What are design patterns?

If I haven’t harped on this point enough, I want you to keep thinking to yourself “Design Patterns are systems” over and over. Let’s say that again, design patterns are systems, and systems are designed to solve specific problems. It doesn’t matter if it’s the brake system in your car, the biological systems that run our bodies, or the banking system in Figure 1.2. They’re all systems and they’re all trying to solve a problem or keep their respective system organized and balanced (and sometimes both).

Figure 1.2: Diagram of how bank accounts work using credit and debit

More than anything, design patterns focus on making code more flexible and reusable, two tenets we’ll hammer away at throughout this book. Just keep repeating our mantra every time we dive into a new pattern: design patterns are systems!

The Gang of Four

Back in 1994, a group of four daring engineers banded together to better tackle OOP practices and recurring problems that kept popping up in their programs; Design Patterns: Elements of Reusable Object-Oriented Software written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides is the product of that team-up. The book covers 23 design patterns split into three categories according to their core functions, with examples written in C++ and Smalltalk. While the book was and continues to be an important resource, these patterns have been adapted and expanded since 1994, leaving some more (and some less) relevant depending on your chosen programming language and environment.

There are also those who have pointed out that a fair few of the patterns are replacements for perceived missing features in the C++ language. However, this argument has never resonated with me when I look at my code in other languages (whether it’s C# or something like Swift) because wearing my systems-thinking glasses has always made me think more critically and write code more intentionally.

You’ve likely heard the phrase “Gang of Four” whispered with quiet reverence (or loud and angry fist-shaking, depending on who you’re talking to), but with topics like this, I’ve found it’s best to learn design patterns as a skill before making judgments. There are impassioned programmers on both sides of the aisle, which means getting your hands dirty is the best thing you can do for yourself. A lot of the debate gets lost in theoretical or pedagogical minutiae (and personalities) but the core skillset behind design patterns has always been a useful tool for programming.

Pattern categories

There are three categories that all original design patterns fall into – Creational, Behavioral, and Structural. As with all things, additional patterns have evolved since 1994, resulting in useful patterns that were not included in the original Gang of Four book that we’ll cover on our journey and a few honorable mentions (because we can’t cover every design pattern in a single book).

Before we get into the category details, I feel it’s important to address a problem that crops up at the beginning of most journeys into design patterns – how do I find the right pattern for the problem I’m facing? Do I need to read and memorize every pattern, or is there a better way to navigate this topic?

The answer might surprise you, but no, this isn’t a memorization game and you don’t get extra points for knowing everything about every design pattern – systems thinking is a learned skill, not a closed-book test. It’s almost a detective game: first, knowing what problems each pattern category addresses is super important because it narrows the field you have to search. Second, reading the first few pages of each chapter in the applicable category will show you pretty quickly if you’re in the right place. From there, the more you use design patterns, the more you’ll get a feel for the problems and effective solutions out in the wild. As you’ll see, design patterns offer solutions to well-documented problems, but they’re not set in stone; it’s up to you to adapt them to your project.

Now that we know the basics, let’s dive into the nitty-gritty of each design pattern category and what specific problems they aim to solve.

Creational patterns

Creational patterns deal with creating objects that are uniquely suited to a given situation or use case. More specifically, these patterns deal with how to hide object and class creation logic, so the calling instance doesn’t get bogged down with the details. As your object and class creation needs become more complex, these patterns will help move you away from hardcoding fixed behaviors toward writing smaller behavior sets that you can use to build up more complex features (think LEGO). A good creational pattern black-boxes the creation logic and simply hands back a utility tool to control what, who, how, and when an object or class is created.

The creational patterns we’ll cover are listed in the following table:

Pattern

Description

Singleton

Ensure a class has only one instance and provide a global point of access to it – commonly used for features like logging or database connections that need to be coordinated and shared through the entire application.

Prototype

Specify the kinds of objects to create using a prototypical instance and create new objects from the “skeleton” of an existing object.

Factory Method

Define an interface for creating a single object, but delegate the instantiation logic to subclasses that decide which class to instantiate.

Abstract Factory

Define an interface for creating families of related or dependent objects, but let subclasses decide which class to instantiate.

Builder

Allows complex objects to be built step by step, separating an object’s construction from its representation – commonly used when creating different versions of an object.

Object Pool

Avoid expensive acquisition and release of resources by recycling objects that are no longer in use – commonly used when resources are expensive, plentiful, or both.

Table 1.1: List of creational design patterns with descriptions

Behavioral patterns

Behavioral patterns are concerned with how classes and objects communicate with each other. More specifically, these patterns concentrate on the different responsibilities and connections objects have with each other when they’re working together. Like structural patterns, behavioral patterns use inheritance to divvy up behaviors between classes, which gives you the freedom to let go of any white-knuckled control flow responsibilities and focus on how objects can work together.

The behavioral patterns we’ll cover are listed in the following table:

Pattern

Description

Command

Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests and the queuing or logging of requests.

Observer

Define a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically.

State

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class – commonly used when object behavior drastically changes depending on its internal state.

Visitor

Define a new class operation without changing the underlying object.

Strategy

Define a family of interchangeable behaviors and defer setting the behavior until runtime.

Type Object

Allow the flexible creation of new “classes” from a single class, each instance of which will represent a different type of object.

Memento

Capture and externalize the internal state of an object so it can be restored or reverted to this state later – without breaking encapsulation.

Table 1.2: List of behavioral design patterns with descriptions

Structural patterns

Structural patterns focus on composition, or how classes and objects are composed into larger, more complex structures. Structural patterns are heavy on abstraction, which makes object relationships easier to manage and customize. Patterns in this category will use inheritance to let you mix and match your class structures as well as create objects with new functionality at runtime.

The structural patterns we’ll cover are listed in the following table:

Pattern

Description

Decorator

Attach additional responsibilities to an object dynamically keeping the same interface.

Adapter

Convert the interface of a class into another interface clients expect. An adapter lets classes work together that could not otherwise because of incompatible interfaces.

Façade

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a high-level interface that makes the subsystem easier to use.

Flyweight

Shares common data between similar objects to limit memory usage and increase performance.

Service Locator

Provide a global access point for services without coupling client code to the concrete service classes.

Table 1.3: List of structural design patterns with descriptions

The goal with each of these problems is to use a solution that is coherent, reusable, and above all, designed for efficient change.

Why use design patterns?

In four words – reusability, flexibility, and maintainability. You’ll see these concepts pop up around software architecture and code complexity topics, but they are the central beneficial tenets of design patterns. For example, if you need to create variations of the same object in your game, it makes more sense to create a build system instead of individual hard-coded objects. Figure 1.3 illustrates an assembly line that turns out little robots, with each step fulfilling a specific function in the object creation process.

This is a simplified but effective mental model for the Builder pattern:

Figure 1.3: Reusable Builder pattern creating objects

Let’s start with reusability, which OOP principles like encapsulation, abstraction, and inheritance already address and are the cornerstones of many design patterns in this book. There’s no better feeling than sitting down with a new feature task to implement and realizing you already have the building blocks you need to get it done. Your only real task is to stack them together into a form that makes the feature work and test it out. With design patterns, you have the option of using class inheritance, delegation, and object composition to create reusable components for your game code. Each has its pros and cons, which we’ll get into later, but they can also give you an efficiency bump when you’re writing new code.

Figure 1.4: Flexible control scheme diagram using the Command pattern

In terms of flexibility, design patterns can help you structure your code for the future, and by the future, I mean change. When we talk about change and reusability, we’re talking about code that can handle changing requirements and changing scales. No program you will ever write is going to be static, because no code is perfectly balanced on the first try. If you don’t build in flexibility, the first strong wind is going to snap your project in two, and that means redesigns, feature implementations, and new rounds of testing.

The beauty of correctly using design patterns is that you don’t just have to choose one – you can mix and match to get the desired result. For example, your game will always need player controls like in Figure 1.4, but what if you want to switch out individual commands or button assignments? Your code would be more flexible if you could separate the control input from the implementation, which is something the Command pattern is great at.

Figure 1.5: Adapter pattern as an example of designing for change and easy maintenance

Finally, we come to maintainability. The ideal is to have working code that has the right level of architecture and complexity; not only does this make our code readable, it’s easier to debug if each component or class has a defined role we can isolate. I italicized “right” because it’s a tricky task. On the one hand, you want enough structure in your project to make updating the code as easy and efficient as possible. On the other hand, too much complexity can needlessly clog up a project, having the exact opposite effect. Correctly identifying design patterns that’ll help you achieve the former instead of the latter is the balancing act of code maintenance.

When to use design patterns

You know you’re doing something interesting when the answer to the question of why or when is both maddeningly simple and complex. The answer you’ll hear the most about design patterns is that you should only use them when they’re needed. Helpful, right?

The easiest way to know if you should use a design pattern is if you identify a concrete problem area in your code. Symptoms of a problem include code smells like tightly coupled classes, monolithic subclass hierarchies, and dependencies on specific operations, object representations, and hardware. Hardware and software dependencies are particularly acute in game development when you need to port your code to different devices or systems.

NOTE

If code smells are a new concept, don’t worry, they’re just a fancy industry term for characteristics of your code that might point to a deeper problem. Code smells can include huge, bloated classes, misuse of Object-Oriented principles, changes in one place necessitating changes in other places in your application, and excessively coupled objects.

All those symptoms are related to the idea of designing for change, and how easy or hard it is to make changes to your code. This is a topic we’ll explore with each pattern individually, but here are a few high-level red flags to look out for:

Is a class hard to add to or maintain?How much knowledge does the programmer making a change need to have of the entire system they are working on?Is it difficult to create slightly different objects from existing classes?Does a change in one area crash a secondary or even unrelated part of the codebase?Can your code satisfy an operational request in only one way?If you’re working with algorithms, does changing the implementation break the object using the algorithm?

If any of these questions ring a bell, then your code could use some design pattern love. But design patterns aren’t all fun and games (pun intended); there are pitfalls with any tool – software or physical – that we have to address before diving in.

Common pitfalls

We can get to a well-architected state with less stress if we keep an eye on some common pitfalls. You’ll notice as you read this book that it’s not so much a step-by-step tutorial as it is a reference for when you need to implement a design pattern. Half the skill is learning if, and when, to use one at all.

Let’s summarize:

Don’t use a design pattern just for the sake of using it. Impressing a review team or someone on GitHub isn’t worth it. If a part of your game needs some abstraction and decoupling, absolutely do it. Only add complexity where it’s needed. If you find that your project is overly architected, ask yourself if your codebase wouldn’t be cleaner and easier to read without it.Good software architecture is never free. Abstracting code takes time to learn, write, and maintain, and you may take runtime hits to performance and speed.

So why do any of this design pattern nonsense in the first place? Because complexity isn’t a bad word, performance can be improved later on, and the efficiency bump in your development process is well worth the up-front investment. You won’t get it right the first time, so enjoy the iteration. It’s all part of the process. And if you’re like me, using design patterns is like a puzzle, and when that code runs, it’s like finding that last pesky piece your cat hid under the couch – it’s magic.

About the example projects

When I first started learning about design patterns, I found the wealth of resources, tutorials, and applicable scenarios overwhelming (if this has happened to you too, I hope finding this book will make your journey much, much easier). The problem wasn’t the technical information that I was finding, but the way it was presented. More often than not, the design pattern content seemed to be shrouded in dry, overbearing technical language, complicated examples, or just plain old incorrect solutions.

One of my main goals in writing this book was to present the foundation of these wonderful tools with as little fluff as possible. However, this turns problematic when trying to find a balance between teaching the design pattern itself and getting it to run in a meaningful way – because just like design patterns, new skills wither and die in a vacuum. This is all to say that I’ve created the starter projects for each chapter as simply and meaningfully as I possibly can. The balance is delicate, and you might find the examples weighted to one side or the other, but it’s always been with the intention of making the design pattern the star.

Client code

Some of the ways I’ve implemented UI code, client scripts, or any of the simple systems around using each design pattern are not production-ready or best practices – they are the simplest way of using the pattern with context. On the flip side, I’ve given as much thought and page count as possible to pattern variations, extensions, and best practices so you have concrete tools to bring to your own projects.

Again, this book isn’t about Unity (although it’s a delightfully fun learning environment), but rather how we can learn problem solving skills and apply them to software.

Old vs new input system

As you’ll see in the next chapter and onwards, I’m using Unity’s old Input System rather than the new Input System. Why? Because it’s the simplest way to get the client code running with a minimum amount of setup and screenshots for you to follow. I love the new input system (I feel I need to put this on the record or risk banishment), but it does require a little specialized knowledge and experience that doesn’t contribute to design patterns. As always, I encourage you to experiment with implementing the design pattern solutions in whatever context works best for your project. For learning, I’ve found the old Input System works best.

Art assets

I love beautiful Unity projects that are full of life, sound, ambiance, and thematic feeling, but these things don’t help us learn new skills – in fact, they tend to distract at best and hinder at worst. All example projects use the most basic primitive objects and materials possible (essentially white boxing everything), not only to keep things consistent but to keep you focused on the design patterns. And who knows, the lack of excitement in our projects might just inspire you to deploy your new skills in ways you hadn’t thought of before.

I hope the simplicity of the examples I’ve prepared doesn’t make you nervous, because it should really make you hopeful. The fact that we can use these patterns as systems to be plugged in and interchanged with the UI, clients, or any other aspect of application development only strengthens the very first point of this chapter – design patterns are systems!

Summary

I know this was a lot of logistics and theory to get through – congratulations on sticking with it even if you were itching to start coding. This chapter has a lot of useful resources and content that you can (and should) refer to as you continue your design pattern adventure. Remember, we defined good software as flexible, reusable, and maintainable, so keep that at the forefront of your mind when reading each chapter.

Design patterns are internal systems that solve specific problems, and half the battle is matching the problem you have to the pattern that solves it best. There’s a time and place to use design patterns, but overusing (or misusing) them can lead to complexity you don’t need and time costs you don’t have to spend. Your ultimate job (which is down to me) is to come out of the other end of this book literate in the language of design patterns.

In the next chapter, we’ll dive into our first official design pattern – the Singleton pattern – and get a taste of thinking in systems!

Further reading

If any of these topics sounds foreign, you can take a step back and check out the resource list I’ve put together below or push through and learn as you go along.

For C#, I’d recommend starting with my beginner-level book Learning C# by Developing Games with Unity (7th Edition). I wrote it specifically to get new programmers up to speed with core programming skills from the ground up using C# and Unity, and you can find it at https://www.amazon.com/Learning-Developing-Games-Unity-coding/dp/1837636877.For a deeper dive into the C# language, Mark Price’s book C# 11 and .NET: Modern Cross-Platform Development Fundamental is fantastic, which you can find at https://www.amazon.com/11-NET-Cross-Platform-Development-Fundamentals/dp/1803237805.For Unity content, I’d recommend Hands-On Unity 2022 Game Development by Nicolas Alejandro Borromeo (https://www.amazon.com/Hands-Unity-2022-Game-Development/dp/1803236914) and Unity 3D Game Development by Anthony Davis, Travis Baptiste, Russell Craig, and Ryan Stunkel (https://www.amazon.com/Unity-Game-Development-design-beautiful/dp/1801076146).

Leave a review!

Enjoying this book? Help readers like you by leaving an Amazon review. Scan the QR code below to get a free eBook of your choice.

2

Managing Access with the Singleton Pattern

In the last chapter, we went over the core of what design patterns are, the common problems they solve, and how we’ll go about learning and implementing each of them throughout our adventure. In this chapter, we’ll start our practical journey by exploring the Singleton pattern, which helps when you want a single instance of a class to be globally accessible. For applications and games, you’ll commonly see this type of functionality with manager or service classes that keep track of global state or provide access to system-wide utilities. However, we need to be aware of potential risks with global state (and how to protect our newly accessible data), which we’ll discuss later in the chapter.

Anytime you bring up the Singleton pattern in programming circles, you’re likely to hear an audible sigh, some hushed booing, and maybe even an angry shout or two. And that’s precisely why I like to teach this pattern first! It’s one of the easiest design patterns to understand and implement (and, thus, abuse) but the hardest to use correctly (because, as we all know, there’s only one way to do things correctly in programming, right?). I might even go so far as to say the Singleton pattern is more useful with Unity-specific features like ScriptableObjects than the original implementation could have anticipated (more on that later).

Like all design patterns, the Singleton pattern has its specific uses, pitfalls, and practical implementation, which we’ll explore in the following topics:

Defining, diagraming, and analyzing the pros and cons of the Singleton patternUpdating a MonoBehaviour script into a persistent singleton classCreating a flexible generic singleton class using lazy instantiationMaking the generic singleton class thread-safe Creating singleton instances as ScriptableObject assets

Throughout this chapter, we’re going to actively avoid typecasting the Singleton pattern as the big, bad boogeyman of design patterns. Instead, we’ll focus on identifying areas where the pattern can help make your code more flexible, reusable, and maintainable while maintaining safety and encapsulation.

Technical requirements

To get started:

Download or clone the GitHub repository at https://github.com/PacktPublishing/C-Design-Patterns-with-Unity-First-Edition.Open the Ch_02_Starter project folder in Unity Hub.Navigate to Assets | Scenes, and double-click on SplashScreen.

The starter project for this chapter has two scenes – a splash screen with the title of our little game and a button to start the adventure. When you click Start, the game transitions to a new scene, where you can move a capsule around a small arena and collect spheres.

As for the scripts:

Item.cs is attached to each Item