0% found this document useful (0 votes)
16 views440 pages

Word Games With Unity 9781912084364

The document is a guide titled 'Word Games With Unity' by Roger Engelbert, aimed at teaching readers how to develop word puzzle games using the Unity game engine and C#. It covers various topics including the Unity editor, scripting, user input, and includes examples of games like Hangman and Scramble. The book is designed for individuals with some programming knowledge and provides source material available for download from a Git repository.

Uploaded by

Joe Waters
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views440 pages

Word Games With Unity 9781912084364

The document is a guide titled 'Word Games With Unity' by Roger Engelbert, aimed at teaching readers how to develop word puzzle games using the Unity game engine and C#. It covers various topics including the Unity editor, scripting, user input, and includes examples of games like Hangman and Scramble. The book is designed for individuals with some programming knowledge and provides source material available for download from a Git repository.

Uploaded by

Joe Waters
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 440

Word Games

With Unity

Roger Engelbert
Word Games

With Unity

Roger Engelbert
The author and publisher have taken care in the preparation of this book, but
make no expressed or implied warranty of any kind and assume no responsibility
for errors or omissions. No liability is assumed for incidental or consequential
damages in connection with or arising out of the use of the information or
programs contained herein.

Cover Design by
Roger Engelbert

Engelbert, Roger, 1976-


Word Games With Unity / Roger Engelbert.
p. cm.
ISBN-13: 978-1-912084-36-4
1. Unity . 2. Game Development.
3. Word Games. I. Engelbert, Roger, 1976- II. Title.

Copyright © 2018 Engelbert Publishing.


All rights reserved. This publication is protected by copyright, and permission
must be obtained from the publisher prior to any prohibited reproduction, storage
in a retrieval system, or transmission in any form or by any means, electronic,
mechanical, photocopying, recording, or likewise. For information regarding
permissions, write to:

ISBN-13: 978-1-912084-36-4
The author and publisher have taken care in the preparation of this book, but
make no expressed or implied warranty of any kind and assume no responsibility
for errors or omissions. No liability is assumed for incidental or consequential
damages in connection with or arising out of the use of the information or
programs contained herein.

Cover Design by
Roger Engelbert

Engelbert, Roger, 1976-


Word Games With Unity / Roger Engelbert.
p. cm.
ISBN-13: 978-1-912084-36-4
1. Unity . 2. Game Development.
3. Word Games. I. Engelbert, Roger, 1976- II. Title.

Copyright © 2018 Engelbert Publishing.


All rights reserved. This publication is protected by copyright, and permission
must be obtained from the publisher prior to any prohibited reproduction, storage
in a retrieval system, or transmission in any form or by any means, electronic,
mechanical, photocopying, recording, or likewise. For information regarding
permissions, write to:

ISBN-13: 978-1-912084-36-4
Dedicated to everybody who knows words. Those who don't, can stop reading
right now.
Dedicated to everybody who knows words. Those who don't, can stop reading
right now.
Acknowledgments
I'd like to thank my family and friends, for the testing, feedback, and infinite
patience.
Acknowledgments
I'd like to thank my family and friends, for the testing, feedback, and infinite
patience.
About the Author
Roger Engelbert is a Game Developer and blogger at www.rengelbert.com/blog
His passion for classic arcade games led him to a long career writing code. He's
also an amateur illustrator, writer and incorrigible meddler. Some say he's got
too much time on his hands. Word!
About the Author
Roger Engelbert is a Game Developer and blogger at www.rengelbert.com/blog.
His passion for classic arcade games led him to a long career writing code. He's
also an amateur illustrator, writer and incorrigible meddler. Some say he's got
too much time on his hands. Word!
Contents at a Glance

Introduction
1. The Unity Editor
2. Scripting With C#
3. Word Lists
4. User Input
5. Game: Hangman
6. Game: Scramble
7. Game: Boggling
8. Game: Crossing
9. Testing On Devices
Contents at a Glance

Introduction
1. The Unity Editor
2. Scripting With C#
3. Word Lists
4. User Input
5. Game: Hangman
6. Game: Scramble
7. Game: Boggling
8. Game: Crossing
9. Testing On Devices
Contents

Introduction
Who This Book Is For
How The Book Is Organized
The Source Material

1. The Unity Editor


Creating A New Unity Project
The Big Four
The Project Panel
The Hierarchy Panel
What Is A Prefab?
The Scene And Game View Areas
The Inspector Panel
Practice, Practice, Practice
Summary

2. Scripting with C#
Not Another Boring Introduction To C#
MonoBehaviour
The Canvas
Events
And... Some More Code
Summary

3. Word Lists
Finding Word Lists
Load The Lists
Clean Them Up
Remove Names
Sort Into Frequency Groups
Loading Your Word List
Mapping By Length
Mapping By First Char
Mapping By Sorted Letters
Mapping With A Trie
Looking For Words
Is A Word
Has Words
Has A Word
Matches Pattern
Summary

4. User Input
Creating The Letter Tile Prefab
The TileChar Script
Creating The Tile Button Prefab
The TileButton Script
Creating The Grid Tile Prefab
The GridTile Script
Detecting Touches On Tiles
Creating The TilePanel
Listening To Input
The Delegate
The InputController
Creating Our TilePanel Script
Putting Our TilePanel Together
Creating a Grid
Building A Grid
Testing The Grid
Handling Input
Testing Our Grid
A Word On Properly Filling A Grid
Solving A Grid
Testing Our Solver
Summary

5. Game: Hangman
The Game
Generating The Puzzles
Generating The Data
The Letter Panel
Game Events
The Puzzle Data
The Game Tiles
The Game Controller
The Game Scene
HangmanPanel
The Hangman Controller
Taking It Further

6. Game: Scramble
The Game
The Dictionary
The Level Design
The Game Data
The Logic
The Game Events
The Player State
Loading The Game Data
The Game Dictionary
The Game Tiles
The Scramble Game Class
The Game Scene
The Tile Panel
The Main Game Script
Taking It Further

7. Game: Boggling
The Game
The Game Puzzles
The Game Logic
The Game Events
The Game Dictionary
The Grid
The Game Controller
The Game Scene
The Grid
The Game Script
Taking It Further

8. Game: Crossing
The Game
The Level Design
The Puzzles
Game Events
The Game Dictionary
The Game Tile
The Game Grids
The Game Controller
The Game Scene
The Word Grid
The Panel Grid
The Crossing Controller
Making The Game Refresh
Taking It Further

9. Testing On Devices
Publishing A Game On Android
Publishing A Game On iOS
Where To Go From Here
The Puzzles
Game Events
The Game Dictionary
The Game Tile
The Game Grids
The Game Controller
The Game Scene
The Word Grid
The Panel Grid
The Crossing Controller
Making The Game Refresh
Taking It Further

9. Testing On Devices
Publishing A Game On Android
Publishing A Game On iOS
Where To Go From Here
Introduction

Welcome to Word Games With Unity.


This book started out from the fact that I once spent over a year developing word
games, and only word games, at a game studio in London. This book contains all
the tricks I learned in that period, while building a good dozen prototypes.
Word games are quick to build and have a potential to be quite fun. The rules are
generally simple, and the knowledge is there already in every player's brain,
granted in different degrees; but people seem generally to enjoy the challenge of
it. And when you add to that the social aspects of the game, you could easily get
a major hit in your hands.
This book will teach you the ins and outs of building word puzzle games. Its aim
is to allow you, the person with all the ideas, to quickly put them to the test.
Introduction

Welcome to Word Games With Unity.


This book started out from the fact that I once spent over a year developing word
games, and only word games, at a game studio in London. This book contains all
the tricks I learned in that period, while building a good dozen prototypes.
Word games are quick to build and have a potential to be quite fun. The rules are
generally simple, and the knowledge is there already in every player's brain,
granted in different degrees; but people seem generally to enjoy the challenge of
it. And when you add to that the social aspects of the game, you could easily get
a major hit in your hands.
This book will teach you the ins and outs of building word puzzle games. Its aim
is to allow you, the person with all the ideas, to quickly put them to the test.
Who This Book Is For
This book is aimed at people with some knowledge of programming. It uses C#
as its main language but if you learned programming using any other language
you should be fine.
Just how much programming you need to know?
You need to be comfortable around loops and conditionals, arrays and functions.
That's it. Quite a lot of the code you do need to learn is Unity related, and it has
to do with Unity APIs and not specifically with C# syntax.
As far as Unity is concerned, this book will cover the bare minimum to have a
prototype built and tested. But it does not delve deeply into highly specialized
areas such as UI, Animations, Sound... We'll focus on getting our game ideas
tested, and then you can focus on making it all pretty later!
Who This Book Is For
This book is aimed at people with some knowledge of programming. It uses C#
as its main language but if you learned programming using any other language
you should be fine.
Just how much programming you need to know?
You need to be comfortable around loops and conditionals, arrays and functions.
That's it. Quite a lot of the code you do need to learn is Unity related, and it has
to do with Unity APIs and not specifically with C# syntax.
As far as Unity is concerned, this book will cover the bare minimum to have a
prototype built and tested. But it does not delve deeply into highly specialized
areas such as UI, Animations, Sound... We'll focus on getting our game ideas
tested, and then you can focus on making it all pretty later!
How The Book Is Organized
Here is a quick once-over of the information contained in this book.
If you have some experience as an Unity developer you may skip the first two
chapters.

Chapter 1: The Unity Editor


In this chapter I go over the main parts of the Unity interface. Just enough to get
us up and running with prototyping.

Chapter 2: Scripting with C#


Here we go over the basics of C#, in particular how to add new scripts inside the
Editor and how to hook them up to game objects and the engine logic.

Chapter 3: Word Lists


In this chapter we start concerning ourselves with word games and with one of
its core elements: word lists. We cover where to find them, how to prepare them
for games, how to search word lists for specific information... You know, all
about the words and the fundamentals of word game logic.

Chapter 4: User Input


We move to user input. How to build buttons, tiles, and grids inside Unity. How
to solve a grid of letters, searching for all possible words that can be generated
with a particular grid.

Chapter 5: Game: Hangman


In this chapter we build our first game, a version of the classic Hangman. We
take a look at how to process word lists for optimum game experience, finding
the best words for our game.

Chapter 6: Game: Scramble


In the second game, we build something a bit more challenging. The player is
presented with a scrambled word, and is challenged to generate all possible
words with the given letters. Once again, we analyse data for the best possible
puzzles.

Chapter 7: Game: Boggling


In our third game, we use grids for the first time. We put our grid solving logic to
the test and build a fun game with it.

Chapter 8: Game: Crossing


In our final game, we once again use grids, and work with the kind of logic you
would need to build Crosswords, or Word Find games.

pts inside the


Chapter 9: Testing on Devices
We go through the steps to publish our games to a device, both Android and iOS.
In the second game, we build something a bit more challenging. The player is
presented with a scrambled word, and is challenged to generate all possible
words with the given letters. Once again, we analyse data for the best possible
puzzles.

Chapter 7: Game: Boggling


In our third game, we use grids for the first time. We put our grid solving logic to
the test and build a fun game with it.

Chapter 8: Game: Crossing


In our final game, we once again use grids, and work with the kind of logic you
would need to build Crosswords, or Word Find games.

Chapter 9: Testing on Devices


We go through the steps to publish our games to a device, both Android and iOS.
The Source Material
You can download all source files from this book's Git repo:
https://github.com/rengelbert/UnityWordGames
You can then click the Clone or Download button and choose either option. If
you are not familiar with Git, you might as well pick Download.

Note:
When you open the sample project inside your version of Unity, the editor
may ask to update because the sample project was built on an earlier version
of the editor. Just say Ok and you should be fine.

If you want to be familiar with Git, check out my other book!


The sample project will have all scripts, textures and data files, plus all the test
scenes. But you will be asked to be build the game scenes yourself, for extra
practice. But don't worry, it's a breeze.
So let's begin.
The Source Material
You can download all source files from this book's Git repo:
https://github.com/rengelbert/UnityWordGames
You can then click the Clone or Download button and choose either option. If
you are not familiar with Git, you might as well pick Download.

Note:
When you open the sample project inside your version of Unity, the editor
may ask to update because the sample project was built on an earlier version
of the editor. Just say Ok and you should be fine.

If you want to be familiar with Git, check out my other book!


The sample project will have all scripts, textures and data files, plus all the test
scenes. But you will be asked to be build the game scenes yourself, for extra
practice. But don't worry, it's a breeze.
So let's begin.
1. The Unity Editor

If you have never used Unity before and are completely clueless as to its
interface, this chapter will get you up and running. The Unity editor can be
divided into four main areas; and a bunch of highly specialized mini editors and
tools targeting more specific elements in game design and development such as
animations, texture atlases, state machines among others. But for now we’ll
focus on the main areas of the editor and learn whatever else we need as the
need arises.

Here’s what we’ll learn:


- What is the Project panel
- What is the Hierarchy panel
- What is a Prefab
- What are the Game and Scene view areas
- What is the component composition area, or Inspector Panel
1. The Unity Editor

If you have never used Unity before and are completely clueless as to its
interface, this chapter will get you up and running. The Unity editor can be
divided into four main areas; and a bunch of highly specialized mini editors and
tools targeting more specific elements in game design and development such as
animations, texture atlases, state machines among others. But for now we’ll
focus on the main areas of the editor and learn whatever else we need as the
need arises.

Here’s what we’ll learn:


- What is the Project panel
- What is the Hierarchy panel
- What is a Prefab
- What are the Game and Scene view areas
- What is the component composition area, or Inspector Panel
Meet Bob

Throughout this Unity and C# extreme-bootcamp tutorial we'll use a mini


application as an example. So imagine you have this:

Figure 1.1 - Bob the duck

I call him Bob, the killer cyborg duck. And I want to make a game with him…
Fire up Unity and let's get started.
So I assume you already went to unity3d.com and downloaded and installed
Unity.
You will need to install the modules for the different platforms you wish to test
the games on. I recommend installing the Android platform module at the very
least. You can install these later as they become necessary, however.
Meet Bob

Throughout this Unity and C# extreme-bootcamp tutorial we'll use a mini


application as an example. So imagine you have this:

Figure 1.1 - Bob the duck

I call him Bob, the killer cyborg duck. And I want to make a game with him…
Fire up Unity and let's get started.
So I assume you already went to unity3d.com and downloaded and installed
Unity.
You will need to install the modules for the different platforms you wish to test
the games on. I recommend installing the Android platform module at the very
least. You can install these later as they become necessary, however.
Creating A New Unity Project
When you open Unity for the first time you will have to login to your Unity
account, so the folks at Unity know what license you picked; and you will need
to register if you still haven't an account. Registration is free.
If you chose the free license, just select that one, and then you will be presented
with a popup that allows you to create a new project, open an existing one, or
open the sample project if you chose to download that when you installed Unity.
Then follow these steps to create a new project:

Figure 1.2 - New Project Dialogue

1. Click the +New button tab if it's not already selected.


2. Name your project whatever you want, I'll name mine CybeDuckWars.
3. Select 2D.
4. Select a location for the project.
5. In the Organization dropdown you should see your Unity account ID. If not,
select it.
6. Click the Create Project button.
The editor will open on a new, unsaved scene. So go ahead and save this scene,
and call it GameScene or whatever you want.
Let's take a closer look at the editor's interface.

unt ID. If not,


6. Click the Create Project button.
The editor will open on a new, unsaved scene. So go ahead and save this scene,
and call it GameScene or whatever you want.
Let's take a closer look at the editor's interface.
The Big Four
The Unity Editor has four main areas we need to focus on. These are the bits of
the interface we’ll spend most of our time on and by the end of this book you’ll
become quite familiar with them. These areas are:
1. The Project Panel
2. The Hierarchy Panel
3. The Scene and Game View areas
4. The Inspector Panel
The image below illustrates where in the interface you find each of these areas.

Figure 1.3 - The Interface

However, depending on what layout arrangement you have selected in the editor,
the position of these areas and panels may change. But it should not be difficult
to find the right areas once we go over each of them in detail.

Source Material:
You can follow along with these tutorials in this chapter by using the Unity
sample project you'll find in this book's source files.. Once you download the
sample project, go to Unity, then simply go to File->Open Project and
navigate to the UnityWordGames folder. Then load up the scene:
Assets/Scenes/Scene_BobTheDuckTest

The Project Panel

Figure 1.4 - The Project Panel

This is where all the assets for your project will reside.
in the editor, When you create a new project Unity will create an Asset folder and its contents
can be seen here in the Project Panel.

Note:
As you can see in the previous image, you find something called the Console
panel, right next to the Project panel tab. When we start printing information
to the console, this is where the information will show up, inside that Console
tab. Nothing to do with the Project Panel though, I just thought I'd mention it.
:)

Normally, you will want to create a series of subfolders inside the Asset folder
representing the different types of assets your project utilizes: images, sounds,
data files...
Whether you want to create these subfolders or not, and whatever naming
convention you decide to follow, is entirely up to you as the developer. Just
know that with a few exceptions, which we'll go over soon, you can name
folders and files whatever you want.
To add files to your Project Panel just drag and drop files from your file system.
Or you can go to the project's folder in your file system and add the files there,
like you would with any folder or file in your system.
You can also create new assets by right clicking inside this area in the interface
and selecting the type of asset you wish to create.
If you right click inside the Project Panel, you will be shown a submenu from
which you can choose to create the following types of assets:

d its contents
Figure 1.5 - New Assets

Some of the folders have reserved names, meaning, they need to be called
something specific if they are to serve specific functions. For instance, if you
want to load data into your game at run time and you want this data to be
bundled into the final game package, you should store these files in a folder
called StreamingAssets. The stuff stored in this folder will be loaded as they
become necessary for your logic. Alternatively you could use a folder
called Resources and everything inside this folder can be instantiated at run
time, but it will be added to the game's initial loading size.
When you saved the first scene in this new project, the scene file was saved
inside your Asset folder and should appear listed in the Project Panel.
You will probably want to organize your assets in different subfolders. So let's
move the scene file:
1. Select the Assets folder in the Project Panel
2. Right click on the folder and select Create and Folder
3. Name the new folder Scenes
4. Drag the scene file to this folder
Let's bring in Bob the Duck:
1. In this book's source files there is a General_Assets folder, and inside it, a
PNG called Bob.png. Drag this image to the Project Panel.
2. Follow the steps from the previous tutorial and create a subfolder called
Textures or Images or whatever you think best and drag the PNG to this folder.
3. Select the PNG. Over on the right side of the interface, in the Inspector
Panel (which we'll go over in more detail soon) you should see the information
related to the image file.
Figure 1.6 - Sprite in the Inspector

In my copy of Unity there is a bug where, even after creating a new 2D project,
the Editor fails to treat imported images as Sprites by default. In order to fix this,
select the Texture Type drop down and select the option Sprite (2D and UI). If
your version of Unity has the same bug, you will see the Default option selected.
If you see the Sprite option selected after creating a 2D project, then the bug
was fixed. (if the bug is there, you will need to repeat this last step for every
image you wish to use as a 2D sprite.)
Note:
This bug also affects the camera. You may need to select the Main Camera in
the Hierarchy Panel and in the Inspector Panel, now showing the Camera
information, select Orthographic in the Projection drop down.

der to fix this,

ption selected.

Figure 1.7 - 2D Camera


Now, let's move on to the next big area in Unity's interface.

The Hierarchy Panel

Figure 1.8 - The Hierarchy Panel

This panel displays a list of all the items currently in the game scene, whether
they are visible to the player or not. These items are referred to as GameObjects
and can serve a number of functions: they could be cameras, UI elements, input
managers, game sprites, sound emitters, particle emitters… and on and on.
In this panel I can add the following elements:
GameObjects

Figure 1.9 - List of GameObjects

Let's add an empty GameObject, for kicks:


1. Right click inside the Hierarchy Panel
2. Select Create Empty
3. This will create a GameObject called, appropriately, GameObject. It only
has a position, and no other components. It can be used as a container to other
GameObjects or to hold a specific functionality.
And I can drag and drop each element inside the Hierarchy and reorganize them
into any type of parent-child relationship I want.
In order to create a sprite for Bob, just follow these steps:
1. Select the Bob.png inside the Assets folder in the Project Panel

Figure 1.10 - Select Bob png

2. Drag it to the Hierarchy Panel


3. This will create a sprite with that image, and position it at 0,0,0 in world
space. It is technically a GameObject with a SpriteRenderer component.
Figure 1.11 - Bob as a Sprite GameObject

4. Double click the bob sprite listed in the Hierarchy Panel and the Scene view
will move to it.
And if you drag a GameObject from the Hierarchy Panel to the Project Panel,
you turn the GameObject into a Prefab.
A pre- Huh?

What Is A Prefab?
This is one of the cornerstones of Unity development, right after GameObjects.
A Prefab is a blueprint for a specific type of GameObject. Once I have a
prefab, I can instantiate as many copies of that GameObject as I wish. I can also
pass a reference to this prefab to any script which will then be able to instantiate
copies of the prefab through code.
Any changes I make to the prefab will show up in every instance of that prefab.
So I can drag prefabs from the Project Panel and drop them into the Hierarchy
Panel, as many as I wish. They will appear highlighted in blue inside the
Hierarchy Panel.
Figure 1.12 - A Prefab in The Hierarchy Panel

If I change anything in the original prefab the change will be applied to all
instances. If I make a change to an instance of the prefab, I can choose whether
or not to apply that change to the original prefab and therefore all other
e Scene view instances. If I decide to apply the changes I need to select the instance of the
prefab inside the Hierarchy Panel, and in the Inspector Panel, click the Apply
button.

ish. I can also

Figure 1.13 - The Prefab Apply Button

It’s a pretty cool, and a pretty powerful feature. Especially since Prefabs can be
instantiated through code as we'll see soon!
Now let's take a look at the main viewing areas for our projects.
The Scene And Game View Areas

Figure 1.14 - The Scene And Game View

These are the main view areas for your game. A kind of stage, if you will.
The Scene panel shows you a world space view of your project. This is where
GameObjects can be selected, and moved about.
For a 2D game you might need to select the 2D tab:
Figure 1.15 - The 2D 3D View Toggle

It toggles the world space view from 2D to 3D.


The Game tab shows the simulation, or the actual game as it runs. The camera
GameObject will delimit a portion of this scene view area which will become
your Game View.

Tip:
When you drag a prefab into the Hierarchy Panel, it is immediately placed in
the Scene View, at the 3D coordinates 0, 0, 0. You can also drag items from
your Project Panel straight into the Scene View and these will appear in the
Hierarchy Panel accordingly.

Of importance here, right now, are the tool bar at the top of the view:

Figure 1.16 - View Tool Bar

And the playback controls:

Figure 1.17 - Simulation Playback Controls

If you are familiar with 3D editor or CAD software you probably recognize the
first set of tools. You use these respectively to:
Drag the view, Move selected objects, Rotate selected objects, Scale selected
objects, and Free Resize selected objects.
And the playback controls are pretty self explanatory, these allow you to run,
pause and stop the game simulation.
Something else you'll be able to change here is the aspect ratio of your game's
preview in the simulator. By default Unity will target Desktops and the aspect
ratios listed inside the dropdown (where it says Free Aspect in Figure 1.14) will
reflect that target. In Chapter 9 I show you how to change targets by selecting the
project Build Settings.
We can now go over the last big interface element.

The Inspector Panel


y selecting the

Figure 1.18 - The Inspector Panel

This is the brains of the Unity Editor. This is where your game will come to life.
Select any GameObject in the Hierarchy or in your Project panel and you will
load its available information in the Inspector area.
When I select the Bob sprite GameObject I see that it contains a
SpriteRenderer component which is responsible for displaying the duck
image we have in our Project panel.
Figure 1.19 - Bob Sprite in The Inspector Panel

The Bob GameObject also contains a Transform component which dictates the
position, size and rotation of the duck GameObject in the game view.
To add more components just click on the Add Component button.
Here are some of the stuff you could add to a GameObject:
Figure 1.20 - GameObject Components

ch dictates theThe combinations are infinite, since here’s is also where you’ll add the majority
of your scripts. These scripts will describe the behavior of your game elements
and how they interact with each other.
For now, let’s add two components to our Duck.
1. Click the Add Component button and then start typing Rigidbody 2D.
2. Click the button again and this time add the Circle Collider 2D component.
3. Click the Apply button in the Inspector to apply this change to the Bob
prefab.

Figure 1.21 - Bob's new Components

If you were to run the game simulator now, you would see the duck falling off
the screen.
This happened because you added two physics components to the sprite, adding
physical mass, physical shape and good old gravity. Bye Bob!
To stop the simulation, press the stop button.
To stop the simulation, press the stop button.
Practice, Practice, Practice
This is it for the interface, at least for now. I think at this stage it’s best to start
from the principle that the Unity Editor is not a complex tool to use. Because
really, it isn’t. It is, instead, a tool that can accomplish many different things
related to game development. But for the most part, each one of those things can
be accomplished easily, and their editors are neatly tucked away in the interface
so as not to bother you when they are not needed.
When we start building scripts and collecting a few games under our belt, we’ll
go over the steps outlined here a gazillion times: dragging assets to the hierarchy,
making prefabs, adding scripts and components to GameObjects, running the
simulation… and on, and on.
But next, we’ll talk a little bit about the language we’ll be using: C#.
Practice, Practice, Practice
This is it for the interface, at least for now. I think at this stage it’s best to start
from the principle that the Unity Editor is not a complex tool to use. Because
really, it isn’t. It is, instead, a tool that can accomplish many different things
related to game development. But for the most part, each one of those things can
be accomplished easily, and their editors are neatly tucked away in the interface
so as not to bother you when they are not needed.
When we start building scripts and collecting a few games under our belt, we’ll
go over the steps outlined here a gazillion times: dragging assets to the hierarchy,
making prefabs, adding scripts and components to GameObjects, running the
simulation… and on, and on.
But next, we’ll talk a little bit about the language we’ll be using: C#.
Summary
The Unity Editor can be a bit intimidating at first. It aims at being the ultimate
solution for game development, and so it must pack all sorts of tools and mini
editors inside a somewhat limited screen real estate. Still, it manages to do a fine
job at that. For now, focus on the four main areas of the interface: The Project
panel, the Hierarchy panel, the Game and Scene Views and the Inspector panel.
We can now move on to the language of choice for this book.
Summary
The Unity Editor can be a bit intimidating at first. It aims at being the ultimate
solution for game development, and so it must pack all sorts of tools and mini
editors inside a somewhat limited screen real estate. Still, it manages to do a fine
job at that. For now, focus on the four main areas of the interface: The Project
panel, the Hierarchy panel, the Game and Scene Views and the Inspector panel.
We can now move on to the language of choice for this book.
2. Scripting with C#

In this book, C# will be the language of choice. In this chapter, I’ll go over the
very least you need to know regarding using C# within Unity. For those who
prefer UnityScript, you should be able to follow the tutorials easily with some
simple syntax conversion.
What we’ll learn in this chapter:
- What is MonoBehaviour
- How to create your own scripts
- How to initialize your own objects through code
- How to use the Game Loop
2. Scripting with C#

In this book, C# will be the language of choice. In this chapter, I’ll go over the
very least you need to know regarding using C# within Unity. For those who
prefer UnityScript, you should be able to follow the tutorials easily with some
simple syntax conversion.
What we’ll learn in this chapter:
- What is MonoBehaviour
- How to create your own scripts
- How to initialize your own objects through code
- How to use the Game Loop
Not Another Boring Introduction To
C#
Relax. I will not show you a loop and tell you: this is a loop. And then do the
same thing with conditionals, arrays, logical operators… I will not show you
tables listing how big an integer is compared to a double.
This book assumes you have some knowledge of programming, not much, but
enough to know how to convert your ideas and the way you solve problems into
actual code. If the C# syntax is new to you, relax. It will become an old hat once
we get to work with actual code. But I do need to talk about some things which
are specific to Unity.
Also, if you are a C#, .NET developer, and not a Unity developer, be aware that
at the time of this writing Unity has released a beta support for .NET version 4.6.
But for years and years and years (and years and years…) Unity developers had
to make do with only 2.0/3.5 support, with a heavy inclination towards the 2.0
side of that combo. Soon 4.6 will be the default, but for now, 2.0/3.5 is the
default, so keep that in mind when planning out your logic. The code in this
book will adhere to the 2.0 version. So be particularly aware of this once we start
parsing and mapping data inside collections. More modern versions of LINQ can
accomplish a lot with a line or two, but not so the 2.0/3.5 version of LINQ!

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_BobTheDuckSpawnTest

So what is the Unity engine, and what can it do for you, the developer?
T version 4.6.

once we start
of LINQ can
MonoBehaviour
We saw in the previous chapter that every object in the game is referred to as a
GameObject. But more specifically, every Unity script will extend a class called
MonoBehaviour.
Go back to the scene you were working on in the previous chapter and follow
these steps:
1. Select the Bob GameObject in the Hierarchy panel.
2. In the Inspector panel click the Add Component button.
3. Type BobTheDuck in the input field. You should be presented with the
option to create a New Script.

Figure 2.1 - A New Script Component

4. Click where it says New Script


5. Then in the next panel, click the Create and Add button.
a class called

Figure 2.2 - Create and Add Button

Note:
In this last panel, you do have more choices for the script language. But we'll
use C# in this book.

Tip:
As you saw in our last chapter you can also create new scripts inside the
Project panel, by right clicking and selecting to create new C# Script. Or by
using the Unity menu. You don't have to create a script as a component to a
GameObject. Some scripts will exist on their own, and not be part of the
components for a GameObject.

6. Double click the name of the Script as it appears in the Inspector panel. This
should open MonoDevelop, or whatever else is your default C# script editor.

Figure 2.3 - The New Script

The script by default will look something like this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BobTheDuck : MonoBehaviour {

// Use this for initialization


void Start () {

// Update is called once per frame


void Update () {

}
}

The two method holders there are two of the many messages the Unity Engine
can dispatch to a MonoBehaviour object.

Start will fire whenever the GameObject the script is attached to is enabled,
and Update will fire, or try to, 60 times a second. It is tied to the game's
rendering loop, so it will fluctuate as much as the game itself, topping at 60
frame per second.
Another import one is the Awake method. It gets called once, the first time the
object is initialized.
But there is a long list of messages the Unity Engine can dispatch to a
MonoBehaviour script.

or panel. This You can see a full list here, in Unity’s documentation:

Some methods only become available if certain pre-conditions are met. For
instance, the OnMouse messages will only be available if the GameObject
contains at least one type of collider component.
A MonoBehaviour script will also come with a series of properties, including
the one for the script's GameObject (called appropriately gameObject ) which
links to the game object the script is attached to. (And don’t worry, this
relationship will be shown in more detail later, once we start coding).
Let's add a few properties of our own:
1. Enter these properties in the Duck script:
public string duckName;
public bool dead;
public Color duckColor;
public int[] someNumbers;

2. Go back to the Unity Editor and see what happens to the Duck script
component inside the Inspector panel. You will see something like this:
Figure 2.4 - Public Properties in the Inspector

Public properties of certain types will become available in the Editor for you to
fiddle with. This is an ideal way to create variables which control the look and
feel of your objects or the way they behave during gameplay, so you can change
the variables at run time or in between simulations and quickly test different
settings. These changes are also saved with your project, so they remain in
memory. So even if you assign a value to one of the properties, inside your
script, say:
public string duckName = "Bob";

But inside the editor, you changed the value to "Marley", it is this second value
which will be used by the object and it will be there the next time you open your
) which project. This is accomplished via a process known as serialization.

If you don't want public properties to become exposed in the Editor, you can use
a meta tag:
1. Just above the boolean for dead , add a new line.

2. Type [HideInInspector] and save the script. This will remove the
property from the Inspector.
This is in case you want to keep a variable public but you don't want people
using the Editor to be able to change the value of the property.
Another meta tag which can be useful sometimes is the RequireComponent :

1. Find this line in the Duck class:


public class BobTheDuck : MonoBehaviour {

2. Create a new line above it and type in:


[RequireComponent(typeof(Rigidbody2D))]

This ensures that the Duck component can only be added to a GameObject that
has a RigidBody2D component.

After you made this change, If you went back to the editor and clicked the little
settings icon next to the RigidBody2D component...

Figure 2.5 - Component Settings

...and selected Remove Component, the Editor will throw an error saying it
cannot remove this component because the Duck needs it!
Now let's try something else.
1. Change the Update method to look like this:
void Update () {
if (transform.position.y < -5) {
gameObject.SetActive (false);
}
}

2. This bit of code is checking the transform property of MonoBehaviour


and checking for the y value of the position. When the Duck is offscreen, we
disable its GameObject.
3. Run the game now, by pressing the Play button, and notice that the Bob
prefab instance in the Hierarchy panel will become disabled after a second or
two.

Figure 2.6 - Disabled GameObject

If this happens too fast for you to notice, try increasing the value we're checking
against, from -5 to -10.
After you're done, make sure to Apply these changes so they are saved to the
Bob prefab.
But there's more you can do with properties and GameObjects. Here's
something else which will be quite useful:
1. Create a new GameObject in the Hierarchy panel, by right clicking inside
the panel and selecting Create Empty. Call it DuckSpawn .

2. With that GameObject selected, so we have its information listed in the


Inspector panel, add a New Script component called DuckSpawn .
Figure 2.7 - DuckSpawn Component

3. Double click the script to open it in the script editor.


4. Delete the Update method.

5. Add a public property like this one and save the script:
public GameObject duckGO

6. Go back to Unity, wait until the script changes are loaded in and you should
see an input field for the property Duck GO.

Figure 2.8 - The Duck Go Field

7. Drag the Bob prefab from the Project panel to that input field.
Figure 2.9 - Dragging A Prefab to Component Field

Note that it's the prefab from the Project panel, the original prefab, not the
instance we have in the Hierarchy panel.
8. Change the Start method in the new script to look like this:

void Start () {
var i = 0;
while (i < 10) {
var duck = Instantiate (duckGO) as GameObject;
duck.transform.position =
new Vector2 (Random.Range (-3, 3), 5);
i++;
}
}

This will instantiate 10 copies of the prefab and randomly distribute them across
the screen.
9. Play the game and see the results. Watch what happens inside the Hierarchy
panel.
Figure 2.10 - Bobs!

10. Change the Start method slightly by adding this line right after you set the
Duck position.
duck.transform.parent = transform;

11. Play the game again and notice the changes. The instantiated ducks are
added as children to the empty GameObject we created.
Figure 2.11 - Bobs Inside Parent GameObject
Figure 2.11 - Bobs Inside Parent GameObject
The Canvas
The Unity Engine has a bunch of built in GameObject types for user interface
elements: text fields, layout managers, scrollers, buttons, panels...
They will all reside inside another specialized GameObject called the Canvas.
When you add a new UI element to the Hierarchy panel, Unity will add a
Canvas object and something called an EventSystem object which contains
configuration for user input.
Here's a quick tutorial on how to add a text field to our Duck scene.
1. Right click the Hierarchy panel and select UI/Text
Figure 2.12 - Adding an UI Element

2. This will add a text field inside a Canvas object. Move the text field to either
the top or bottom of the screen. You can do this in the Scene View.
The best way to achieve this is to double click text Text GameObject inside the
Hierarchy Panel and the Scene View will move and zoom to display it. Then
Zoom out until you see the Canvas rectangle shape and position the text field,
somewhere near the top.
In the Inspector panel you will see all the properties you have to mess around
with for your text field. Change the Color one to white.

Figure 2.13 - Our New Text Field

3. Delete the default text New Text.


field to either
4. Open the DuckSpawn script you've been working on and add an import
line at the top:
using UnityEngine.UI;

5. Add a Text public property.


public Text ducksCount;
6. Drag the new next field from the Hierarchy Panel to the Ducks Count field
in the Inspector.
7. Update the Start method adding this line:
ducksCount.text = "10 ducks";

Let's see a quick and easy way to update this value next.
6. Drag the new next field from the Hierarchy Panel to the Ducks Count field
in the Inspector.
7. Update the Start method adding this line:
ducksCount.text = "10 ducks";

Let's see a quick and easy way to update this value next.
Events
Another useful thing you can add to your projects is the ability to dispatch
messages across an entire game, to be picked up by any game object. The Unity
Engine has its own way to implement an Event system, but in my opinion, it
sucks eggs! Besides, C# already supports a pretty straightforward way to do the
same thing.
An event is made out of three parts.
1. First we create a delegate method. This is a declaration of what the
delegate must "look" like (think of it as the event handler).

public delegate void Event_UpdateDucks (int cnt);

The method could have as many arguments as you wish, or be blank. In this
example, the delegate we're declaring must be a function that returns nothing,
and takes one integer as a parameter.
2. Next we tie the delegate to an event, by declaring both an event and a
delegate variable to be of the same type (the delegate). This is a bit beyond the
scope of this book, but in actuality it simply means this next line:
public static event Event_UpdateDucks DucksUpdated;

Notice that the OnDuckCreated property is both an event and a delegate. I also
made it static, so any class can access it, but strictly speaking, events do not need
to be static.
3. We write a method to raise the event:
public static void OnDucksUpdated (int cnt) {
if (DucksUpdated != null)
DucksUpdated (cnt);
}

We're ready to hook events to listeners.


This happens like this. Say a script wants to be informed whenever a duck is
instantiated.
We fire the event during instantiation like this:
...
GameEvents.OnDucksUpdated(1);
...

Assuming GameEvents is the script where the static OnDuckCreated resides.


Any object can listen to that event and handle it like this:
...
//first subscribe to event ( on Awake would be best)
GameEvents.DucksUpdated += HandleDuckUpdate;
...
//then write handler
//(it must match the delegate type,
//which in our example it's a method with
//one int parameter and which returns nothing.)
void HandleDuckUpdate (int cnt) {
//write handler
}
...
//Make sure to unsubscribe on destroy
GameEvents.DucksUpdated -= HandleDuckUpdate;

As an exercise, make sure these events are static and written inside a C# script
called GameEvents . This does not need to extend MonoBehaviour so it could
look like this:
public class GameEvents {
public delegate void Event_UpdateDucks (int cnt);
public static event Event_UpdateDucks DucksUpdated;
public static void OnDucksUpdated (int cnt) {
if (DucksUpdated != null)
DucksUpdated (cnt);
}
}

s do not need
So create that script inside your project.
Then create a count variable for the ducks in the DuckSpawn script.

Whenever a duck is instantiated, fire:


GameEvents.OnDucksUpdated(1);

And inside the Duck script, where the Duck is disabled after falling offscreen,
fire the event:
GameEvents.OnDucksUpdated(-1);
The DuckSpawn script then can handle the event, update the duck count based
on the event's parameter and update the text field:
int ducks = 0;
void Start () {

...

GameEvents.DucksUpdated += HandleDuckUpdate;
}

void HandleDuckUpdate (int cnt) {


ducks += cnt;
ducksCount.text = "" + ducks;
}

Try it!
The DuckSpawn script then can handle the event, update the duck count based
on the event's parameter and update the text field:
int ducks = 0;
void Start () {

...

GameEvents.DucksUpdated += HandleDuckUpdate;
}

void HandleDuckUpdate (int cnt) {


ducks += cnt;
ducksCount.text = "" + ducks;
}

Try it!
And... Some More Code
I just wanted to finish this section on C# by going over a simple Utils class
we'll be using throughout the prototypes in this book.
An Utils class is merely a series of utility and helper functions you collect
over time inside a static class. These can be further organized and subdivided
into collection utilities, text utilities... and so forth.
Here's what the one we'll be using looks like:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public static class Utils {

public static T[] Scramble<T> (T[] array) {

// shuffle chars
for (int t = 0; t < array.Length; t++ )
{
T tmp = array[t];
int r = UnityEngine.Random.Range(t, array.Length);
array[t] = array[r];
array[r] = tmp;
}
return array;
}

public static void DelayAndCall (MonoBehaviour caller,


float delay, Action callBack) {
caller.StartCoroutine (DelayAndCallRoutine (delay, callBack));
}

public static void StaggerAndCall<T> (MonoBehaviour caller,


float delay, Action<T> callBack, List<T> items) {
caller.StartCoroutine (StaggerAndCallRoutine<T> (delay, callBack, items));
}

static IEnumerator StaggerAndCallRoutine<T> (float delay,


Action<T> callBack, List<T> items) {
var i = 0;
while (i < items.Count) {
callBack (items[i]);
yield return new WaitForSeconds (delay);
i++;
}
}

static IEnumerator DelayAndCallRoutine (float delay, Action callBack) {


yield return new WaitForSeconds (delay);
callBack ();
}
}

I have a shuffling method for Arrays, using something C# calls Generics (the T
thingy) and other languages may call it Templates, although they are not really
the same thing. The T stands for a type, and the generic method, or a class, can
be made to handle different types by using the generic T declaration instead of
a specific type.
The Scramble method will get an array of some type and shuffle it.

As an exercise, try doing the same code, but using a List instead. There are
only a couple of things you need to change.
Then I have a bunch of coroutine starter helpers. These are one-line methods I
can use to add a delay or stagger calls. You'll see more on what a coroutine is in
the next chapter, but for now think of them as functions that can wait for
something, a time delay, or a result before completing.
With these helper methods I can even start a coroutine from a non-
MonoBehaviour based class, as long as I have access to a MonoBehaviour
when I make the call.
We'll be using these a lot.
But enough talking!
}
}

I have a shuffling method for Arrays, using something C# calls Generics (the T
thingy) and other languages may call it Templates, although they are not really
the same thing. The T stands for a type, and the generic method, or a class, can
be made to handle different types by using the generic T declaration instead of
a specific type.
The Scramble method will get an array of some type and shuffle it.

As an exercise, try doing the same code, but using a List instead. There are
only a couple of things you need to change.
Then I have a bunch of coroutine starter helpers. These are one-line methods I
can use to add a delay or stagger calls. You'll see more on what a coroutine is in
the next chapter, but for now think of them as functions that can wait for
something, a time delay, or a result before completing.
With these helper methods I can even start a coroutine from a non-
MonoBehaviour based class, as long as I have access to a MonoBehaviour
when I make the call.
We'll be using these a lot.
But enough talking!
Summary
C# is the language of choice in our tutorials for this book, although conversion
between the available script languages is not too difficult. A GameObject can
have as many components as you'd like, each extends a Unity Engine class
called MonoBehaviour which ensures a very tight communication between
component and the engine at run time. The engine will dispatch messages to all
enabled components, and these components will change the nature of the game
object they are attached to.
Unity comes bundled with a series of specialized GameObjects or specialized
combination of components. A camera will be a game object pre-populated with
Camera related components, a Sprite will be a game object pre-populated with a
sprite renderer component, a Canvas will be a game object pre-populated with
Canvas related components.
It is a lot of information, but keep in mind all theses things exist in the engine to
save time. Your time!
But it's time to finally move our focus to what this book is all about: word
games. So let's begin by talking about dictionaries...
Summary
C# is the language of choice in our tutorials for this book, although conversion
between the available script languages is not too difficult. A GameObject can
have as many components as you'd like, each extends a Unity Engine class
called MonoBehaviour which ensures a very tight communication between
component and the engine at run time. The engine will dispatch messages to all
enabled components, and these components will change the nature of the game
object they are attached to.
Unity comes bundled with a series of specialized GameObjects or specialized
combination of components. A camera will be a game object pre-populated with
Camera related components, a Sprite will be a game object pre-populated with a
sprite renderer component, a Canvas will be a game object pre-populated with
Canvas related components.
It is a lot of information, but keep in mind all theses things exist in the engine to
save time. Your time!
But it's time to finally move our focus to what this book is all about: word
games. So let's begin by talking about dictionaries...
3. Word Lists

We come now to one of the core elements in any word game: its
dictionary. Although "word list" would be a more appropriate name. Learning
good algorithms to manage and parse a dictionary will make a huge difference
in many types of word games.

So let's get busy and cover all that you need to know about dictionaries. This is
what we'll cover in this section.

- Where to find dictionaries for every purpose


- How to properly clean up word lists
- How to sort your word lists into groups
- How to find word lists that match your game needs
3. Word Lists

We come now to one of the core elements in any word game: its
dictionary. Although "word list" would be a more appropriate name. Learning
good algorithms to manage and parse a dictionary will make a huge difference
in many types of word games.

So let's get busy and cover all that you need to know about dictionaries. This is
what we'll cover in this section.

- Where to find dictionaries for every purpose


- How to properly clean up word lists
- How to sort your word lists into groups
- How to find word lists that match your game needs
Finding Word Lists

If you have some cash available, you could find content providers online which
sell dictionaries. This is the best option if you need both the word and the
definition (for crossword puzzles for instance), and also if you need extra data on
word frequency. And I'll talk about that a lot more soon.
But what about free dictionaries?
I'll cover searching and editing English word lists, but the same steps could be
repeated for other languages, it all depends on what's available online, really.
• If you Google free word lists, you will find quite a few free resources. These
will range from between 50 thousand to 120 thousand word lists. A safe bet is to
look for Scrabble dictionaries, although these lists are rarely updated, so they
lack more current words. One such list can be found at this address.
• Another good source is this repo which contains lists with 10k to 20k.
Although be warned, these lists contain an enormous amount of brand names,
computer related terms and acronyms which may not suit your game ideas.
• This is another good one. It contains lists with the 1k and 3k most common
words in the English Language. And an ESL word list to boot (ESL as in English
as Second Language) with about 20k words. These ESL lists are often perfect for
word games! Because the words in them are not very unusual, and even non-
native speakers will be able to enjoy your game–if you decide to make one using
the English language that is. But the tip about finding word lists which are
usually presented for non-native language learners, can work for any language.
• The same website is also a great resource for theme based vocabulary.
• And for theme based games, Wikipedia is another awesome resource with
long lists of names related to pretty much everything under the sun, from book
and movie titles, to species of animals, and even famous quotes.
• If you own a Mac or know somebody who does, you can copy the word list
MacOS uses in its spell checker tool. In the Finder, click Go then Go To
Folder... and enter /usr/share/dict/ and when that folder opens, double click the
shortcut words. This is a huge list, with over 230 thousand words, and it serves
well as a master list. The one you will use to check if a word spelled by the
player IS A WORD.
In the source files for this book you will find 1k, 2k, 10k, 20k, 50k, and 250k
lists. Including the large word list from MacOS.
extra data onBut you're not safe yet. After you get your list, or multiple word lists, there are
quite a few extra steps, although some optional, depending on what type of game
you want to build...
Once you collect the words, you need to filter unwanted crap out of them and
sort the word information; separating the weird from the well-known words.
The online word lists I mentioned here are pretty awesome, but please be
advised they contain many brand names, as well as acronyms.
Word lists will also include people's names and place names; and you might
decide later to remove all these, or some of these, for specific games.
We'll go over all those steps now, starting on the logic to load a text file.

Load The Lists


as in English
en perfect for As I mentioned before, this book's source files will contain a few word lists. You
may use them for the next tutorials, or find your own word lists online and use
ake one using those. So our first step is to create a test scene.

Source Material:
In the sample project, in our source files, there is one scene already created, at
Assets/Scenes/Scene_DictionaryTest but you may create your own if you
wish.
Once you have a Unity project and a scene ready to go, follow these steps:
1. Create a folder inside the Project panel, called StreamingAssets, if one has
not been created already.
2. Move your word lists to this StreamingAssets, folder. I put mine inside
another subfolder called words and the code we'll see, will use that path, but it's
up to you how to organize the files inside StreamingAssets.
type of game

Note:
You will need to create this StreamingAssets folder and name it exactly that.
This is one of the reserved named folders that Unity Engine can address in its
logic.

3. Create an empty GameObject inside the Hierarchy panel. We'll add our
scripts as components to this GameObject.
Our first script simply loads a word list:
using System.Collections;
using System.Collections.Generic;
ord lists. You using UnityEngine;
using System;

public class WordListLoader : MonoBehaviour {

public string fileName ="words/1k.txt";

void Start () {
StartCoroutine ("LoadWordList");
}

IEnumerator LoadWordList () {

string filePath = System.IO.Path.Combine (


Application.streamingAssetsPath, fileName);

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);
var words = result.Split ('\n');

foreach (var w in words) {


var word = w.TrimEnd ();
if (string.IsNullOrEmpty (word))
continue;
Debug.Log(word);
}
}
}

Assuming we have a file called 1k.txt at the end of the path


Assets/StreamingAssets/words/, we can load it by using a Coroutine . This is
a method that can wait for an action to be completed before it closes shop. This
could be a fixed delay inside the method or an asynchronous operation, like
loading a file.
There are other ways to load a text file inside Unity using C# IO API, and we see
one of them here too, System.IO.File.ReadAllText . But a Coroutine , as
shown here, is a good generic method if you need to load something that might
come from an URL later and not from a local folder.
The method checks the path to the file being loaded and determines the best way
to handle its loading: via an asynchronous call to the WWW API, which will some
time in the future yield the data (which is why we need the Coroutine ) or via a
simple read text, done to a local file.
The text is then split into individual words. It's usually best, and simplest, to
separate words by commas, but in this book I'll separate them by lines, so the
data is easy to visualize (the actual word lists, I mean.) This means I need to
make double sure there are no trailing characters at the end of each line, which is
why I use the TrimEnd method.

Note:
If you are writing code that loads text locally, for an application meant to run
in your machine and not on a phone–a puzzle generator for instance–then you
can skip the coroutine and use the IO call inside a regular method:
var allText = System.IO.File.ReadAllText (filePath);
Once the text is loaded, we can now proceed to remove all the words we won't
need for our game. Right now, all we're doing is printing the words out to the
console.
So let's write the logic to clean up the word lists.

. This is

Note:
Don't worry if you don't know enough C# to understand all the scripts in this
PI, and we see chapter. I will blast through some of them, and not go into too much detail
because a lot of the scripts here are for reference only since they cover
optional steps in preparing your word lists. As you become more familiar with
the language you can come back later and review them. You will still be able
to follow along with the game tutorials using the files you find in the source
the best way materials for this book.
ich will some
And once we start building the games, we'll code the logic handling word lists
) or via a
for each specific game, and that logic will be covered in more detail.

line, which is Clean Them Up


It's time to clean up our word list of all unwanted words.

Source Material:
In the sample project, in our source files, there is one scene already created for
this tutorial, at Assets/Scenes/Scene_DictionaryCleanUpTest but you may
create your own if you wish.
So let's create a new script, called DictionaryCleanUp.cs
Here's the code for it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class DictionaryCleanUp : MonoBehaviour {

public string inputFile;


public string outputFile;
public int minLength = 3;
public char[] badChars = new char[] {' ', '.', '-', '\'',
'1', '2', '3', '4', '5', '6',
'7', '8', '9', '0', ':'};

void Start () {

if (string.IsNullOrEmpty (inputFile))
throw new Exception ("Missing Input File!");

if (string.IsNullOrEmpty (outputFile))
throw new Exception ("Missing Output File!");

StartCoroutine ("LoadWordList");
}

IEnumerator LoadWordList() {

string filePath = System.IO.Path.Combine (


Application.streamingAssetsPath, inputFile);

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);

var words = result.Split ('\n');

var allWords = new HashSet<string> ();

foreach (var w in words) {


var word = w.TrimEnd ();
if (string.IsNullOrEmpty (word))
continue;
if (word.Length < minLength)
continue;
if (word.IndexOfAny (badChars) != -1)
continue;
if (!allWords.Contains(word.ToLower()))
allWords.Add (word.ToLower());
}
var sr = File.CreateText(
System.IO.Path.Combine (Application.streamingAssetsPath,
outputFile ));

foreach (var w in allWords) {


sr.WriteLine ( w );
}
sr.Close();
}
}

The script loads a word list, breaks it apart with a Split , loops through the
words and remove the ones which are less than three characters long and which
contain any of our unwanted characters ( word.IndexOfAny (badChars) ). It
then saves the words that passed through the filter, all set to lowercase.
The public references to be exposed inside the editor are the file names for both
input and output, the minimum word length you wish, and the list of bad chars to
use as a filter. You can add more chars or remove the ones pre-populating the
list. The most important ones are the space and the dash (hyphen), which gets rid
of compound words.
If you want to test the script, place a word list in the StreamingAssets folder and
fill in the file input and output name information. The new list will also be saved
inside StreamingAssets.
In the code I gave you, words are taken out if they have less than three letters,
and if they have periods in them, or apostrophes.
You may also choose to remove words with any kind of accented symbols, by
adding these chars:
é, á , ú , ó , í , ü , ö , ä , ç , ã , õ , ñ

This is optional of course. If you prefer to let players spell the words don't or
séance but as dont and seance, then you'll need to add logic to strip words of
accent characters and apostrophes. But in this case, it may be simpler to add the
alternative spellings you want to the final dictionary.
You could also easily add a max length property, if your game imposes such
limitation. If in your game, the player cannot generate words longer than 10
characters, then by all means take out all 11 character words, and longer, out of
your word list.
Your games will dictate what this "filtering out" step will do in the end.
Let's see what else we could remove.

Remove Names
Another optional step would be to remove unwanted words listed in a second
file. This could be used to remove swear words, or to remove people's names.
I personally don't like if the game allows you to spell people's names as a valid
game move. But that's a matter of preference.

f bad chars to Even if you want proper names in your game master list, you might still wish to
separate them from the word list used to generate the puzzles, so that your
which gets ridmystery words don't turn out to be people's names, since that might feel a bit
weird for players.

folder and That way you can ignore the names when building puzzles, but allow them when
also be saved players spell them.
But how to remove people's names?
The problem with people's names is that it is quite common to have names which
are also words on their own, like Bob, June, Clay...
And you can't simply target words which are capitalized, because in English at
least, these will include days of the week, months, nationalities, and many other
things you want to keep in your word list.
So how can we do this?
The MacOS list of words I talked about capitalizes anything which is a proper
name. Everything else is listed in lowercase.
So it lists Bob, and bob. Clay and clay. As separate entries.
We have two options:
1. We can remove all words which are capitalized in the master word list. And
then add words from lists found online: places (countries, cities, continents...),
nationalities, month names and days of the week.
2. Or, we can get a list of people's name online, and remove those from the
master list.
I'll show you a way to accomplish the second option, since the same solution
would be used for swear words.
What we need then is a list of people's names, which we can easily find in the
internet. We need to load this list, and loop through it, checking if our master list
of words contains all-lowercase versions of each name.
So, for instance, in the list of names we want to remove, we'll find the name
Bob, but if we search the master word list for a lowercase version of the word,
we'll find bob and therefore know this is not just a name but also a word. We
then remove Bob from the list of banned names, because we don't want to filter
out bob. Got it?
w them when
In the end we'll have a list of people's names we can safely remove from all our
word lists! More or less.
Be aware this method is not 100% foolproof. There are words which only exist
names whichin their capitalized version and are not proper names perse. For instance, month
names like June, and April. And besides the name list could contains names like
London, or York. Seriously... the names people give to their children... So in the
end, it's a good idea to review the words you'll be removing from your word
lists.

Source Material:
In the sample project, in our source files, there is one scene already created for
this tutorial, at Assets/Scenes/Scene_DictionaryNamesTest but you may
create your own if you wish.
In the source files for this book you'll find a list of names, called names.txt,
which has already gone through this process. But let me show what the actual
code looks like.
1. You will need the big word list called AllWords.txt which you'll find in the
source files for this book. This list still contains capitalized proper names. If
you're working with the sample project, it already contains the files placed inside
Assets/StreamingAssets/words/.
ur master list 2. In your dictionary test scene, disable any scripts you might have added
before, and add a new one to your GameObject. The script you want is called
NameFinder.cs

Here's that script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class NameFinder : MonoBehaviour {

// Use this for initialization


void Start () {
var namesTxt = File.ReadAllText (
System.IO.Path.Combine (Application.streamingAssetsPath,
"words/names.txt"));

var namesData = namesTxt.Split ('\n');


var names = new HashSet<string> ();
foreach (var line in namesData) {
var name = line.TrimEnd ();
if (string.IsNullOrEmpty (name))
continue;
if (!names.Contains (name))
names.Add (name);
}

var allWords = new HashSet<string> ();


var bigOne = File.ReadAllText (
System.IO.Path.Combine (Application.streamingAssetsPath,
"words/AllWords.txt"));

var data = bigOne.Split ('\n');


foreach (var line in data) {
var word = line.TrimEnd ();
if (string.IsNullOrEmpty (word))
continue;
if (!allWords.Contains (word))
allWords.Add (word);

var properNames = new HashSet<string> ();


foreach (var word in allWords) {
if (word [0] >= 'A' && word [0] <= 'Z') {
if (!allWords.Contains (word.ToLower ())) {
if (names.Contains (word.ToLower ())) {
properNames.Add (word.ToLower ());
}
}
}
}

placed inside var sr = File.CreateText("names.txt");


foreach (var w in properNames) {
sr.WriteLine (w);
}
sr.Close();
}
}

This script will load the names list, and the big word list which contains
capitalized names.
It will then loop through the words in the long word list, and for every word it
will run these checks:
foreach (var word in allWords) {
if (word [0] >= 'A' && word [0] <= 'Z') {
if (!allWords.Contains (word.ToLower ())) {
if (names.Contains (word.ToLower ())) {
properNames.Add (word.ToLower ());
}
}
}
}

• First, it checks if the first letter is capitalized.


• If so, it will check to see if the word also exists as an all-lowercase version
(in our example earlier, it means it found Bob and it looked for bob).
• If the word has an all-lowercase version, it means it's not only a proper name
but also a verb, adjective or noun. But if it doesn't find the all-lowercase version,
we continue with the last check.
• If our list of names contains the word, than this capitalized word is a proper
name, and only a proper name. If not, it's something else like a place name, or a
nationality...
Once the script is done, we have a list of names that we want to remove from our
word lists. Once again, this could be a list of swear or taboo words. The logic
from now on, would be the same.
And again, I recommend you review any list of words you want to filter out, just
to make sure every entry should be removed.
Now we're ready to filter out names in our word lists. I created a version of
DictionaryCleanUp and called it DictionaryCleanUp2

Source Material:
In the sample project, in our source files, there is one scene already created for
this tutorial, at Assets/Scenes/Scene_DictionaryNameRemoveTest but you
may create your own if you wish.

This is what it looks like:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class DictionaryCleanUp2 : MonoBehaviour {

public string inputFile;


public string outputFile;
public int minLength = 3;
public char[] badChars = new char[] {' ', '.', '-', '\'',
'1', '2', '3', '4', '5', '6',
'7', '8', '9', '0',':'};

private HashSet<string> names;


void Start () {

if (string.IsNullOrEmpty (inputFile))
case version, throw new Exception ("Missing Input File!");

if (string.IsNullOrEmpty (outputFile))
throw new Exception ("Missing Output File!");

StartCoroutine ("LoadNamesList");
}

IEnumerator LoadNamesList() {

move from our string filePath = System.IO.Path.Combine (


Application.streamingAssetsPath, "words/names.txt");

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
filter out, just yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);

names = new HashSet<string> ();

var namesList = result.Split ('\n');

foreach (var n in namesList) {


var name = n.TrimEnd ();
if (string.IsNullOrEmpty (name))
continue;
if (!names.Contains(name.ToLower()))
names.Add (name.ToLower());
}

StartCoroutine ("LoadDictionary");
}

IEnumerator LoadDictionary() {

string filePath = System.IO.Path.Combine (


Application.streamingAssetsPath, inputFile);

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);

var words = result.Split ('\n');

var allWords = new HashSet<string> ();

foreach (var w in words) {


var word = w.TrimEnd ().ToLower();
if (string.IsNullOrEmpty (word))
continue;
if (word.Length < minLength)
continue;
if (word.IndexOfAny (badChars) != -1)
continue;
if (names.Contains (word))
continue;

if (!allWords.Contains(word))
allWords.Add (word);

var sr = File.CreateText( System.IO.Path.Combine (


Application.streamingAssetsPath, outputFile ));

foreach (var w in allWords) {


sr.WriteLine ( w );
}
sr.Close();
}
}

We simply add a new step, where we load the list of words we want to filter out
(names in this case) and then get them out of the word list we are cleaning up.
The final result is a word list, properly cleaned up.
We're now ready to sort the word lists into frequency groups.

Sort Into Frequency Groups


As I mentioned before, it is a very good idea to know how frequent a word is, or
how common it is.
This will be vital information when developing and designing almost any type of
word game.
It's important to make a distinction between words you will check against, and
words you'll use to build your puzzles with. If a player knows the word
zymophyte and can spell it in your game, he or she deserves the point!
But under normal circumstances you won't want to build a puzzle with that
word. Unless this word comes in at a very advanced level in your game (very,
very, very advanced). Either way your logic will need to know that zymophyte
is an advanced word. Or a highly uncommon one.
Another good use for this extra bit of data is when building AI players to
compete with the human one. A quick and easy way to create multiple types of
computer players is to use different types of dictionaries for each AI. And the
fake opponents will feel more “real” to the human player if you base the
different dictionaries each computer player is based on, on word commonality
and not randomness (otherwise you could end up with a computer player that
knows the word zygomatic but not the word eat, for instance, and this will look
odd).
Content providers will sell you lists properly divided by word frequency and that
is a huge help. But there are ways to sort your free, long lists into frequency
chucks. And we'll see how to accomplish that in a moment. You can use the lists
I provide in the source files, or go hunt your own lists.
So going back to the word frequency problem... Let me show you now how to
easily create a dictionary with the extra information of frequency, or how
common a word is.
We saw how to get the lists based on word frequency and commonality. We even
collected lists with the 1k, 2k, 10k, 20k, 50k most common words, and a large
list with over 250k words.
The key then is to tag the first thousand words with the information that they
belong to that group, then remove them from the 2 thousand words list and tag
the remainder with the information they belong to the 2 thousand group. And
continue to do that with every subsequent and bigger word list.

st any type of
Source Material:
In the sample project, in our source files, there is one scene already created for
this tutorial, at Assets/Scenes/Scene_DictionarySplitter but you may create
your own if you wish.

We'll do just that next!


1. Going back to your dictionary test scene, disable any previous scripts you
may have added already and add a new script component with the file
FrequencySorter.cs
2. There are no public properties exposed here, but the script does require the
word lists that come with the source files for this book. These all sit inside a
folder called words inside the StreamingAssets folder.
The FrequencySorter.cs script will load each one of these files. It will remove
duplicates, and add a separator (I used the string: ### for this purpose) between
ency and that each of the frequency groups.

Here's what the script looks like:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class FrequencySorter : MonoBehaviour {


lity. We even
private string[] fileList = new string[] {"words/1k.txt","words/2k.txt",
"words/10k.txt","words/20k.txt",
"words/50k.txt","words/300k.txt", };

void Start () {
StartCoroutine ("SortByFrequency");
}

IEnumerator SortByFrequency() {

var sets = new List< HashSet<string>> ();


string path = null;

var setIndex = 0;
while (setIndex < fileList.Length) {

sets.Add (new HashSet<string> ());

path = fileList [setIndex];


string filePath = System.IO.Path.Combine (
Application.streamingAssetsPath, path);

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);

var words = result.Split ('\n');

foreach (var w in words) {


var word = w.TrimEnd ();
var previousIndex = setIndex - 1;
var unique = true;

while (previousIndex >= 0) {


if (sets [previousIndex].Contains (word)) {
unique = false;
break;
}
previousIndex--;
}
if (unique) sets [setIndex].Add (word);
}

setIndex++;

//cool off
yield return new WaitForSeconds (0.5f);

}
//now we have words sorted by frequency

var sr = File.CreateText("wordsByFrequency.txt");

setIndex = 0;

while (setIndex < sets.Count) {


var wSet = sets [setIndex];

foreach (var w in wSet) {


sr.WriteLine ( w );
}

//add separator
sr.WriteLine ( "###" );

setIndex++;
}

sr.Close();

Debug.Log ("ALL DONE");

}
}

First we load each file inside a loop:


var setIndex = 0;
while (setIndex < fileList.Length) {
sets.Add (new HashSet<string> ());

path = fileList [setIndex];


string filePath = System.IO.Path.Combine (
Application.streamingAssetsPath, path);

string result = null;


if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);
...

Next we split the file to get the individual words, and we remove duplicates from
any of the previously loaded word lists:
var words = result.Split ('\n');

foreach (var w in words) {


var word = w.TrimEnd ();
if (string.IsNullOrEmpty (word)) continue;

var previousIndex = setIndex - 1;


var unique = true;

while (previousIndex >= 0) {


if (sets [previousIndex].Contains (word)) {
unique = false;
break;
}
previousIndex--;
}
if (unique) sets [setIndex].Add (word);
}

So, for example, first we load the 1k list and we don't need to worry about
removing duplicates from previous files because this is the first file to be loaded,
which is what this bit of logic enforces:
while (previousIndex >= 0) {}

When we load the second file, 2k, we run a check with the previously loaded 1k
word list, and remove the 1k words from the 2k list. And we keep doing this
until we reach the end of the files list.
Remember to run TrimEnd on every line, or you can choose to have the words
separated by commas, instead of line breaks. TrimEnd will get rid of garbage
raw data that might be packed within a line break.
Then when we are ready to write out the whole list, we simply save it all into
one file:
var sr = File.CreateText("wordsByFrequency.txt");
setIndex = 0;
while (setIndex < sets.Count) {
var wSet = sets [setIndex];
foreach (var w in wSet) {
sr.WriteLine ( w );
}
//add separator
sr.WriteLine ( "###" );
setIndex++;
}
sr.Close();
uplicates from Debug.Log ("ALL DONE");

Adding our string separator, in this example: ###


So with that script you end up with one word list, with no duplicates, with all
words sorted by frequency.
So how can we load that into a game and preserve the frequency information?
to be loaded,
Loading Your Word List
Some game ideas might require a database of words and their meaning, like
crossword games, for instance. But for most cases, you will load the word list as
a text file and parse it. This is what we’ll do in this book.
In the same test scene, follow these steps to load the word list we just created.
1. Disable all existing scripts.
2. Add a script component with the file DictionaryLoader.cs
And here's that file:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class DictionaryLoader : MonoBehaviour {

private HashSet<string> dictionary1k;


private HashSet<string> dictionary2k;
private HashSet<string> dictionary10k;
private HashSet<string> dictionary20k;
private HashSet<string> dictionary50k;
private HashSet<string> dictionary300k;
private HashSet<string>[] dictionaries;

void Start () {
StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

ProcessWordData (result);
}
void ProcessWordData (string wordData) {

var words = wordData.Split ('\n');

dictionary1k = new HashSet<string> ();


dictionary2k = new HashSet<string> ();
dictionary10k = new HashSet<string> ();
dictionary20k = new HashSet<string> ();
dictionary50k = new HashSet<string> ();
dictionary300k = new HashSet<string> ();
dictionaries = new HashSet<string>[] { dictionary1k,
dictionary2k, dictionary10k, dictionary20k,
dictionary50k, dictionary300k};

var index = 0;
foreach (var w in words) {
if (string.IsNullOrEmpty(w))
continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
continue;
} else {
dictionaries [index].Add (word);
}
}
}
}

We split the words by frequency, into different HashSet collections and collect
these into one array.
Of course, you may not need a reference to each one of the individual groups
( dictionary1k , dictionary10k ...) but it might help in your games to have
that information handy.
The way you'll parse and collect the words will depend on what your game
needs. Some games may not require the information of how common a word is,
not at runtime anyway, but only when designing the puzzles inside a puzzle
generator.
It all comes down to what type of game you're building, and you will see these
concerns dealt with differently in the games we'll build later.
But let's go over some of the most common ways you may wish to parse and
collect the words in your list other than by frequency.
Mapping By Length
In this case, we simply separate the words into groups based on word length.
This speeds up the process of finding words when word length is a determinant
point. For instance, a game where you need words to fit specific areas inside a
grid.
You'll see examples of this in at least one of the games we'll build.

Source Material:
In the sample project, in our source files, there is one scene already created for
this tutorial, at Assets/Scenes/Scene_DictionarySorter but you may create
your own if you wish. That scene will contain all sorting scripts mentioned in
this chapter.

ns and collect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LengthSorter : MonoBehaviour {

private Dictionary<int, HashSet<string>> wordsByLength;

void Start () {
StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {
string dictionaryPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

ProcessWordData (result);
}

void ProcessWordData (string wordData) {


wordsByLength = new Dictionary<int, HashSet<string>> ();

var words = wordData.Split ('\n');


foreach (var w in words) {
if (string.IsNullOrEmpty(w))
continue;
var word = w.TrimEnd ();
if (word.IndexOf ('#') != -1) {
continue;
} else {
if (!wordsByLength.ContainsKey (word.Length)) {
wordsByLength.Add (word.Length, new HashSet<string> ());
}
wordsByLength [word.Length].Add (word);
}
}
}
}

Mapping By First Char


This method of sorting the words can be very helpful when you need to
determine all the words that can be generated from a jumble of letters. You'll see
an example of this in one of our games: Scramble.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstCharSorter : MonoBehaviour {

private Dictionary<char, HashSet<string>> wordsByFirstChar;

void Start () {
StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {
string dictionaryPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

ProcessWordData (result);
}

void ProcessWordData (string wordData) {

wordsByFirstChar = new Dictionary<char, HashSet<string>> ();


var words = wordData.Split ('\n');
foreach (var w in words) {
if (string.IsNullOrEmpty(w))
continue;
var word = w.TrimEnd ();
if (word.IndexOf ('#') != -1) {
continue;
} else {
var c = word [0];
if (!wordsByFirstChar.ContainsKey (c)) {
wordsByFirstChar.Add (c, new HashSet<string> ());
}
wordsByFirstChar [c].Add (word);
}
}
}
}

Mapping By Sorted Letters


This method is a bit more unique, and it suits a very specific type of game, but
rs. You'll see can be extremely helpful. What it does is, it sorts all the letters in a word
alphabetically and stores that string.
What this means is that the word apple for instance, will be stored as aelpp,
with its letters sorted alphabetically.
This is particularly useful in games where you need to check if a random jumble
of letters can be used to form a word. You simply sort the jumble of letters
alphabetically and look for that string in your special word list.
And since more than one word can be made from the same group of letters, we
may need to store all those individual words and use their alphabetically sorted
letters as the key:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AllLettersSorter : MonoBehaviour {

private Dictionary<string, HashSet<string>> wordSortedChars;

void Start () {
StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {
string dictionaryPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

ProcessWordData (result);
}

void ProcessWordData (string wordData) {

wordSortedChars = new Dictionary<string, HashSet<string>> ();

var words = wordData.Split ('\n');


foreach (var w in words) {
if (string.IsNullOrEmpty(w))
continue;
var word = w.TrimEnd ();
if (word.IndexOf ('#') != -1) {
continue;
} else {
var sortedWord = w.ToCharArray();
System.Array.Sort(sortedWord);

var sortedString = new string (sortedWord);

if (!wordSortedChars.ContainsKey (sortedString))
wordSortedChars.Add (sortedString, new HashSet<string>());

wordSortedChars[sortedString].Add(word);
}
}
}
}

So for instance, the key aemn will store the words: name, mane, mean, amen.

Mapping With A Trie


If your word list is a massive one, with hundreds of thousands of entries, and all
you care about is if a word IS a word or NOT; you might want to use something
called a Trie, which can compress the data slightly, and make word searches
incredibly fast, but it may be cumbersome for purposes other than simply
checking if a word is valid.
The Trie algorithm can be used to compress data in your dictionary because of
the way the words are stored. Take the word “apple” for instance. A Trie is a
Dictionary collection, with 26 keys, one for each letter of the alphabet, and
each key contains another dictionary with 26 keys, and on and on, creating a
tree-like graph.
The word apple will be searched first by the “a” key, which will return another
dictionary with 26 letters, and in that one we’ll grab the “p” key and on and on
until we reach the last “e” which will contain the information that you have
reached the end of a word.
This compresses the data slightly because, in our example with the word apple,
think of every word that starts with an A, but we'll only need to store one initial
A for all those words, as the key to a Dictionary object. The same will happen
with each character in the word apple. You would need then to store the word
data as a serialized Trie object. We'll see about Serialized data in the
Scramble game.
In the source files for this book I include code to generate a Trie and to search
for words in it. But I do not use a Trie list in any of the games. The reason for
that is a Trie can give you the information of whether a word IS A word. But it
does not give you anything else. And we'll need more information for the games
we're building. But you can review the logic for creating a Trie in the source
files for this book.
It's time to learn how to look for words!
Dictionary collection, with 26 keys, one for each letter of the alphabet, and
each key contains another dictionary with 26 keys, and on and on, creating a
tree-like graph.
The word apple will be searched first by the “a” key, which will return another
dictionary with 26 letters, and in that one we’ll grab the “p” key and on and on
until we reach the last “e” which will contain the information that you have
reached the end of a word.
This compresses the data slightly because, in our example with the word apple,
think of every word that starts with an A, but we'll only need to store one initial
A for all those words, as the key to a Dictionary object. The same will happen
with each character in the word apple. You would need then to store the word
data as a serialized Trie object. We'll see about Serialized data in the
Scramble game.
In the source files for this book I include code to generate a Trie and to search
for words in it. But I do not use a Trie list in any of the games. The reason for
that is a Trie can give you the information of whether a word IS A word. But it
does not give you anything else. And we'll need more information for the games
we're building. But you can review the logic for creating a Trie in the source
files for this book.
It's time to learn how to look for words!
Looking For Words
Depending on your game, you will need to:
• Check if a word generated by the player IS A WORD.
• Check if a random group of letters can ALL BE USED TO FORM A
WORD
• Check if any combination of letters from a random group can be used to
FORM WORDS and how many words can they form. For instance, how many
words with three letters or more can be generated using only these characters
ABBDETSY?
• Know how many words fit a specific pattern, like --A-E-.

Is A Word
This is the simplest check, and it only depends on how the data is organized. If
all words are collected in one list:
bool IsAWord (string word) {
return allWords.Contains(word);
}

Or sorted by frequency:
bool IsAWord (string word) {
foreach (var list in dictionaries) {
if (list.Contains(word)) return true;
}
return false;
}

Or by length:
bool IsAWord (string word) {
if (wordsByLength.ContainsKey (word.Length)) {
return wordsByLength [word.Length].Contains (word);
}
return false;
}
Or by first char:
bool IsAWord (string word) {
if (wordsByFirstChar.ContainsKey (word[0])) {
return wordsByFirstChar [word[0]].Contains (word);
}
return false;
}

Has Words
Checking what words can be made from a list of characters is only slightly more
complicated.
Here is one possible algorithm for this purpose:
public HashSet<string> WordsFromChars (char[] chars){

//collect all unique chars in array,


// so we don't run duplicate checks
var firstChars = new List<char> ();
foreach (var c in chars) {
if (!firstChars.Contains (c))
firstChars.Add (c);
}

var result = new HashSet<string> ();


var i = 0;

//loop through every word that begins with one of those chars
foreach (var first in firstChars) {

if (wordsByFirstChar.ContainsKey (first)){

var list = wordsByFirstChar [first];


foreach (var word in list) {
if (word.Length <= chars.Length && !result.Contains (word) ) {

var sourceChars = new char[chars.Length];


Array.Copy (chars, sourceChars, chars.Length);
var cIndex = Array.IndexOf (sourceChars, first);
if (cIndex != -1)
sourceChars [cIndex] = '-';

var wordChars = word.ToCharArray ();

var match = true;

//skip first character, we have it the words already


for (var j = 1; j < wordChars.Length; j++) {

var index = Array.IndexOf (sourceChars, wordChars [j]);

if (index != -1) {
sourceChars [index] = ' ';
} else {
match = false;
break;
}
}
if (match)
result.Add (word);
}
}
}
}
return result;
}

For this, we'll use the collection where words are sorted by their first letter.
slightly more
The method returns a list of words, at least three letters long, that can be made
from letters found in the array of chars.

Has A Word
This will check if a random group of letters can all be used to form a word.
We'll use the wordSortedChars collection for this.

string HasAWord (char[] chars) {


var sortedChars = System.Array.Sort(chars);
var key = new string (sortedChars);
if (wordSortedChars.ContainsKey(key)) {
var list = wordSortedChars[key];
return list[ Random.Range(0, list.Count)];
}
return null;
}

In most cases, you will want to know the word that can be generated, so you'll
probably want to return a string instead of a boolean and check if the result is
null for any particular array of chars.
This other algorithm does the same thing but works off a generic list of words:
public HashSet<string> HasAWord (char[] chars){

var result = new HashSet<string> ();


var i = 0;

foreach (var word in allWords) {


if (word.Length >= chars.Length) {
var wordChars = word.ToCharArray ();
var match = true;
for (var j = 0; j < chars.Length; j++) {
var index = Array.IndexOf (wordChars, chars [j]);
if (index != -1) {
wordChars [index] = ' ';
} else {
match = false;
break;
}
}
if (match)
result.Add (word);
}
}

return result;
}

Matches Pattern
This can be useful when building grids for crossword puzzles, and you need to
find words that match a certain pattern, say words which are five letters long and
the second character is an A.
We can use the sorted by length word map, and use a dash (-) to represent
positions in the pattern which can hold any letter:
public HashSet<string> MatchesAPattern (char[] chars){
var result = new HashSet<string> ();
var list = wordsByLength [chars.Length];

foreach (var word in list) {


var match = true;
for (var i = 0; i < word.Length; i++) {
if (chars [i] != '-' && word [i] != chars [i]) {
match = false;
break;
}
}
if (match)
result.Add (word);
}

return result;
}

And that's it for word lists!


tters long and
Summary
A good word list is one of the most important elements in most word games. The
lists you find online, although awesome, do require some extra care and thought.
You need to clean out unwanted words, and to organize the various word lists
into groups sorted by word frequency. You will want to know how common a
word is when designing your puzzles, so that the player is presented with a
decent and smooth difficulty curve.
Next, we'll handle user input for our word games.
Summary
A good word list is one of the most important elements in most word games. The
lists you find online, although awesome, do require some extra care and thought.
You need to clean out unwanted words, and to organize the various word lists
into groups sorted by word frequency. You will want to know how common a
word is when designing your puzzles, so that the player is presented with a
decent and smooth difficulty curve.
Next, we'll handle user input for our word games.
4. User Input

We move now to user input, buttons and grids! For the vast majority of word
games, we'll need to support buttons, and possibly grids. This means we need to
add logic to track a player's input to determine if a letter has been pressed, and
which letter, and to know when a complete word has been spelled.
So let's jump right in, and build a tile for the letters of the alphabet, and later
turn this tile into a button and track the player's interaction with it, before we
move on to grids.
Here's what you'll learn:
- How to build a tile prefab
- How to build a button
- How to handle user input
- How to build and scale grids
- How to select tiles in a grid
- How to find words on a letter grid
4. User Input

We move now to user input, buttons and grids! For the vast majority of word
games, we'll need to support buttons, and possibly grids. This means we need to
add logic to track a player's input to determine if a letter has been pressed, and
which letter, and to know when a complete word has been spelled.
So let's jump right in, and build a tile for the letters of the alphabet, and later
turn this tile into a button and track the player's interaction with it, before we
move on to grids.
Here's what you'll learn:
- How to build a tile prefab
- How to build a button
- How to handle user input
- How to build and scale grids
- How to select tiles in a grid
- How to find words on a letter grid
Creating The Letter Tile Prefab

Let's start with the prefab we'll be using in pretty much all of our games. We
need a GameObject to display a letter of the alphabet and which, later, in some
games, will act as a button, and then as a tile in a grid.
With all the tutorials in this section, you have the option of opening a test scene
from the sample Unity project included in the source material for this book, or
create your own scenes, for extra practice.
In the word games we'll build in this book we'll use three different types of tiles,
meaning the game object that contains the letter.
I called the basic type a TileChar , and it's simply a prefab that can display a
letter of the alphabet. It has a background sprite and a char sprite. You will find
this prefab in the example project in the source files for this book.

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_TileCharTest

If you are interested in building the tile from scratch, for practice, here are the
main steps to do it:
1. Create an empty GameObject and call it TileChar.
2. Drag the png texture called tile from the Textures folder and drop it inside
the TileChar GameObject. This should create a GameObject called tile in
your Hierarchy panel, and this tile contains a SpriteRenderer component. So
basically, it's a sprite.
3. Repeat the same process with all the textures for the letters of the alphabet
you'll find in the Textures folder. From tile2_a to tile2_z. Make sure they are
placed in the Hierarchy panel in the correct alphabetical order. You will see soon
why this is important.

Figure 4.0 - The Letter Sprites

4. All the sprites should be at position 0, 0, 0 by default. If not, make sure of


this by selecting all the sprites and setting their position to 0 in their Transform
component in Inspector panel.
5. Select all the letter sprites inside the Hierarchy panel and inside the
SpriteRenderer component inspector in the Inspector panel, make sure Order
will see soon in Layer is set to 1.

Figure 4.1 - Order In Layer Value

This will make the letters always appear on top of the background sprite, which
is sorted by default with an order in layer of 0.
6. With all the letter GameObjects still selected, disabled them by unchecking
the enable box in the Inspector panel

Figure 4.2 - The Enable Check Bok

7. Go back to the container GameObject we called TileChar and in its


Inspector panel add one more component. This time a script component called
OrderTileChar.cs.
We'll go over this script next.

The TileChar Script


Let's review the TileChar script, which only handles the individual letters and
sets the color for the tile background.
We'll extend this script to add button functionality later, and to serve as a grid tile
also.
You'll find the finished script in the source files.
This is what the script looks like:
using UnityEngine;
using System;
using System.Collections;

public class TileChar : MonoBehaviour {


public static char[] chars = new char[] {'a','b','c','d','e','f','g',
'h','i','j','k','l','m','n','o','p',
'q','r','s','t','u','v','w','x','y','z'};
y unchecking
public static char[] vowels = new char[] {'a','e','i','o','u'};
public static char[] consonants = new char[] {'b','c','d',
'f','g','h','j','k','l','m','n','p','q',
'r','s','t','v','w','x','y','z'};

public GameObject[] charsGO;


public GameObject tileBg;
[HideInInspector]
public int type;

public char TypeChar {


get { return chars [type]; }
private set{}
}

public void SetTileData (char c, bool display = true)


{
charsGO [type].SetActive (false);
var index = Array.IndexOf (chars, c);
charsGO [index].SetActive (true);
type = index;
tileBg.GetComponent<Renderer> ().material.color = Color.black;
charsGO [index].GetComponent<Renderer> ().material.color = Color.white;
gameObject.SetActive (display);
}
public void SetColor (Color tileBgColor, Color tileCharColor) {
tileBg.GetComponent<Renderer> ().material.color = tileBgColor;
charsGO [type].GetComponent<Renderer> ().material.color = tileCharColor;
}
}

The properties are, in order:


• chars : A static list of the lowercase alphabet.

• vowels and consonants : The same for vowels and consonants. I split the
vowels and consonants in case I need to create tiles with a set ratio of vowels to
consonants (we'll do this later when we create grids.)
e as a grid tile
• charsGO : A public array of GameObjects which will reference the tile's
letter GameObjects, in the same order of the alphabet (gameObject a,
gameObject b, gameObject c...)
• tileBg : A public reference to the background tile sprite.

• type : An integer holding the current index of the letter being displayed.

Then we have the methods.


• TypeChar : A getter for the char value the tile displays.

• SetTileData : A method that allows you to pass the tile a char value, and
the tile will display the letter for it. It sets the default color to be black for the
background tile and white for the letter being displayed.
• SetColor : And finally, there is a helper method to set the color of the tile
background and the tile char sprite, and we can use it later to display selection
states and so forth.
It's time to populate the values inside the Unity Editor.
So go back to your Prefab, if you decided to build one for practice.
1. Select the TileChar GameObject and lock its Inspector panel by clicking
the tiny lock icon:
Figure 4.3 - The Inspector Lock button

What this does is it locks the Inspector panel to display only the information for
the TileChar GameObject and nothing else, so even if you select a different
GameObject the Inspector panel will still display information for the
TileChar GameObject.

2. Drag the tile sprite game object and drop it inside the input field for Tile Bg
in the TileChar script component Inspector.

3. Select all letter game objects in the Hierarchy panel and drag them to the
Chars Go field until you see a plus icon:

Figure 4.4 - Dragging Multiple Items to Array Field

Then drop them. This will populate the array with the letter sprites in the same
order they appear in the Hierarchy. And they must be in alphabetical order for
the logic to work. That's why I insisted that the sprites must be stacked in the
Hierarchy panel in the correct alphabetical order. Otherwise you would need to
drag each individual sprite to their corresponding array field. Boring!
4. Don't forget to unlock the Inspector once you're done with the previous step.

Note:
There is another way to populate Inspector fields, by using the target icon for
each component reference field.

Figure 4.5 - The Target button


And we'll see how to do that once we start building our games.

5. This is it for the TileChar GameObject. All that's left to do is to drag it to


the Project panel to turn it into a prefab.
Let's create a tile button now, which can respond to user input later.
order they appear in the Hierarchy. And they must be in alphabetical order for
the logic to work. That's why I insisted that the sprites must be stacked in the
Hierarchy panel in the correct alphabetical order. Otherwise you would need to
drag each individual sprite to their corresponding array field. Boring!
4. Don't forget to unlock the Inspector once you're done with the previous step.

Note:
There is another way to populate Inspector fields, by using the target icon for
each component reference field.

Figure 4.5 - The Target button


And we'll see how to do that once we start building our games.

5. This is it for the TileChar GameObject. All that's left to do is to drag it to


the Project panel to turn it into a prefab.
Let's create a tile button now, which can respond to user input later.
Creating The Tile Button Prefab
We will extend our TileChar script and track user basic interaction with it.
We'll call this new script TileButton and it will be a separate prefab.

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_TileButtonTest

If you want to build you own for practice here are the steps to do it:
1. Create an empty GameObject and call it TileButton.
2. Repeat the previous steps all the way until we're ready to add the script
component.
3. Add a BoxCollider2D component to the TileButton GameObject.

4. Select the TileButton GameObject and add its script component. The script
is called TileButton.cs .

We'll go over this script next.


5. Fill out the information exposed by the script and turn TileButton into a
prefab.
Let's take a look at that script.

The TileButton Script


It extends TileChar and adds special functionality in order to behave as a
button:
using UnityEngine;
using System;
using System.Collections;

[RequireComponent(typeof(Collider2D))]
public class TileButton : TileChar {

[HideInInspector]
public bool selected;

[HideInInspector]
public bool touched;

public void Select (bool value) {


selected = value;
if (selected) {
SetColor (Color.white, Color.black);
} else {
SetColor (Color.black, Color.white);
}
}

void OnMouseDown () {
touched = true;
}

void OnMouseOver () {
touched = true;
}

void OnMouseOut () {
touched = false;
}

void OnMouseUp () {
touched = false;
}
}
ent. The script
The prefab is still the same as our previous one, only now it has a different script
component. With it, we add interactivity to the tile: the user now can select it,
which will change the tile colors based on whether or not it's selected.
But in order to add touch support, we must add a requirement. This
GameObject must now contain a Collider2D component, which in this
particular case happens to be a BoxCollider2D . This will trigger the Unity
engine methods of OnMouseDown , OnMouseOver , OnMouseOut , and
OnMouseUp . The GameObject requires a Collider component in order to
respond to those events.
The tile now has a selected and touched property. We'll use these later,
particularly the second one, when reading user input.
When the tile is selected or deselected, I change the color of the background and
char sprites.
These colors could be made public properties for the inspector, since it would
allow you to use a nice color picker inside the editor for the on and off states of
the tile.
Next, we'll build our third and final tile type.

ifferent script
particularly the second one, when reading user input.
When the tile is selected or deselected, I change the color of the background and
char sprites.
These colors could be made public properties for the inspector, since it would
allow you to use a nice color picker inside the editor for the on and off states of
the tile.
Next, we'll build our third and final tile type.
Creating The Grid Tile Prefab
Now we come to our final tile type, and the one we'll use the most: The
GridTile .

It has only a slight difference when compared to the TileButton .

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_GridTileTest

If you want to build it yourself here's what you need to do:


1. Create an empty GameObject and call it GridTile
2. Repeat the previous steps all the way until we're ready to add the script
component.
3. Add a BoxCollider2D component to the GridTile GameObject.

4. Select the GridTile GameObject and add its script component. The script is
called GridTile.cs .

5. Fill out the information exposed by the script and turn GridTile into a
prefab.
Next, we'll take a closer look at the GridTile script.

The GridTile Script


This is what the script looks like:
using UnityEngine;
using System;
using System.Collections;

public class GridTile : TileButton {

[HideInInspector]
public int row;

[HideInInspector]
public int column;

public void SetTilePosition ( int row, int column){


var size = tileBg.GetComponent<SpriteRenderer> ().bounds.size.x;
this.column = column;
this.row = row;
var tilePosition = new Vector3 ( (column * size) , (-row * size) , 0);
transform.position = tilePosition;

foreach (var go in charsGO) {


go.SetActive(false);
}
Select (false);
}
}

It extends TileButton , so it has interactivity. And it contains logic to place the


tile inside a grid, through the method SetTilePosition . And besides that, it
holds a value for row and column .

And that's it for this tile. They don't do much, and if you run the scenes now in
the simulator you won't see anything other than a white square.
What we need to do now, is hook them up to a proper input handler!
We'll do that next.
. The script is
using UnityEngine;
using System;
using System.Collections;

public class GridTile : TileButton {

[HideInInspector]
public int row;

[HideInInspector]
public int column;

public void SetTilePosition ( int row, int column){


var size = tileBg.GetComponent<SpriteRenderer> ().bounds.size.x;
this.column = column;
this.row = row;
var tilePosition = new Vector3 ( (column * size) , (-row * size) , 0);
transform.position = tilePosition;

foreach (var go in charsGO) {


go.SetActive(false);
}
Select (false);
}
}

It extends TileButton , so it has interactivity. And it contains logic to place the


tile inside a grid, through the method SetTilePosition . And besides that, it
holds a value for row and column .

And that's it for this tile. They don't do much, and if you run the scenes now in
the simulator you won't see anything other than a white square.
What we need to do now, is hook them up to a proper input handler!
We'll do that next.
Detecting Touches On Tiles
We still need to add the logic that will connect the user input information with
these tiles.
You could add the input logic inside the OnMouse events and be done with it.
But trust me on this, those events don't work as well as you would think. Instead,
what you want is to combine that logic with something a bit more refined.
First, let's create something we can run tests on. Time to create another scene!

Creating The TilePanel


It's time to put our interactive tiles to the test, building a sort of control panel,
where the player can select individual tile buttons.

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_TilePanelTest

Once again, for practice, you may create your own test scene following these
steps:
1. Create your test scene and add an empty GameObject and call it TilePanel
(as in control panel, get it?)
2. Add 9 TileButton prefabs inside the TilePanel GameObject. Just drag
and drop the prefab inside the Hierarchy panel, nine times.
3. Select all instances of the prefab in the Hierarchy panel and scale them to
about 0.3.
hink. Instead,

Figure 4.6 - Scaled Tiles


4. Distribute the instances in the Scene view to create a panel.

Figure 4.7 - Tile Buttons Spread in the Scene


We're ready now to create our scripts that handle interactivity!

Listening To Input
In Unity, the information regarding user input is available through the Input
api.
You can get information about touches, mouse input, accelerometer input...
You'll find a complete description of it here.
In our games, we'll be mostly concerned with:
Input.GetMouseButtonDown(0);
Input.GetMouseButtonUp(0);
Input.mousePosition;
Input.touches;

The way I like to do this is to create a reusable component I can drop anywhere,
using a simple delegation system. There is one script, called an input controller,
which will collect all input information and pass it on to a delegate.

The Delegate
This is a simple interface, so we can use something called 'Polymorphism'. This
means, basically, that any type of class or object can be treated as something
else, based on what it does, rather than what it is.
The interface looks like this:
using UnityEngine;
public interface IInputHandler {
void HandleTouchDown (Vector2 touch);
void HandleTouchUp (Vector2 touch);
void HandleTouchMove (Vector2 touch);
}

With this, any class, no matter what type of class IT IS, as long as it implements
this interface, can be refered to as being of type IInputHandler because you
know that whatever else that class IS, it at least DOES these three things
described in the interface. As long as you're only referencing that functionality,
you may refer to any class type implementing this interface as being of type
IInputHandler and access the properties described in the interface. This is
what Polymorphism is all about.
So the delegate will handle these three methods. The delegate can be anything: it
could be a grid, or in this particular tutorial, our TilePanel .

But what is the thing calling those methods in the delegate?

The InputController
The other class we need, I'll call it InputController.cs , will become the
component that will collect user input information and pass it on to the delegate.
Remember, any class which implements the IInputHandler interface can
become a delegate.
The actual input controller looks like this:
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(IInputHandler))]
public class InputController : MonoBehaviour {

private IInputHandler handler;


private bool touchDown;

void Awake () {
handler = GetComponent<IInputHandler> ();
}

void Update () {

if (handler == null)
return;

if (Input.touches.Length > 0) {

Touch touch = Input.touches[0];

if (touch.phase == TouchPhase.Began)
{
handler.HandleTouchDown (touch.position);
touchDown = true;
}
else if (touch.phase == TouchPhase.Canceled ||
touch.phase == TouchPhase.Ended)
{
handler.HandleTouchUp(touch.position);
touchDown = false;
}
else if (touch.phase == TouchPhase.Moved ||
touch.phase == TouchPhase.Stationary)
{
handler.HandleTouchMove (touch.position);
}
e anything: it if (touchDown)
handler.HandleTouchMove (touch.position);

return;
}
else if (Input.GetMouseButtonDown(0) )
{
handler.HandleTouchDown (Input.mousePosition);
touchDown = true;
}
else if (Input.GetMouseButtonUp(0))
{
handler.HandleTouchUp(Input.mousePosition);
touchDown = false;
}
else {
if (touchDown)
handler.HandleTouchMove (Input.mousePosition);
}
}
}

Basically, we use the touch phases, or the mouse button down and up states, to
determine if we have a touch down (press), up (release) or move.

Note:
The logic presented here does not account for multiple touches. Mainly
because none of the tests or prototypes we'll build requires multi-touch
support. If your game does require it, it is simply a matter of handling all
touch data inside the Input.touches array, instead of handling just the first
one as we do here.

The logic handles mouse input, and touch input, so you can test your game on
the desktop as well as on a device.
It requires an IInputHandler component, and it uses that to handle all touch
events.
This means the same GameObject that contains the InputController
component must contain a component that implements the delegate interface.

You could also use regular Events in your logic to dispatch the different touch
phases and the touch positions, if you find you prefer it that way. Then the
InputController becomes a simple event dispatcher.

Let's work on our tile panel script next.

Creating Our TilePanel Script


We're ready to implement our TilePanelTest logic. This is a class which will
implement the IInputHandler interface and use the input information to select
and deselect the various TileButton GameObjects we placed in the scene.
1. So first, we declare the implementation:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TilePanelTest : MonoBehaviour, IInputHandler {


}

2. Let's add a few properties:


public List<TileButton> tiles;
private TileButton selectedTile;
private List<TileButton> selectedTiles;

• tiles : We need a list of references to the tiles placed in the scene.

• selectedTile : A reference to the currently selected tile.

• selectedTiles : And a list of all currently selected tiles, in case we want to


allow the player to spell an entire word, selecting multiple tiles at a time.
3. On Awake we initialize the local properties and randomly select letters for
our panel tiles.
void Awake () {
selectedTiles = new List<TileButton> ();
foreach (var tile in tiles) {
tile.SetTileData (TileChar.chars [
Random.Range (0, TileChar.chars.Length)]);
}
}

4. Next, we need a method to determine the tile closest to a point on the screen.
The point will come from our input information.
private TileButton TileCloseToPoint (Vector2 point){
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

var minDistance = 0.5f;


TileButton closestTile = null;
foreach (var tile in tiles) {
if (!tile.gameObject.activeSelf)
continue;
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
ation to select }
}

return closestTile;
}

The InputController will spit out points on touch down, up and move. These
are screen points, meaning they refer to a position in pixels in relation to the
device screen, or monitor. We need to convert this to World Space where our
tiles are. We use a helper method from the Camera API for that.

Then we simply loop the available tiles and check which active tile is the closest
to that point.
We establish a minimum distance at first, otherwise every touch, no matter how
far away it lands from the tiles, would still return a "closest tile" result. You will
need to play around with this minimum threshold value depending on your
game. Keep in mind that values which are good on your monitor may not be as
good on a phone.
We're ready to handle the touches.
5. Touch down gets the ball rolling.
public void HandleTouchDown (Vector2 touch) {
selectedTile = TileCloseToPoint (touch);
if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
}

We simply find the closest tile and if we have one, we select it. The call to
on the screen. SubmitTile passes the tile along to whatever logic we need in our game. So
far, we simply wrote a print out to console of the currently spelled word:
private void SubmitTile () {
char[] word = new char[selectedTiles.Count];
for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);
}

6. Touch move is a variation of touch down:


public void HandleTouchMove (Vector2 touch) {
if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);


if (nextTile != null && nextTile != selectedTile &&
move. These nextTile.touched) {
selectedTile = nextTile;
selectedTile.Select(true);
if (!selectedTiles.Contains(selectedTile)) {
selectedTiles.Add (selectedTile);
}
SubmitTile ();
}
is the closest }

We need to check if the next closest tile is not the currently selected one, and we
also check the touched property of TileButton . If you recall, the OnMouse
events which are linked to the button's collider component, will switch the
touched property on and off.

This adds an extra layer of precision, so the user does not select unwanted tiles.
This becomes more and more important as the tiles get closer and closer to each
other, especially when they are organized in grids.
The collider acts as a hit area. And the secret is to make it much smaller than the
actual tile.
The key here is to make the Collider in the tile be just big enough to detect the
user touch as I illustrate here:

Figure 4.8 - User Touch and Collider Size

The circle represents the touch area. Of course this does not mean the player's
finger is huge! We're still talking about a point after all. But a touch has an "area
of probability", any tilt in the finger, any slight change in pressure and the touch
point may shift to a position the player did not intend. It's part of your job as the
developer to reduce errors with selection, and since you can't force the player to
select things in the best possible way, it's up to you to create allowances for the
player. The hit collision area will force the player to be more precise in his or her
selections.
In this last image, the line inside the squares represents the collider. Combining
the "closest tile" information, plus whether or not that tile's collider is flagging a
selection, we make away with most selection errors which can easily piss off the
aller than the
player.
You will need to play around with the size of the collider and test this a lot. (You
can change the size of the collider, you guessed it, inside the Collider2D
inspector information.)

But the idea here is to use the touch position first, to identify which tile or tiles
are closest to the touch point, and then make sure the touch actually overlaps the
collider of that tile.
7. Touch up is relatively simple:
public void HandleTouchUp (Vector2 touch) {
if (selectedTile == null) {
return;
}
//if only 3 letter word or up are of importance to your game
if ( selectedTiles.Count > 2 ) {
SubmitWord();
}
ClearSelection ();

selectedTile = null;
}

Here, we can submit the final word the player has spelled:
private void SubmitWord () {
char[] word = new char[selectedTiles.Count];
for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);
}

The logic right now looks exactly the same as the submit tile one. But in your
game, you might need to create a distinction between the selection of a tile, and
the complete spelling of a possible word.
e in his or her 8. The ClearSelection method at the end of touch up simply resets the tiles:
private void ClearSelection () {
foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
is a lot. (You
The entire script looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TilePanelTest : MonoBehaviour, IInputHandler {


public List<TileButton> tiles;
private TileButton selectedTile;
private List<TileButton> selectedTiles;

void Awake () {
selectedTiles = new List<TileButton> ();
foreach (var tile in tiles) {
tile.SetTileData (TileChar.chars [
Random.Range (0, TileChar.chars.Length)]);
}
}

public void HandleTouchDown (Vector2 touch) {


selectedTile = TileCloseToPoint (touch);

if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
}

public void HandleTouchUp (Vector2 touch) {


if (selectedTile == null) {
return;
}

if ( selectedTiles.Count > 2 ) {
SubmitWord();
}
ClearSelection ();

selectedTile = null;
}

public void HandleTouchMove (Vector2 touch) {

if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);


esets the tiles:
if (nextTile != null && nextTile != selectedTile &&
nextTile.touched) {

selectedTile = nextTile;

selectedTile.Select(true);

if (!selectedTiles.Contains(selectedTile))
selectedTiles.Add (selectedTile);

SubmitTile ();
}
}

private TileButton TileCloseToPoint (Vector2 point){


var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

var minDistance = 0.5f;


TileButton closestTile = null;
foreach (var tile in tiles) {
if (!tile.gameObject.activeSelf)
continue;
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
}
}

return closestTile;
}

private void SubmitTile () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);
}

private void SubmitWord () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);
}

private void ClearSelection () {


foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
}

We can now put all the pieces together and finish our panel test.
Putting Our TilePanel Together
Very little is left to do to complete our test:
1. Add the TilePanelTest script as a component to our TilePanel
GameObject.
2. Add the InputController script as a component to the same
GameObject.
3. Lock the Inspector, and drag the TileButton instances to populate the Tiles
field in the Tile Panel Test component.

Figure 4.9 - Feeding the Tiles to the Script

4. Run the scene in the simulator. You should be able to select the buttons by
tapping and dragging the mouse.
Figure 4.10 - The Tile Panel Test

You might need to change the aspect ratio of the simulation to make it appear as
you see in the image above. For that, you will have to change the target platform.
So go to File/Build Settings and set the platform to Android, for instance. Then
in the Scene View area, select the desired aspect ratio inside the aspect ratio drop
down (by default, it is set to Free Aspect).
Now let's do the same thing, but with a grid instead!
rget platform.

ect ratio drop


Creating A Grid
With a TilePanel or something like it, you can handle any odd collection of
buttons. But a grid is the best option for games like Boggle, as well as any Word
Find games and crosswords, where tile position matters because it makes a tile
selection valid or invalid based on where a tile is in relation to last selected tile.
Plus, for games that do require a grid, we have some juicy extra bit of logic to
cover here, like how to solve a grid, finding in it all the possible words a player
can generate using the tiles as they are stacked in the grid!
But first, we need to build the damn thing.
We'll need a number of rows and a number of columns. We'll also need our dear
old prefab, the GridTile.
First, let's take care of the business of building a grid.

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_SimpleGridTest

Building A Grid
You may create a new scene for this, or follow along with the sample project
scene.
1. Create an empty GameObject and call it Grid.
2. Attach a new script component, and call it SimpleGrid.cs . We'll only
concern ourselves with building a grid for now and add interactivity later.
3. The script's properties are:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SimpleGrid : MonoBehaviour {


public int ROWS = 4;
public int COLUMNS = 3;
public GameObject gridTileGO;
public static float GRID_TILE_SIZE;
private List<GridTile> tiles;
private List<List<GridTile>> gridTiles;
}

• ROWS and COLUMNS : Number of rows and columns, which you can change
inside the editor.
• gridTileGO : A reference to the grid tile prefab we'll instantiate to create the
grid.
• GRID_TILE_SIZE : A static variable which will hold the grid tile size, this is
important since we need to scale the big grids to fit the screen, and we need the
final grid tile size to identify which tile the player is selecting.
• tiles and gridTiles : Then we have two collections for the tiles, a one
dimensional, and and two dimensional collection. Sometimes you want to select
a random tile, or loop quickly through all the tiles–to reset them, for instance.
And sometimes you want them sorted by rows and columns. So it's useful to
keep them organized in two different types of collections.
4. The logic to build the Grid:
public void BuildGrid () {
if (tiles != null && tiles.Count != 0) {
foreach (var t in tiles)
Destroy (t.gameObject);
//reset scale
transform.localScale = Vector2.one;
transform.localPosition = Vector2.zero;
}

//the grid as a one dimensional list of tiles


tiles = new List<GridTile> ();

//the two dimensional grid


gridTiles = new List<List<GridTile>> ();

for (int row = 0; row < ROWS; row++) {


var rowsTiles = new List<GridTile>();
for (int column = 0; column < COLUMNS; column++) {
var item = Instantiate (gridTileGO) as GameObject;
var tile = item.GetComponent<GridTile>();
tile.SetTilePosition(row, column);
tile.transform.parent = gameObject.transform;
tiles.Add (tile);
rowsTiles.Add (tile);
}
gridTiles.Add(rowsTiles);
}

ScaleGrid ( Mathf.Abs (gridTiles [0] [0].transform.localPosition.y -


gridTiles [1] [0].transform.localPosition.y));
}

First we check if we have a grid built already, if we do, we destroy it and reset
the grid's container size and position, in case the previous grid needed to be
scaled.

e to create the The two, nested for loops will build the grid row by row.

We have the call to instantiate new tiles based on our prefab:


var item = Instantiate (gridTileGO) as GameObject;
var tile = item.GetComponent<GridTile>();

Remember that you instantiate a GameObject and then you need to refer to their
GridTile component.

We finish by calling our ScaleGrid method:

5. Here's what that method looks like:


private void ScaleGrid ( float tileSize) {
GRID_TILE_SIZE = tileSize;

var stageWidth = 4.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}
GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 ((gridWidth * scale) * -0.5f
3.5f - 0.5f * (gridHeight * scale));
}

The method uses the tile size in world view, which we get by subtracting the
position of two neighboring tiles.
The ScaleGrid method will use a pre-determined size for our stage (4 x 4.8),
or game view, and make sure the grid will fit inside it. It scales the grid container
and repositions it so it's centered on screen.
In some games we might need to offset the grid position a bit, and that last line is
what we need to change in order to do that:
transform.localPosition = new Vector2 ((gridWidth * scale) * -0.5f , 3.5f - 0.5f *
(gridHeight * scale));

You can easily make the grid appear closer to the bottom of the top of the screen,
or offset it to the left or right, in order to fit in some other layout elements. You'll
see an example of this in our last game, Crossing.

o refer to their One important point here is to grab the scaled tile size and store it in the
GRID_TILE_SIZE property. We'll use that value next when handling user
interaction.
6. We just need to make sure to call the BuildGrid method at some point in
our script so we can test it:
void Start () {
BuildGrid ();
}

Here is the logic that builds and scales a grid:


using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SimpleGrid : MonoBehaviour {

public int ROWS = 8;


public int COLUMNS = 6;
public GameObject gridTileGO;
public static float GRID_TILE_SIZE;
private List<GridTile> tiles;
private List<List<GridTile>> gridTiles;
void Start () {
BuildGrid ();
}

public void BuildGrid ()


{
if (tiles != null && tiles.Count != 0) {
foreach (var t in tiles)
Destroy (t.gameObject);
//reset scale
transform.localScale = Vector2.one;
transform.localPosition = Vector2.zero;
grid container }

//the grid as a one dimensional list of tiles


tiles = new List<GridTile> ();
hat last line is
//the two dimensional grid
gridTiles = new List<List<GridTile>> ();

for (int row = 0; row < ROWS; row++) {


var rowsTiles = new List<GridTile>();
for (int column = 0; column < COLUMNS; column++) {
var item = Instantiate (gridTileGO) as GameObject;
var tile = item.GetComponent<GridTile>();
of the screen, tile.SetTilePosition(row, column);
ments. You'll tile.transform.parent = gameObject.transform;
tiles.Add (tile);
rowsTiles.Add (tile);
}
gridTiles.Add(rowsTiles);
}

ScaleGrid ( Mathf.Abs (gridTiles [0] [0].transform.localPosition.y -


gridTiles [1] [0].transform.localPosition.y));
}

private void ScaleGrid ( float tileSize) {

GRID_TILE_SIZE = tileSize;

var stageWidth = 4.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}
GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 ((gridWidth * scale) * -0.5f ,
3.5f - 0.5f * (gridHeight * scale));
}
}
Let's hook up our grid and its script component.

Testing The Grid


Just add that SimpleGrid script to your Grid GameObject, drag the GridTile
prefab to the Grid Tile GO field in the Simple Grid component.
Run the scene in the simulator and you should see a big black rectangle.

Figure 4.11 - The Initial Grid


Let's add interactivity.

Handling Input
We took care of the logic that builds and scales a grid. But now we're ready to
make the grid a bit more fun to play with, by adding our input controller
component and handling user interactivity.

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_GridTest

You can make the changes listed here to your SimpleGrid class. Or use the
sample scene provided. The source files for this book will contain the following
logic inside a script called Grid.cs and that's the name I'll use in the next steps.
The Grid class will contain everything the SimpleGrid class contains, plus
some extra funcitionality. So if you want to build this from scratch, make sure
your Grid class starts off as copy of SimpleGrid .

1. First, make sure the script implements the IInputHandler interface:

public class Grid : MonoBehaviour, IInputHandler {

2. We'll add logic to handle two types of interactions this time. In the first type,
the user can tap the individual tiles to form a word, and when the user taps on
something other than a tile, it signals the end of a word.
The second type of interaction has the user dragging his or her finger across the
screen, collecting tiles. When the touch is released, this signals the end of a
spelled word.
We'll add two definitions called TAP_SELECTION and DRAG_SELECTION which
will separate the two bits of logic.
I also define a EIGHT_DIRECTIONAL property which defines whether or not a
tile can be selected diagonally from another tile (meaning a tile can have up to
eight selectable neighbors in a grid).
So at the very top of the class, add the three definitions:
#define EIGHT_DIRECTIONAL
//#define TAP_SELECTION
#define DRAG_SELECTION

Leave the second one commented out. Again, definitions are useful in order to
enable and disable chunks of code. If a section in our code only becomes active
if TAP_SELECTION is defined, and since we commented it out, that section of
code will remain inactive, and it won't actually compile with the rest of the code.
It is as if the logic never existed. We can pick and choose the logic that best suits
out needs this way.
3. We add a few new properties to our Grid class, to store selected tiles.
These should look familiar to you:
private GridTile selectedTile;
private List<GridTile> selectedTiles;

4. The logic to find the tile closest to a point will change slightly, because now
we can use grid logic for that:
private GridTile TileCloseToPoint (Vector2 point) {
//convert screen point to world position
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;
int c = Mathf.FloorToInt ((t.x -
gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);

if (c < 0) return null;


the first type,
if (c >= COLUMNS) return null;

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - t.y ) / GRID_TILE_SIZE);

if (r < 0) return null;


if (r >= ROWS) return null;
if (gridTiles.Count <= r) return null;
if (gridTiles[r].Count <= c) return null;
if (!gridTiles[r][c].gameObject.activeSelf) return null;

return gridTiles[r][c];
}

One of the main advantages of a grid, in every game one is used—even arcade
games—is that we can easily determine which cell in the grid is closest to a
point. It's this bit of logic that determines the column index and the row index of
the cell closest to a point:
int c = Mathf.FloorToInt (
(t.x - gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - t.y ) / GRID_TILE_SIZE);

I know it looks complicated. But it really isn't. We need the result to be an


t of the code. integer, since we're looking for the array index of the cell. We need to know how
hat best suits big each cell is, and we have that from GRID_TILE_SIZE . And we need to know
where the grid begins in order to offset the touch point, so we simply grab the
value from the first cell and its position on screen to mark the point where the
grid begins:
gridTiles[0][0].transform.position.x,
gridTiles[0][0].transform.position.y

There is only a bit more logic added as an offset to the tiles themselves because
their origin point is actually in the center of the tile and not at the bottom left
corner.
Offset the grid position on the screen, plus half the size of a tile, and then divide
that value by the tile size and boom, you get an index!
5. This is the HandleTouchDown logic:
public void HandleTouchDown (Vector2 touch) {
selectedTile = TileCloseToPoint (touch);
if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
}
}

When we get a touch down event, we look for the closest tile to the touch
position and set that as the selected tile and our first in the list of all selected
tiles.
6. Touch up looks like this:
public void HandleTouchUp (Vector2 touch) {
if (selectedTile == null) {
#if TAP_SELECTION
//if no tile selected submit selected tiles
ClearSelection ();
#else
return;
#endif
}
ClearSelection ();
selectedTile = null;
}

Here we check for our definitions. If we are using the TAP_SELECTION logic
where we can only select one button at a time, we need a way to submit the
spelled word once the player is done spelling. So if there is a touch up event but
to know how no selected tile, this means the player tapped outside the grid, and we can
need to know interpret that as the action to submit the spelled word.

7. And now, here's how we handle Touch Move:


public void HandleTouchMove (Vector2 touch) {
#if TAP_SELECTION
return;
#endif
if (selectedTile == null) return;
var nextTile = TileCloseToPoint (touch);
if (nextTile != null && nextTile != selectedTile &&
nextTile.touched) {
if (nextTile.row == selectedTile.row &&
(nextTile.column == selectedTile.column - 1 ||
nextTile.column == selectedTile.column + 1)) {
selectedTile = nextTile;
} else if (nextTile.column == selectedTile.column &&
(nextTile.row == selectedTile.row - 1 ||
nextTile.row == selectedTile.row + 1)) {
selectedTile = nextTile;
}
#if EIGHT_DIRECTIONAL
else if (Mathf.Abs (nextTile.column - selectedTile.column) == 1 &&
Mathf.Abs (nextTile.row - selectedTile.row) == 1) {
selectedTile = nextTile;
}
#endif

selectedTile.Select(true);
if (!selectedTiles.Contains (selectedTile))
selectedTiles.Add (selectedTile);
else {
//deselect tile if it's already been added to selected tiles
var index = selectedTiles.IndexOf(nextTile);
for(var i = selectedTiles.Count -1; i >= 0; i--) {
var tile = selectedTiles[i];
if (i > index) {
tile.Select(false);
selectedTiles.RemoveAt(i);
}
}
selectedTile = selectedTiles[selectedTiles.Count - 1];
}
}
}

If we're running the TAP_SELECTION mode, we don't need to bother with the
touch move event at all. Since we don't drag to select, but tap to select.
Otherwise, we find the closest tile to the touch position and we check the
GridTile touched property it inherits from the TileButton class.

Next, we check if the next tile is a neighbor of our currently selected tile. Our
EIGHT_DIRECTIONAL definition can add or remove the logic that allows
diagonal neighbors being selected. If the next tile is a neighbor, and the next tile
is not in our list of selected tiles already, we add it to the list. If it is already in
the list, we deselect all tiles that come after it and splice the list so that we only
have tiles selected up to that one.
This means the player is moving the finger back on top of a previously selected
tile. This usually means the player made a wrong move and is trying to correct
the selection.
This logic then allows the player to backtrack and remove, unwanted, previously
selected tiles from the word he or she is trying to spell.

Note:
Remember our collider, and how it acts as a hit area inside our tiles? With
grids, as the grid scales down, we have an extra concern where it might be a
good idea to make the collider even smaller. The smaller the grid tile, the
smaller the collider area should be. Again, the recommendation is to test tile
selection on the target device as soon as possible. All tile selection problems
go away once you hit upon the perfect collider size.
8. We need now a method to clear the selections:
private void ClearSelection () {
foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}

Pretty much identical to what we saw in out tile panel test.


9. And finally a way to fill the grid with random letters:
public void ShowGridChars () {
for (var i = 0; i < tiles.Count; i++) {
tiles [i].SetTileData ( TileChar.chars [
Random.Range (0, TileChar.chars.Length)] );
}
}

10. Just add logic to build a grid for our test and you're done:
void Start () {
BuildGrid();
ShowGridChars();
}

d, previously
Let's put it to the test!

Testing Our Grid


This time you will need to add the InputController component to the same
game object that contains the Grid component.
Figure 4.12 - The Grid Components

Once you have filled in the grid prefab field of that component, just go ahead
and run the scene in the simulator.
Figure 4.13 - The Final Grid Test
Figure 4.13 - The Final Grid Test
A Word On Properly Filling A Grid
A grid is a vital part on many word games. You will need it for crossword
puzzles, word search games, Scrabble like games, and Boggle like games.
It is important therefore to build grids that yield a great number of words, and
preferably a great deal of these will be commonly known words (once again,
knowing how common a word is, is vital to designing a decent word game with a
smooth difficulty curve.)
But how do you know which letters to pick?
In order to generate good grids, meaning grids that contain a large number of
words, there are quite a few things you can try.
I’ve run many tests before and a grid that use about 50-60% of vowels and 50-
40% of consonants seem to yield the best results in English.
Another good way is to shuffle a high yielding word, a word whose characters
can be used to form the greatest number of other words (we'll see how to find
these magical words when we build our second game, Scramble.) And then use
the shuffled letters from those words to fill in a grid. This also seem to result in
good word-density.
We also have data, easily available online, that sorts the letters of the alphabet
based on how frequent they appear in the dictionary. We'll also use this data in
our first game prototype, Hangman. So when selecting the 60% vowels and
40% consonants that fill up a grid, you might want to increase the chance of
selecting the most frequent vowels and consonants.
For now, I'll simply use this next method, with a 50/50 ratio of vowels and
consonants. Just give it the number of tiles you need to fill a grid, (COLUMNS *
ROWS):
public char[] GetRandomChars (int len) {
if (len == 0)
len = 100;

var randomString = "";


var vRatio = 0.5f;
var cRatio = 0.5f;
var vowels = Mathf.RoundToInt (len * vRatio);
var consonants = Mathf.RoundToInt (len * cRatio);

var i = 0;
while (i < vowels) {
randomString += TileChar.vowels[
UnityEngine.Random.Range(0, TileChar.vowels.Length)];
i++;
}

i = 0;
while (i < consonants) {
randomString += TileChar.consonants[
UnityEngine.Random.Range(0, TileChar.consonants.Length)];
d game with a i++;
}

while (randomString.Length < len) {


randomString += TileChar.vowels[
UnityEngine.Random.Range(0, TileChar.vowels.Length)];
}

return Utils.Scramble<char> (randomString.ToCharArray ());


}

We then change our ShowGridChars method to receive an array of chars:

public void ShowGridChars (char[] chars) {


for (var i = 0; i < tiles.Count; i++) {
tiles [i].SetTileData ( chars[i] );
}
}

And now, let's see how to solve any grid, and get all the words a player can
generate inside it.

COLUMNS *
var vowels = Mathf.RoundToInt (len * vRatio);
var consonants = Mathf.RoundToInt (len * cRatio);

var i = 0;
while (i < vowels) {
randomString += TileChar.vowels[
UnityEngine.Random.Range(0, TileChar.vowels.Length)];
i++;
}

i = 0;
while (i < consonants) {
randomString += TileChar.consonants[
UnityEngine.Random.Range(0, TileChar.consonants.Length)];
i++;
}

while (randomString.Length < len) {


randomString += TileChar.vowels[
UnityEngine.Random.Range(0, TileChar.vowels.Length)];
}

return Utils.Scramble<char> (randomString.ToCharArray ());


}

We then change our ShowGridChars method to receive an array of chars:

public void ShowGridChars (char[] chars) {


for (var i = 0; i < tiles.Count; i++) {
tiles [i].SetTileData ( chars[i] );
}
}

And now, let's see how to solve any grid, and get all the words a player can
generate inside it.
Solving A Grid
In order to solve a grid, looking for all the words a player could find in it, you
will need an algorithm known as backtracking. With it, you can run through all
available combination of tiles in a grid and whenever a combination fails to yield
the result you want, you “backtrack” to a point where a different combination
might yield a better result. Rather like following the branches on a tree. The
algorithm spreads itself to all branches, in search of a result and dismisses
branches that yield a failure.
You can use this to solve and generate grids for word games, crosswords, word
find games, sudoku games, and even path finding games, etc…
It is however a more heuristic algorithm and therefore doesn’t always suit a
game's runtime, meaning it might be best to use the algorithm to generate data
for a puzzle inside a puzzle editor or generator, but not inside the game itself.
For smaller grids however, it is perfectly safe to run this inside the game, and
that’s what we’ll do in this book.
But enough talking. Here is the code we’ll use:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

public struct ProcessedGrid {


public List<List<char>> grid;
public List<string> words;
public List<List<Vector2>> positions;
public int columns;
public int rows;
}

public class GridUtils {

static Hashtable results;


static string searchKey;
static ProcessedGrid gridToProcess;

public static List<int> FindWord ( string w, string g, int rows, int cols) {

CollectWord (w, g, rows, cols);

if (results.ContainsKey (w)) {
return results [w] as List<int>;
}
return null;
}

static void CollectWord (string word, string g, int rows, int cols) {

results = new Hashtable ();

gridToProcess = new ProcessedGrid();


gridToProcess.columns = cols;
n fails to yield gridToProcess.rows = rows;
gridToProcess.grid = CharGrid (g, rows, cols);
gridToProcess.positions = new List<List<Vector2>>();
gridToProcess.words = new List<string>();

searchKey = word;
SearchWord (searchKey);

foreach (DictionaryEntry entry in results) {


gridToProcess.words.Add ((string)entry.Key);

var wpositions = new List<Vector2> ();


foreach(var v in (List<Vector2>) entry.Value) {
wpositions.Add (v);
}
gridToProcess.positions.Add (wpositions);
}
}

public static ProcessedGrid SolveGrid (ref List<string> dictionary,


string g, int rows, int cols) {

results = new Hashtable ();

gridToProcess = new ProcessedGrid();


gridToProcess.columns = cols;
gridToProcess.rows = rows;
gridToProcess.grid = CharGrid (g, rows, cols);
gridToProcess.positions = new List<List<Vector2>>();
gridToProcess.words = new List<string>();

var matchString = new StringBuilder ();


foreach (var row in gridToProcess.grid) {
foreach (var c in row) {
matchString.Append(c);
}
}

Regex Validator = new Regex(@"^["+matchString.ToString()+"]+$");

foreach (var word in dictionary) {

var length = word.Length;

if( Validator.IsMatch(word) ) {
SearchWord (word);
}
}

foreach (DictionaryEntry entry in results) {


gridToProcess.words.Add ((string)entry.Key);
var wpositions = new List<Vector2> ();
foreach(var v in (List<Vector2>) entry.Value) {
wpositions.Add (v);
}
gridToProcess.positions.Add (wpositions);
}

return gridToProcess;
}

static void SearchWord(string w) {


searchKey = w;
// from each cell of the matrix, Search for the pattern!
for (int i = 0; i < gridToProcess.grid.Count; i++) {
for (int j = 0; j < gridToProcess.grid[i].Count; j++) {
List<List<int>> board = GetTraverseBoard();
Traverse(0, i, j, board);
}
}
}

static void Traverse(int depth, int row, int column, List<List<int>> board) {

if (gridToProcess.grid[row][column] != searchKey[depth])
return;

if (board == null)
return;

depth++;
if (depth == searchKey.Length) { // if the word has been found

if (!results.ContainsKey(searchKey)) {
board[row][column] = depth;
results.Add (searchKey, PrintCoordinates(board));
board[row][column] = 0;
}
return;
} else {
board[row][column] = depth;

if (row - 1 >= 0 && column - 1 >= 0


&& board[row - 1][column - 1] == 0) {
Traverse(depth, row - 1, column - 1, board);
}

if (column - 1 >= 0 && board[row][column - 1] == 0) {


Traverse(depth, row, column - 1, board);
}
if (row + 1 < board.Count && column - 1 >= 0
&& board[row + 1][column - 1] == 0) {
Traverse(depth, row + 1, column - 1, board);
}
if (row + 1 < board.Count && board[row + 1].Count > column &&
board[row + 1][column] == 0) {
Traverse(depth, row + 1, column, board);
}
if (row + 1 < board.Count && column + 1 < board[row + 1].Count
&&
board[row + 1][column + 1] == 0) {
Traverse(depth, row + 1, column + 1, board);
}
if (column + 1 < board[row].Count
&& board[row][column + 1] == 0) {
Traverse(depth, row, column + 1, board);
}
if (row - 1 >= 0 && column + 1 < board[row - 1].Count
&& board[row - 1][column + 1] == 0) {
Traverse(depth, row - 1, column + 1, board);
}
if (row - 1 >= 0 && board[row - 1][column] == 0) {
Traverse(depth, row - 1, column, board);
}
board[row][column] = 0;
}
}

static List<Vector2> PrintCoordinates(List<List<int>> boolBoard) {


int start = 1;
int end = searchKey.Length + 1;

var result = new List<Vector2> ();

for (int count = start; count < end; count++) {


for (int i = 0; i < boolBoard.Count; i++) {
bool isFound = false;
for (int j = 0; j < boolBoard[i].Count; j++) {
if(boolBoard[i][j] == count){
result.Add (new Vector2(i, j));
isFound = true;
break;
}
}
if(isFound)break;
}
}

return result;
}

static List<List<int>> GetTraverseBoard() {


List<List<int>> res = new List<List<int>> ();

for (int i = 0; i < gridToProcess.grid.Count; i++) {


var row = new List<int>();
for (int j = 0; j < gridToProcess.grid[i].Count; j++) {
row.Add(0);
}
res.Add (row);
}
return res;
}

static List<List<char>> CharGrid (string gridString,


int rows, int columns) {

var result = new List<List<char>> ();

var index = 0;
while (index < rows) {
result.Add (new List<char>());
index++;
}

index = 0;
var i = 0;

while (i < gridString.Length) {


var c = gridString[i];
//if your grid has a gap, represented here by a dash,
//replace it with a symbol which is not found in
//words (a number, for instance)

//if (c == '-') c = '2';

result[index].Add(c);
i++;
if (i != 0 && i % columns == 0)
index++;
}
return result;
}
}

And now let me go through it point by point.


You can use this class to look for a specific word in a grid, using the method
called FindWord , or to solve the entire grid, collecting all words that can be
found in it, using the method SolveGrid which receives as a parameter a
reference to our dictionary, or word list, and the information regarding our grid.
The most important methods in the class are the SearchWord and the
Traverse methods.

Here's the first one:


static void SearchWord(string w) {
searchKey = w;
// from each cell of the matrix, Search for the pattern!
for (int i = 0; i < gridToProcess.grid.Count; i++) {
for (int j = 0; j < gridToProcess.grid[i].Count; j++) {
List<List<int>> board = GetTraverseBoard();
Traverse(0, i, j, board);
}
}
}

As you can see, SearchWord will look for a specific word, running an
individual search on every single cell in the grid, starting with a "depth" of zero:
this means the first character in the word.
As you'll see in the beginning of the Traverse method, if the character found
in that specific row and column does not match the one at that specific depth, the
search returns a failure for that cell, and collpases that particular search branch.
So if you're searching for the word apple, you want an a at depth 0, a p at
depth 1, and so forth. But if you get a b at depth 1, for example, this means this
path or branch failed to yield a result and it collapses.
The search will also store its ongoing result inside a traverse board. This is a grid
that matches the grid being searched in number of rows and columns but every
cell in it contains an initial value of zero. A value of zero means the cell has not
been checked yet by the algorithm. It's a simple way to keep track of cells which
have been searched already along each branch of the search.
We'll also use this to store the depth at which a cell returned a good value. This
means that when we find the word in the grid, we have a record of where each
letter was found because those cells were marked as 1, 2, 3, 4 and so forth. The
cells which failed or were not checked at all remain with a value of 0.
The Traverse method does all the heavy lifting:

static void Traverse(int depth, int row,


int column, List<List<int>> board) {
if (gridToProcess.grid[row][column] != searchKey[depth])
return;

if (board == null)
return;

depth++;
if (depth == searchKey.Length) { // if the word has been found

if (!results.ContainsKey(searchKey)) {
board[row][column] = depth;
results.Add (searchKey, PrintCoordinates(board));
board[row][column] = 0;
}
return;
} else {
board[row][column] = depth;

if (row - 1 >= 0 && column - 1 >= 0


&& board[row - 1][column - 1] == 0) {
Traverse(depth, row - 1, column - 1, board);
}
if (column - 1 >= 0 && board[row][column - 1] == 0) {
Traverse(depth, row, column - 1, board);
}
if (row + 1 < board.Count && column - 1 >= 0
&& board[row + 1][column - 1] == 0) {
Traverse(depth, row + 1, column - 1, board);
}
if (row + 1 < board.Count && board[row + 1].Count > column &&
fic depth, the board[row + 1][column] == 0) {
Traverse(depth, row + 1, column, board);
}
if (row + 1 < board.Count && column + 1 < board[row + 1].Count
&& board[row + 1][column + 1] == 0) {
Traverse(depth, row + 1, column + 1, board);
his means this }
if (column + 1 < board[row].Count
&& board[row][column + 1] == 0) {
Traverse(depth, row, column + 1, board);
}
if (row - 1 >= 0 && column + 1 < board[row - 1].Count
This is a grid && board[row - 1][column + 1] == 0) {
Traverse(depth, row - 1, column + 1, board);
}
if (row - 1 >= 0 && board[row - 1][column] == 0) {
Traverse(depth, row - 1, column, board);
f cells which }
board[row][column] = 0;
}
}

You can see in that long string of conditionals that the logic continues to search,
recursively, for the word in every possible direction, branching off, always
checking for the current depth—the current letter in the word.

Note:
The logic here assumes a word can be formed via an eight-directional
selection, meaning diagonal neighbors of a cell are valid selections. Which is
why we have eight checks there. If your game does not support this, meaning
only the neighboring cells immediately above, below or to the sides of another
cell can be selected, you just need to remove the four conditionals that
changes both the column value and the row value, as in
board[row - 1][column + 1] .

Obviously, it is possible that the same word can be found more than once in the
grid, but we check for that condition once we run out of characters to check.
Unless of course you want or need that information. Otherwise we could
optimize this script even further and make sure the search is interrupted in the
Traverse method if the searchKey word has been found already.

if (depth == searchKey.Length) { // if the word has been found


//have we found this word already?
if (!results.ContainsKey(searchKey)) {
board[row][column] = depth;
results.Add (searchKey, PrintCoordinates(board));
board[row][column] = 0;
}
return;
...
Here we reached a depth that matches the word length, which means every
character was found in the grid following a legal eight directional path.
I'm only interested in one instance per word, but like I said if in your game idea
you need to know if a word is found more than once in a grid, just remove that
one if statement. And you might want to add the line
if (!results.ContainsKey(searchKey)) at the top of the method, checking
if the words has been found already, and if so, return from the method.
I then collect the cells where the word is located, using the traverse board which
at this point in the logic became a kind of boolean grid containing the
information of where the valid tiles are located. The final traverse board, if you
recall will have zeroes only for cells not checked, and will have depth values for
each one of the letters. I use the PrintCoordinates to convert the information
in the traverse board into a list of Vector2 values, which gives the x and y cell
index where each letter was found in the grid, in the same order the letters
appear in the word.
static List<Vector2> PrintCoordinates(List<List<int>> boolBoard) {
int start = 1;
int end = searchKey.Length + 1;

var result = new List<Vector2> ();

for (int count = start; count < end; count++) {


for (int i = 0; i < boolBoard.Count; i++) {
bool isFound = false;
for (int j = 0; j < boolBoard[i].Count; j++) {
if(boolBoard[i][j] == count){
result.Add (new Vector2(i, j));
isFound = true;
break;
}
}
if(isFound)break;
}
}
return result;
}

The cell for the first letter in the word will have a value of 1, the second will
have a value of 2, and so on.
I search the entire grid for the correct values and add the cell x and y address as a
Vector2 in the final result collection.
The script returns a struct:
public struct ProcessedGrid {
public List<List<char>> grid;
public List<string> words;
public List<List<Vector2>> positions;
public int columns;
public int rows;
}

This will contain all words found, their positions, as well as the initial
information we require to run the solver: number of rows and columns as well as
the grid of chars itself.
So let's put our solver to work and see the results, shall we?

Testing Our Solver


You will find a scene in the source material for this book called
Scene_GridSolverTest.
It contains a empty GameObject for the Grid bit of the logic.

We'll add now the logic to run the solver on this grid!
1. The scene's one game object, called Grid, has a Grid.cs component, a
InputController.cs component, and a GridTester.cs component.

2. Open that last script in the editor.


3. Add this method to the script:
void SolveGrid () {
var solvedGrid = GridUtils.SolveGrid (ref allWords, GetGridString(),
grid.ROWS, grid.COLUMNS);

Debug.Log (solvedGrid.words.Count + " Words Found.");

foreach (var w in solvedGrid.words) {


Debug.Log (w);
}
}

y address as aThis will run the solver, and print out a list of all the words found in the
generated grid.
4. Add the call to the SolveGrid method at the end of the
Coroutine LoadWordData , so it looks like this:
IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
mns as well as WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new List<string> ();

foreach (var w in words) {


if (string.IsNullOrEmpty(w))
continue;
var word = w.TrimEnd ();
if (word.IndexOf ('#') == -1) {
allWords.Add (word);
}
}

SolveGrid();
}

5. Run the application, and see how many words were found!
6. Increase the size of the grid and test each different size to have an idea of
how long the algorithm takes to run depending on how big the grid is. Anything
above 7x7 might start chocking a bit.
And that's it for input.
Coroutine LoadWordData , so it looks like this:
IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new List<string> ();

foreach (var w in words) {


if (string.IsNullOrEmpty(w))
continue;
var word = w.TrimEnd ();
if (word.IndexOf ('#') == -1) {
allWords.Add (word);
}
}

SolveGrid();
}

5. Run the application, and see how many words were found!
6. Increase the size of the grid and test each different size to have an idea of
how long the algorithm takes to run depending on how big the grid is. Anything
above 7x7 might start chocking a bit.
And that's it for input.
Summary
We just went over enough information to build a ton of word games. A good
word list, and a few tiles on the screen and you're good to go.
Some of the games we'll build next, uses a tile panel style of controls, others
require a grid. But I trust that your own ideas will be easily put to the test with
the logic presented in this chapter.
Time to start building our games!
Summary
We just went over enough information to build a ton of word games. A good
word list, and a few tiles on the screen and you're good to go.
Some of the games we'll build next, uses a tile panel style of controls, others
require a grid. But I trust that your own ideas will be easily put to the test with
the logic presented in this chapter.
Time to start building our games!
5. Game: Hangman

We'll begin with a classic: Hangman. But with a few twists here and there. This
is a perfect introduction to how word data can be analyzed for optimum
gameplay. We'll use our tile prefab, our input handler logic, a tile panel, and
build a new version of hangman with them!
5. Game: Hangman

We'll begin with a classic: Hangman. But with a few twists here and there. This
is a perfect introduction to how word data can be analyzed for optimum
gameplay. We'll use our tile prefab, our input handler logic, a tile panel, and
build a new version of hangman with them!
The Game

The player is presented with a mystery word, and a small panel of letters to pick
from as the player's guess. The final prototype looks something like this:

Figure 5.1 - The Hangman Game Screen

Once a letter is picked we check to see if the letter is in the word or not. Then we
show another panel of letters to select from, until the player has either made too
many mistakes, or has solved the mystery word, in which case the player is
presented with the next puzzle.
We'll go over next, what sort of data we'll need for the game.

not. Then we
show another panel of letters to select from, until the player has either made too
many mistakes, or has solved the mystery word, in which case the player is
presented with the next puzzle.
We'll go over next, what sort of data we'll need for the game.
Generating The Puzzles
We don’t need a dictionary for this game, since the player will not spell words
that would need to be checked. However, we do need to select the words we
wish to use in the puzzles. But should we select them randomly? What about a
difficulty curve?
If we select words randomly the player might get too bored or too tired too soon.
Could we analyze words in a dictionary and give them some kind of difficulty
score? So we can place each puzzle on a difficulty curve?
If you Google letter frequency in the English language you will get something
like this ranking:
red too soon.

Figure 5.2 - English Letter Frequency Ranking

Based on this, we can propose that words with the highest number of the least-
frequent letters should be more difficult to guess. Since we hope, players might
instinctively pick more frequent letters as guesses.
Our assumption here, is that in a game of hangman, many people tend to first
pick the more frequent letters before moving to the least common ones. And
therefore words with less frequent letters will generate a higher number of wrong
guesses.
Other than that, what else would make a word harder to guess?
Well, length is a good one. Longer words are harder to guess.
And the more unique letters a word has, the harder it is to guess as well. Or
alternatively, the more repeated letters a word has, the easier it can be to guess it
because the more letters are revealed with each guess the easier it is to find out
the missing ones.
Word commonality could be another point to consider. The player will have a
harder time guessing the word if the word is an uncommon one. But trying to
guess an unknown word may leave the player feeling more bewildered than
pleased.
Apple for instance would be an easy word to guess.
But Zoisite not so much. The player will feel better about guessing a
recognizable word.
As I mentioned, exoteric words may leave the player feeling too frazzled after a
few rounds. It might be good to test gameplay with common against uncommon
words.
So in order to build the data, it would be good to give a score for each word
based on some of these points.
We can use this score to build our difficulty curve. We’ll naturally start with
shorter words, and move up from there, finding longer words, with more
infrequent letters, and less repeated letters as well.
We can do this scoring fairly easily. Here’s how.

Generating The Data


Here is the script that will load the dictionary and give a difficulty score for each
word. You can test this inside the scene called Scene_HangmanData in the
mber of wrongsample project you'll find in the source materials for this book.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class HangmanWordScorer : MonoBehaviour {

void Start () {
StartCoroutine ("LoadData");
}

IEnumerator LoadData() {
string filePath = System.IO.Path.Combine (
Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (filePath.Contains ("://")) {
WWW www = new WWW (filePath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (filePath);

var difficulty = new List<char[]> () {


new char[] { 'e' },
new char[] { 't' },
new char[] { 'a', 'i', 'n', 'o', 's' },
new char[] { 'h' },
new char[] { 'r' },
new char[] { 'd' },
new char[] { 'l' },
new char[] { 'u' },
new char[] { 'c', 'm' },
new char[] { 'f' },
new char[] { 'w', 'y' },
new char[] { 'g', 'p' },
new char[] { 'b', 'v' },
new char[] { 'k' },
new char[] { 'q' },
new char[] { 'j', 'x' },
new char[] { 'z' },

};

var words = result.Split ('\n');


var difficultyMap = new Dictionary<int, List<string>> ();
var index = 0;

foreach (var w in words) {


if (string.IsNullOrEmpty(w) || w.Length < 3)
continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


core for each index++;
continue;
} else {
var score = 0;
var chars = new List<char> ();
foreach (var c in word) {
for (var i = 0; i < difficulty.Count; i++) {
if (Array.IndexOf (difficulty [i], c) != -1) {
score += i;
break;
}
}
if (!chars.Contains (c))
chars.Add (c);
}
score += chars.Count;
score += index;

if (!difficultyMap.ContainsKey (score)) {
difficultyMap.Add (score, new List<string> ());
}
difficultyMap [score].Add (word);
}
}

var sr = File.CreateText("hangman.txt");
for (var i = 0; i < 1000; i++) {
if (difficultyMap.ContainsKey (i)) {
var list = difficultyMap [i];
foreach (var w in list) {
sr.WriteLine (i + "|" + w);
}
}
}
sr.Close();
}
}

First we load our dictionary, the one with the words sorted by frequency.
Nothing new here, we've seen this logic when we covered dictionaries. The key
point here is the List of char arrays mapping the frequency of each letter in the
English language, from most common to least common:
var difficulty = new List<char[]> () {
new char[] { 'e' },
new char[] { 't' },
new char[] { 'a', 'i', 'n', 'o', 's' },
new char[] { 'h' },
new char[] { 'r' },
new char[] { 'd' },
new char[] { 'l' },
new char[] { 'u' },
new char[] { 'c', 'm' },
new char[] { 'f' },
new char[] { 'w', 'y' },
new char[] { 'g', 'p' },
new char[] { 'b', 'v' },
new char[] { 'k' },
new char[] { 'q' },
new char[] { 'j', 'x' },
new char[] { 'z' },
};

Each word in the dictionary will be given a score based on the letters it contains.
The highest the score the more uncommon the letters.
In the final logic, I chose to use score only the first ten thousand most common
words. But you could easily add the word frequency value to the final score for
each word, by adding the dictionary index (represented in the logic by the
variable index ). But I would not recommend moving past the 50 thousand
most common words.
The logic will end up producing a text file with the format: score|word .

Tip:
This would be an ideal game for themes. Searching online for lists of animals,
objects, and even movie titles might provide an even better experience for the
player, and these words, or combination of words can be scored the same way
we did with our word list.

Note:
If you do decide to score a word by its commonality, make sure the points
grow exponentially between the lists. Say, the first thousand have a score of 1,
words from the 2 thousand group have a score of 10, the ten thousand group
has a score of 100, and son on.

What else could we use in our difficulty curve?

The Letter Panel


rs it contains. One other thing we can do to make things interesting, is increase the chance of
mistakes when building the list of letters the player must choose from. We can
build letter panels with increasingly difficult odds. Say we start with 2 correct
letters out of 3, for example, and then move up until we have 1 correct letter out
of 10!
We can also fool the player by purposefully adding to our panel high frequency
letters which are not in the word!
I know. Evil. Pure evil.
So let's start building this thing. First, let's get our events out of the way.
letters out of 3, for example, and then move up until we have 1 correct letter out
of 10!
We can also fool the player by purposefully adding to our panel high frequency
letters which are not in the word!
I know. Evil. Pure evil.
So let's start building this thing. First, let's get our events out of the way.
Game Events
Here are the game's main events. We need to notify different components of a
few things:
• When the game data is loaded
• When the player selects a letter
Any class in the game can subscribe to one of these two events and respond to it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class HangmanGameEvents {

public delegate void Event_SelectLetter (char letter);


public delegate void Event_PuzzleDataLoaded ();

public static event Event_SelectLetter OnLetterSelected;


public static event Event_PuzzleDataLoaded OnPuzzleDataLoaded;

public static void LetterSelected (char letter) {


if (OnLetterSelected != null)
OnLetterSelected (letter);
}

public static void PuzzleDataLoaded () {


if (OnPuzzleDataLoaded != null)
OnPuzzleDataLoaded ();
}
}

We can now proceed to loading everything we need. We'll start by loading our
data into the game.
Game Events
Here are the game's main events. We need to notify different components of a
few things:
• When the game data is loaded
• When the player selects a letter
Any class in the game can subscribe to one of these two events and respond to it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class HangmanGameEvents {

public delegate void Event_SelectLetter (char letter);


public delegate void Event_PuzzleDataLoaded ();

public static event Event_SelectLetter OnLetterSelected;


public static event Event_PuzzleDataLoaded OnPuzzleDataLoaded;

public static void LetterSelected (char letter) {


if (OnLetterSelected != null)
OnLetterSelected (letter);
}

public static void PuzzleDataLoaded () {


if (OnPuzzleDataLoaded != null)
OnPuzzleDataLoaded ();
}
}

We can now proceed to loading everything we need. We'll start by loading our
data into the game.
The Puzzle Data
So we have our game data. We simply need to load it and parse it. You will find
the pre-built level data in the source files for this book.
The HangmanPuzzleData class will load the data we generated and sort it by
difficulty.
Let's build it then!
1. First create a new script called HangmanPuzzleData .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class HangmanPuzzleData : MonoBehaviour {


}

2. We add two properties:


private Dictionary<int, List<string>> puzzles;
private int difficulty = 10;

puzzles contains all the puzzles, sorted by difficulty score.

difficulty holds the current difficulty level in the game.

3. We add a public method to load the data:


public void LoadData () {
StartCoroutine ("LoadPuzzleData");
}

4. And the coroutine:


IEnumerator LoadPuzzleData () {
string dataPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "hangman.txt");

string result = null;

if (dataPath.Contains ("://")) {
WWW www = new WWW (dataPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dataPath);

var data = result.Split ('\n');


puzzles = new Dictionary<int, List<string>> ();

foreach (var entry in data) {


var e = entry.TrimEnd ();
var d = e.Split ('|');
if (d.Length == 2) {
var key = Int32.Parse (d [0]);
if (key > 2) {
if (!puzzles.ContainsKey (key))
puzzles.Add (key, new List<string> ());
puzzles [key].Add (d [1]);
}
}
}
HangmanGameEvents.PuzzleDataLoaded ();
}

The hangman.txt file can be found in the source materials for this book. It is the
result of our word scoring script.
We fire our PuzzleDataLoaded event at the end of the coroutine .

5. We add next a method to retrieve the word used in each puzzle:


public string GetWord () {
var word = "";
var list = puzzles [difficulty];
word = list [UnityEngine.Random.Range (0, list.Count)];
UpdateLevel ();
return word;
}

We randomly select one of the words in each difficulty group.


6. The UpdateLevel method looks like this:
public void UpdateLevel () {
for (var i = difficulty + 1; i < 1000; i++) {
if (puzzles.ContainsKey (i)) {
difficulty = i;
return;
}
}
}

The variable difficulty is incremented every time we retrieve a word,


ensuring each puzzle is slightly harder than the previous.
Here is the complete HangmanPuzzleData class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class HangmanPuzzleData : MonoBehaviour {


private Dictionary<int, List<string>> puzzles;
private int difficulty = 10;

public void LoadData () {


StartCoroutine ("LoadPuzzleData");
}

public string GetWord () {


var word = "";
var list = puzzles [difficulty];
word = list [UnityEngine.Random.Range (0, list.Count)];
UpdateLevel ();
return word;
}

public void UpdateLevel () {


for (var i = difficulty + 1; i < 1000; i++) {
if (puzzles.ContainsKey (i)) {
difficulty = i;
return;
}
}
}

IEnumerator LoadPuzzleData () {
string dataPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "hangman.txt");

string result = null;

if (dataPath.Contains ("://")) {
WWW www = new WWW (dataPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dataPath);

var data = result.Split ('\n');


puzzles = new Dictionary<int, List<string>> ();

foreach (var entry in data) {


var e = entry.TrimEnd ();
var d = e.Split ('|');
if (d.Length == 2) {
var key = Int32.Parse (d [0]);
if (key > 2) {
if (!puzzles.ContainsKey (key))
puzzles.Add (key, new List<string> ());
puzzles [key].Add (d [1]);
}
}
}
HangmanGameEvents.PuzzleDataLoaded ();
}
}
The main game controller will get a word from our data object, present it to the
player as a secret word, and then select a group of letters to pick from, as the
player’s guess.
Time to build the tile panel we'll need in the game.
The main game controller will get a word from our data object, present it to the
player as a secret word, and then select a group of letters to pick from, as the
player’s guess.
Time to build the tile panel we'll need in the game.
The Game Tiles
Next, comes the logic that creates and controls the letter tiles the player must
select as his or her guess.
We need logic to build the tile buttons and to handle player's input.
We've seen most of this logic in out panel test.
The method ShowPanel builds, aligns and scales the panel, with a specific set
of chars. And then we simply need to handle user input: namely touch down and
up.

Tip:
You could build this even faster using the canvas with UI buttons for the tiles.
At least in the prototyping stage, this could save you time. But don't go
making entire games out of the canvas if you can help it. It wasn't meant for
that purpose.

1. The first step is to create a class, and call it HangmanPanel or whatever you
wish. The class must implement IInputHandler .
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HangmanPanel : MonoBehaviour, IInputHandler {


}

2. Let's begin by creating the properties:


public GameObject tileGO;
public GameObject container;
private List<TileButton> tiles;
private TileButton selectedTile;

void Awake () {
tiles = new List<TileButton> ();
}

First we have the reference to the prefab we'll instantiate.


Then a reference to the game object which will contain the tile buttons as its
children–so we can scale the container in order to fit all buttons on screen and
also in order to align it properly.
Then a List for all tiles, and a variable to hold the currently selected tile. On
Awake we instantiate our List.
3. We add a method to clear the current panel:
private void ClearPanel () {
foreach (var tile in tiles)
Destroy (tile.gameObject);
tiles.Clear ();
}

4. Next comes the method that builds the tile button panel:
public void ShowPanel (char[] chars) {

ClearPanel ();

container.transform.localScale = Vector2.one;
container.transform.position = Vector2.zero;

for (var i = 0; i < chars.Length; i++) {


var go = Instantiate (tileGO) as GameObject;
var tile = go.GetComponent<TileButton> ();
tile.SetTileData (chars [i]);
tile.SetColor (Color.black, Color.white);
whatever you tile.transform.parent = container.transform;

tile.transform.position = new Vector2 (


(i * 3.5f),
-1.5f
);

tiles.Add (tile);
}

var size = tiles [1].transform.position.x -


tiles [0].transform.position.x;

var scale = 1.0f;


var panelWidth = (chars.Length -1) * size;
var stageWidth = 3.5f;

if (panelWidth > stageWidth ) {


scale = stageWidth / panelWidth;
container.transform.localScale = new Vector2(scale, scale);
}
container.transform.localPosition = new Vector2 (
(panelWidth * scale) * -0.5f , -0.5f);
}

We clear any existent panel tiles, and reset the position and scale of the container
GameObject.
The method receives as an argument an array of chars to be displayed.
We loop this array, instantiate the buttons, make the button display the correct
char , and place the button inside the container GameObject. We then position
the tiles, one next to the other.
Outside the loop we scale the container and reposition it so it fits the screen.
5. We next add the code to handle user input. In order to implement the
IInputHandler interface we need to implement three methods.

public void HandleTouchDown (Vector2 touch) {


if (selectedTile != null) {
selectedTile.Select(false);
}
selectedTile = TileCloseToPoint (touch);
if (selectedTile != null) {
selectedTile.Select(true);
}
}

public void HandleTouchUp (Vector2 touch) {


if (selectedTile != null) {
selectedTile.Select(false);
SubmitTile ();
}
selectedTile = null;
}

public void HandleTouchMove (Vector2 touch) {


//nothing to implement
}

With touch down we find the closest tile, we'll look at that logic next. We select
the tile on touch down, and on touch up we deselect it and submit the tile to the
game logic, meaning at this stage the player has made a guess by selecting one
of the available letters.
We don't need to implement touch move for this game, so that method remains
empty.
6. The last two methods then are:
private TileButton TileCloseToPoint (Vector2 point){
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;
the container var minDistance = 0.6f;
TileButton closestTile = null;
foreach (var tile in tiles) {
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
}
}
then position return closestTile;
}

private void SubmitTile () {


HangmanGameEvents.LetterSelected (selectedTile.TypeChar);
}

We find the closest tile to the touch point, with the TileCloseToPoint method.
And in the SubmitTile , we dispatch an event, broadcasting the selected char.

The complete class looks like this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HangmanPanel : MonoBehaviour, IInputHandler {

public GameObject tileGO;


public GameObject container;
private List<TileButton> tiles;
private TileButton selectedTile;

void Awake () {
tiles = new List<TileButton> ();
}

public void ShowPanel (char[] chars) {

ClearPanel ();

container.transform.localScale = Vector2.one;
container.transform.position = Vector2.zero;

for (var i = 0; i < chars.Length; i++) {


var go = Instantiate (tileGO) as GameObject;
var tile = go.GetComponent<TileButton> ();
tile.SetTileData (chars [i]);
tile.SetColor (Color.black, Color.white);
tile.transform.parent = container.transform;

tile.transform.position = new Vector2 (


(i * 3.5f),
-1.5f
);

tiles.Add (tile);
}

var size = tiles [1].transform.position.x -


tiles [0].transform.position.x;

var scale = 1.0f;


var panelWidth = (chars.Length -1) * size;
var stageWidth = 3.5f;

if (panelWidth > stageWidth ) {


scale = stageWidth / panelWidth;
container.transform.localScale = new Vector2(scale, scale);
}
container.transform.localPosition = new Vector2 (
(panelWidth * scale) * -0.5f , -0.5f);
}

private void ClearPanel () {


foreach (var tile in tiles)
Destroy (tile.gameObject);
tiles.Clear ();
}
method.
public void HandleTouchDown (Vector2 touch) {
if (selectedTile != null) {
selectedTile.Select(false);
}
selectedTile = TileCloseToPoint (touch);
if (selectedTile != null) {
selectedTile.Select(true);
}
}

public void HandleTouchUp (Vector2 touch) {


if (selectedTile != null) {
selectedTile.Select(false);
SubmitTile ();
}
selectedTile = null;
}

public void HandleTouchMove (Vector2 touch) {


//nothing to implement
}

private TileButton TileCloseToPoint (Vector2 point){


var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

var minDistance = 0.6f;


TileButton closestTile = null;
foreach (var tile in tiles) {
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
}
}
return closestTile;
}

private void SubmitTile () {


HangmanGameEvents.LetterSelected (selectedTile.TypeChar);
}
}

We're finally ready to build the game controller logic.


}

We're finally ready to build the game controller logic.


The Game Controller
Finally, the actual game logic. This will get the puzzles from the puzzle data
object, select the chars for the buttons and control general game flow.
Let's get started:
1. Create a Hangman class.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;

public class Hangman : MonoBehaviour {


}

2. Add it's properties:


public Text word;
public HangmanPuzzleData puzzleData;
private HangmanPanel tiles;
private Level level;
private int lives = 5;
private bool gameActive;

• word is a reference to a UI text field where we'll display the mystery word.

• puzzleData is a reference to the game's puzzle data.

• tiles is a private reference to our tile buttons.

• level refers to a class representation of a puzzle level (we'll implement that


next.)
• lives is a number representing the player's lives.

• gameActive is a boolean controlling whether or not the game is responsive


to input (we'll use that to represent "Game Over" in our prototype, as well as
making sure the game is inactive while we're loading the data.)
3. The Level object is described by an internal class:
Note:
There is no need for it to be an internal class, you could just as well write it as
a separate file, but it saves time having it all in one script.

internal class Level {


public string word;
public char[] wordChars;
public bool[] revealed;
public List<char> noChars;
public List<char> okChars;
private int roundIndex = 0;
private static Vector2[] ROUNDS = new Vector2[] {
new Vector2(2,3),
new Vector2(2,4),
new Vector2(1,3),
new Vector2(2,5),
new Vector2(1,4),
new Vector2(2,6),
new Vector2(1,5),
new Vector2(1,6),
new Vector2(2,7),
new Vector2(1,7)
};
}

Each level will contain the following properties:


• word : the mystery word.

• wordChars : the mystery word as a char array.

mplement that• revealed : an array tracking the letters which have been revealed in the
mystery word.
• noChars : a list of characters not found in the word.

• okChars : a list of unique chars found in the word.

• roundIndex : the number of the current round being played in the puzzle
(each time the player is offered a selection of characters will count as a round.)
• ROUNDS : the proportion of correct chars to wrong chars in each round,
stored as a Vector2 for convenience. So for example the first round will
present two correct letters, out of a total of 3 letters. Round two will present four
tiles, and only two will be correct.
4. The constructor looks like this:
public Level (string word) {
this.word = word.ToLower();
this.wordChars = word.ToCharArray();
revealed = new bool[this.wordChars.Length];

noChars = new List<char>();


okChars = new List<char>();

foreach (var c in TileChar.chars) {


if (this.word.IndexOf(c) == -1)
noChars.Add(c);
else
okChars.Add(c);
}
}

Where we simply populate our values based on a single argument: the mystery
word.
5. Next, we add a helper method to check if a letter selected by the player is
present in the word:
public bool HasChar (char c) {
if (okChars.IndexOf(c) == -1) return false;

var index = okChars.IndexOf (c);


okChars.RemoveAt (index);

for (var i = 0; i < wordChars.Length; i++) {


if ( wordChars [i] == c ) {
revealed [i] = true;
}
}
return true;
}

We also keep track of the index of all revealed letters. We remove the correct
letter from the okChars list, so we don't present it any more as an option in our
tile panel.
6. Finally, we just need a method that gives us the letters to populate out tile
panel:
public char[] GetHints () {
var result = new List<char>();
var correct = ROUNDS [roundIndex].x;
var total = ROUNDS [roundIndex].y;
l present four if (correct > okChars.Count)
correct = okChars.Count;

var i = 0;
while (result.Count < total) {
if (result.Count < correct) {
var c = okChars [Random.Range (0, okChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
} else {
var c = noChars [Random.Range (0, noChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
}
}
roundIndex++;
if (roundIndex == ROUNDS.Length)
roundIndex = ROUNDS.Length - 1;

return result.ToArray ();


}

We will use this to generate an array of chars to be sent to our HangmanPanel .

We randomly select letters from the lists of noChars and okChars .

Here is where you could make the game even more complex, by selecting letters
based on their frequency score.
We increase the round index number every time we build a panel—so every time
this method is called.
That takes care of the Level internal class.

7. Let's continue on with our controller:


void Awake () {
tiles = GetComponent<HangmanPanel> ();

HangmanGameEvents.OnPuzzleDataLoaded += HandleGameLoaded;
option in our HangmanGameEvents.OnLetterSelected += HandleLetterSelected;
}

void Start () {
puzzleData.LoadData ();
}

On Awake we grab a reference to our HangmanPanel (this means the game


controller and the panel need to be sibling components of the same
GameObject.)
We subscribe to the main game events, and we'll handle those next.
On Start we simply load the game data.

Once that's loaded, if you remember, we fire the OnPuzzleDataLoaded event.

8. We handle the letter selected event thus:


void HandleLetterSelected (char letter) {
if (!gameActive)
return;

if (lives <= 0)
return;

if (level.HasChar (letter)) {
ShowWord ();
if (System.Array.IndexOf (level.revealed, false) == -1) {
Debug.Log ("CORRECT");
gameActive = false;

Utils.DelayAndCall (this, 2, () => {


NewRound ();
});
return;
}
} else {
ecting letters lives--;
if (lives <= 0) Debug.Log ("GAME OVER");
}

tiles.ShowPanel (level.GetHints ());


so every time }

We receive the selected char as an argument, and we run the HasChar


method from the Level object. If the word has that char , we update the
mystery word string with a method called ShowWord . If there are no more chars
to be revealed, we start a timer to load the next round. If the word does not have
the char, we take a life from the player and check for GameOver .

9. The ShowWord method generates the string we present in the UI text field
and it looks like this:
void ShowWord () {
var sb = new StringBuilder ();
for (var i = 0; i < level.wordChars.Length; i++) {
if (level.revealed [i] == true) {
sb.Append (level.wordChars[i]);
} else {
sb.Append ("_");
}
}
word.text = sb.ToString ();
}

10. The game data loaded event will simply start a new round:
void HandleGameLoaded () {
NewRound ();
}

11. Which looks like this:


void NewRound () {
SelectWord ();
ShowWord ();
tiles.ShowPanel (level.GetHints ());
gameActive = true;
}

We run the logic to select the next word, based on the current difficulty level. We
then build the string displayed in the UI text field. And we build the panel.
12. SelectWord is quite simple:

void SelectWord () {
var word = puzzleData.GetWord ();
level = new Level (word);
}

The method is responsible for creating the Level object.

And that's it. The core logic as you can see is present inside the
OnLetterSelected event handler.

no more chars The complete game controller looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;

public class Hangman : MonoBehaviour {

public Text word;


public HangmanPuzzleData puzzleData;
private HangmanPanel tiles;
private Level level;
private int lives = 5;
private bool gameActive;
void Awake () {
tiles = GetComponent<HangmanPanel> ();

HangmanGameEvents.OnPuzzleDataLoaded += HandleGameLoaded;
HangmanGameEvents.OnLetterSelected += HandleLetterSelected;
}

void Start () {
puzzleData.LoadData ();
}

void HandleGameLoaded () {
NewRound ();
}

void NewRound () {
SelectWord ();
ShowWord ();
tiles.ShowPanel (level.GetHints ());
gameActive = true;
}

void HandleLetterSelected (char letter) {


if (!gameActive)
return;
ulty level. We
if (lives <= 0)
return;

if (level.HasChar (letter)) {
ShowWord ();
if (System.Array.IndexOf (level.revealed, false) == -1) {
Debug.Log ("CORRECT");
gameActive = false;

Utils.DelayAndCall (this, 2, () => {


NewRound ();
});
return;
}
} else {
lives--;
if (lives <= 0) Debug.Log ("GAME OVER");
}

tiles.ShowPanel (level.GetHints ());


}

void SelectWord () {
var word = puzzleData.GetWord ();
level = new Level (word);
}

void ShowWord () {
var sb = new StringBuilder ();
for (var i = 0; i < level.wordChars.Length; i++) {
if (level.revealed [i] == true) {
sb.Append (level.wordChars[i]);
} else {
sb.Append ("_");
}
}
word.text = sb.ToString ();
}
internal class Level {

public string word;


public char[] wordChars;
public bool[] revealed;
public List<char> noChars;
public List<char> okChars;
private int roundIndex = 0;
private static Vector2[] ROUNDS = new Vector2[] {
new Vector2(2,3),
new Vector2(2,4),
new Vector2(1,3),
new Vector2(2,5),
new Vector2(1,4),
new Vector2(2,6),
new Vector2(1,5),
new Vector2(1,6),
new Vector2(2,7),
new Vector2(1,7)
};

public Level (string word) {

this.word = word.ToLower();
this.wordChars = word.ToCharArray();
revealed = new bool[this.wordChars.Length];

noChars = new List<char>();


okChars = new List<char>();

foreach (var c in TileChar.chars) {


if (this.word.IndexOf(c) == -1)
noChars.Add(c);
else
okChars.Add(c);
}
}

public bool HasChar (char c) {


if (okChars.IndexOf(c) == -1) return false;

var index = okChars.IndexOf (c);


okChars.RemoveAt (index);

for (var i = 0; i < wordChars.Length; i++) {


if ( wordChars [i] == c ) {
revealed [i] = true;
}
}
return true;
}

public char[] GetHints () {

var result = new List<char>();

var correct = ROUNDS [roundIndex].x;


var total = ROUNDS [roundIndex].y;

if (correct > okChars.Count)


correct = okChars.Count;

var i = 0;
while (result.Count < total) {
if (result.Count < correct) {
var c = okChars [Random.Range (0, okChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
} else {
var c = noChars [Random.Range (0, noChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
}
}

roundIndex++;
if (roundIndex == ROUNDS.Length)
roundIndex = ROUNDS.Length - 1;

return result.ToArray ();


}
}
}

Now we need to put these elements all together in our game Scene inside Unity.
while (result.Count < total) {
if (result.Count < correct) {
var c = okChars [Random.Range (0, okChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
} else {
var c = noChars [Random.Range (0, noChars.Count)];
if (!result.Contains (c)) {
result.Add (c);
}
}
}

roundIndex++;
if (roundIndex == ROUNDS.Length)
roundIndex = ROUNDS.Length - 1;

return result.ToArray ();


}
}
}

Now we need to put these elements all together in our game Scene inside Unity.
The Game Scene
You will find a scene ready to be hooked up to our scripts, inside our sample
project. The scene can be found at Assets/Scenes/Scene_Hangman.
When you open it inside Unity you'll see all GameObjects already in place.
There is a Canvas object which contains one text field called Word. This is used
to show the secret word to the player.
The scene has no scripts attached to it, and the idea is to build those scripts one
by one and add them to the game scene.
We'll need to add scripts to the PuzzleData and the GameView game objects.

Figure 5.3 - The Initial Scene

Note:
The names picked for the individual game objects are entirely up to you.
So let's start with our panel.

. This is used
So let's start with our panel.
HangmanPanel
We'll add the HangmanPanel as a component to the GameView GameObject.

The script has a public reference to two things: the prefab used for the tiles, and
the GameObject acting as a container to the tiles.
1. After adding the script as a component, drag the GridTile prefab from the
prefab folder into the first field.
2. Drag the Panel GameObject from the hierarchy panel into the second field.
3. The HangmanPanel script implements the IInputHandler interface, and
that operates alongside another script called InputController.cs . So add a
new component to the GameView GameObject and point to that script.
So far the GameView components list should look something like this:

Figure 5.4 - GameView Components


Note:
One other way of selecting values to your script fields is to click on the little
target icon next to each field you want to ascribe a value to.

second field.

Figure 5.5 - Selecting Value from Targets


This will bring a popup where you can select from your Asset list, or from
your Scene. Furthermore, this popup is already smart enough to filter the
content based on what type "fits" each particular field. We'll be using this
method a lot from now on. So, keep this in mind when naming your
GameObjects and Assets!

Next, we take care of our game controller.


The Hangman Controller
We'll add the final component to our game: the game controller. We'll add this to
the same GameView GameObject.
1. Add a new component and point to the Hangman.cs script.

2. It requires one reference field. This should point to the Text field called
Word, inside the Canvas GameObject. Click the target button next to the
component field for Word and select the correct item from the Scene tab.

Figure 5.6 - Selecting Text Field

The final GameView list of components should look like this:


'll add this to

Figure 5.7 - Final GameView

If you run the simulator, the game will appear!


Figure 5.7 - Final GameView

If you run the simulator, the game will appear!


Taking It Further
You could build the game as an endless hangman game, where the player must
use the same number of lives to solve as many words as possible. And every five
words or so, the player gets a new life.
You could also add support for entire sentences, like famous quotes, to be
guessed at, or use groups of related words.
These could be scored just as we did with single words, with the score of each
word in the sentence added up.
Taking It Further
You could build the game as an endless hangman game, where the player must
use the same number of lives to solve as many words as possible. And every five
words or so, the player gets a new life.
You could also add support for entire sentences, like famous quotes, to be
guessed at, or use groups of related words.
These could be scored just as we did with single words, with the score of each
word in the sentence added up.
6. Game: Scramble

Next, we'll build a game similar to the popular game Word Cookies. This time
we'll scramble a bunch of letters and run our algorithms to determine a series of
target words the player must find.
6. Game: Scramble

Next, we'll build a game similar to the popular game Word Cookies. This time
we'll scramble a bunch of letters and run our algorithms to determine a series of
target words the player must find.
The Game

In our next game, Scramble, the player is presented with a scrambled mystery
word; the letters are all spread out randomly on screen. The player is then asked
to find all words that can be made with different combinations of those available
letters, as well as the original mystery word.
The prototype looks something like this:

Figure 6.1 - The Scramble Game


We have a tile panel component, with the shuffled letters. These will be selected
by dragging the finger (or mouse) across the screen.
We have the mystery word presented inside a text field, this is the original word
which we shuffled.
ose available
And we have a selection of target words the player must find, also listed inside a
text field.
The player must find the mystery word and all target words in order to finish the
puzzle. (And if the player manages to find more words than we selected in the
target words, we’ll turn those into a bonus point.)

First, let's go over the word list we'll need for the game.
We have a tile panel component, with the shuffled letters. These will be selected
by dragging the finger (or mouse) across the screen.
We have the mystery word presented inside a text field, this is the original word
which we shuffled.
And we have a selection of target words the player must find, also listed inside a
text field.
The player must find the mystery word and all target words in order to finish the
puzzle. (And if the player manages to find more words than we selected in the
target words, we’ll turn those into a bonus point.)

First, let's go over the word list we'll need for the game.
The Dictionary
For this type of game we need to check if the word the player generated is a
word or not. And for that, we'll need our full word list.
We also need to make sure the list of target words are not too bizarre. So we
have a perfect candidate for our “frequency-sorted” dictionary here. We'll select
target words out of the groups with the most common words.
Once again we're faced with the need for a nice difficulty curve to our puzzles.
So how can we go about doing that?
The Dictionary
For this type of game we need to check if the word the player generated is a
word or not. And for that, we'll need our full word list.
We also need to make sure the list of target words are not too bizarre. So we
have a perfect candidate for our “frequency-sorted” dictionary here. We'll select
target words out of the groups with the most common words.
Once again we're faced with the need for a nice difficulty curve to our puzzles.
So how can we go about doing that?
The Level Design
The more words you can generate from a selection of letters, the more difficult
the puzzle will be, and more interesting.
How can we find out what makes a good word and what doesn't?
Easy.
We need to use our algorithm that collects all words that can be generated from a
selection of letters. Run one of our dictionaries through this algorithm and score
each word based on the number of words each word can generate!
So let's take a look at creating the data for our game's puzzles.
The Level Design
The more words you can generate from a selection of letters, the more difficult
the puzzle will be, and more interesting.
How can we find out what makes a good word and what doesn't?
Easy.
We need to use our algorithm that collects all words that can be generated from a
selection of letters. Run one of our dictionaries through this algorithm and score
each word based on the number of words each word can generate!
So let's take a look at creating the data for our game's puzzles.
The Game Data
In the game, we scramble one word, and then collect all words (with at least
three characters) which can be made by using the letters of the word we selected.
Therefore, it would be helpful to know which word generates the highest number
of words.
The difficulty curve could be built in many ways, from this starting point:
• We could decided that the more words the player needs to find the harder the
puzzle becomes.
• We could also decide that the more uncommon a word is, the harder it would
be for the player to find it. Therefore, words could be scored based on which
frequency list they come from.
We can then write logic that can give each word we intent to use in the game's
puzzles a score based on one or both of these points.
There are a few ways we could that. Let me show you one, using logic we've
covered previously. Only now, we'll run it inside a Thread! (You may skip this
part as the source files for this book already contains the game data, but if you're
curious about how I arrived at it, read on.)

Source Material:
You can follow along with the next tutorial by using the Unity sample project.
Just load up the scene: Assets/Scenes/Scene_ScrambleData

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Threading;
public class ScrambleWordScorer: MonoBehaviour {
private HashSet<string> allWords;
private Dictionary<char, List<string>> wordsByFirstChar;
private Dictionary<int, List<WordScore>> wordYieldData;
private Thread wordYieldThread;

void Start () {
StartCoroutine ("LoadWordData");
d we selected. }

IEnumerator LoadWordData() {
ghest number
string dictionaryPath =
System.IO.Path.Combine(Application.streamingAssetsPath,
"wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

ProcessWordData (result);
}

void ProcessWordData (string wordData) {


var words = wordData.Split ('\n');

allWords = new HashSet<string> ();


wordsByFirstChar = new Dictionary<char,
List<string>> ();

but if you're var index = 0;

foreach (var w in words) {

if (string.IsNullOrEmpty(w))
continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
continue;
} else {
if (index < 5) {
allWords.Add (word);

var c = word [0];


if (!wordsByFirstChar.ContainsKey (c)) {
wordsByFirstChar.Add (c, new List<string> ());
}
wordsByFirstChar [c].Add (word);
}
}
}

GetWordsYieldData ();
}
void GetWordsYieldData () {
Debug.Log ("START");
wordYieldData = new Dictionary<int, List<WordScore>> ();
wordYieldThread = new Thread(YieldSearchWorker);
wordYieldThread.Start ();
}

public void YieldSearchWorker() {


try{
FindWordsData() ;
} finally {
NotifyCompleted ();
}
}

void FindWordsData () {

Debug.Log ("START JOB");

foreach (var word in allWords) {


if (word.Length < 13) {
var words = WordsFromChars (word.ToCharArray ());

if (!wordYieldData.ContainsKey (word.Length)) {
wordYieldData.Add (word.Length, new List<WordScore> ());
}
var ws = new WordScore ();
ws.word = word;
ws.wordYield = words.Count;
wordYieldData [word.Length].Add ( ws );
}
}

void NotifyCompleted () {

var sr = File.CreateText("wordYield.txt");
for (var i = 0; i < 13; i++) {
if (wordYieldData.ContainsKey (i)) {
foreach (var word in wordYieldData[i]) {
if (word.wordYield > 2) {
sr.WriteLine (word.word+"|"+word.wordYield);
}
}
sr.WriteLine ("\n\n");
}
}
sr.Close();
Debug.Log ("JOB DONE");
}

HashSet<string> WordsFromChars (char[] chars){

//no Distinct :(
//collect all unique chars in array
var firstChars = new List<char> ();
foreach (var c in chars) {
if (!firstChars.Contains (c))
firstChars.Add (c);
}
var result = new HashSet<string> ();
var i = 0;

//loop through every word that begins with one of those chars
foreach (var first in firstChars) {
if (wordsByFirstChar.ContainsKey (first)){

var list = wordsByFirstChar [first];

foreach (var word in list) {

if (word.Length <= chars.Length


&& !result.Contains (word) ) {

var sourceChars = new char[chars.Length];


Array.Copy (chars, sourceChars, chars.Length);
var cIndex = Array.IndexOf (sourceChars, first);
if (cIndex != -1)
sourceChars [cIndex] = '-';

var wordChars = word.ToCharArray ();

var match = true;

for (var j = 1; j < wordChars.Length; j++) {

var index = Array.IndexOf (sourceChars, wordChars [j]);

if (index != -1) {
sourceChars [index] = ' ';
} else {
match = false;
break;
}
}

if (match)
result.Add (word);
}
}
}
}

return result;
}

public struct WordScore {


public int wordYield;
public string word;
}
}

The class contains our old friend the WordsFromChars method which returns
all words that can be made from an array of characters.
So despite the length of the class, all it does is loop through a word list, grabbing
all the words that can be generated with the letters from each word on the list,
and then stores the result sorted by number of words generated.
Here, I create a Thread to process the entire dictionary. And I sort the words
by length and spit out the total number of possible words that can be generated
from each entry.
Why use a separate Thread ? Just so your computer won't choke while
performing these calculations, depending on how long your word list is.
The data is printed out in a word|number of words format. I could easily add
to this score another value that represented how common each word is. But I
chose to use only the yield number.
Later, when I load the data into the game, I will sort the puzzles by number of
words yielded.
One interesting fact: Using 12 as a maximum length for the words used in the
game, the word slaughtering is the one with the highest yield. Who knew!
You don't need to run this logic, since you already have the output data sitting in
your project's StreamingAssets folder.
Once we have the data, we can load it in the game.
So let's quickly review how the game will operate...

list, grabbing
Here, I create a Thread to process the entire dictionary. And I sort the words
by length and spit out the total number of possible words that can be generated
from each entry.
Why use a separate Thread ? Just so your computer won't choke while
performing these calculations, depending on how long your word list is.
The data is printed out in a word|number of words format. I could easily add
to this score another value that represented how common each word is. But I
chose to use only the yield number.
Later, when I load the data into the game, I will sort the puzzles by number of
words yielded.
One interesting fact: Using 12 as a maximum length for the words used in the
game, the word slaughtering is the one with the highest yield. Who knew!
You don't need to run this logic, since you already have the output data sitting in
your project's StreamingAssets folder.
Once we have the data, we can load it in the game.
So let's quickly review how the game will operate...
The Logic
The game will limit the number of characters to a maximum of 12, so we won’t
have words longer than that. You can remove this limitation if you wish.
I already placed twelve tiles on the scene, and I’ll use these as the scrambled
letters in a panel script.
We select a word from the puzzle data, based on the current difficulty level, and
then we collect the words that can be generated from these letters and use these
as the target words for this level.
Cool? Then let's start by getting our events ready.
The Logic
The game will limit the number of characters to a maximum of 12, so we won’t
have words longer than that. You can remove this limitation if you wish.
I already placed twelve tiles on the scene, and I’ll use these as the scrambled
letters in a panel script.
We select a word from the puzzle data, based on the current difficulty level, and
then we collect the words that can be generated from these letters and use these
as the target words for this level.
Cool? Then let's start by getting our events ready.
The Game Events
Here is the complete list of Events we need to worry about in the game.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class ScrambleGameEvents {

public delegate void Event_SelectTile (string word);


public delegate void Event_SelectWord (string word, List<TileButton> tiles);
public delegate void Event_DictionaryLoaded ();
public delegate void Event_PuzzleDataLoaded ();
public delegate void Event_GameLoaded ();

public static event Event_SelectTile OnTileSelected;


public static event Event_SelectWord OnWordSelected;
public static event Event_DictionaryLoaded OnDictionaryLoaded;
public static event Event_PuzzleDataLoaded OnPuzzleDataLoaded;
public static event Event_GameLoaded OnGameLoaded;

public static void TileSelected (string word) {


if (OnTileSelected != null)
OnTileSelected (word);
}

public static void WordSelected (string word, List<TileButton> tiles) {


if (OnWordSelected != null)
OnWordSelected (word, tiles);
}

public static void DictionaryLoaded () {


if (OnDictionaryLoaded != null)
OnDictionaryLoaded ();
}

public static void PuzzleDataLoaded () {


if (OnPuzzleDataLoaded != null)
OnPuzzleDataLoaded ();
}

public static void GameLoaded () {


if (OnGameLoaded != null)
OnGameLoaded ();
}

Just as our previous game, we'll have events for when a tile is selected, and when
the puzzle data is loaded.
We'll also need an event to notify us the dictionary was loaded, that a word was
formed by the player, and one extra event to notify the game is fully loaded.
This last event is useful if you need to stagger actions during the game preload.
But it's optional.
In this game we'll also need to keep a player state. So we don't restart the game
every time the player comes back. We, instead, store the current puzzle and a
few other things so the player can come back to an unfinished puzzle.
We'll create the player state next.

ted, and when


This last event is useful if you need to stagger actions during the game preload.
But it's optional.
In this game we'll also need to keep a player state. So we don't restart the game
every time the player comes back. We, instead, store the current puzzle and a
few other things so the player can come back to an unfinished puzzle.
We'll create the player state next.
The Player State
We need to implement some sort of Player State for our game, so the player can
go right back to where he or she left of. Oh, yes, we have proper levels now!
For our prototype, we can use the PlayerPrefs object in Unity to store the
information we need. But it may be good to create a separate class that can map
the data to different targets, in case later you decide you actually need to keep
the data somewhere else, like actual text files or lying in a server somewhere.
We'll store the current level, the current unsolved word (if any) and the current
word yield value which acts as our current difficulty level.
The class will be a Singleton. A Singleton class only has one instance of it at any
given time, and that instance has a static getter for easy access throughout your
game domain.
So we can refer to the player state data, anywhere in our game, by using the
getter:
ScramblePlayerState.Instance;

Let'd do that now:


1. We create a C# script and call it ScramblePlayerState . Here's a quick
implementation of a Singleton class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class ScramblePlayerState : MonoBehaviour {

private static ScramblePlayerState _instance = null;


public static ScramblePlayerState Instance {
get {
if(_instance == null)
{
GameObject instanceGo = new GameObject("PlayerState");
_instance = instanceGo.AddComponent<ScramblePlayerState> ();
}
return _instance;
}
}
void Awake() {
if (_instance == null)
_instance = this;

else if (_instance != this)


Destroy(gameObject);
}

The first time you try to retrieve this object, the logic will instantiate it in your
scene (and call it PlayerState ). So you don't need to bother adding it inside
the Editor.
2. Next we'll add a serializable class we'll use as our state:
[Serializable]
public class ScrambleState {
public int gameLevel;
public string gameWord;
public int wordYield;
ce of it at any }

It holds the three properties we need to store, and we use the Serializable tag
which will allow instances of the class to be spit out as text and converted back
into objects.
3. We also need two properties:
private static string PLAYER_STATE_DATA = "PLAYER_STATE_DATA";
private ScrambleState playerState;

The first is for convenience, we'll use that as the key in our PlayerPrefs data
storage. PlayerPrefs is a memory storage Unity makes available to store bits of
information. It can store integers, floats, strings and booleans.
The second will hold the actual player state.
4. On Start we check to see if we have a saved state, if not we create one.
void Start () {
if (string.IsNullOrEmpty (PlayerPrefs.GetString (PLAYER_STATE_DATA))) {

CreateState ();

} else {
playerState = JsonUtility.FromJson<ScrambleState> (
PlayerPrefs.GetString (PLAYER_STATE_DATA) );
}
ScrambleGameEvents.GameLoaded ();
}
Here we use a utility class JsonUtility which can convert serializable objects
into JSON formatted data and convert JSON formatted data back into objects. If
we have data stored in PlayerPrefs under the PLAYER_STATE_DATA key, we
use JsonUtility.FromJson<ScrambleState> to convert the stored data.

5. The CreateState method looks like this.

void CreateState () {
playerState = new ScrambleState ();
playerState.gameLevel = 1;
playerState.gameWord = string.Empty;
playerState.wordYield = 3;
}

6. We have a method to save the data:


void SaveState () {
if (playerState == null)
return;
PlayerPrefs.SetString (PLAYER_STATE_DATA,
JsonUtility.ToJson(playerState) );
tag PlayerPrefs.Save();

This uses the JsonUtility.ToJson command to convert the serializable object


into a JSON formatted string.
7. We make sure to save the data when the application quits, or pauses:
void OnApplicationPause () {
SaveState ();
}

void OnApplicationQuit () {
SaveState ();
}

These are Unity Engine messages we're implementing.


Although OnApplicationQuit should suffice for our purpose here.

8. We also add a means to clear the data, which can be useful when testing:
public void ClearData () {
CreateState ();
}

While testing, you may need to call this method every time the game starts, or
zable objects add a debug button that clears the data.
9. And we end with convenient getters and setters for the state's properties:
public int GetGameLevel () {
return playerState.gameLevel;
}

public string GetGameWord () {


return playerState.gameWord;
}

public int GetWordYield () {


return playerState.wordYield;
}

public void SetGameLevel (int level) {


playerState.gameLevel = level;
}

public void SetGameWord (string word) {


playerState.gameWord = word;
}

public void SetWordYield (int wordYield) {


playerState.wordYield = wordYield;
}

The complete class looks like this:


lizable object
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class ScramblePlayerState : MonoBehaviour {

[Serializable]
public class ScrambleState {
public int gameLevel;
public string gameWord;
public int wordYield;
}

private static ScramblePlayerState _instance = null;

public static ScramblePlayerState Instance


{
get {
if(_instance == null)
{
GameObject instanceGo = new GameObject("PlayerState");
_instance = instanceGo.AddComponent<ScramblePlayerState> ();
}

return _instance;
}
}
private static string PLAYER_STATE_DATA = "PLAYER_STATE_DATA";
private ScrambleState playerState;

void Awake() {

if (_instance == null)
_instance = this;

else if (_instance != this)


Destroy(gameObject);
}

void OnApplicationPause () {
SaveState ();
}

void OnApplicationQuit () {
SaveState ();
}

void Start () {

if (string.IsNullOrEmpty (PlayerPrefs.GetString (PLAYER_STATE_DATA))) {

CreateState ();

} else {
playerState = JsonUtility.FromJson<ScrambleState> (
PlayerPrefs.GetString (PLAYER_STATE_DATA) );
}

ScrambleGameEvents.GameLoaded ();
}

void CreateState () {
playerState = new ScrambleState ();
playerState.gameLevel = 1;
playerState.gameWord = string.Empty;
playerState.wordYield = 3;
}

void SaveState () {
if (playerState == null)
return;
PlayerPrefs.SetString (PLAYER_STATE_DATA,
JsonUtility.ToJson(playerState) );
PlayerPrefs.Save();
}

public int GetGameLevel () {


return playerState.gameLevel;
}

public string GetGameWord () {


return playerState.gameWord;
}

public int GetWordYield () {


return playerState.wordYield;
}
public void SetGameLevel (int level) {
playerState.gameLevel = level;
}

public void SetGameWord (string word) {


playerState.gameWord = word;
}

public void SetWordYield (int wordYield) {


playerState.wordYield = wordYield;
}

public void ClearData () {


CreateState ();
}
}

Time to load the puzzle data!


public void SetGameLevel (int level) {
playerState.gameLevel = level;
}

public void SetGameWord (string word) {


playerState.gameWord = word;
}

public void SetWordYield (int wordYield) {


playerState.wordYield = wordYield;
}

public void ClearData () {


CreateState ();
}
}

Time to load the puzzle data!


Loading The Game Data
Let's now create the game puzzle data class.
1. Create a class called ScramblePuzzleData . This will be another
Singleton .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class ScramblePuzzleData : MonoBehaviour {

private static ScramblePuzzleData _instance = null;


public static ScramblePuzzleData Instance {
get {
if(_instance == null)
{
GameObject instanceGo = new GameObject("PuzzleData");
_instance = instanceGo.AddComponent<ScramblePuzzleData> ();
}
return _instance;
}
}
void Awake() {

if (_instance == null)
_instance = this;

else if (_instance != this)


Destroy(gameObject);
}

2. The class has one property, which holds all puzzles sorted by word yield:
private Dictionary<int, List<string>> puzzles;

3. We expose a public method to start loading the data:


public void LoadData () {
StartCoroutine ("LoadPuzzleData");
}

4. And we load and parse the data like this:


IEnumerator LoadPuzzleData () {
string dataPath = System.IO.Path.Combine (Application.streamingAssetsPath,
"wordYieldData.txt");
string result = null;

if (dataPath.Contains ("://")) {
WWW www = new WWW (dataPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dataPath);

var data = result.Split ('\n');


puzzles = new Dictionary<int, List<string>> ();

foreach (var entry in data) {


var e = entry.TrimEnd ();
var d = e.Split ('|');
if (d.Length == 2) {
var key = Int32.Parse (d [1]);
if (key > 2) {
if (!puzzles.ContainsKey (key))
puzzles.Add (key, new List<string> ());
puzzles [key].Add (d [0]);
}
}
}
ScrambleGameEvents.PuzzleDataLoaded ();
}

We load the text, split the data and parse it, populating our Dictionary list
container called puzzles .

5. This class has a few more helper methods. First, one to get a puzzle word:
public string GetWord () {
var word = "";
var level = ScramblePlayerState.Instance.GetGameLevel ();
var wordYield = ScramblePlayerState.Instance.GetWordYield ();
var gameWord = ScramblePlayerState.Instance.GetGameWord ();

if (!string.IsNullOrEmpty(gameWord) && gameWord.Length > 2) {


word = gameWord;
} else {
var list = puzzles [wordYield];
word = list [UnityEngine.Random.Range (0, list.Count)];
UpdateLevel (word);
}
return word;
}

We retrieve the information from our player state. We need to know if there is an
existing, unsolved word, and also the current level and word yield value.
If we have an unsolved word in the player state, we'll use that. Otherwise we'll
randomly select a word from the correct yield pool.
6. We update the level each time we retrieve a new word:
public void UpdateLevel (string word) {
var level = ScramblePlayerState.Instance.GetGameLevel ();
level++;
ScramblePlayerState.Instance.SetGameLevel (level);
ScramblePlayerState.Instance.SetGameWord (word);
}

We then need to update the state, of course, with the new level and the new
word.
7. Everytime the player finishes a puzzle we update the data with the following
method:
public void PuzzleSolved () {
var wordYield = ScramblePlayerState.Instance.GetWordYield ();
wordYield = GetNextYield (wordYield);
ScramblePlayerState.Instance.SetWordYield (wordYield);
ScramblePlayerState.Instance.SetGameWord (string.Empty);
}

private int GetNextYield (int currentYield) {


for (var i = currentYield + 1; i < 1000; i++) {
if (puzzles.ContainsKey (i)) {
return i;
}
}
return 1000;
}

Here we get the next word yield value, and store that in our Player State. We also
clear the current word.
The entire class looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

public class ScramblePuzzleData : MonoBehaviour {

private static ScramblePuzzleData _instance = null;

if there is an public static ScramblePuzzleData Instance {


get {
if(_instance == null)
{
GameObject instanceGo = new GameObject("PuzzleData");
_instance = instanceGo.AddComponent<ScramblePuzzleData> ();
}

return _instance;
}
}
private Dictionary<int, List<string>> puzzles;

void Awake() {

if (_instance == null)
_instance = this;

else if (_instance != this)


Destroy(gameObject);

}
the following
public void LoadData () {
StartCoroutine ("LoadPuzzleData");
}

public string GetWord () {

var word = "";


var level = ScramblePlayerState.Instance.GetGameLevel ();
var wordYield = ScramblePlayerState.Instance.GetWordYield ();
var gameWord = ScramblePlayerState.Instance.GetGameWord ();

if (!string.IsNullOrEmpty(gameWord) && gameWord.Length > 2) {


word = gameWord;
} else {
var list = puzzles [wordYield];
word = list [UnityEngine.Random.Range (0, list.Count)];
UpdateLevel (word);
}

State. We also return word;


}

public void UpdateLevel (string word) {


var level = ScramblePlayerState.Instance.GetGameLevel ();
level++;
ScramblePlayerState.Instance.SetGameLevel (level);
ScramblePlayerState.Instance.SetGameWord (word);
}

public void PuzzleSolved () {


var wordYield = ScramblePlayerState.Instance.GetWordYield ();
wordYield = GetNextYield (wordYield);
ScramblePlayerState.Instance.SetWordYield (wordYield);
ScramblePlayerState.Instance.SetGameWord (string.Empty);
}

private int GetNextYield (int currentYield) {


for (var i = currentYield + 1; i < 1000; i++) {
if (puzzles.ContainsKey (i)) {
return i;
}
}
return 1000;
}

IEnumerator LoadPuzzleData () {

string dataPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordYieldData.txt");

string result = null;

if (dataPath.Contains ("://")) {
WWW www = new WWW (dataPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dataPath);

var data = result.Split ('\n');

puzzles = new Dictionary<int, List<string>> ();

foreach (var entry in data) {


var e = entry.TrimEnd ();
var d = e.Split ('|');
if (d.Length == 2) {
var key = Int32.Parse (d [1]);
if (key > 2) {
if (!puzzles.ContainsKey (key))
puzzles.Add (key, new List<string> ());
puzzles [key].Add (d [0]);
}
}
}

ScramblePlayerState.Instance.ClearData ();
ScrambleGameEvents.PuzzleDataLoaded ();
}
}

We still need to load our Dictionary.


Application.streamingAssetsPath, "wordYieldData.txt");

string result = null;

if (dataPath.Contains ("://")) {
WWW www = new WWW (dataPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dataPath);

var data = result.Split ('\n');

puzzles = new Dictionary<int, List<string>> ();

foreach (var entry in data) {


var e = entry.TrimEnd ();
var d = e.Split ('|');
if (d.Length == 2) {
var key = Int32.Parse (d [1]);
if (key > 2) {
if (!puzzles.ContainsKey (key))
puzzles.Add (key, new List<string> ());
puzzles [key].Add (d [0]);
}
}
}

ScramblePlayerState.Instance.ClearData ();
ScrambleGameEvents.PuzzleDataLoaded ();
}
}

We still need to load our Dictionary.


The Game Dictionary
Scramble needs a dictionary so we can check whether the words made by the
player are valid, and we also need to run our logic that finds the list of generated
words from an array of chars.
We'll use another Singleton.
1. Create a C# script and call it ScrambleDictionary .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class ScrambleDictionary : MonoBehaviour {


private static ScrambleDictionary _instance = null;
public static ScrambleDictionary Instance
{
get
{
if(_instance == null)
{
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<ScrambleDictionary> ();
}

return _instance;
}
}

//...
}

2. We'll add a few properties:


[HideInInspector]
public HashSet<string> allWords;
public Dictionary<char, HashSet<string>> wordsCharMap;

The first is a HashSet with all the dictionary words, and the second is another
list with the words sorted by their first letter.
3. First, we take care of the business of loading the dictionary, just as we've
done before:
public void Initialize () {
StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new HashSet<string> ();
wordsCharMap = new Dictionary<char, HashSet<string>> ();

var index = 0;
foreach (var w in words) {
if (string.IsNullOrEmpty(w) || w.Length < 3)
continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
continue;
} else {
if (index < 5) {
var c = word [0];
if (!wordsCharMap.ContainsKey (c)) {
wordsCharMap.Add (c, new HashSet<string> ());
}
wordsCharMap [c].Add (word);
}
}
allWords.Add (word);
}

ScrambleGameEvents.DictionaryLoaded ();
ScramblePuzzleData.Instance.LoadData ();
}

There is nothing new here. We populate both our lists of words and dispatch an
event telling the game controller the dictionary is loaded. The "words by first
char" collection will only list words from the first 5 frequency groups.
I chose to load the player state data here, but it could be done anywhere prior to
starting the first puzzle.
4. The logic to check whether a word is valid is as simple as:
public bool IsValidWord (string word){
return allWords.Contains(word);
}

5. We'll also add a convenience method that scrambles a given word, using one
of our Utils method I mentioned in an earlier chapter:
public char[] ScrambleWord (string word) {
return Utils.Scramble<char> (word.ToCharArray ());
}

6. All that is left is our old method to find all words that can be made from a
pool of chars:
public HashSet<string> WordsFromChars (char[] chars){
//collect all unique chars in array
var firstChars = new List<char> ();
foreach (var c in chars) {
if (!firstChars.Contains (c))
firstChars.Add (c);
}

var result = new HashSet<string> ();


var i = 0;

//loop through every word that begins with one of those chars
foreach (var first in firstChars) {
if (wordsCharMap.ContainsKey (first)){

var list = wordsCharMap [first];

foreach (var word in list) {

if (word.Length <= chars.Length && !result.Contains (word) ) {


var sourceChars = new char[chars.Length];
Array.Copy (chars, sourceChars, chars.Length);
var cIndex = Array.IndexOf (sourceChars, first);
if (cIndex != -1)
sourceChars [cIndex] = ' ';

var wordChars = word.ToCharArray ();


var match = true;

for (var j = 1; j < wordChars.Length; j++) {


var index = Array.IndexOf (sourceChars, wordChars [j]);
if (index != -1) {
sourceChars [index] = ' ';
} else {
match = false;
break;
}
}
if (match)
result.Add (word);
}
}
}
}
return result;
}
Here is the complete scramble dictionary class:
ord, using one using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class ScrambleDictionary : MonoBehaviour {

private static ScrambleDictionary _instance = null;


public static ScrambleDictionary Instance
{
get
{
if(_instance == null)
{
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<ScrambleDictionary> ();
}

return _instance;
}
}

[HideInInspector]
public HashSet<string> allWords;
public Dictionary<char, HashSet<string>> wordsCharMap;

public void Initialize () {


StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new HashSet<string> ();
wordsCharMap = new Dictionary<char, HashSet<string>> ();

var index = 0;
foreach (var w in words) {
if (string.IsNullOrEmpty(w) || w.Length < 3)
continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
continue;
} else {
if (index < 5) {
var c = word [0];
if (!wordsCharMap.ContainsKey (c)) {
wordsCharMap.Add (c, new HashSet<string> ());
}
wordsCharMap [c].Add (word);
}
}
allWords.Add (word);
}

ScrambleGameEvents.DictionaryLoaded ();
ScramblePuzzleData.Instance.LoadData ();
}

public bool IsValidWord (string word){


return allWords.Contains(word);
}

public char[] ScrambleWord (string word) {


return Utils.Scramble<char> (word.ToCharArray ());
}

public HashSet<string> WordsFromChars (char[] chars){

//collect all unique chars in array


var firstChars = new List<char> ();
foreach (var c in chars) {
if (!firstChars.Contains (c))
firstChars.Add (c);
}

var result = new HashSet<string> ();


var i = 0;

//loop through every word that begins with one of those chars
foreach (var first in firstChars) {
if (wordsCharMap.ContainsKey (first)){

var list = wordsCharMap [first];

foreach (var word in list) {

if (word.Length <= chars.Length && !result.Contains (word) ) {


var sourceChars = new char[chars.Length];
Array.Copy (chars, sourceChars, chars.Length);
var cIndex = Array.IndexOf (sourceChars, first);
if (cIndex != -1)
sourceChars [cIndex] = ' ';

var wordChars = word.ToCharArray ();


var match = true;

for (var j = 1; j < wordChars.Length; j++) {


var index = Array.IndexOf (sourceChars, wordChars [j]);
if (index != -1) {
sourceChars [index] = ' ';
} else {
match = false;
break;
}
}
if (match)
result.Add (word);
}
}
}
}
return result;
}
}

Now we're ready to tackle our game's tiles.


match = false;
break;
}
}
if (match)
result.Add (word);
}
}
}
}
return result;
}
}

Now we're ready to tackle our game's tiles.


The Game Tiles
The tile buttons the player must select are not unlike the ones we used in the
Hangman game, only this time a selection is made through touch move,
allowing multiple tiles to be selected.
1. Create a MonoBehaviour script that implements IInputHandler , and call
it ScramblePanel.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScramblePanel : MonoBehaviour, IInputHandler {


}

2. Its properties are:


public List<TileButton> tiles;
private TileButton selectedTile;
private List<TileButton> selectedTiles;

These are:
• tiles : a list of available tiles.

• selectedTile : the currently selected tile, if any.

• selectedTiles : a list with all currently selected tiles.

3. Let's take care of the input handlers first:


public void HandleTouchDown (Vector2 touch) {
selectedTile = TileCloseToPoint (touch);

if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
}

Touch Down will look for the closest tile to the touch position and select it. We
call the method SubmitTile , which will let us spell the word being selected by
the player. We'll code that in a second.
4. Next, comes touch up:
public void HandleTouchUp (Vector2 touch) {
if (selectedTile == null) {
return;
}
if ( selectedTiles.Count > 2 ) {
SubmitWord();
}
ClearSelection ();
selectedTile = null;
}
, and call
Touch Up will call a method that submits a word, if the player has selected more
than 2 tiles. If you remember, there is a minimum of three characters for valid
words in the game.
Then we clear the current selection.
5. And then we add touch move:
public void HandleTouchMove (Vector2 touch) {
if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);


if (nextTile != null && nextTile != selectedTile &&
nextTile.touched) {

selectedTile = nextTile;
selectedTile.Select(true);
if (!selectedTiles.Contains(selectedTile))
selectedTiles.Add (selectedTile);

SubmitTile ();

}
}

Touch Move will look for the closest tile to the current touch position and if this
tile has not been selected already, we'll make it so, repeating the logic from the
Touch Down handler.
6. The method that retrieves the closest tile to a point in space will loop
through all available tiles.
private TileButton TileCloseToPoint (Vector2 point){
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

g selected by var minDistance = 0.5f;


TileButton closestTile = null;
foreach (var tile in tiles) {
if (!tile.gameObject.activeSelf) continue;
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
}
}
return closestTile;
}

7. The SubmitTile and SubmitWord methods will spell the word being
selected and dispatch it as an Event. With the difference that SubmitWord will
also dispatch the selected tiles, in case we want to animate them to demonstrate a
successful word or an error.
We could, of course, just as easily use one method, with a boolean indicating a
complete word. We simply need to know when we have a complete word so we
can check it against our dictionary to see if it's valid or not.
private void SubmitTile () {
char[] word = new char[selectedTiles.Count];
for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);
ScrambleGameEvents.TileSelected (s);
}
private void SubmitWord () {
char[] word = new char[selectedTiles.Count];
for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);
ScrambleGameEvents.WordSelected (s, selectedTiles);
}

8. Then we have a simple ClearSelection method:


private void ClearSelection () {
foreach (var t in selectedTiles) {
t.Select (false);
}
if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}

9. Next, we need to add the logic that builds the tile panel:
public void ShowWord (string word) {

ClearPanel ();

var scrambledWord =
ScrambleDictionary.Instance.ScrambleWord (word);

var scrambledTiles = new TileButton[word.Length];

for (var i = 0; i < word.Length; i++) {


scrambledTiles [i] = tiles [i];
scrambledTiles [i].SetTileData (scrambledWord [i]);

if (Random.Range (0, 10) > 5) {


tiles [i].transform.Rotate (Vector3.forward *
demonstrate a Random.Range (-45, 46));
} else {
tiles [i].transform.Rotate (Vector3.back *
Random.Range (-45, 46));
}
}

scrambledTiles = Utils.Scramble<TileButton> (scrambledTiles);

Utils.StaggerAndCall<TileButton> (this, 0.06f,


(TileButton go) => { go.gameObject.SetActive(true); },
new List<TileButton> (scrambledTiles));
}

The Tiles are picked from an existing pool already placed in the scene. We just
need to make sure only the ones we need for each puzzle are turned active, and
that the remaining tiles are made inactive.
I use another method from the Utils class to stagger the appearance of the
tiles and rotate them slightly to make them look nicer.
10. This is what the ClearPanel method does:
private void ClearPanel () {
foreach (var tile in tiles)
tile.gameObject.SetActive (false);
}

So to recap, ShowWord will first clear all existing tiles.

The method receives the mystery word as its one parameter.


We scramble it and populate the tiles with the word's characters.
For an extra effect, we rotate the tiles slightly and randomly. And then stagger
their appearance on stage. Making each tile become active one by one and not all
at once.
Here it is then, the ScramblePanel class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScramblePanel : MonoBehaviour, IInputHandler {

public List<TileButton> tiles;


private TileButton selectedTile;
private List<TileButton> selectedTiles;

void Awake () {
selectedTiles = new List<TileButton> ();
}

public void ShowWord (string word) {

ClearPanel ();

var scrambledWord =
ScrambleDictionary.Instance.ScrambleWord (word);

var scrambledTiles = new TileButton[word.Length];

for (var i = 0; i < word.Length; i++) {


scrambledTiles [i] = tiles [i];
scrambledTiles [i].SetTileData (scrambledWord [i]);

if (Random.Range (0, 10) > 5) {


tiles [i].transform.Rotate (Vector3.forward *
Random.Range (-45, 46));
} else {
tiles [i].transform.Rotate (Vector3.back *
Random.Range (-45, 46));
}
}

scrambledTiles = Utils.Scramble<TileButton> (scrambledTiles);

Utils.StaggerAndCall<TileButton> (this, 0.06f,


(TileButton go) => { go.gameObject.SetActive(true); },
new List<TileButton> (scrambledTiles));
}

private void ClearPanel () {


foreach (var tile in tiles)
tile.gameObject.SetActive (false);
}

public void HandleTouchDown (Vector2 touch) {


selectedTile = TileCloseToPoint (touch);

if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
ne and not all }

public void HandleTouchUp (Vector2 touch) {


if (selectedTile == null) {
return;
}

if ( selectedTiles.Count > 2 ) {
SubmitWord();
}
ClearSelection ();
selectedTile = null;
}

public void HandleTouchMove (Vector2 touch) {


if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);


if (nextTile != null && nextTile != selectedTile &&
nextTile.touched) {

selectedTile = nextTile;

selectedTile.Select(true);

if (!selectedTiles.Contains(selectedTile))
selectedTiles.Add (selectedTile);

SubmitTile ();
}
}

private TileButton TileCloseToPoint (Vector2 point){


var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

var minDistance = 0.5f;


TileButton closestTile = null;
foreach (var tile in tiles) {
var distanceToTouch = Vector2.Distance (tile.transform.position, t);
if (distanceToTouch < minDistance) {
minDistance = distanceToTouch;
closestTile = tile;
}
}

return closestTile;
}

private void SubmitTile () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);
ScrambleGameEvents.TileSelected (s);
}

private void SubmitWord () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);
ScrambleGameEvents.WordSelected (s, selectedTiles);
}

private void ClearSelection () {


foreach (var t in selectedTiles) {
t.Select (false);
}
if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
}

Now it's time to put all this logic together inside the game controller.
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);
ScrambleGameEvents.WordSelected (s, selectedTiles);
}

private void ClearSelection () {


foreach (var t in selectedTiles) {
t.Select (false);
}
if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
}

Now it's time to put all this logic together inside the game controller.
The Scramble Game Class
Let's create our final class for this game.
1. Start by creating a new C# script called Scramble :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;

public class Scramble : MonoBehaviour {

2. And then add a few properties:


public Text spelling;
public Text scrambledWord;
public Text wordsList;
private ScramblePanel tiles;
private List<string> selectedWords;
private List<string> wordsFound;
private Dictionary<int, List<MappedWord>> mappedWords;
private string selectedWord;
private int totalWords;

In order of appearance:
• spelling : a reference to a UI text field where we'll display the word being
spelled.
• scrambledWord : a reference to a UI text field where we'll display the main
mystery word when the player solves it.
• wordsList : a reference to a UI text field where we'll display the other
target words which can be generated by using the available characters.
• tiles : a list of all available tiles.

• selectedWords : a list of selected words. This keeps track of all words


we've selected as puzzles, so we don't repeat a puzzle.
• wordsFound : a list of all the words found, by the player, within a puzzle. So
we don't run multiple checks on the same word.
• mappedWords : a Dictionary containing the target words, sorted by length,
and whether or not the word has been found. We use an internal class for that bit
of data (a sort of tupple):
internal struct MappedWord {
public string word;
public bool found;
public MappedWord (string word, bool found = false) {
this.word = word;
this.found = found;
}
}

We just need the word, and whether or not it has been found.
• selectedWord : a variable for the currently selected word, meaning the
main mystery word, the one which will appear in the scrambledWord text field
when it's found.
• totalWords : a count for total words left to be found in each puzzle.

3. In the Awake method we initialize our containers and our listeners. We also
tell the game dictionary to begin loading the word list:
void Awake () {
tiles = GetComponent<ScramblePanel> ();
selectedWords = new List<string> ();
wordsFound = new List<string> ();
mappedWords = new Dictionary<int, List<MappedWord>> ();

ScrambleGameEvents.OnGameLoaded += HandleGameLoaded;
ScrambleGameEvents.OnTileSelected += HandleTileSelected;
ScrambleGameEvents.OnWordSelected += HandleWordSelected;

ScrambleDictionary.Instance.Initialize();
}

4. We'll write the event handlers next:


void HandleTileSelected (string word) {
spelling.text = word.ToUpper();
}

When a tile is selected, we display the word being spelled by the player.
5. When a word is selected, we need to check if it's the mystery word, or one
n a puzzle. So of the target words, or failing that, if it's still a valid word. It is possible that the
player will spell a word not present in the target words list. This is because we
choose to only select relatively common words for our list of target words in
each puzzle.
void HandleWordSelected (string word, List<TileButton> tiles) {
if (!wordsFound.Contains (word)) {

if (word == selectedWord) {
scrambledWord.text = selectedWord.ToUpper();
wordsFound.Add (selectedWord);
totalWords--;
} else {

if (ScrambleDictionary.Instance.IsValidWord (word)) {
wordsFound.Add (word);

//check if word is in the map


if (mappedWords.ContainsKey (word.Length)) {
var list = mappedWords [word.Length];
var placed = false;
for (var i = 0; i < list.Count; i++) {
var w = list [i];
if (w.word == word) {
placed = true;
w.found = true;
totalWords--;
list [i] = w;
}
}

if (!placed) {
//the word found is not in our list,
//so add it to it if possible
for (var i = 0; i < list.Count; i++) {
var w = list [i];
if (w.found == false) {
placed = true;
w.word = word;
w.found = true;
list [i] = w;
totalWords--;
break;
}
}

if (!placed) ShowBonusWord(word);
}

ShowMappedWords ();
} else {
//this is a bonus word!
ShowBonusWord(word);

}
}
}
}
spelling.text = "";

if (totalWords <= 0) {
//start new round
Utils.DelayAndCall (this, 1, () => {
ScramblePlayerState.Instance.SetGameWord (null);
NewRound();
});
}
}

If the player found the main mystery word, we display that word in its own text
field.
If any of the target words are found–remember, we keep these words inside a
dictionary sorted by length, and each mapped word stores a boolean indicating
wether or not the word has been found by the player—we display it when we
next call ShowMappedWords .

So if the player finds a target word, which has not been found yet, we need to
update our list of target words.
In both cases, we update the integer value of totalWords and update the text
field displaying the target words via the method ShowMappedWords .

If the player managed to find another word, not listed in our puzzle, we try to
replace a target word for it. For that, we need a target word with the same
number of letters which has not been found yet. If we fail to find one, we can
handle the extra word as a bonus word. And I added a call to an empty method
called ShowBonusWord . We could use this to give the player extra points, or
hints...
At the end of this method, we check to see if totalWords is zero, if so, we start
a new round.
6. Next, comes the method that selects the words for our puzzle. That method
accomplishes quite a few things in our game:
void SelectWord () {
var word = ScramblePuzzleData.Instance.GetWord ();
if (selectedWords.Contains (word)) {
SelectWord ();
} else {

selectedWord = word;
selectedWords.Add (selectedWord);

//load panel
tiles.ShowWord(selectedWord);

//load word tiles


var i = 0;
scrambledWord.text = "";
while (i < selectedWord.Length) {
scrambledWord.text += "_";
i++;
}

var words = ScrambleDictionary.Instance.WordsFromChars (


selectedWord.ToCharArray ());

totalWords = words.Count;

mappedWords.Clear ();

foreach (var w in words) {


if (w != selectedWord) {
if (mappedWords.ContainsKey (w.Length)) {
mappedWords [w.Length].Add (new MappedWord(w));
} else {
mappedWords.Add (w.Length, new List<MappedWord> () {
new MappedWord(w) });
}
}
}

ShowMappedWords ();

Debug.Log (selectedWord);
}
}

• We load a word from the puzzle data object.


• If we haven't used this word yet, we turn it into the mystery word. Then we
create the tile panel with this word.
• We display the mystery words as a series of dashes.
if so, we start
•. We collect all words that can be generated from the letters contained in the
mystery word.
• Then we populate our dictionary with the mapped word objects.
• Finally, we display the target words, as a series of dashes as well.
7. The method that displays the target words looks like this:
void ShowMappedWords () {
var sb = new StringBuilder ();
var words = new List<string> ();
var i = 3;
while (i < 13) {
if (mappedWords.ContainsKey (i)) {
var list = mappedWords [i];
foreach (var w in list) {
if (w.found) {
words.Add (w.word.ToUpper());
} else {
var dash = "";
foreach (var c in w.word) {
dash += "_";
}
words.Add (dash);
}
}
}
i++;
}

for (i = 0; i < words.Count; i++) {


sb.Append (words [i]);
if (i != words.Count - 1) {
sb.Append (", ");
}
}

wordsList.text = sb.ToString ();


}

If the word is found, we display the word's characters, otherwise we replace


them with dashes.
8. Finally we wrap it all up with the method called when the game loaded
event is dispatched, and the NewRound method which calls SelectWord to
start a new puzzle.
void HandleGameLoaded () {
NewRound ();
}

void NewRound () {
SelectWord ();
}

Here is the complete game controller class:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;

public class Scramble : MonoBehaviour {

public Text spelling;


public Text scrambledWord;
public Text wordsList;

private ScramblePanel tiles;

private List<string> selectedWords;


private List<string> wordsFound;
private Dictionary<int, List<MappedWord>> mappedWords;
private string selectedWord;
private int totalWords;

internal struct MappedWord {


public string word;
public bool found;
public MappedWord (string word, bool found = false) {
this.word = word;
this.found = found;
}
}

void Awake () {
tiles = GetComponent<ScramblePanel> ();
selectedWords = new List<string> ();
wordsFound = new List<string> ();
mappedWords = new Dictionary<int, List<MappedWord>> ();

ScrambleGameEvents.OnGameLoaded += HandleGameLoaded;
ScrambleGameEvents.OnTileSelected += HandleTileSelected;
ScrambleGameEvents.OnWordSelected += HandleWordSelected;

ScrambleDictionary.Instance.Initialize();
}

void HandleGameLoaded () {
NewRound ();
}

void NewRound () {

SelectWord ();
}

void HandleTileSelected (string word) {


spelling.text = word.ToUpper();
}

void HandleWordSelected (string word, List<TileButton> tiles) {


if (!wordsFound.Contains (word)) {

if (word == selectedWord) {
scrambledWord.text = selectedWord.ToUpper();
wordsFound.Add (selectedWord);
totalWords--;
} else {

if (ScrambleDictionary.Instance.IsValidWord (word)) {
wordsFound.Add (word);

//check if word is in the map


if (mappedWords.ContainsKey (word.Length)) {

var list = mappedWords [word.Length];


var placed = false;

for (var i = 0; i < list.Count; i++) {


var w = list [i];
if (w.word == word) {
placed = true;
w.found = true;
totalWords--;
list [i] = w;

}
}

if (!placed) {

//the word found is not in our list,


//so add it to it if possible
for (var i = 0; i < list.Count; i++) {
var w = list [i];
if (w.found == false) {
placed = true;
w.word = word;
w.found = true;
list [i] = w;
totalWords--;
break;
}
}

if (!placed) ShowBonusWord(word);
}

ShowMappedWords ();
} else {
//this is a bonus word!
ShowBonusWord(word);

}
}
}
}

spelling.text = "";

if (totalWords <= 0) {
//start new round
Utils.DelayAndCall (this, 1, () => {
ScramblePlayerState.Instance.SetGameWord (null);
NewRound();
});
}
}

void SelectWord () {

var word = ScramblePuzzleData.Instance.GetWord ();

if (selectedWords.Contains (word)) {
SelectWord ();
} else {

selectedWord = word;
selectedWords.Add (selectedWord);

//load panel
tiles.ShowWord(selectedWord);

//load word tiles


var i = 0;
scrambledWord.text = "";
while (i < selectedWord.Length) {
scrambledWord.text += "_";
i++;
}

var words = ScrambleDictionary.Instance.WordsFromChars (


selectedWord.ToCharArray ());

totalWords = words.Count;

mappedWords.Clear ();

foreach (var w in words) {


if (w != selectedWord) {
if (mappedWords.ContainsKey (w.Length)) {
mappedWords [w.Length].Add (new MappedWord(w));
} else {
mappedWords.Add (w.Length, new List<MappedWord> () {
new MappedWord(w) });
}
}
}

ShowMappedWords ();

Debug.Log (selectedWord);
}
}

void ShowBonusWord (string word) {


//handle display of bonus word here
Debug.Log ("BONUS");
}

void ShowMappedWords () {
var sb = new StringBuilder ();
var words = new List<string> ();
var i = 3;
while (i < 13) {
if (mappedWords.ContainsKey (i)) {
var list = mappedWords [i];
foreach (var w in list) {
if (w.found) {
words.Add (w.word.ToUpper());
} else {
var dash = "";
foreach (var c in w.word) {
dash += "_";
}
words.Add (dash);
}
}
}
i++;
}

for (i = 0; i < words.Count; i++) {


sb.Append (words [i]);
if (i != words.Count - 1) {
sb.Append (", ");
}
}

wordsList.text = sb.ToString ();


}
}

That's it for code. We can put it all together and test the game.
}
}

That's it for code. We can put it all together and test the game.
The Game Scene
You will find a scene ready to be hooked up to our scripts inside our sample
project. The scene can be found at Assets/Scenes/Scene_Scramble.
When you open it inside Unity you'll see all GameObjects already in place.
This game only requires script components for the main game controller, since
everything else that could be a singleton, was done as a singleton.
Here are the steps to build the game scene.
Figure 6.2 - The Initial Game Scene

First, the tiles.


Figure 6.2 - The Initial Game Scene

First, the tiles.


The Tile Panel
In order to properly hook up the TilePanel with our logic, you must follow these
steps:
1. Add a new component and begin typing ScramblePanel and select it when
it becomes available in the list.
2. This component requires references to all game tiles. These are already
placed in the scene. There are a total of twelve tiles, and we need to select them
in the order they appear in the Hierarchy panel. So lock the inspector panel
displaying the information for ScramblePanel and select all tiles in the
Hierarchy and drag them to the Tiles field.

Figure 6.3 - Dragging Tile to Inspector Field

3. The ScramblePanel component requires the InputController script in


order for it to behave properly. So add that component as well.
select it when

Figure 6.4 - The Complete Panel GameObject

And lastly, we set up our game controller.


Figure 6.4 - The Complete Panel GameObject

And lastly, we set up our game controller.


The Main Game Script
Let's add the controller next:
1. Add a new component and search for the Scramble.cs script.

2. Click the target button next to the Spelling field and search the Scene tab for
the Spelling UI Text field.

Figure 6.5 - Selecting Text Field in the Scene

3. Click the target button next to the Scramble Word field and search the
Scene tab for the Scramble UI Text field.
4. Click the target button next to the Words List field and search the Scene tab
for the Words UI Text field.
Your game controller list of components should look like this:
Figure 6.6 - The Game Controller
tab for

And that's it. The game should run now.

tab
Figure 6.6 - The Game Controller

And that's it. The game should run now.


Taking It Further
You could easily add hints, where every instance of a letter is revealed in every
single target word.
The main mystery word could also be themed in this game, say animals, or
professions... This is also a fun game to let players generate their own puzzles
and send them to their friends. So adding a "challenge your friends" mode could
be easily done!
Taking It Further
You could easily add hints, where every instance of a letter is revealed in every
single target word.
The main mystery word could also be themed in this game, say animals, or
professions... This is also a fun game to let players generate their own puzzles
and send them to their friends. So adding a "challenge your friends" mode could
be easily done!
7. Game: Boggling

This time, we’ll build a grid based game and use our backtracking algorithm to
solve the grid, collecting all the words that can be generated, following the
grid’s rules for tile selection: A tile can only be selected if one of its eight
neighbors are already selected. The logic shown here can be used to build
Boggle like games.
7. Game: Boggling

This time, we’ll build a grid based game and use our backtracking algorithm to
solve the grid, collecting all the words that can be generated, following the
grid’s rules for tile selection: A tile can only be selected if one of its eight
neighbors are already selected. The logic shown here can be used to build
Boggle like games.
The Game

In Boggling, the grid is generated, and then pre-solved by our algorithm, and a
few words are selected from the list of words collected. The player then has to
find those words in the grid. Just as with Scramble, we display the list of target
words as dashes. But a timer will slowly reveal the letters in the words, one at a
time. The player must guess and find the target words in the grid before the timer
reveals all the letters.
Here's what the prototype looks like:
fore the timer

Figure 7.1 - The Game Boggling

Let's take a look at the actual puzzle and how to generate them for our game.
Figure 7.1 - The Game Boggling

Let's take a look at the actual puzzle and how to generate them for our game.
The Game Puzzles
For the prototype, we'll build a random grid and use that for our puzzles.
But we could easily have better data available to us. All we'd need to do is create
a bunch of random grids inside a Thread , solve them, and analyse the words
generated by length, and commonality.
As the game gets harder, the grids will get bigger but so will the length of the
target words and number of target words to find.
The way the word is positioned in the grid can also be scored as to its difficulty.
Words which are presented backwards, with lots of diagonals, and with lots of
zig-zags should receive a higher score.
Another nice touch for this game would be to focus on words that share a
common root with many other words. This would trick the player into guessing
the wrong word a few times.
Again: pure evil.
Before we jump into code, let's review how the game is supposed to work.
The Game Puzzles
For the prototype, we'll build a random grid and use that for our puzzles.
But we could easily have better data available to us. All we'd need to do is create
a bunch of random grids inside a Thread , solve them, and analyse the words
generated by length, and commonality.
As the game gets harder, the grids will get bigger but so will the length of the
target words and number of target words to find.
The way the word is positioned in the grid can also be scored as to its difficulty.
Words which are presented backwards, with lots of diagonals, and with lots of
zig-zags should receive a higher score.
Another nice touch for this game would be to focus on words that share a
common root with many other words. This would trick the player into guessing
the wrong word a few times.
Again: pure evil.
Before we jump into code, let's review how the game is supposed to work.
The Game Logic
We load our dictionary, and then we build a random grid. We then solve the grid,
collecting the words and their positions.
From the list of words, we collect a few as target words, and create a timer
which will reveal a random letter every ten seconds.
The player must find each of the target words before time runs out and all letters
are revealed.
If successful, the game goes on to a new puzzle. Easy peasy!
So let's get started.
The Game Logic
We load our dictionary, and then we build a random grid. We then solve the grid,
collecting the words and their positions.
From the list of words, we collect a few as target words, and create a timer
which will reveal a random letter every ten seconds.
The player must find each of the target words before time runs out and all letters
are revealed.
If successful, the game goes on to a new puzzle. Easy peasy!
So let's get started.
The Game Events
The game Boggling will have events similar to the ones used in Scramble: game
loaded, tile selected and word selected.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class BogglingGameEvents {

public delegate void Event_SelectTile (string word);


public delegate void Event_SelectWord (string word,
List<GridTile> tiles);
public delegate void Event_GameLoaded ();

public static event Event_SelectTile OnTileSelected;


public static event Event_SelectWord OnWordSelected;
public static event Event_GameLoaded OnGameLoaded;

public static void TileSelected (string word) {


if (OnTileSelected != null)
OnTileSelected (word);
}

public static void WordSelected (string word, List<GridTile> tiles) {


if (OnWordSelected != null)
OnWordSelected (word, tiles);
}

public static void GameLoaded () {


if (OnGameLoaded != null)
OnGameLoaded ();
}
}

With that out of the way, let's get our dictionary loaded and ready.
The Game Events
The game Boggling will have events similar to the ones used in Scramble: game
loaded, tile selected and word selected.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class BogglingGameEvents {

public delegate void Event_SelectTile (string word);


public delegate void Event_SelectWord (string word,
List<GridTile> tiles);
public delegate void Event_GameLoaded ();

public static event Event_SelectTile OnTileSelected;


public static event Event_SelectWord OnWordSelected;
public static event Event_GameLoaded OnGameLoaded;

public static void TileSelected (string word) {


if (OnTileSelected != null)
OnTileSelected (word);
}

public static void WordSelected (string word, List<GridTile> tiles) {


if (OnWordSelected != null)
OnWordSelected (word, tiles);
}

public static void GameLoaded () {


if (OnGameLoaded != null)
OnGameLoaded ();
}
}

With that out of the way, let's get our dictionary loaded and ready.
The Game Dictionary
We need a dictionary to solve the grid, but since we don’t want the more
uncommon words, we will use the first 20k words for this purpose.
I'll add logic that checks if a word generated by the player is a valid word, and
maybe handle it as a bonus. And for that we'll need the full dictionary also.
1. Create a C# script and call it BogglingDictionary .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class BogglingDictionary : MonoBehaviour {


}

2. We'll make this one... you guessed it! A Singleton.


private static BogglingDictionary _instance = null;
public static BogglingDictionary Instance
{
get
{
if(_instance == null)
{
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<BogglingDictionary> ();
}

return _instance;
}
}

3. We need two dictionaries for our game. One with all the words, and one
with the first 4 dictionary groups.
[HideInInspector]
public List<string> allWords;

[HideInInspector]
public List<string> commonDictionaryWords;

The one with all the words will be used to check if a player generated word is
valid.
The one with the first most common 20 thousand words will be used to solve the
grid, so we don't end up with words the player may not easily recognize. You
could easily change the configurations for the second list, and end up with the
absolute most common words, say... the first 10 thousand only. And we could
also increase the words in this dictionary as the game progresses, adding the 50k
words, and then the 250k words...
4. We load the data listing the words by frequency, store all the words in the
allWords list and the words from the first 4 frequency groups will go into the
commonDictionaryWords list.

public void Initialize () {


StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {
if (allWords != null && allWords.Count != 0)
yield break;
else {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new List<string> ();
commonDictionaryWords = new List<string> ();

var index = 0;
foreach (var w in words) {
if (string.IsNullOrEmpty(w) || w.Length < 3)
continue;

var word = w.TrimEnd ();


if (word.IndexOf ('#') != -1) {
index++;
} else {

allWords.Add (word);
if (index < 3) {
commonDictionaryWords.Add (word);
}
}
}
BogglingGameEvents.GameLoaded ();
}
}
d to solve the
5. We'll need a method to check if a word is valid:
public bool IsValidWord (string word) {
return allWords.Contains(word);
dding the 50k }

6. We'll need a method to give us a random string of characters. Technically


this does not need to be made part our Dictionary class but, what the hell.
We'll need to populate the grid with characters. As I discussed before, one of the
best methods is to use roughly fifty percent vowels, and fifty percent consonants.
Although a 60 to 40 ratio between vowels and consonants work very well too.
And of course you could improve this and use our data on most frequent letters
in the english language and apply those ratios too, so you end up with relative
frequent vowels and consonants.
The goal here is to end with a grid that can yield quite a lot of words.
You could also run a script that generates random grids, and stores the ones that
yielded above an X number of words and use those in the puzzles.
With that approach then, you'd end up having to load puzzle data for the game.
But it's a very good option for the final game.
Here's the script we'll use to fill the grid:
public char[] GetRandomChars (int len) {

if (len == 0)
len = 100;

var randomString = "";


var vRatio = 0.5f;
var cRatio = 0.5f;

var vowels = Mathf.RoundToInt (len * vRatio);


var consonants = Mathf.RoundToInt (len * cRatio);

var i = 0;
while (i < vowels) {
randomString += TileChar.vowels[UnityEngine.Random.Range(0,
TileChar.vowels.Length)];
i++;
}

i = 0;
while (i < consonants) {
randomString += TileChar.consonants[UnityEngine.Random.Range(0,
TileChar.consonants.Length)];
i++;
}

while (randomString.Length < len) {


randomString += TileChar.vowels[UnityEngine.Random.Range(0,
TileChar.vowels.Length)];
}

return Utils.Scramble<char> (randomString.ToCharArray ());


}

The entire class looks like this:


using System.Collections;
nt consonants. using
using
System.Collections.Generic;
UnityEngine;
using System;
using System.IO;

public class BogglingDictionary : MonoBehaviour {

private static BogglingDictionary _instance = null;


public static BogglingDictionary Instance
{
get
{
if(_instance == null)
{
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<BogglingDictionary> ();
}

return _instance;
}
}

[HideInInspector]
public List<string> allWords;

[HideInInspector]
public List<string> commonDictionaryWords;

public void Initialize () {


StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {

if (allWords != null && allWords.Count != 0)


yield break;
else {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new List<string> ();
commonDictionaryWords = new List<string> ();

var index = 0;

foreach (var w in words) {

if (string.IsNullOrEmpty(w) || w.Length < 3)


continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
} else {

allWords.Add (word);
if (index < 3) {
commonDictionaryWords.Add (word);
}
}
}

BogglingGameEvents.GameLoaded ();
}
}

public bool IsValidWord (string word) {


return allWords.Contains(word);
}

public char[] ScrambleWord (string word) {


return Utils.Scramble<char> (word.ToCharArray ());
}

public char[] GetRandomChars (int len) {

if (len == 0)
len = 100;

var randomString = "";


var vRatio = 0.5f;
var cRatio = 0.5f;

//0.6 0.4 = 30!


//0.5 0.5 = 30!

var vowels = Mathf.RoundToInt (len * vRatio);


var consonants = Mathf.RoundToInt (len * cRatio);

var i = 0;
while (i < vowels) {
randomString += TileChar.vowels[UnityEngine.Random.Range(0,
TileChar.vowels.Length)];
i++;
}
i = 0;
while (i < consonants) {
randomString += TileChar.consonants[UnityEngine.Random.Range(0,
TileChar.consonants.Length)];
i++;
}

while (randomString.Length < len) {


randomString += TileChar.vowels[UnityEngine.Random.Range(0,
TileChar.vowels.Length)];
}

return ScrambleWord (randomString);


}
}

Time to build our grid.


i = 0;
while (i < consonants) {
randomString += TileChar.consonants[UnityEngine.Random.Range(0,
TileChar.consonants.Length)];
i++;
}

while (randomString.Length < len) {


randomString += TileChar.vowels[UnityEngine.Random.Range(0,
TileChar.vowels.Length)];
}

return ScrambleWord (randomString);


}
}

Time to build our grid.


The Grid
We've covered the logic for building grids for word games in a previous chapter.
But I'll give a break down of the class used in this game anyway.
1. Create a C# script called BogglingGrid which implements the interface
IInputHandler .

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BogglingGrid : MonoBehaviour, IInputHandler {


}

2. We add our properties:


public int ROWS = 4;
public int COLUMNS = 3;
public GameObject gridTileGO;
public static float GRID_TILE_SIZE;
private List<GridTile> tiles;
private List<List<GridTile>> gridTiles;
private GridTile selectedTile;
private List<GridTile> selectedTiles;

In order, these are:


• ROWS and COLUMNS : The first two are number of Rows and Columns in the
grid.
• gridTileGO : The GameObject used as the tile.

• GRID_TILE_SIZE : A variable to hold the tile size, once the grid is scaled, if
necessary.
• tiles : The list of tiles as a one dimensional list.

• gridTiles : The list of tiles as a two dimensional list (organized by rows).

• selectedTile : The currently selected tile.

• selectedTiles : All currently selected tiles.


Then we move to the main input events.
3. With touch down we look for the closest tile.
public void HandleTouchDown (Vector2 touch) {
selectedTile = TileCloseToPoint (touch);

if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
}

3. With touch up we submit a word, if one has been selected, and clear the
current selection.
public void HandleTouchUp (Vector2 touch){
if (selectedTile == null) {
return;
}

if ( selectedTiles.Count > 2 ) {
SubmitWord();
}

ClearSelection ();
selectedTile = null;
}

5. With touch move, we again identify the closest tile to the player's finger.
If the tile has been selected already, we make it the new "tail" of the selection
olumns in the and deselect those tiles that come afterwards.
So if the player selected ABCDE, but moved the selection back to the C tile, I
deselect D and E and make C the last tile in the current selection.
With every new tile selected, or deselected, I dispatch an event with the current
selected chars.
public void HandleTouchMove (Vector2 touch) {
if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);

if (nextTile != null && nextTile != selectedTile &&


nextTile.touched) {

if (nextTile.row == selectedTile.row &&


(nextTile.column == selectedTile.column - 1 ||
nextTile.column == selectedTile.column + 1))
{
selectedTile = nextTile;
}
else if (nextTile.column == selectedTile.column &&
(nextTile.row == selectedTile.row - 1 ||
nextTile.row == selectedTile.row + 1))
{
selectedTile = nextTile;
}
#if EIGHT_DIRECTIONAL
else if (Mathf.Abs (
nextTile.column - selectedTile.column) == 1 &&
Mathf.Abs (nextTile.row - selectedTile.row) == 1)
{
selectedTile = nextTile;
}
#endif

selectedTile.Select(true);

if (!selectedTiles.Contains (selectedTile))
selectedTiles.Add (selectedTile);
else {
//deselect tile if it's already
//been added to selected tiles
var index = selectedTiles.IndexOf(nextTile);
for(var i = selectedTiles.Count -1; i >= 0; i--)
{
var tile = selectedTiles[i];
if (i > index) {
tile.Select(false);
selectedTiles.RemoveAt(i);
}
}
selectedTile = selectedTiles[selectedTiles.Count - 1];
}

SubmitTile ();

}
}

6. The Grid logic to find the closest tile to a point:


private GridTile TileCloseToPoint (Vector2 point) {
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

int c = Mathf.FloorToInt (
(t.x - gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);

if (c < 0)
return null;
if (c >= COLUMNS)
return null;

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - t.y ) / GRID_TILE_SIZE);

if (r < 0) return null;


if (r >= ROWS) return null;

if (gridTiles.Count <= r)
return null;

if (gridTiles[r].Count <= c)
return null;

if (!gridTiles[r][c].gameObject.activeSelf) return null;

return gridTiles[r][c];

7. The method to clear the current selection.


private void ClearSelection () {
foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}

8. The BuildGrid method.

public void BuildGrid () {


if (tiles != null && tiles.Count != 0) {
foreach (var t in tiles)
Destroy (t.gameObject);

transform.localScale = Vector2.one;
transform.localPosition = Vector2.zero;
}

//a one dimensional list of tiles we can shuffle


tiles = new List<GridTile> ();
//the two dimensional grid
gridTiles = new List<List<GridTile>> ();

for (int row = 0; row < ROWS; row++) {

var rowsTiles = new List<GridTile>();

for (int column = 0; column < COLUMNS; column++) {

var item = Instantiate (gridTileGO) as GameObject;


var tile = item.GetComponent<GridTile>();
tile.SetTilePosition(row, column);
tile.transform.parent = gameObject.transform;

tiles.Add (tile);

rowsTiles.Add (tile);
}
gridTiles.Add(rowsTiles);
}

ScaleGrid ( Mathf.Abs (
gridTiles [0] [0].transform.localPosition.y -
gridTiles [1] [0].transform.localPosition.y));
}

9. The method to scale the grid and ensure grids of all sizes will fit our screen.
private void ScaleGrid ( float tileSize) {
GRID_TILE_SIZE = tileSize;

var stageWidth = 4.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}
GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 (
(gridWidth * scale) * -0.5f ,
3.5f - 0.5f * (gridHeight * scale));
}

10. A method to feed each tile its data, meaning, its letter.
public void ShowGridChars (char[] chars) {
for (var i = 0; i < tiles.Count; i++) {
tiles [i].SetTileData (chars [i]);
}
}

11. The SubmitTile and SubmitWords methods:


private void SubmitTile () {
char[] word = new char[selectedTiles.Count];
for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);

BogglingGameEvents.TileSelected (s);
}
private void SubmitWord () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);

BogglingGameEvents.WordSelected (s, selectedTiles);


}

12. And finally, a method to retrieve the letters in the grids as a string of chars.
We'll need this when we run the logic to solve the grid and collected all words
which can be generated in it.
public string GetGridString () {
var result = "";
foreach (var tile in tiles) {
result += tile.TypeChar;
}
return result;
}

The entire Grid class looks like this:


#define EIGHT_DIRECTIONAL
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BogglingGrid : MonoBehaviour, IInputHandler {


public int ROWS = 4;
public int COLUMNS = 3;
public GameObject gridTileGO;
public static float GRID_TILE_SIZE;
private List<GridTile> tiles;
private List<List<GridTile>> gridTiles;
private GridTile selectedTile;
private List<GridTile> selectedTiles;

void Awake () {
selectedTiles = new List<GridTile> ();
selectedTile = null;
}

public void ShowGridChars (char[] chars) {


for (var i = 0; i < tiles.Count; i++) {
tiles [i].SetTileData (chars [i]);
}
}

public void HandleTouchDown (Vector2 touch) {

selectedTile = TileCloseToPoint (touch);

if (selectedTile != null) {
selectedTile.Select(true);
selectedTiles.Add (selectedTile);
SubmitTile();
}
}

public void HandleTouchUp (Vector2 touch)


{

if (selectedTile == null) {
return;
}

if ( selectedTiles.Count > 2 ) {
SubmitWord();
ring of chars. }

ClearSelection ();
selectedTile = null;

public void HandleTouchMove (Vector2 touch)


{

if (selectedTile == null)
return;

var nextTile = TileCloseToPoint (touch);

if (nextTile != null && nextTile != selectedTile &&


nextTile.touched)
{

if (nextTile.row == selectedTile.row &&


(nextTile.column == selectedTile.column - 1 ||
nextTile.column == selectedTile.column + 1))
{
selectedTile = nextTile;
}
else if (nextTile.column == selectedTile.column &&
(nextTile.row == selectedTile.row - 1 ||
nextTile.row == selectedTile.row + 1))
{
selectedTile = nextTile;
}
#if EIGHT_DIRECTIONAL
else if (Mathf.Abs (
nextTile.column - selectedTile.column) == 1 &&
Mathf.Abs (nextTile.row - selectedTile.row) == 1)
{
selectedTile = nextTile;
}
#endif

selectedTile.Select(true);

if (!selectedTiles.Contains (selectedTile))
selectedTiles.Add (selectedTile);
else {
//deselect tile if it's already
//been added to selected tiles
var index = selectedTiles.IndexOf(nextTile);
for(var i = selectedTiles.Count -1; i >= 0; i--)
{
var tile = selectedTiles[i];
if (i > index) {
tile.Select(false);
selectedTiles.RemoveAt(i);
}
}
selectedTile = selectedTiles[selectedTiles.Count - 1];
}

SubmitTile ();

}
}

public string GetGridString () {

var result = "";

foreach (var tile in tiles) {


result += tile.TypeChar;
}

return result;
}

private GridTile TileCloseToPoint (Vector2 point)


{
var t = Camera.main.ScreenToWorldPoint (point);
t.z = 0;

int c = Mathf.FloorToInt (
(t.x - gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);
if (c < 0)
return null;
if (c >= COLUMNS)
return null;

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - t.y ) / GRID_TILE_SIZE);

if (r < 0) return null;

if (r >= ROWS) return null;

if (gridTiles.Count <= r)
return null;

if (gridTiles[r].Count <= c)
return null;

if (!gridTiles[r][c].gameObject.activeSelf) return null;

return gridTiles[r][c];

public void BuildGrid ()


{
if (tiles != null && tiles.Count != 0) {
foreach (var t in tiles)
Destroy (t.gameObject);
transform.localScale = Vector2.one;
transform.localPosition = Vector2.zero;
}

//a one dimensional list of tiles we can shuffle


tiles = new List<GridTile> ();
//the two dimensional grid
gridTiles = new List<List<GridTile>> ();

for (int row = 0; row < ROWS; row++) {

var rowsTiles = new List<GridTile>();

for (int column = 0; column < COLUMNS; column++) {

var item = Instantiate (gridTileGO) as GameObject;


var tile = item.GetComponent<GridTile>();
tile.SetTilePosition(row, column);
tile.transform.parent = gameObject.transform;

tiles.Add (tile);

rowsTiles.Add (tile);
}
gridTiles.Add(rowsTiles);
}

ScaleGrid ( Mathf.Abs (
gridTiles [0] [0].transform.localPosition.y -
gridTiles [1] [0].transform.localPosition.y));

private void ScaleGrid ( float tileSize) {

GRID_TILE_SIZE = tileSize;

var stageWidth = 4.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}
GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 (
(gridWidth * scale) * -0.5f ,
3.5f - 0.5f * (gridHeight * scale));

}
private void SubmitTile () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);

BogglingGameEvents.TileSelected (s);
}

private void SubmitWord () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);

BogglingGameEvents.WordSelected (s, selectedTiles);


}

private void ClearSelection () {


foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
}

That was a lot of code. But we're almost done. All we need now is the actual
game controller logic!
private void SubmitTile () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT TILES: " + s);

BogglingGameEvents.TileSelected (s);
}

private void SubmitWord () {

char[] word = new char[selectedTiles.Count];


for (var i = 0; i < selectedTiles.Count; i++) {
var tile = selectedTiles [i];
word [i] = tile.TypeChar;
}
var s = new string (word);
Debug.Log("SUBMIT WORD: "+ s);

BogglingGameEvents.WordSelected (s, selectedTiles);


}

private void ClearSelection () {


foreach (var t in selectedTiles) {
t.Select (false);
}

if (selectedTile != null)
selectedTile.Select (false);

selectedTiles.Clear ();
}
}

That was a lot of code. But we're almost done. All we need now is the actual
game controller logic!
The Game Controller
Finally, we can start working on our game class.
1. Create a C# script and call it Boggling .

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;
using System.Linq;

public class Boggling : MonoBehaviour {


}

2. We have quite a few properties to add, and another internal class to be used
as a representation of our mystery word, and puzzle.
The properties are:
public Text spelling;
public Text wordsList;
private BogglingGrid grid;
private List<string> wordsFound;
private List<string> selectedWords;
private List<MappedWord> mappedWords;
private ProcessedGrid solvedGrid;
private Vector2[] gridSizes =
{ new Vector2(3,4),
new Vector2(3,4),
new Vector2(4,5),
new Vector2(4,5),
new Vector2(4,5),
new Vector2(5,6),
new Vector2(5,6),
new Vector2(6,7)
};
private int[] numWords = {
1,2,3,4,5,6,7,8
};
private int difficulty = 0;
private int level = 0;
private bool gameActive = false;

Here's what each one of them means:


• spelling : A reference to a UI text field where we'll display the words the
player spells.
• wordsList : A reference to a UI text field where we'll display the puzzle's
mystery words.
• grid : A reference to the game's Grid object.

• wordsFound : A list of all words found by the player.

• selectedWords : A list of all the words selected to be puzzle mystery


words.
• mappedWords : A list of the target words in the puzzle, represented by an
internal class called MappedWord .

• solvedGrid : The result of the grid solver, represented by a struct called


ProcessedGrid (this object basically contains all the words that can be generated
in the grid and the tile locations for all the words.)
• gridSizes : A simple difficulty curve object, listing the grid sizes as the
game progresses.
• numWords : A similar thing to the previous property, but this one lists the
number of mystery, or target, words the player must find.
• difficulty : The difficulty index, it will be used in both previous arrays to
select grid size and number of mystery words.
• level : The current level of the player.

• gameActive : A boolean controlling whether or not the game can receive


input.
3. The MappedWord class is a lot more complex than the one we had
previously. It will store quite a bit of state information for each puzzle. It looks
like this:
internal struct MappedWord {
public string word;
public char[] wordChars;
public bool[] revealedChars;
public bool revealed;
public MappedWord (string word, int difficulty,
bool revealed = false) {
this.word = word;
this.revealed = revealed;
this.wordChars = word.ToCharArray();
revealedChars = new bool[wordChars.Length];
if (difficulty < 4) {
revealedChars[0] = true;
} else {
revealedChars[Random.Range( 0, wordChars.Length)] = true;
}
}
}

• word : We have the word string, representing a target, or mystery, word.

• wordChars : The word as a char array.

• revealedChars : An array containing the information of which char is


revealed to the player and which is still hidden. The game has a timer, as I said
before, and the words are slowly revealed to the player, who must find the word
n be generatedin the grid before it is completely revealed.
• revealed : A boolean indicating whether the word is already revealed. I
made the decision that until the player reaches level 4, I always reveal the first
letter of the word. Afterwards a random index is initially revealed. It is
significantly harder to play the game if the initial clues are spread out and not at
the beginning of the word.
4. Moving on. On Awake I initialize my objects and add event listener
handlers, and tell the dictionary to load its data.
void Awake () {
wordsFound = new List<string> ();
selectedWords = new List<string>();
mappedWords = new List<MappedWord> ();
grid = GetComponent<BogglingGrid> ();

BogglingGameEvents.OnGameLoaded += HandleGameLoaded;
BogglingGameEvents.OnTileSelected += HandleTileSelected;
BogglingGameEvents.OnWordSelected += HandleWordSelected;

BogglingDictionary.Instance.Initialize ();
}

5. Let's take a look at the events.


On game loaded, I update the information related to the difficulty index, like the
size of the grid.
I chose to increase the difficulty every other level. Every new round updates the
level count.
void HandleGameLoaded () {
grid.COLUMNS = (int) gridSizes [difficulty].x;
grid.ROWS = (int) gridSizes [difficulty].y;

grid.COLUMNS = (int) gridSizes [difficulty].x;


grid.ROWS = (int) gridSizes [difficulty].y;

NewRound ();

level++;

if (level % 2 == 0)
difficulty++;

if (difficulty > gridSizes.Length - 1)


difficulty = gridSizes.Length - 1;
}

6. On tile selected, we show the spelling:


void HandleTileSelected (string word) {
if (!gameActive)
return;
spelling.text = word.ToUpper();
}

7. On word selected, we check to see if the word hasn't been found before, and
if not, we check to see if it's one of the target words.
void HandleWordSelected (string word, List<GridTile> tiles) {

if (!gameActive)
return;

//animate tiles?
foreach (var tile in tiles) {
tile.Select (false);
}

if (!wordsFound.Contains (word)) {
wordsFound.Add (word);
if (selectedWords.Contains (word)) {
var totalWords = 0;
for (var i = 0; i < mappedWords.Count; i++) {
var mappedWord = mappedWords [i];
if (mappedWord.revealed)
totalWords++;

if (!mappedWord.revealed &&
mappedWord.word.ToLower () == word.ToLower ()) {
mappedWord.revealed = true;
mappedWords [i] = mappedWord;
totalWords++;
}
}

if (totalWords == mappedWords.Count) {
StopCoroutine ("GameTimer");
gameActive = false;
spelling.text = "Well done!";

//start new round


Utils.DelayAndCall (this, 2, () => {
NewRound();
});
}

ShowMappedWords ();

} else {
if (BogglingDictionary.Instance.IsValidWord (word)) {
//show bonus?
}
}
}
}

If it is, we update the data for that MappedWord object, setting the word as
revealed, if that word has not been already fully revealed by the game's timer.
If all the words appear as revealed, we stop the game timer, and start a new
round.
We also update the information displayed in the UI text field where all the target
words are shown.
Just as with Scramble, we have the possibility of generating a word which is
valid but not one of the targets.
The choice is yours on how to handle it.
Now, what's left to do is our puzzle creation logic.
We'll tackle that next.
8. First we need to build the puzzle:
void BuildGrid () {
grid.BuildGrid ();
grid.ShowGridChars (BogglingDictionary.Instance.GetRandomChars (
grid.COLUMNS * grid.ROWS));

//solve grid
solvedGrid = GridUtils.SolveGrid (
ref BogglingDictionary.Instance.commonDictionaryWords,
grid.GetGridString(), grid.ROWS, grid.COLUMNS);

//select mapped words


selectedWords.Clear();
mappedWords.Clear ();
var wordList = solvedGrid.words.OrderByDescending(
x=>x.Length).ThenBy(x=>x).ToList();
var num = numWords [difficulty];
if (num > wordList.Count)
num = wordList.Count;

var i = 0;
while (i < num) {
selectedWords.Add (wordList [i]);
mappedWords.Add( new MappedWord (
wordList [i].ToUpper(), difficulty) );
i++;
}
ShowMappedWords ();
}

We tell Grid to build a grid with the right row and column count. Then we
select the chars to display in the grid.
With the grid complete, we run the grid solver method to retrieve all the
information we need about words that can be generated in our new grid. Notice
that we use out common words dictionary to base the solver on.
We sort the word list we get from the solver by length. We want the long words
first.
Then we select the words, based on the difficulty level, which will give us a
maximum number of words per puzzle. And we use those words to create
MappedWord objects for our puzzle.
9. We need a method to display the MappedWord objects:
void ShowMappedWords () {
var sb = new StringBuilder ();
var line = 0;
foreach (var w in mappedWords) {
if (w.revealed) {
sb.AppendLine (w.word);
line++;
} else {
if (line != 0) sb.AppendLine ("");
line++;
for (var i = 0; i < w.wordChars.Length; i++) {
if (w.revealedChars [i]) {
sb.Append (w.wordChars [i]);
} else {
sb.Append ("_");
}
}
}
}
wordsList.text = sb.ToString ();
}
This is similar to the one we have in Scramble.
10. Next, we need a method that selects a random hint, meaning the next char
to be revealed by our game timer.
void GiveRandomHint () {
for (var i = 0; i < mappedWords.Count; i++) {
var mw = mappedWords [i];
if (!mw.revealed) {
var cnt = 0;
foreach (var c in mw.revealedChars)
if (c == true)
cnt++;
if (cnt == mw.wordChars.Length) {
mw.revealed = true;
mappedWords [i] = mw;
continue;
}
var attempts = 0;
while (attempts < 1000) {
var index = Random.Range (0, mw.wordChars.Length);
if (mw.revealedChars [index] == false) {
mw.revealedChars [index] = true;
mappedWords [i] = mw;
ShowMappedWords ();
break;
}
attempts++;
}
}
}
CheckIfWon ();
}

We simply randomly select a char which has not been revealed yet.
11. We make a call to a method called CheckIfWon which will check if all
hints have been revealed by the timer, or by the player (or a combination of
both.)
void CheckIfWon () {
var revealed = 0;

for (var i = 0; i < mappedWords.Count; i++) {


var mw = mappedWords [i];
if (!mw.revealed) {
var cnt = 0;
foreach (var c in mw.revealedChars)
if (c == true)
cnt++;
if (cnt == mw.wordChars.Length) {
revealed++;
}
} else {
revealed++;
}
}
if (revealed == mappedWords.Count) {
foreach (var w in selectedWords) {
if (!wordsFound.Contains (w)) {
//SHOW GAME OVER
gameActive = false;
spelling.text = "Game Over";
}
}
}
}

Basically, if all words are revealed, but the player did not succeed in revealing
the words himself or herself, then we have Game Over. We do that by checking
if the target words are all present in the wordsFound list. If not, and all letters
are revealed, the player has failed, since at least one of the words was not found
in time.
12. Finally, our game timer. The time interval could also be part of the
difficulty level, if you are feeling particularly evil.
IEnumerator GameTimer () {
while (true) {
yield return new WaitForSeconds (10);
GiveRandomHint ();
}
}

13. The new round method resets everything and starts the timer.
void NewRound () {

spelling.text = "";
wordsList.text = "";

//create timer
StartCoroutine ("GameTimer");

BuildGrid ();

gameActive = true;
}

The entire game controller class looks like this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;
using System.Linq;

public class Boggling : MonoBehaviour {


public Text spelling;
public Text wordsList;

private BogglingGrid grid;


private List<string> wordsFound;
private List<string> selectedWords;
private List<MappedWord> mappedWords;
private ProcessedGrid solvedGrid;

private Vector2[] gridSizes =


{ new Vector2(3,4),
new Vector2(3,4),
new Vector2(4,5),
new Vector2(4,5),
new Vector2(4,5),
new Vector2(5,6),
new Vector2(5,6),
new Vector2(6,7)
};

private int[] numWords =


{
1,2,3,4,5,6,7,8
};

private int difficulty = 0;


private int level = 0;
private bool gameActive = false;

internal struct MappedWord {


public string word;
public char[] wordChars;
public bool[] revealedChars;
public bool revealed;
public MappedWord (string word, int difficulty,
bool revealed = false) {
this.word = word;
this.revealed = revealed;
this.wordChars = word.ToCharArray();
revealedChars = new bool[wordChars.Length];
if (difficulty < 4) {
revealedChars[0] = true;
} else {
revealedChars[Random.Range( 0, wordChars.Length)] = true;
}
}
}

void Awake () {

wordsFound = new List<string> ();


selectedWords = new List<string>();
mappedWords = new List<MappedWord> ();
grid = GetComponent<BogglingGrid> ();

BogglingGameEvents.OnGameLoaded += HandleGameLoaded;
BogglingGameEvents.OnTileSelected += HandleTileSelected;
BogglingGameEvents.OnWordSelected += HandleWordSelected;

BogglingDictionary.Instance.Initialize ();
}
void HandleGameLoaded () {

grid.COLUMNS = (int) gridSizes [difficulty].x;


grid.ROWS = (int) gridSizes [difficulty].y;

grid.COLUMNS = (int) gridSizes [difficulty].x;


grid.ROWS = (int) gridSizes [difficulty].y;

NewRound ();

level++;

if (level % 2 == 0)
difficulty++;

if (difficulty > gridSizes.Length - 1)


difficulty = gridSizes.Length - 1;
}

void NewRound () {

spelling.text = "";
wordsList.text = "";

//create timer
StartCoroutine ("GameTimer");

BuildGrid ();

gameActive = true;
}

void HandleTileSelected (string word) {


if (!gameActive)
return;
spelling.text = word.ToUpper();
}

void HandleWordSelected (string word, List<GridTile> tiles) {

if (!gameActive)
return;

//animate tiles?
foreach (var tile in tiles) {
tile.Select (false);
}

if (!wordsFound.Contains (word)) {

wordsFound.Add (word);

if (selectedWords.Contains (word)) {

var totalWords = 0;

for (var i = 0; i < mappedWords.Count; i++) {


var mappedWord = mappedWords [i];
if (mappedWord.revealed)
totalWords++;

if (!mappedWord.revealed &&
mappedWord.word.ToLower () == word.ToLower ()) {
mappedWord.revealed = true;
mappedWords [i] = mappedWord;
totalWords++;
}
}

if (totalWords == mappedWords.Count) {
StopCoroutine ("GameTimer");

gameActive = false;
spelling.text = "Well done!";

//start new round


Utils.DelayAndCall (this, 2, () => {
NewRound();
});
}

ShowMappedWords ();

} else {
if (BogglingDictionary.Instance.IsValidWord (word)) {
//show bonus?
}
}
}
}

void BuildGrid () {

grid.BuildGrid ();
grid.ShowGridChars (
BogglingDictionary.Instance.GetRandomChars (
grid.COLUMNS * grid.ROWS));

//solve grid
solvedGrid = GridUtils.SolveGrid (
ref BogglingDictionary.Instance.commonDictionaryWords,
grid.GetGridString(), grid.ROWS, grid.COLUMNS);

//select mapped words


selectedWords.Clear();
mappedWords.Clear ();

var wordList = solvedGrid.words.OrderByDescending(


x=>x.Length).ThenBy(x=>x).ToList();
var num = numWords [difficulty];
if (num > wordList.Count)
num = wordList.Count;

var i = 0;
while (i < num) {
selectedWords.Add (wordList [i]);
mappedWords.Add( new MappedWord (
wordList [i].ToUpper(), difficulty) );
i++;
}
ShowMappedWords ();
}

void ShowMappedWords () {
var sb = new StringBuilder ();
var line = 0;
foreach (var w in mappedWords) {
if (w.revealed) {
sb.AppendLine (w.word);
line++;
} else {
if (line != 0) sb.AppendLine ("");
line++;
for (var i = 0; i < w.wordChars.Length; i++) {
if (w.revealedChars [i]) {
sb.Append (w.wordChars [i]);
} else {
sb.Append ("_");
}
}
}
}
wordsList.text = sb.ToString ();
}

void GiveRandomHint () {

for (var i = 0; i < mappedWords.Count; i++) {


var mw = mappedWords [i];
if (!mw.revealed) {
var cnt = 0;
foreach (var c in mw.revealedChars)
if (c == true)
cnt++;
if (cnt == mw.wordChars.Length) {
mw.revealed = true;
mappedWords [i] = mw;
continue;
}
var attempts = 0;
while (attempts < 1000) {
var index = Random.Range (0, mw.wordChars.Length);
if (mw.revealedChars [index] == false) {
mw.revealedChars [index] = true;
mappedWords [i] = mw;
ShowMappedWords ();
break;
}
attempts++;
}
}
}

CheckIfWon ();
}

void CheckIfWon () {
var revealed = 0;

for (var i = 0; i < mappedWords.Count; i++) {


var mw = mappedWords [i];
if (!mw.revealed) {
var cnt = 0;
foreach (var c in mw.revealedChars)
if (c == true)
cnt++;
if (cnt == mw.wordChars.Length) {
revealed++;
}
} else {
revealed++;
}
}

if (revealed == mappedWords.Count) {
foreach (var w in selectedWords) {
if (!wordsFound.Contains (w)) {
//SHOW GAME OVER
gameActive = false;
spelling.text = "Game Over";
}
}
}
}

IEnumerator GameTimer () {
while (true) {
yield return new WaitForSeconds (10);
GiveRandomHint ();
}
}
}

Time to get this thing running!


revealed++;
}
}

if (revealed == mappedWords.Count) {
foreach (var w in selectedWords) {
if (!wordsFound.Contains (w)) {
//SHOW GAME OVER
gameActive = false;
spelling.text = "Game Over";
}
}
}
}

IEnumerator GameTimer () {
while (true) {
yield return new WaitForSeconds (10);
GiveRandomHint ();
}
}
}

Time to get this thing running!


The Game Scene
If you want to follow along with this simple tutorial illustrating the many parts
of the Unity Editor interface you can do so by loading the Scene_Boggling
scene inside the Unity project in this book source files.
You should be able to see something like this:

Figure 7.2 - The Initial Game Scene

We'll hook up our grid first.


The Game Scene
If you want to follow along with this simple tutorial illustrating the many parts
of the Unity Editor interface you can do so by loading the Scene_Boggling
scene inside the Unity project in this book source files.
You should be able to see something like this:

Figure 7.2 - The Initial Game Scene

We'll hook up our grid first.


The Grid
Let's start by hooking up the Grid :

1. Add a new component and search for the BogglingGrid script.

2. In the Grid Tile GO field, click the target button an search in the Assets tab
for the GridTile prefab and select it.
3. The Grid requires the InputController component in order to behave
properly. So add a new component and search for the InputController script

Figure 7.3 - The Grid Component

All that's left is the game controller.


The Grid
Let's start by hooking up the Grid :

1. Add a new component and search for the BogglingGrid script.

2. In the Grid Tile GO field, click the target button an search in the Assets tab
for the GridTile prefab and select it.
3. The Grid requires the InputController component in order to behave
properly. So add a new component and search for the InputController script

Figure 7.3 - The Grid Component

All that's left is the game controller.


The Game Script
We finish by hooking up the Game Controller.
1. On the same GameObject we've been working on, add a new component
and search for the Boggling script.

2. Click the target button next to the Spelling field and search the Scene tab for
the Spelling UI Text field.
3. Click the target button next to the Words List field and search the Scene tab
for the Words UI Text field.
Your game controller list of components should look like this:
tab for

tab

Figure 7.4 - The Game Controller


Figure 7.4 - The Game Controller
Taking It Further
I think the best thing to try it out is a decent level generator. You could build a
Thread chugging out Grids and storing unique grids with a high word yield,
and words with a good overall score, suitable to various levels of difficulty. Then
load that data into the game, and have fun.
Another thing to play around with is highlighting the revealed tiles in the grid.
We could also highlight each target word, after they are found, each with a
separate color, so the grid looks like a word search grid! Pretty.
Taking It Further
I think the best thing to try it out is a decent level generator. You could build a
Thread chugging out Grids and storing unique grids with a high word yield,
and words with a good overall score, suitable to various levels of difficulty. Then
load that data into the game, and have fun.
Another thing to play around with is highlighting the revealed tiles in the grid.
We could also highlight each target word, after they are found, each with a
separate color, so the grid looks like a word search grid! Pretty.
8. Game: Crossing

We now move on to our last game: Crossing. It is another grid based game
whose logic could be expanded for crossword and word search games.
8. Game: Crossing

We now move on to our last game: Crossing. It is another grid based game
whose logic could be expanded for crossword and word search games.
The Game

In Crossing, the player is presented with a word occupying a row in the grid.
The player is also presented with a bunch of letter tiles.

Figure 8.1 - The Game Crossing

The player must then place these tiles, in the correct order, inside the gaps
highlighted on the grid, in order to form words. These target words are presented
in columns, and each one of these words share one letter with the main mystery
word.
What sort of level design would we need then?
highlighted on the grid, in order to form words. These target words are presented
in columns, and each one of these words share one letter with the main mystery
word.
What sort of level design would we need then?
The Level Design
For the prototype, we can use randomly generated puzzles, but once again, in the
final version of the game, it might be best to use pre-built data, to make sure the
target words are fun and follow a smooth difficulty curve.
However, the puzzle generator logic we'll use in the prototype, will remain the
same when used to pre-build puzzles. The pre-built data idea simply adds an
extra step where the spit data needs to reviewed before use in the final game.
And how do we generate the puzzles?

The Puzzles
In order to generate a puzzle, we need to select a mystery word, and then find
"cross" words along the mystery word. For that, we'll use our "words that
matches pattern" algorithm.
The game could then go on, alternating directions. So first a horizontal word is
presented, and the player must find vertical words. After that, horizontal target
words are presented. And on and on until we run out of space or possible target
words matching the increasingly complex patterns.
Let's get coding.
The Level Design
For the prototype, we can use randomly generated puzzles, but once again, in the
final version of the game, it might be best to use pre-built data, to make sure the
target words are fun and follow a smooth difficulty curve.
However, the puzzle generator logic we'll use in the prototype, will remain the
same when used to pre-build puzzles. The pre-built data idea simply adds an
extra step where the spit data needs to reviewed before use in the final game.
And how do we generate the puzzles?

The Puzzles
In order to generate a puzzle, we need to select a mystery word, and then find
"cross" words along the mystery word. For that, we'll use our "words that
matches pattern" algorithm.
The game could then go on, alternating directions. So first a horizontal word is
presented, and the player must find vertical words. After that, horizontal target
words are presented. And on and on until we run out of space or possible target
words matching the increasingly complex patterns.
Let's get coding.
Game Events
This game has a really simply event system, we could easily do without it, but
since new ideas may come up that you may wish to try out in the prototype later,
it wouldn't hurt to have this in place.
For now, all we care about is to be notified the dictionary has been loaded.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingEvents {


public delegate void Event_GameLoaded ();
public static event Event_GameLoaded OnGameLoaded;

public static void GameLoaded () {


if (OnGameLoaded != null)
OnGameLoaded ();
}
}

Time to load our dictionary.


Game Events
This game has a really simply event system, we could easily do without it, but
since new ideas may come up that you may wish to try out in the prototype later,
it wouldn't hurt to have this in place.
For now, all we care about is to be notified the dictionary has been loaded.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingEvents {


public delegate void Event_GameLoaded ();
public static event Event_GameLoaded OnGameLoaded;

public static void GameLoaded () {


if (OnGameLoaded != null)
OnGameLoaded ();
}
}

Time to load our dictionary.


The Game Dictionary
The dictionary class for Crossing has no new logic we haven't seen already.
1. Create the script called CrossingDictionary and make it a singleton.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingDictionary : MonoBehaviour {


private static CrossingDictionary _instance = null;
public static CrossingDictionary Instance
{
get
{
if(_instance == null) {
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<CrossingDictionary> ();
}
return _instance;
}
}
}

2. Make three properties for the word lists:


[HideInInspector]
public Dictionary<int, List<string>> allWords;
private Dictionary<int, List<string>> wordsByLength;
private Dictionary<int, List<string>> uniqueWordsByLength;

All the lists will be sorted by length. For the type of game we are building, that
makes sense. wordsByLength and uniqueWordsByLength have the same
words listed inside them, these are the words from the first four frequency lists.
The difference between the two is that with uniqueWordsByLength , when I get
a word from this list, I delete the word so it can never be picked again.
3. Write the public initializer:
public void Initialize () {
StartCoroutine ("LoadWordData");
}

4. Then the load coroutine:


IEnumerator LoadWordData() {
string dictionaryPath = System.IO.Path.Combine (
Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new Dictionary<int, List<string>> ();
wordsByLength = new Dictionary<int, List<string>> ();
uniqueWordsByLength = new Dictionary<int, List<string>> ();

var index = 0;

foreach (var w in words) {

if (string.IsNullOrEmpty(w) || w.Length < 3)


continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
} else {

if (!allWords.ContainsKey (word.Length))
allWords.Add (word.Length, new List<string> ());

allWords[word.Length].Add (word);

if (index < 4) {
if (!wordsByLength.ContainsKey (word.Length))
wordsByLength.Add (word.Length,
new List<string> ());
wordsByLength [word.Length].Add (word);
}
}
}

//shuffle lists
for (var i = 3; i < 10; i++) {
, when I get if (wordsByLength.ContainsKey (i)) {
var list = wordsByLength [i];
list = Utils.Scramble<string> (list);
wordsByLength [i] = list;
uniqueWordsByLength.Add (i, list);
}
}
CrossingEvents.GameLoaded ();

Again, nothing new here. I chose to load the first four frequency lists, so the 1k,
2k, 10k and 20k lists, into the sorted by length word lists. All words are also
sorted by length and stored on a separate list.
I finish by shuffling the most common word lists because this will save time
later when randomly selecting a word.
5. Add our good old IsValidWord function:
public bool IsValidWord (string word) {
return allWords[word.Length].Contains(word);
}

6. A "get random word" function we can call to get a word with a specific
length:
public string RandomWord (int len, bool all = false) {

if (all) {
var alllist = allWords [len];
return alllist [Random.Range (0, alllist.Count)];
}
var list = wordsByLength [len];
return list [Random.Range (0, list.Count)];
}

And we'll also have a RandomUniqueWord method:

public string RandomUniqueWord (int len) {


if (uniqueWordsByLength.ContainsKey (len)) {
var list = uniqueWordsByLength [len];
var word = list [0];
list.RemoveAt (0);
return word;
}
return RandomWord (len, true);
}

Once again, the difference here is that the word is popped out of the list so it can
never be picked again, hence the "unique" aspect of it.
7. And finally, our "matches pattern" function:
public List<string> MatchesAPattern (char[] chars){

var result = new List<string> ();

var list = wordsByLength [chars.Length];

foreach (var word in list) {


var match = true;
for (var i = 0; i < word.Length; i++) {
if (chars [i] != '-' && word [i] != chars [i]) {
match = false;
break;
}
}
if (match)
result.Add (word);
}

return result;
}

The entire game dictionary class looks like this:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingDictionary : MonoBehaviour {

private static CrossingDictionary _instance = null;


public static CrossingDictionary Instance
{
get
{
if(_instance == null)
{
GameObject instanceGo = new GameObject("GameDictionary");
_instance = instanceGo.AddComponent<CrossingDictionary> ();
}

return _instance;
}
}

[HideInInspector]
public Dictionary<int, List<string>> allWords;

private Dictionary<int, List<string>> wordsByLength;

private Dictionary<int, List<string>> uniqueWordsByLength;

public void Initialize () {


StartCoroutine ("LoadWordData");
}

IEnumerator LoadWordData() {

string dictionaryPath = System.IO.Path.Combine (


Application.streamingAssetsPath, "wordsByFrequency.txt");

string result = null;

if (dictionaryPath.Contains ("://")) {
WWW www = new WWW (dictionaryPath);
yield return www;
result = www.text;
} else
result = System.IO.File.ReadAllText (dictionaryPath);

var words = result.Split ('\n');

//collect words
allWords = new Dictionary<int, List<string>> ();
wordsByLength = new Dictionary<int, List<string>> ();
uniqueWordsByLength = new Dictionary<int, List<string>> ();

var index = 0;

foreach (var w in words) {

if (string.IsNullOrEmpty(w) || w.Length < 3)


continue;

var word = w.TrimEnd ();

if (word.IndexOf ('#') != -1) {


index++;
} else {

if (!allWords.ContainsKey (word.Length))
allWords.Add (word.Length, new List<string> ());

allWords[word.Length].Add (word);

if (index < 4) {
if (!wordsByLength.ContainsKey (word.Length))
wordsByLength.Add (word.Length, new List<string> ());
wordsByLength [word.Length].Add (word);
}
}
}

//shuffle lists
for (var i = 3; i < 10; i++) {
if (wordsByLength.ContainsKey (i)) {
var list = wordsByLength [i];
list = Utils.Scramble<string> (list);
wordsByLength [i] = list;
uniqueWordsByLength.Add (i, list);
}
}

CrossingEvents.GameLoaded ();

public bool IsValidWord (string word) {


return allWords[word.Length].Contains(word);
}

public string RandomWord (int len, bool all = false) {

if (all) {
var alllist = allWords [len];
return alllist [Random.Range (0, alllist.Count)];
}
var list = wordsByLength [len];
return list [Random.Range (0, list.Count)];
}

public string RandomUniqueWord (int len) {

if (uniqueWordsByLength.ContainsKey (len)) {
var list = uniqueWordsByLength [len];
var word = list [0];
list.RemoveAt (0);
return word;
}
return RandomWord (len, true);
}

public List<string> MatchesAPattern (char[] chars){

var result = new List<string> ();

var list = wordsByLength [chars.Length];

foreach (var word in list) {


var match = true;
for (var i = 0; i < word.Length; i++) {
if (chars [i] != '-' && word [i] != chars [i]) {
match = false;
break;
}
}
if (match)
result.Add (word);
}

return result;
}
}

We have a new tile for this game, let's take a look at it next.
}
return RandomWord (len, true);
}

public List<string> MatchesAPattern (char[] chars){

var result = new List<string> ();

var list = wordsByLength [chars.Length];

foreach (var word in list) {


var match = true;
for (var i = 0; i < word.Length; i++) {
if (chars [i] != '-' && word [i] != chars [i]) {
match = false;
break;
}
}
if (match)
result.Add (word);
}

return result;
}
}

We have a new tile for this game, let's take a look at it next.
The Game Tile
Crossing will need to extend our GridTile class to add new functionality and
properties. We need to add logic to handle the different states of a tile in this
game. It can appear as a gap, to be "filled" with a letter. It can appear as a letter
the player moved around, or a letter which was given as a clue by the game
logic. Each needs to be color coded and handled correctly, which is why we need
to extend our previous grid tile class. Plus, I decided to make the tile a circle this
time. For kicks.
1. Create a script called CrossingTile and make sure it extends GridTile :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingTile : GridTile {


}

2. We'll add a few enums, to handle the states and the grids. We have two grids
in this game, one where the letters are placed, and one where the letters are
picked from:
public enum GRID_TYPE{
WORD_GRID,
PANEL_GRID
}

public enum TILE_TYPE {


EMPTY,
GAP,
PLACED,
CLUE,
TEMPORARY,
BUTTON
}

The grid types then are word and panel, based on whether it's the grid we make
the words on (word) or the grid we pick the letters from (panel).
The tile types are:
• Empty, for invisible tiles.
• Gap, for target tiles which must be filled with letters.
• Placed, for tiles which were gaps, but now have letters.
• Clue, for tiles which already contain letters, and cannot be changed/moved.
• Temporary, this is a special state used to handle previously placed tiles
which are being moved to a new position.
• Button, is the state of a tile inside the panel grid.

why we need 3. Next, we add our new properties:


e a circle this public SpriteRenderer outline;
[HideInInspector]
public GRID_TYPE gridType;
[HideInInspector]
: public TILE_TYPE tileType;
public Color wrongColor = Color.red;
public Color placedColor = Color.white;
public Color gapColor = Color.blue;
private Vector2 localPosition;

In order, these are:


• outline : A reference to an outline sprite we'll use to help distinguish gaps
ave two grids from empty tiles.

• gridType : The grid type the tile belongs to.

• tileType : The tile's type.

• wrongColor , placedColor and gapColor : Colors for wrong, placed and


gap. Wrong means the placed tile does not spell a word.
• localPosition : The original position of the tile in the grid, in case we
need to return the tile to it.
4. On Start we store the position of the tile, in case we need to return a moved
tile to its original position:
void Start () {
localPosition = transform.localPosition;
}

5. We add a helper method to check if the tile can be moved by the player:
public bool IsMovable () {
if (tileType == TILE_TYPE.PLACED)
return true;
if (tileType == TILE_TYPE.BUTTON)
return true;

return false;
}

Basically, only buttons and placed tiles can be moved. Button means the tile is in
the panel grid (we could check for grid type here instead and get rid of the button
tile type, it's up to you).
Nothing else can be moved.
6. A function to reset the position:
public void ResetPosition () {
transform.localPosition = localPosition;
}

7. And finally functions for each state:


If the tile is Temporary, this means a previously placed tile has been removed,
and a temporary tile has been created to be used in the transition from one grid
position to another (this will become clearer when we handle the game
controller):
public void ShowTemporary() {
gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
g, placed and tileType = TILE_TYPE.TEMPORARY;
}

Then another for gaps, which show no letters, only the outline:
public void ShowGap() {
gameObject.SetActive (true);
outline.gameObject.SetActive (true);
tileBg.SetActive (false);
foreach (var c in charsGO)
c.SetActive (false);
outline.color = Color.blue;
tileType = TILE_TYPE.GAP;
}

Another for clues, which are fixed and cannot be changed or moved:
public void ShowFixed () {
gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (placedColor, Color.black);
tileType = TILE_TYPE.CLUE;
}

Another for placed tiles:


s the tile is in
of the button public void ShowPlaced () {
gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.PLACED;
}

One for the tiles in the panel grid:


public void ShowButton () {
gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.BUTTON;
}

And one for when the tile fails to spell a word:


public void ShowWrong () {

if (tileType != TILE_TYPE.PLACED)
return;

gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (wrongColor, Color.white);
}

Only a placed tile can be shown as wrong.


Here is the complete CrossingTile class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingTile : GridTile {

public enum GRID_TYPE{


WORD_GRID,
PANEL_GRID
}

public enum TILE_TYPE {


EMPTY,
GAP,
PLACED,
CLUE,
TEMPORARY,
BUTTON
}

public SpriteRenderer outline;

[HideInInspector]
public GRID_TYPE gridType;

[HideInInspector]
public TILE_TYPE tileType;

public Color wrongColor = Color.red;


public Color placedColor = Color.white;
public Color gapColor = Color.blue;
private Vector2 localPosition;

void Start () {
localPosition = transform.localPosition;
}

public bool IsMovable () {

if (tileType == TILE_TYPE.PLACED)
return true;
if (tileType == TILE_TYPE.BUTTON)
return true;

return false;
}

public void ShowTemporary() {


gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.TEMPORARY;
}

public void ShowGap() {


gameObject.SetActive (true);
outline.gameObject.SetActive (true);
tileBg.SetActive (false);
foreach (var c in charsGO)
c.SetActive (false);
outline.color = Color.blue;
tileType = TILE_TYPE.GAP;
}

public void ShowFixed () {


gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (placedColor, Color.black);
tileType = TILE_TYPE.CLUE;
}

public void ShowPlaced () {


gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.PLACED;
}

public void ShowButton () {


gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.BUTTON;
}

public void ShowWrong () {

if (tileType != TILE_TYPE.PLACED)
return;

gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (wrongColor, Color.white);
}

public void ResetPosition () {


transform.localPosition = localPosition;
}
}

We can now use the new tiles in our grids.


outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.PLACED;
}

public void ShowButton () {


gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (gapColor, Color.white);
tileType = TILE_TYPE.BUTTON;
}

public void ShowWrong () {

if (tileType != TILE_TYPE.PLACED)
return;

gameObject.SetActive (true);
outline.gameObject.SetActive (false);
tileBg.SetActive (true);
SetColor (wrongColor, Color.white);
}

public void ResetPosition () {


transform.localPosition = localPosition;
}
}

We can now use the new tiles in our grids.


The Game Grids
As I mentioned earlier, we'll use two grids in this game, one where the tiles are
picked from, and one where they are placed to spell words.
The class however, is very similar to our previous grid. Only this time, the grid
does not respond to the player's input, and each grid stores its own value for
GRID_TILE_SIZE as they are each scaled differently.

We have a few new methods however, and a few other minor changes.
1. Create the CrossingGrid class, this time it does not implement our input
handler interface:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingGrid : MonoBehaviour {


}

2. The properties are:


public int ROWS = 4;
public int COLUMNS = 3;
public GameObject gridTileGO;
public float GRID_TILE_SIZE;
public float offsetY;
public CrossingTile.GRID_TYPE gridType;
private List<CrossingTile> tiles;
private List<List<CrossingTile>> gridTiles;

Nothing new here from our old grid logic, except for a value for a Y offset,
offsetY , since each grid needs to be shifted on screen after scaling and not
simply centered. Each will have it's own Y position.
3. Our TileCloseToPoint method is changed slightly. I add a flag now
which enforces that only tiles with the touched property set to true will be
returned from the method:
public CrossingTile TileCloseToPoint
(Vector2 point, bool mustTouch = true) {
int c = Mathf.FloorToInt (
(point.x - gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);
if (c < 0)
return null;
if (c >= COLUMNS)
return null;

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - point.y )/GRID_TILE_SIZE);

if (r < 0) return null;

if (r >= ROWS) return null;

if (gridTiles.Count <= r)
return null;

if (gridTiles[r].Count <= c)
return null;

if (!gridTiles [r] [c].touched && mustTouch)


return null;

return gridTiles[r][c];

You can see it in use in the conditional:


if (!gridTiles [r] [c].touched && mustTouch)
return null;

I did this because I found that in this particular game, the panel grid did not need
to be so strict with user interaction.
4. Then we have a new method to display letters on a specific row:
public void ShowRowChars (List<char> chars) {
var i = 0;
foreach (var t in tiles) {
t.SetTileData (chars [i]);
t.ShowPlaced ();
i++;
if (i == chars.Count)
break;
}
}

5. A new method to get all tiles in a row, where I can place a word of n letters,
in such a way that the word will appear centered on the grid row:
public List<CrossingTile> GetRowTiles (int len, int row) {

var result = new List<CrossingTile> ();


var diff = COLUMNS - len;
var startIndex = Mathf.FloorToInt (diff / 2);
while (result.Count < len) {
result.Add (gridTiles [row] [startIndex]);
startIndex++;
}

return result;
}

The diff variable holds the difference between row length and word length, so
I can arrive at a column index offset so the word is centered horizontally on the
grid.
6. A similar method for tiles along a column:
public List<CrossingTile> GetColumnTiles ( int row, int column) {
var result = new List<CrossingTile> ();
var startIndex = row - Random.Range(0, 4);
var bottomHalf = Random.Range (0, 4);
while (true) {
if (startIndex >= ROWS ||
(startIndex > row &&
startIndex - row >= bottomHalf && result.Count >= 3)) {
break;
}
var tile = gridTiles [startIndex] [column];
result.Add (tile);
startIndex++;
}
return result;
}
did not need
The difference here is that the tiles must contain a tile from the mystery word we
present to the player. This is what the row integer is for. This method will
generate a word of random length (bigger than 2), which contains the tile at row
X. This is where we'll try to place the "cross" words, and if successful we return
the tiles for each word.
7. The remaining logic, the build and scale grid methods, is nearly the same as
we had before.
Here's the complete class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrossingGrid : MonoBehaviour {

public int ROWS = 4;

public int COLUMNS = 3;


public GameObject gridTileGO;

public float GRID_TILE_SIZE;

public float offsetY;

public CrossingTile.GRID_TYPE gridType;

private List<CrossingTile> tiles;

ord length, so private List<List<CrossingTile>> gridTiles;

public CrossingTile TileCloseToPoint


(Vector2 point, bool mustTouch = true) {
int c = Mathf.FloorToInt (
(point.x - gridTiles[0][0].transform.position.x +
( GRID_TILE_SIZE * 0.5f )) / GRID_TILE_SIZE);

if (c < 0)
return null;
if (c >= COLUMNS)
return null;

int r = Mathf.FloorToInt (
(gridTiles[0][0].transform.position.y +
( GRID_TILE_SIZE * 0.5f ) - point.y )/GRID_TILE_SIZE);

if (r < 0) return null;

if (r >= ROWS) return null;

if (gridTiles.Count <= r)
return null;

if (gridTiles[r].Count <= c)
return null;
tery word we if (!gridTiles [r] [c].touched && mustTouch)
return null;

return gridTiles[r][c];

public void ClearTiles () {


foreach (var t in tiles) {
t.gameObject.SetActive (false);
}
}

public void ShowRowChars (List<char> chars) {


var i = 0;
foreach (var t in tiles) {
t.SetTileData (chars [i]);
t.ShowPlaced ();
i++;
if (i == chars.Count)
break;
}
}

public List<CrossingTile> GetRowTiles (int len, int row) {


var result = new List<CrossingTile> ();
var diff = COLUMNS - len;
var startIndex = Mathf.FloorToInt (diff / 2);

while (result.Count < len) {


result.Add (gridTiles [row] [startIndex]);
startIndex++;
}

return result;
}

public List<CrossingTile> GetColumnTiles ( int row, int column) {


var result = new List<CrossingTile> ();
var startIndex = row - Random.Range(0, 4);
var bottomHalf = Random.Range (0, 4);
while (true) {
if (startIndex >= ROWS ||
(startIndex > row && startIndex -
row >= bottomHalf && result.Count >= 3)) {
break;
}
var tile = gridTiles [startIndex] [column];
result.Add (tile);
startIndex++;
}
return result;
}

public void BuildGrid ()


{
if (tiles != null && tiles.Count != 0) {
foreach (var t in tiles)
Destroy (t.gameObject);

transform.localScale = Vector2.one;
transform.localPosition = Vector2.zero;
}

//a one dimensional list of tiles we can shuffle


tiles = new List<CrossingTile> ();
//the two dimensional grid
gridTiles = new List<List<CrossingTile>> ();

for (int row = 0; row < ROWS; row++) {

var rowsTiles = new List<CrossingTile>();

for (int column = 0; column < COLUMNS; column++) {

var item = Instantiate (gridTileGO) as GameObject;

var tile = item.GetComponent<CrossingTile>();


tile.SetTilePosition(row, column);
tile.transform.parent = gameObject.transform;
tile.gridType = gridType;

tiles.Add (tile);
tile.gameObject.SetActive (false);

rowsTiles.Add (tile);
}
gridTiles.Add(rowsTiles);
}

ScaleGrid ( Mathf.Abs (
gridTiles [0] [0].transform.localPosition.y -
gridTiles [1] [0].transform.localPosition.y));

private void ScaleGrid ( float tileSize) {

GRID_TILE_SIZE = tileSize;

var stageWidth = 5.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}

GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 (
(gridWidth * scale) * -0.5f ,
(3.5f - 0.5f * (gridHeight * scale)) + offsetY);
}
}

We put it all together and code the game logic next.


}

ScaleGrid ( Mathf.Abs (
gridTiles [0] [0].transform.localPosition.y -
gridTiles [1] [0].transform.localPosition.y));

private void ScaleGrid ( float tileSize) {

GRID_TILE_SIZE = tileSize;

var stageWidth = 5.0f;


var stageHeight = 4.8f;

var gridWidth = (COLUMNS - 1) * GRID_TILE_SIZE;


var gridHeight = (ROWS - 1) * GRID_TILE_SIZE;

var scale = 1.0f;

if (gridWidth > stageWidth || gridHeight > stageHeight) {

if (gridWidth >= gridHeight) {


scale = stageWidth / gridWidth;
} else {
scale = stageHeight / gridHeight;
}
transform.localScale = new Vector2(scale, scale);
}

GRID_TILE_SIZE *= scale;
transform.localPosition = new Vector2 (
(gridWidth * scale) * -0.5f ,
(3.5f - 0.5f * (gridHeight * scale)) + offsetY);
}
}

We put it all together and code the game logic next.


The Game Controller
In Crossing we transfer the input handling logic to the game controller. It will
handle tiles selected in both grids, generate the puzzles, and check for solutions.
Let's get crackin'.
1. Create a script called Crossing and make it implement the input handler
interface. We'll add the handlers soon.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Crossing : MonoBehaviour, IInputHandler {


}

2. Let's add the properties:


public CrossingGrid wordGrid;
public CrossingGrid panelGrid;
public Text wellDone;
private Vector3 touchPosition;
private CrossingTile selectedTile;
private List<PuzzleWord> puzzle;
private PuzzleWord mysteryWord;
private int[] wordLen = new int[] { 4,4,4,5,5,5,5,5,5,
5,6,6,6,6,6,6,
6,6,6,6,6,6,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,8 };
private int difficultyIndex = 0;
private CrossingTile tempTileOrigin;

These are:
• wordGrid : A public reference to the word grid.

• panelGrid : A public reference to the panel grid.

•. wellDone : A public reference to a UI text field which will display a brief


message once a puzzle is solved.
• touchPosition : A variable holding the current touch position.

• selectedTile : The currently selected tile.


• puzzle : The current puzzle, as a list of words the player needs to find. This
is a struct called PuzzleWord which we'll add soon.

• mysteryWord : The current target word as an instance of PuzzleWord .

• wordLen : An array holding the word lengths we'll use for each puzzle. This
is the length of the main mystery word selected for each puzzle. The longer the
word, the more words the player will need to find. This can be easily edited to
give us a smooth difficulty curve.
• difficultyIndex : The index to be used in conjunction with the word
length array.
• tempTileOrigin : A reference to the original tile our current temporary tile
was cloned from. A temporary tile is created whenever the player decides to
move a previously placed tile in the word grid. We need its origin in case we
need to "move back" the selected tile to its original position. We do that by
destroying the cloned temp tile, and making the original one visible again. So
even though to the player it will look like a tile can be moved from the word
grid, in reality, we hide the selected tile, clone it as a temporary tile the player
can drag around, and when the temp tile is dropped, we destroy it and make the
target grid tile closest to the dropped temp tile visible and show the temp tile's
information. A simple swap.
3. The puzzle struct looks like this:
private struct PuzzleWord {
public List<CrossingTile> wordTiles;
public string word;
public PuzzleWord (string word, List<CrossingTile> tiles) {
this.word = word;
this.wordTiles = tiles;
}

public bool IsCompleted () {


foreach (var t in wordTiles) {
if (!t.gameObject.activeSelf)
return false;
if (t.tileType == CrossingTile.TILE_TYPE.GAP)
return false;
}
return true;
}

public bool IsAWord () {


if (IsCompleted ()) {
char[] c = new char[wordTiles.Count];
var i = 0;
foreach (var t in wordTiles) {
to find. This c [i] = t.TypeChar;
i++;
}
var newWord = new string (c);
return CrossingDictionary.Instance.IsValidWord (newWord);
}
return false;
}

public void ShowErrors () {


foreach (var t in wordTiles) {
t.ShowWrong ();
}
}
}

• wordTiles : a list of tiles where the word is located in the grid.

• word : the target word itself.

• Then we add a few helper methods, to check if a word is completed, by


checking if all tiles are active. If so, we check if the word is valid, if not, we
display an error, by calling ShowWrong on every placed tile.

4. On Start, we initialize the dictionary and our grids:


void Start () {
CrossingEvents.OnGameLoaded += CrossingEvents_OnGameLoaded;
CrossingDictionary.Instance.Initialize ();
}

void CrossingEvents_OnGameLoaded (){


NewPuzzle ();
}

void NewPuzzle () {
wordGrid.BuildGrid ();
panelGrid.BuildGrid ();
targetWords = new HashSet<string> ();
puzzle = new List<PuzzleWord> ();

SelectMysteryWord ();
difficultyIndex++;
}

5. The SelectMysteryWord method will start building our puzzle by


selecting the main word displayed to the player: the horizontal word.
And that method looks like this:
void SelectMysteryWord () {
var len = difficultyIndex >= wordLen.Length ?
wordLen[wordLen.Length -1] : wordLen [difficultyIndex];
string word = CrossingDictionary.Instance.RandomUniqueWord (len);

Debug.Log ("MYSTERY: " + word);

mysteryWord = new PuzzleWord (word,


wordGrid.GetRowTiles(word.Length, 3) );

var chars = mysteryWord.word.ToCharArray ();

for (var n = 0; n < mysteryWord.wordTiles.Count; n++) {


mysteryWord.wordTiles [n].SetTileData (chars [n]);
mysteryWord.wordTiles [n].ShowFixed ();
mysteryWord.wordTiles [n].SetColor (Color.white, Color.black);
}
SelectCrossWords ();
}

We get the word length from our wordLen array using our current difficulty
index.
We then use that length to get an unique word from our dictionary. We use that
word to create a new PuzzleWord object, and we get tiles from it through our
GetRowTiles in our Grid class.

These tiles are all revealed to the player and set as fixed. But you could add logic
that would make some of the tiles appear as gaps, in later levels of this game, so
the player must fill out missing parts of the mystery word as well.
We end by calling another method to generate the cross words.
6. The SelectCrossWords method looks like this:
void SelectCrossWords () {

var shuffledTiles = new List<CrossingTile> ();


shuffledTiles.AddRange (mysteryWord.wordTiles);
shuffledTiles = Utils.Scramble<CrossingTile> (shuffledTiles);

var num = shuffledTiles.Count - 2;


puzzle.Clear ();

var i = 0;
while (puzzle.Count < num) {

var tile = shuffledTiles [i];


var tileChar = tile.TypeChar;
var tiles = wordGrid.GetColumnTiles (3, tile.column);
var pattern = GetVerticalCrossPattern (tiles);
var words =
CrossingDictionary.Instance.MatchesAPattern (pattern);

if (words != null && words.Count != 0) {


var word = words [Random.Range (0, words.Count)];
var puzzleWord = new PuzzleWord (word, tiles);
puzzle.Add(puzzleWord);
}
i++;
if (i == shuffledTiles.Count)
i = 0;
}

var buttonChars = new List<char> ();

foreach (var p in puzzle) {


var chars = p.word.ToCharArray ();
var hints = Random.Range(0, 2);
Debug.Log ("CROSS: "+ p.word);
for (var n = 0; n < p.wordTiles.Count; n++ ) {
if (!p.wordTiles [n].gameObject.activeSelf) {

if (hints > 0 && Random.Range (0, 10) > 4) {


p.wordTiles [n].SetTileData (chars [n]);
p.wordTiles [n].ShowFixed ();
hints--;
} else {
buttonChars.Add (chars [n]);
p.wordTiles [n].ShowGap ();
}

}
}
}

buttonChars = Utils.Scramble<char> (buttonChars);


ould add logic }
panelGrid.ShowRowChars (buttonChars);

The purpose of this method is to select all words the player must guess, we call
these the "crossing" words, because they cross the initial mystery word. But we
don't want to force the player to guess the entire words, not at first, so we need to
turn only some of the tiles into gaps the player must fill. In order to do that, we
collect all the tiles from all the crossing words we've selected, we shuffle them,
and then randomly decide if each tile is a gap or a clue (fixed).
So let's cover this method in detail, since it's at the core of this game:
var shuffledTiles = new List<CrossingTile> ();
shuffledTiles.AddRange (mysteryWord.wordTiles);
shuffledTiles = Utils.Scramble<CrossingTile> (shuffledTiles);

var num = shuffledTiles.Count - 2;


puzzle.Clear ();

Here, I shuffle the tiles used in the mystery word.


The number of cross words generated will always be the length of the mystery
word minus two. You can change that if you wish.
We clear the current puzzle data and proceed to build a new one.
var i = 0;
while (puzzle.Count < num) {

var tile = shuffledTiles [i];


var tileChar = tile.TypeChar;
var tiles = wordGrid.GetColumnTiles (3, tile.column);
var pattern = GetVerticalCrossPattern (tiles);
var words = CrossingDictionary.Instance.MatchesAPattern (pattern);
if (words != null && words.Count != 0) {
var word = words [Random.Range (0, words.Count)];
var puzzleWord = new PuzzleWord (word, tiles);
puzzle.Add(puzzleWord);
}
i++;
if (i == shuffledTiles.Count)
i = 0;
}

We'll build cross words until the number of puzzle words generated equals our
target num , which we decided is the length of the mystery word minus two.

This is the reason we shuffled the mystery word tiles, so the cross words will be
spread out along the mystery word randomly.
We check the char value of the mystery word tile we selected to be part of a
crossing word. We get tiles for the crossing word using our grid helper method
GetColumnTiles . The value for row in this method will always be 3, which is

so we need to the middle of our grid. If you want the grid size to change throughout the game,
then you need to calculate the middle row here.
I make a call to a helper method called GetVerticalCrossPattern :
char[] GetVerticalCrossPattern (List<CrossingTile> tiles) {
var result = new char[tiles.Count];
for (var i = 0; i < tiles.Count; i++) {
if (!tiles[i].gameObject.activeSelf)
result [i] = '-';
else
result [i] = tiles[i].TypeChar;
}
return result;
}

This grabs the tiles for the word and generates a pattern for our dictionary's
"matches pattern" method. Basically this is a char array where empty indexes on
a word are shown as dashes.
With that pattern in hand, I call the dictionary's MatchesAPattern method to
retrieve words that match that pattern. If we have a word, we create a new
PuzzleWord with it and move on to the next crossing word until our loop is
finished. Next in our method we have:
var buttonChars = new List<char> ();
foreach (var p in puzzle) {
var chars = p.word.ToCharArray ();
var hints = Random.Range(0, 2);
Debug.Log ("CROSS: "+ p.word);
for (var n = 0; n < p.wordTiles.Count; n++ ) {
if (!p.wordTiles [n].gameObject.activeSelf) {
if (hints > 0 && Random.Range (0, 10) > 4) {
p.wordTiles [n].SetTileData (chars [n]);
p.wordTiles [n].ShowFixed ();
hints--;
} else {
buttonChars.Add (chars [n]);
p.wordTiles [n].ShowGap ();
}

}
}
}

This is the logic that will decide which letter in the crossing words becomes a
gap, or a clue. If a gap, we need to turn it into a button in our panel grid, which is
why I collect those chars inside a list buttonChars which I'll use to populate
the panel grid next.
If not a gap, but a clue, then I set it to be fixed in the word grid so the player
cannot move that tile. The logic determining whether a letter is a clue or a gap is
up to you. I used a very simple logic in the prototype, where a maximum of two
clues can be shown per crossing word. Then I randomly select whether or not to
turn a tile into a clue.
With the letters for the panel grid ready, I proceed to fill that grid:
buttonChars = Utils.Scramble<char> (buttonChars);
panelGrid.ShowRowChars (buttonChars);

7. We're ready to start handling input. First, touch down:


public void HandleTouchDown (Vector2 touch) {

ClearSelection ();

touchPosition = Camera.main.ScreenToWorldPoint (touch);


touchPosition.z = 0;

//check panel grid


var tile = panelGrid.TileCloseToPoint (touchPosition);

if (tile == null || !tile.gameObject.activeSelf ) {

//check word grid


tile = wordGrid.TileCloseToPoint (touchPosition);
if (tile != null && tile.gameObject.activeSelf &&
tile.IsMovable()) {

//pick tile from panel


var tempTile = Instantiate(panelGrid.gridTileGO) as GameObject;
tempTileOrigin = tile;

selectedTile = tempTile.GetComponent<CrossingTile> ();


selectedTile.transform.localScale = panelGrid.transform.localScale;
selectedTile.transform.parent = wordGrid.transform;
selectedTile.transform.localPosition = tile.transform.localPosition;
selectedTile.gridType = CrossingTile.GRID_TYPE.WORD_GRID;
selectedTile.SetTileData (tile.TypeChar);
selectedTile.ShowTemporary ();

tile.ShowGap ();
}

} else {
selectedTile = tile;
}

if (selectedTile != null) selectedTile.Select (true);


}
grid, which is
Remember that we're handling input on both grids. So on touch down, I clear
any previously selected tiles, and check first if the touch is lying somewhere on
the panel grid.
If not, I check the word grid.
If we have a word grid tile, and it's movable, it means the player is trying to
move a previously placed tile. We need in this case to instantiate a temp tile.
The temp tile will be loaded with the same information of the originally selected
word grid tile and be made into the currently selected tile. The selectedTile is
the tile we drag around the screen.
We turn the originally selected word grid tile into a gap again. As far as the user
is concerned it will look like the selected tile was "popped out" of its place on
the grid. But in fact we only ever swap information with a temp tile.
One important point here is that we store the original tile in our
tempTileOrigin property, in case we need to return the tile to its original
position after failing to place it somewhere else, as we'll see when we cover the
touch up logic.
8. Then we have Touch Move:
public void HandleTouchMove (Vector2 touch) {
touchPosition = Camera.main.ScreenToWorldPoint (touch);
touchPosition.z = 0;
}

This will simply update the stored value of the player's touch position, so we can
appear to be dragging the currently selectedTile, if any, in our Update method.
9. Next, comes Touch Up:
public void HandleTouchUp (Vector2 touch) {
if (selectedTile == null) {
return;
}

if (selectedTile.tileType ==
CrossingTile.TILE_TYPE.TEMPORARY) {

var target = wordGrid.TileCloseToPoint(touchPosition, false);


if (target != null && target.gameObject.activeSelf &&
target.tileType == CrossingTile.TILE_TYPE.GAP) {
target.SetTileData (selectedTile.TypeChar);
target.ShowPlaced ();
} else {
target = panelGrid.TileCloseToPoint(touchPosition, false);
if (target != null && !target.gameObject.activeSelf) {

target.SetTileData (selectedTile.TypeChar);
target.ShowButton ();
} else {
tempTileOrigin.SetTileData (selectedTile.TypeChar);
tempTileOrigin.ShowPlaced ();
}
}
Destroy (selectedTile.gameObject);

} else if (selectedTile.gridType ==
CrossingTile.GRID_TYPE.PANEL_GRID) {
var target = wordGrid.TileCloseToPoint(touchPosition, false);
if (target != null && target.gameObject.activeSelf &&
target.tileType == CrossingTile.TILE_TYPE.GAP) {

target.SetTileData (selectedTile.TypeChar);
target.ShowPlaced ();
selectedTile.ResetPosition ();
selectedTile.gameObject.SetActive (false);
} else {
selectedTile.ResetPosition ();
selectedTile.ShowPlaced ();
}

} else {
var target =
wordGrid.TileCloseToPoint(touchPosition, false);
if (target != null && target.gameObject.activeSelf &&
target.tileType == CrossingTile.TILE_TYPE.GAP) {
target.SetTileData (selectedTile.TypeChar);
selectedTile.ShowGap();
} else {
selectedTile.ResetPosition ();
}

CheckSolution ();
ClearSelection ();
on, so we can }

We handle three cases. If the currently selected tile being released is a temp tile,
we look for a new home for it. First we look in the word grid, and here we skip
the need to precisely select a grid tile, passing false for mustTouch . So the
player won't have too much difficulty placing the tile inside a desired gap. If we
have a target, and it's gap, we destroy the temp tile, but before we do that we
load its char data into the gap we selected to be our target, and we show that gap
as a letter now, by calling ShowPlaced .

If we fail to find a target in the word grid, we look for it in the panel grid. If we
have a target we load the information from the temp tile into it, and show it as a
button. If we don't find a target, we once again show the original tile, stored in
tempTileOrigin . In any event, we destroy the temp tile.

Our second case involves the releasing of a panel grid tile. This means the player
dragged a tile from the panel grid and is probably attempting to drop it in the
word grid. We look for a target there, if we find one, we swap the data like we
did before. If we don't find one, we return it to the panel grid.
The final case simply checks if we have a target for our selected tile and repeats
the basic logic of swapping values.
We finish by checking if there is a solution before clearing the current selections.
private void ClearSelection () {

if (selectedTile != null) {
selectedTile.selected = false;
selectedTile = null;
}
}

10. The method that checks if we have a solution looks like this:
private void CheckSolution () {
ClearErrors ();

bool allCompleted = true;


bool allWords = true;
foreach (var p in puzzle) {
if (!p.IsCompleted ()) {
allCompleted = false;
} else {
if (!p.IsAWord ()) {
allWords = false;
p.ShowErrors ();
}
}
}

if (allCompleted) {
if (allWords) {
//SUCCESS
wellDone.gameObject.SetActive(true);
Utils.DelayAndCall (this, 2, () => {
wellDone.gameObject.SetActive(false);
NewPuzzle();
});
}
}
}

We make a call to a helper method that clears any errors shown in the grid:
private void ClearErrors () {
foreach (var p in puzzle) {
foreach (var t in p.wordTiles) {
if (t.tileType == CrossingTile.TILE_TYPE.PLACED) {
ans the player t.ShowPlaced ();
}
}
}
}

Then we check if all the PuzzleWord objects are completed, meaning all the
word tiles are displaying letters (no gaps). If an object is completed we check if
it's a word. If not, we show it as an error.
ent selections.
If at the end of the check, all words are completed and they are all valid words,
we display our well done message briefly and load a new puzzle.
11. We next add our Update method which ensures that the selected tile, if
any, will move around the screen following the player's current touch position:
void Update () {
if (selectedTile != null) {
selectedTile.transform.position = touchPosition;
}
}
12. And we finish with a method to allow the player to refresh the puzzle,
essentially restarting it:
public void Refresh () {
var buttonChars = new List<char> ();
foreach (var p in puzzle) {
var chars = p.word.ToCharArray ();
for (var n = 0; n < p.wordTiles.Count; n++ ) {
var tile = p.wordTiles [n];
if (tile.tileType == CrossingTile.TILE_TYPE.GAP ||
tile.tileType == CrossingTile.TILE_TYPE.PLACED) {
buttonChars.Add (chars [n]);
tile.ShowGap ();
} else if (tile.tileType == CrossingTile.TILE_TYPE.CLUE) {
tile.SetTileData (chars [n]);
tile.ShowFixed ();
}
}
}
panelGrid.ClearTiles ();
panelGrid.ShowRowChars (Utils.Scramble<char> (buttonChars));
}

We once again collect the chars for our panel grid based on the tiles currently in
our word grid--all tiles which are of type gap or placed-- and we reset those to
appear as gaps.
The complete Crossing script looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Crossing : MonoBehaviour, IInputHandler {

public CrossingGrid wordGrid;

public CrossingGrid panelGrid;

public Text wellDone;

private Vector3 touchPosition;

private CrossingTile selectedTile;

private List<PuzzleWord> puzzle;

private PuzzleWord mysteryWord;

private int[] wordLen = new int[] { 4,4,4,5,5,5,5,5,


5,5,6,6,6,6,6,6,6,6,6,6,6,
6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8 };

private int difficultyIndex = 0;

private CrossingTile tempTileOrigin;


void Start () {

CrossingEvents.OnGameLoaded += CrossingEvents_OnGameLoaded;
CrossingDictionary.Instance.Initialize ();

void CrossingEvents_OnGameLoaded ()
{
NewPuzzle ();
}

public void Refresh () {

var buttonChars = new List<char> ();


foreach (var p in puzzle) {
var chars = p.word.ToCharArray ();
for (var n = 0; n < p.wordTiles.Count; n++ ) {
var tile = p.wordTiles [n];
if (tile.tileType == CrossingTile.TILE_TYPE.GAP ||
tile.tileType == CrossingTile.TILE_TYPE.PLACED) {
buttonChars.Add (chars [n]);
tile.ShowGap ();
} else if (tile.tileType == CrossingTile.TILE_TYPE.CLUE) {
tile.SetTileData (chars [n]);
tile.ShowFixed ();
}
}
}

panelGrid.ClearTiles ();
panelGrid.ShowRowChars (Utils.Scramble<char> (buttonChars));
}

void NewPuzzle () {

wordGrid.BuildGrid ();
panelGrid.BuildGrid ();
puzzle = new List<PuzzleWord> ();

SelectMysteryWord ();
difficultyIndex++;
}

void SelectMysteryWord () {
var len = difficultyIndex >= wordLen.Length ?
wordLen[wordLen.Length -1] : wordLen [difficultyIndex];

string word =
CrossingDictionary.Instance.RandomUniqueWord (len);

Debug.Log ("MYSTERY: " + word);

mysteryWord = new PuzzleWord (word,


wordGrid.GetRowTiles(word.Length, 3) );

var chars = mysteryWord.word.ToCharArray ();


for (var n = 0; n < mysteryWord.wordTiles.Count; n++) {
mysteryWord.wordTiles [n].SetTileData (chars [n]);
mysteryWord.wordTiles [n].ShowFixed ();
mysteryWord.wordTiles [n].SetColor (Color.white, Color.black);
}

SelectCrossWords ();
}

void SelectCrossWords () {
var shuffledTiles = new List<CrossingTile> ();
shuffledTiles.AddRange (mysteryWord.wordTiles);
shuffledTiles = Utils.Scramble<CrossingTile> (shuffledTiles);

var num = shuffledTiles.Count - 2;


puzzle.Clear ();

var i = 0;
while (puzzle.Count < num) {

var tile = shuffledTiles [i];


var tileChar = tile.TypeChar;
var tiles = wordGrid.GetColumnTiles (3, tile.column);
var pattern = GetVerticalCrossPattern (tiles);
var words = CrossingDictionary.Instance.MatchesAPattern (pattern);
if (words != null && words.Count != 0) {
var word = words [Random.Range (0, words.Count)];
var puzzleWord = new PuzzleWord (word, tiles);
puzzle.Add(puzzleWord);
}
i++;
if (i == shuffledTiles.Count)
i = 0;
}

var buttonChars = new List<char> ();

foreach (var p in puzzle) {


var chars = p.word.ToCharArray ();
var hints = Random.Range(0, 2);
Debug.Log ("CROSS: "+ p.word);
for (var n = 0; n < p.wordTiles.Count; n++ ) {
if (!p.wordTiles [n].gameObject.activeSelf) {

if (hints > 0 && Random.Range (0, 10) > 4) {


p.wordTiles [n].SetTileData (chars [n]);
p.wordTiles [n].ShowFixed ();
hints--;
} else {
buttonChars.Add (chars [n]);
p.wordTiles [n].ShowGap ();
}

}
}
}

buttonChars = Utils.Scramble<char> (buttonChars);


panelGrid.ShowRowChars (buttonChars);
}

char[] GetVerticalCrossPattern (List<CrossingTile> tiles) {


var result = new char[tiles.Count];
for (var i = 0; i < tiles.Count; i++) {
if (!tiles[i].gameObject.activeSelf)
result [i] = '-';
else
result [i] = tiles[i].TypeChar;
}
return result;
}

public void HandleTouchDown (Vector2 touch) {

ClearSelection ();

touchPosition = Camera.main.ScreenToWorldPoint (touch);


touchPosition.z = 0;

//check panel grid


var tile = panelGrid.TileCloseToPoint (touchPosition);

if (tile == null || !tile.gameObject.activeSelf ) {

//check word grid


tile = wordGrid.TileCloseToPoint (touchPosition);
if (tile != null && tile.gameObject.activeSelf &&
tile.IsMovable()) {
//pick tile from panel
var tempTile = Instantiate(panelGrid.gridTileGO) as GameObject;
tempTileOrigin = tile;

selectedTile = tempTile.GetComponent<CrossingTile> ();


selectedTile.transform.localScale = panelGrid.transform.localScale;
selectedTile.transform.parent = wordGrid.transform;
selectedTile.transform.localPosition = tile.transform.localPosition;
selectedTile.gridType = CrossingTile.GRID_TYPE.WORD_GRID;
selectedTile.SetTileData (tile.TypeChar);
selectedTile.ShowTemporary ();

tile.ShowGap ();
}

} else {
selectedTile = tile;
}

if (selectedTile != null) selectedTile.Select (true);

public void HandleTouchUp (Vector2 touch) {


if (selectedTile == null) {
return;
}

if (selectedTile.tileType ==
CrossingTile.TILE_TYPE.TEMPORARY) {

var target =
wordGrid.TileCloseToPoint(touchPosition, false);
if (target != null && target.gameObject.activeSelf &&
target.tileType == CrossingTile.TILE_TYPE.GAP) {
target.SetTileData (selectedTile.TypeChar);
target.ShowPlaced ();
} else {
target = panelGrid.TileCloseToPoint(touchPosition, false);
if (target != null && !target.gameObject.activeSelf) {
target.SetTileData (selectedTile.TypeChar);
target.ShowButton ();
} else {
tempTileOrigin.SetTileData (selectedTile.TypeChar);
tempTileOrigin.ShowPlaced ();
}
}
Destroy (selectedTile.gameObject);

} else if (selectedTile.gridType ==
CrossingTile.GRID_TYPE.PANEL_GRID) {
var target = wordGrid.TileCloseToPoint(touchPosition, false);
if (target != null && target.gameObject.activeSelf &&
target.tileType == CrossingTile.TILE_TYPE.GAP) {
target.SetTileData (selectedTile.TypeChar);
target.ShowPlaced ();
selectedTile.ResetPosition ();
selectedTile.gameObject.SetActive (false);
} else {
selectedTile.ResetPosition ();
selectedTile.ShowPlaced ();
}

} else {
var target =
wordGrid.TileCloseToPoint(touchPosition, false);

if (target != null && target.gameObject.activeSelf &&


target.tileType == CrossingTile.TILE_TYPE.GAP) {
target.SetTileData (selectedTile.TypeChar);
selectedTile.ShowGap();
} else {
selectedTile.ResetPosition ();
}

CheckSolution ();
ClearSelection ();
}

public void HandleTouchMove (Vector2 touch) {


touchPosition = Camera.main.ScreenToWorldPoint (touch);
touchPosition.z = 0;
}

private void ClearSelection () {

if (selectedTile != null) {
selectedTile.selected = false;
selectedTile = null;
}
}

private void ClearErrors () {


foreach (var p in puzzle) {
foreach (var t in p.wordTiles) {
if (t.tileType == CrossingTile.TILE_TYPE.PLACED) {
t.ShowPlaced ();
}
}
}
}

private void CheckSolution () {


ClearErrors ();

bool allCompleted = true;


bool allWords = true;
foreach (var p in puzzle) {
if (!p.IsCompleted ()) {
allCompleted = false;
} else {
if (!p.IsAWord ()) {
allWords = false;
p.ShowErrors ();
}
}
}

if (allCompleted) {
if (allWords) {
//SUCCESS
wellDone.gameObject.SetActive(true);
Utils.DelayAndCall (this, 2, () => {
wellDone.gameObject.SetActive(false);
NewPuzzle();
});
}
}
}

void Update () {
if (selectedTile != null) {
selectedTile.transform.position = touchPosition;
}
}

private struct PuzzleWord {


public List<CrossingTile> wordTiles;
public string word;
public PuzzleWord (string word, List<CrossingTile> tiles) {
this.word = word;
this.wordTiles = tiles;
}

public bool IsCompleted () {


foreach (var t in wordTiles) {
if (!t.gameObject.activeSelf)
return false;
if (t.tileType == CrossingTile.TILE_TYPE.GAP)
return false;
}
return true;
}

public bool IsAWord () {


if (IsCompleted ()) {
char[] c = new char[wordTiles.Count];
var i = 0;
foreach (var t in wordTiles) {
c [i] = t.TypeChar;
i++;
}
var newWord = new string (c);
return CrossingDictionary.Instance.IsValidWord (newWord);
}
return false;
}

public void ShowErrors () {


foreach (var t in wordTiles) {

t.ShowWrong ();
}
}
}
}

Time to build our test scene!


var newWord = new string (c);
return CrossingDictionary.Instance.IsValidWord (newWord);
}
return false;
}

public void ShowErrors () {


foreach (var t in wordTiles) {

t.ShowWrong ();
}
}
}
}

Time to build our test scene!


The Game Scene
You will find the basic game scene in the source files for this book inside the
sample project, inside a scene called Scene_Crossing.
This is what the initial scene looks like:

Figure 8.2 - The Initial Game Scene

The Canvas object contains one button called Refresh and one Text Field called
WellDone.
We'll hook up the button to our Refresh method. But first, let's take care of the
Grids.

The Word Grid


Find the game object called WordGrid and let's add the grid script to it.
1. Add the CrossingGrid script as a component.

2. Set Rows to 7.
3. Set Columns to 8
4. Drag the CircleTile prefab from the prefab folder into the field called
Grid Tile GO (you'll find this prefab in the sample project.)
5. Set the Offset Y value to 2.2.
6. Make sure the Grid Type value is set to WORD_GRID .

ke care of the

Figure 8.3 - The Word Grid Components

And now to our second grid...


The Panel Grid
Find the game object called PanelGrid and let's add the grid script to it.

1. Add the CrossingGrid script as a component.

2. Set Rows to 4.
3. Set Columns to 12.
4. Drag the CircleTile prefab from the prefab folder into the field called
Grid Tile GO
5. Set the Offset Y value to -5.5.
6. Set the Grid Type value is set to PANEL_GRID .
Figure 8.4 - The Panel Grid Components

Note:
Rows times Columns should result in a number equal or greater than the
possible number of gaps in your puzzle.

We add our game controller next...


Figure 8.4 - The Panel Grid Components

Note:
Rows times Columns should result in a number equal or greater than the
possible number of gaps in your puzzle.

We add our game controller next...


The Crossing Controller
We'll add the game controller next. Just select the GameView object.

1. Add a new component and point to the Crossing.cs script.

2. Drag, or find in the scene, the WordGrid GameObject to the first field,
Word Grid.
3. Drag, or find in the scene, the PanelGrid GameObject to the second field,
Panel Grid.
4. Drag, or find in the scene, the WellDone text field inside the Canvas to the
third field, Well Done.
5. Add another script component and this time point to InputController.cs
Figure 8.5 - The Game Controller

And for kicks, let's hook up the refresh button to our logic.
Figure 8.5 - The Game Controller

And for kicks, let's hook up the refresh button to our logic.
Making The Game Refresh
We need now to hook up the Canvas button click action to our Refresh
method inside Crossing.cs

1. Select the Refresh button inside the Canvas.


2. On the OnClick() property of the Button component, click the plus
button.

Figure 8.6 - The OnClick Property

3. Either drag to, or click the target icon next to the field where it says None
(Objec... and find in the scene, the GameView GameObject. This will make all
methods found in all components of that GameObject available to become the
OnClick handler for this button.
4. In the drop-down select Crossing and then Refresh.

Figure 8.7 - Selecting a Handler


If you run the simulator, the game will appear!
OnClick handler for this button.
4. In the drop-down select Crossing and then Refresh.

Figure 8.7 - Selecting a Handler


If you run the simulator, the game will appear!
Taking It Further
As I said before, the best direction to take this game would be to keep finding
cross words using a growing number of already placed tiles. The game could
function in a checkpoint system, where the player has an X number of seconds to
find an Y number of words in order to reach a checkpoint, then more gaps are
placed on the board for the player to spell, and the timer is reset until a new
checkpoint is reached.
Taking It Further
As I said before, the best direction to take this game would be to keep finding
cross words using a growing number of already placed tiles. The game could
function in a checkpoint system, where the player has an X number of seconds to
find an Y number of words in order to reach a checkpoint, then more gaps are
placed on the board for the player to spell, and the timer is reset until a new
checkpoint is reached.
9. Testing On Devices

We're finally ready to test all our prototypes on actual devices. In this chapter I'll
go over the steps to quickly deploy the games to Android and iOS devices.
9. Testing On Devices

We're finally ready to test all our prototypes on actual devices. In this chapter I'll
go over the steps to quickly deploy the games to Android and iOS devices.
Publishing A Game On Android

Here are the steps to have any of the games shown in this book published to
Android.
1. We need to download a few things first. You'll need the Android SDK, so
go to the Android Studio Download Page and scroll down to where it says Get
just the command line tools and select the correct one for your computer. You'll
need to remember where you saved and decompressed the SDK, because Unity
will ask you point to that location.
2. You need to download the JDK, Java Development Kit. After downloading
it, you'll need to install it in your machine.
3. You will also need to enable developer builds in your phone or tablet. Just
follow the steps listed here.
4. Finally, in Unity, select the game you wish to publish, and open that scene.
5. Select File/Build Settings.... You'll be presented with this screen:
mputer. You'll

Figure 9.1 - Build Settings

6. Click the Add Open Scenes button.


7. Select the Android target in the Platform list. And click the Switch
Platform button if it becomes enabled.
8. Wait till the target is switched. Then click the Player Settings... button.
9. The settings will open in the Inspector Panel. Set the company name and
product name to something meaningful, particularly the product name.
Figure 9.2 - Company and Product Names

10. In the Other Settings tab, enter a package name.


Figure 9.3 - Package Name

Note:
When doing a build for Android, you might run into trouble with the Android
version you're targeting. You can set that value here in this same tab, under the
Minimum API Level dropdown. One trick you might need to do, is select the
exact version for the device you're testing on. Later, on deployment builds,
however, you'll need to set the value back to the lowest possible target for
your game.

11. You might also want to set the allowed orientations of the device in the
Resolution and Presentation tab.
Figure 9.4 - Device Orientation

12. With your phone connected to your computer, click the Build and Run
button in the Build Settings panel. Name and save the result APK anywhere you
want and sit back and relax.
13. Unity will ask you to point out to the downloaded SDK. It might also need
to download files for the selected API, just click OK for that.
And this should do it.
Figure 9.4 - Device Orientation

12. With your phone connected to your computer, click the Build and Run
button in the Build Settings panel. Name and save the result APK anywhere you
want and sit back and relax.
13. Unity will ask you to point out to the downloaded SDK. It might also need
to download files for the selected API, just click OK for that.
And this should do it.
Publishing A Game On iOS
Here are the steps to have any of the games shown in this book published to iOS.
1. There is no need to download anything, if you have Xcode already installed.
But we do need to fill out the same information we did for Android. This
includes the Product Name and the Bundle Identifier.

Figure 9.5 - Bundle Identifier

2. You may also need to set device orientation as shown in the Android
deployment steps.
3. When you click Build you'll create a iOS project. If you click Build and
Run and your Unity version is top-notch, this will build the iOS project, open it
in Xcode and run a build on it. More often that not, you'll use Build and take
care of the Xcode steps manually.
And that's it. You will then proceed to Xcode, and if you are a registered Apple
developer, you'll know the drill. Do a run with your properly setup provisioned
phone connected to your computer, and the game should run in your device.

lished to iOS.
ady installed.
And that's it. You will then proceed to Xcode, and if you are a registered Apple
developer, you'll know the drill. Do a run with your properly setup provisioned
phone connected to your computer, and the game should run in your device.
Where To Go From Here
After this prototyping phase is done, there is still quite a lot to cover to get your
game in all the App Stores of your choice. This book could not cover the whole
process of game development and publishing, as it needed to focus on building
Word Games in particular.
But if you have an idea, and you tested it, and after testing it you still like your
idea, you're 80% done!
You'll need to know quite a bit more about UI for one thing. Then we have art,
animations, sounds... From here, I'd recommend focus-learning these individual
topics as they become necessary, and Youtube will be your best source of
information.

Note:
Alternatively, you may visit my blog, at http://www.rengelbert.com where I'll
post a series of tutorials covering UI and animations, turning some of the
games in this book into a finished product.

You may also need to know about implementing a stronger meta structure for the
game: sharing, rating, multiplayer support, analytics, In App Purchases,
achievements, locked items...
Each one of these topics would fill a book. And I hope in future I get to write
about some of them.
Just make sure to never stop having ideas, and to put them to the test as quickly
as possible.
There IS the need for a lot of luck in this business, like with most other ventures,
but this business is still a sucker for great ideas. And great ideas cost you
nothing.
Rock on.
PS. Check out my word game Alpha Beta Boom in an iOS and Android store
near you!

ucture for the

her ventures,
nothing.
Rock on.
PS. Check out my word game Alpha Beta Boom in an iOS and Android store
near you!

You might also like