How to make Among Us in Unity | Part 6 — The Wire Task
Welcome to the series that will teach you how to create a game similar to Among Us, in Unity3D Engine.
This is the 6th part of the series. For the first part and the full index follow this link.
Get the Game Kit and start building an Among Us-like game in minutes!
Also, you might want to check How to Make Among Us in Unity series on my channel, Redefine Gamedev. If you prefer the video version, I highly recommend you check that one as well.
In this episode we will find out how to create the Wire Task from Among Us, in Unity. More specifically, we will look at:
- How to set up the scene
- How to generate random colors for each wire pair
- How to create drag & drop for the wires
- How to check if the task is finished
Setting up the scene
Following this tutorial, you already know that Unity needs a Canvas component to draw UI in the game scene.
In this Canvas we will create an empty game object and name it the WireTask.
Then, we will create a child image which will serve as a background. I have two layers of background so, in my particular case it will be Background and WiresBackground.
For the wires, we will use the Image Component to render simple images which will be the wire’s ends.
We will group 4 ends of the left side under another game object. This new game object will be called Wires Left and will have a Vertical Layout Group component. This component allows for simple UI alignment.
We will copy / paste the Wires Left, move them to the right and rename to Wires Right. This is how the project hierarchy should look right now:
Note: the order of the items is very important in Unity’s UI system. The first thing that will be drawn on the screen is the Background then the WireBackground and then continuing with each wire pair. Make sure you have set up the correct order or the wires might appear below the background (be invisible).
Generate Random Colors for each Wire Pair
Before we can start, we will need to create two new empty scripts Wire.cs and WireTask.cs — with them we will be able to achieve the rest of the functionality.
For the first step, selecting the color for the wire pair (left-right) we will need to add the code to set color in the wire. Here is the first iteration of the script:
Wire.cs (v1)
public class Wire : MonoBehaviour { private Image _image;
public void Initialize() {
_image = GetComponent<Image>();
}
public void SetColor(Color color) {
_image.color = color;
}
}
The code is very simple, we initialize the image component of the wire and in SetColor we set a color for it.
Don’t forget to attach this script to all the wires in the scene. It will be needed later.
To randomize the colors for each wire and generate pairs out of it we will use the WireTask script. This script will centralize all the information and functionality related to the wire task.
First, we need to store the wires created in 2 lists: left wires and right wires.
public List<Wire> _leftWires = new List<Wire>();
public List<Wire> _rightWires = new List<Wire>();
Don’t forget to assign them into the inspector. If you can’t drag & drop the wires to the list it might be because you have not assigned the wire script component to the wire (which should be done first).
We will also need a list of available wire colors.
public List<Color> _wireColors = new List<Color>();
This can be filled as you like. A small issue here is that when assigning colors in the inspector, Unity starts with alpha 0 which means the colors won’t show. Be sure to have the alpha 1 so that everything works correctly.
As for the assignment of random colors, we will use 3 lists. In the first we will keep the available colors — initially, this will just be a copy of the wireColors list — but as we extract random colors from it we will remove them in order to not select the same color twice.
private List<Color> _availableColors;
The same strategy, but for wires left and right will be applied. That means that we will have 2 lists with indexes representing each wire 0, 1, 2, etc. At each iteration we will create a pair of wires by choosing randomly 2 indexes from each list.
private List<int> _availableLeftWireIndex;
private List<int> _availableRightWireIndex;
Let’s see how the random color picking looks like!
// Initialization_availableColors = new List<Color>(_wireColors);
_availableLeftWireIndex = new List<int>();
_availableRightWireIndex = new List<int>();
for (int i = 0; i < _leftWires.Count; i++) {
_availableLeftWireIndex.Add(i);
}for (int i = 0; i < _rightWires.Count; i++) {
_availableRightWireIndex.Add(i);
}// Color pickingwhile (_availableColors.Count > 0 &&
_availableLeftWireIndex.Count > 0 &&
_availableRightWireIndex.Count > 0) {
Color pickedColor =
_availableColors[Random.Range(0,
_availableColors.Count)];
int pickedLeftWireIndex = Random.Range(0,
_availableLeftWireIndex.Count);
int pickedRightWireIndex = Random.Range(0,
_availableRightWireIndex.Count);
_leftWires[_availableLeftWireIndex[pickedLeftWireIndex]]
.SetColor(pickedColor);
_rightWires[_availableRightWireIndex[pickedRightWireIndex]]
.SetColor(pickedColor);
_availableColors.Remove(pickedColor);
_availableLeftWireIndex.RemoveAt(pickedLeftWireIndex);
_availableRightWireIndex.RemoveAt(pickedRightWireIndex);
}
In the initialization part we need to populate both the available colors list as well as the other 2 index lists.
In the color picking phase — which will happen as long as we still have colors and wires to pick from — we will pick a random color as well as a random wire pair.
Next, we will assign both the correct color.
Lastly, we will remove the picked color from the list as well as the wire pair.
This process continues until there is no more colors and/or wires to pick from.
This is the wire task with all that we have discussed up until this moment:
WireTask.cs (v1)
public class WireTask : MonoBehaviour {
public List<Color> _wireColors = new List<Color>();
public List<Wire> _leftWires = new List<Wire>();
public List<Wire> _rightWires = new List<Wire>(); private List<Color> _availableColors;
private List<int> _availableLeftWireIndex;
private List<int> _availableRightWireIndex;
private void Start() {
_availableColors = new List<Color>(_wireColors);
_availableLeftWireIndex = new List<int>();
_availableRightWireIndex = new List<int>();
for (int i = 0; i < _leftWires.Count; i++) {
_availableLeftWireIndex.Add(i);
} for (int i = 0; i < _rightWires.Count; i++) {
_availableRightWireIndex.Add(i);
} while (_availableColors.Count > 0 &&
_availableLeftWireIndex.Count > 0 &&
_availableRightWireIndex.Count > 0) {
Color pickedColor =
_availableColors[Random.Range(0,
_availableColors.Count)]; int pickedLeftWireIndex = Random.Range(0,
_availableLeftWireIndex.Count); int pickedRightWireIndex = Random.Range(0,
_availableRightWireIndex.Count); _leftWires[_availableLeftWireIndex[pickedLeftWireIndex]]
.SetColor(pickedColor); _rightWires[_availableRightWireIndex[pickedRightWireIndex]]
.SetColor(pickedColor);
_availableColors.Remove(pickedColor);
_availableLeftWireIndex.RemoveAt(pickedLeftWireIndex);
_availableRightWireIndex.RemoveAt(pickedRightWireIndex);
}
}
}
Wire Drag & Drop Functionality
Now that we have assigned the colors to the wires it’s time to drag them around.
There are two important features here that I want to mention before diving into them:
- Unity drag & drop support and,
- Drawing lines as wires
We will need to combine them in order to create this step in the functionality.
Unity provides these interfaces for drag & drop — IDragHandler, IBeginDragHandler, IEndDragHandler — and they come with some functions that need to be implemented.
Let’s go back to the Wire.cs script and implement those interfaces:
private bool _isDragStarted = false;public void OnDrag(PointerEventData eventData) {
// needed for drag but not used
}public void OnBeginDrag(PointerEventData eventData) {
_isDragStarted = true;
}public void OnEndDrag(PointerEventData eventData) {
_isDragStarted = false;
}
Note that OnDrag while having no implementation is necessary for the functionality to work.
Let’s get back to the Wire objects and make sure they have assigned the Wire.cs script as a component (note: this should have already been done from the previous step).
Now we will also add, for each wire, a line renderer component. This will help draw the lines from one wire end to another.
We will be using the WireMaterial and attach it to the LineRenderer. If you don’t have the material, don’t worry! Just create an empty new material and make sure you have the same as here:
It seems that there might be a lot of problems with drawing of the wires. If you encounter the wires not being draw properly on top of other UI elements you might also want to:
a) Check this parameter in the shader and increase it
b) In the line renderer, increase this number
c) Make sure the canvas is Screen-Space Camera
Let’s see the final wire script:
Wire.cs (v2 — final)
public class Wire : MonoBehaviour,
IDragHandler, IBeginDragHandler, IEndDragHandler { public bool IsLeftWire;
public Color CustomColor;
private Image _image;
private LineRenderer _lineRenderer;
private Canvas _canvas;
private bool _isDragStarted = false; private WireTask _wireTask;
public bool IsSuccess = false; private void Awake() {
_image = GetComponent<Image>();
_lineRenderer = GetComponent<LineRenderer>();
_canvas = GetComponentInParent<Canvas>();
_wireTask = GetComponentInParent<WireTask>();
}
private void Update() {
if (_isDragStarted) {
Vector2 movePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_canvas.transform as RectTransform,
Input.mousePosition,
_canvas.worldCamera,
out movePos); _lineRenderer.SetPosition(0, transform.position);
_lineRenderer.SetPosition(1,
_canvas.transform.TransformPoint(movePos));
}
else {
// Hide the line if not dragging.
// We will not hide it when it connects, later on.
if (!IsSuccess) {
_lineRenderer.SetPosition(0, Vector3.zero);
_lineRenderer.SetPosition(1, Vector3.zero);
}
} bool isHovered =
RectTransformUtility.RectangleContainsScreenPoint(
transform as RectTransform, Input.mousePosition,
_canvas.worldCamera); if (isHovered) {
_wireTask.CurrentHoveredWire = this;
}
}
public void SetColor(Color color) {
_image.color = color;
_lineRenderer.startColor = color;
_lineRenderer.endColor = color;
CustomColor = color;
} public void OnDrag(PointerEventData eventData) {
// needed for drag but not used
}
public void OnBeginDrag(PointerEventData eventData) {
if (!IsLeftWire) { return; }
// Is is successful, don't draw more lines!
if (IsSuccess) { return; }
_isDragStarted = true;
_wireTask.CurrentDraggedWire = this;
}
public void OnEndDrag(PointerEventData eventData) {
if (_wireTask.CurrentHoveredWire != null) {
if (_wireTask.CurrentHoveredWire.CustomColor ==
CustomColor &&
!_wireTask.CurrentHoveredWire.IsLeftWire) {
IsSuccess = true;
// Set Successful on the Right Wire as well. _wireTask.CurrentHoveredWire.IsSuccess = true;
}
} _isDragStarted = false;
_wireTask.CurrentDraggedWire = null;
}
}
To understand this script, we will split it up in different functions:
- Awake
- Update
- SetColor
- OnBeginDrag
- OnEndDrag
We already discussed SetColor as the function that gets called by the WireTask.cs setting up the random wire color.
We touched OnBegin/EndDrag before. Now they have more code to keep the drag state (_isDragStarted). The OnBeginDrag prevents dragging the right end of the wire by using this value (IsLeftWire) changed in the inspector.
The OnEndDrag does a collision check to see if the final hovered wire end is the same color as the dragged one. If it is, it’s good and we should keep the wire.
In Awake it’s mostly initialization of the component.
In Update we draw the line between two points: the current left wire end and the mouse’s point. Also, Update resets the wire if there is no success with the final right end (IsSuccess).
Lastly, Update keeps track of the wire’s hovered state (isHovered).
Task Finished Checkup and wrapping-up
Firstly, make sure you have the wire setup like this:
WireTask.cs (v2 — final)
public class WireTask : MonoBehaviour { public List<Color> _wireColors = new List<Color>();
public List<Wire> _leftWires = new List<Wire>();
public List<Wire> _rightWires = new List<Wire>();
public Wire CurrentDraggedWire;
public Wire CurrentHoveredWire;
public bool IsTaskCompleted = false;
private List<Color> _availableColors;
private List<int> _availableLeftWireIndex;
private List<int> _availableRightWireIndex; private void Start() {
_availableColors = new List<Color>(_wireColors);
_availableLeftWireIndex = new List<int>();
_availableRightWireIndex = new List<int>();
for (int i = 0; i < _leftWires.Count; i++) {
_availableLeftWireIndex.Add(i);
}
for (int i = 0; i < _rightWires.Count; i++) {
_availableRightWireIndex.Add(i);
}
while (_availableColors.Count > 0 &&
_availableLeftWireIndex.Count > 0 &&
_availableRightWireIndex.Count > 0) {
Color pickedColor =
_availableColors[Random.Range(0, _availableColors.Count)];
int pickedLeftWireIndex = Random.Range(0,
_availableLeftWireIndex.Count);
int pickedRightWireIndex = Random.Range(0,
_availableRightWireIndex.Count);
_leftWires[_availableLeftWireIndex[pickedLeftWireIndex]]
.SetColor(pickedColor); _rightWires[_availableRightWireIndex[pickedRightWireIndex]]
.SetColor(pickedColor);
_availableColors.Remove(pickedColor);
_availableLeftWireIndex.RemoveAt(pickedLeftWireIndex);
_availableRightWireIndex.RemoveAt(pickedRightWireIndex);
}
StartCoroutine(CheckTaskCompletion());
}
private IEnumerator CheckTaskCompletion() {
while (!IsTaskCompleted) {
int successfulWires = 0;
for (int i = 0; i < _rightWires.Count; i++) {
if (_rightWires[i].IsSuccess) { successfulWires++; }
} if (successfulWires >= _rightWires.Count) {
Debug.Log("TASK COMPLETED");
}
else {
Debug.Log("TASK INCOMPLETED");
}
yield return new WaitForSeconds(0.5f);
}
}
}
We already covered the wire random color picker. What is left to cover is the checking of the task’s completion. We can easily check for this by iterating all the wires and counting are successful. If the number is equal to the wire’s number, it’s a success.
Whoa, that was a lot! Be sure to check the video tutorial as well. This task is not easy to implement but totally doable! Good luck!
Want More?
You are covered! Head over to Youtube at Redefine Gamedev channel and check the video tutorial.