AWS, Cloud Computing, and the Monetization of Education

It’s been a while since my last post, and while I do plan on picking up where we left off soon enough, I’ve been quite busy studying for one of many AWS Certification Exams offered by Amazon. AWS, or Amazon Web Services, is a series of products that are designed around rather game-changing cloud technologies that mean to disrupt the IT world. Some of the services offered include cloud computing, storage, database solutions, and security tools, among a host of other products. Most of these services offer push-of-a-button deployment, on demand scalability, and Amazon-managed administration that lets businesses instance virtual servers on the cloud at minimal expense. Companies like Oracle haven’t taken too kindly to the sudden market domination of many IT-related technologies, and businesses everywhere seem to be in a rush to migrate their production databases, servers, and administrative duties to the cloud.

To incentivize this market take-over, Amazon has also implemented Partnership programs for AWS-savy businesses. Unfortunately, it comes at a price that, for all the company’s population and innovation, may be downright “scammy”. Let me back up for a second. Amazon offers about 7 certification exams at the moment (although the number is growing). Three of these are associate level certifications (Solutions Architect, Developer, and SysOps), which are needed to sit for the exams that bestow the remaining certs. There are two “professional” exams for Solutions Architect and DevOps, as well as specialty certifications for Advanced Networking and Big Data. We’re talking about $150 for each exam, which means that to reach professional status, you’re shelling out $300. Furthermore, Amazon’s company culture seems to promote a “gotta-catch-’em-all” attitude à la Pokemon, indicating a total investment of over $1000 in certifications.

Alright, at face value this isn’t so bad. I mean, the estimated market value for even the most basic of these certifications breaks into six-figure salaries. That said, I’ve spoken with a handful of people who’ve sat for the tests, and they’ve all mentioned one notable aspect of the examination – incorrect answers on a subject start somewhat of a chain reaction of questions hammering away at that specific subject. For example, if you miss a question about Amazon’s Redshift service (database warehousing), chances are you’ll continue to be hit with database-oriented questions. Now, I won’t give credit to this claim as anything more than speculation, and in fact this style of testing is effective in determining a truly comprehensive understanding of the material. However, at a 65% pass rate, I don’t imagine that Amazon is coming from that angle. Rather, the people I’ve spoken with surmise that it might be Amazon’s way of weeding out test takers to encourage multiple attempts (and extra exam fees).

It’s also worth noting that Amazon’s test questions aren’t updated at the consistency that their actual web services are, so it’s highly likely that you could study the multitude of services available and by test day be caught off guard by two or three entirely new ones. Or, perhaps more likely, the chance that a solution that came out in 2017 didn’t exist at the time the test questions were written. The point here is that there is no verifiable way of knowing what to expect going into the exam, and that any particular weak point you may have with the services (as perceived by the test) may be exploited by the testing system.

This issue seems exacerbated by a business fad culture that jumps at being the first to the punch line. As mentioned previously, Amazon’s AWS Partner program requires tech or consulting businesses to pay a $2500/year fee for a list of benefits that amount to a merch-package of Amazon gear and fluff services. An additional requirement for this partnership is that a number of employees in the business be certified at either the associate or professional level, reinforcing the need for Amazon-provided certification.

I suppose this isn’t in bad taste, and I won’t fault Amazon for coming up with a killer series of products and promotion to boot. I will say, though, that it’s always worthwhile to take a step back and view the triggers for our behaviors as individuals and as organizations. Anyway, that’s my mini-rambling for the night. When I finish this certification, I’ll get back to posting more game and programming content. Thanks for reading!

Advertisements

Create a Chessboard with 2D Arrays

Recently, I decided to write a chess program to experiment with AI, inspired by the ongoing World Chess Cup tournament held in Tbilsi, Georgia. I thought I could take some time today to share the foundation for my game – the chessboard. We will be using Unity and writing in C#, and we will also need a few piece assets. As a side note, project files for this series on chess will be uploaded to GitHub as soon as I finish them. In the mean time, any asset or line of code you need will be provided in the lessons that introduce them. If you have any questions or seek clarification on anything at all in this tutorial, I’m always available to answer them here. Additionally, if you are coming to this tutorial solely because you are interested in chess, you’ll need a little more background to follow along with me in Unity. I’d recommend downloading the editor, experimenting with it, and then viewing some of the learning resources Unity provides to get accustomed to the workflow. Finally, I just finished a tutorial series on Pong, so feel free to read parts one and two if you want a little practice. Back to the main feature – Chess in Unity!

 

If you’re not familiar with chess, or just a little rusty, it’s an age old game (we’re talking single-digit centuries) played between two players by moving pieces on an 8×8 checkered board. Rows on the board are called “ranks” and are numbered 1 to 8, and columns are called “files” and denoted with letters, ‘a’ through ‘h’. The aforementioned six pieces are arranged on opposite sites of the board as opposing “teams”, white and black:

259-69d5ae42-300x300o-ab57f1eacf3a

 

Each side starts with their pawns on the 2nd or 7th rank, with more powerful pieces guarded behind them on the 1st and 8th ranks. The Queens rest on the middle file squares that match their color (the white queen is on D1, a white or “light” square, while the black queen starts on D8, a black or “dark” square). The Kings rests beside the Queens on the E file. These royal pieces are protected by two bishops, then two knights, and finally, two rooks. Each of these pieces has its own ruleset for moving to new squares and capturing other pieces. White moves first, and the game continues until the opponent’s King is placed in irreconcilable danger, an action known as a “checkmate”.

Enough talk about chess, let’s make it! What we will need:

  • Sprites of each piece, all white (6 total)
  • Two materials that we can assign to the pieces (white or black)
  • A Piece.cs script that each piece can inherit from, with
    • a boolean variable indicating white/black,
    • a Move method,
    • a Capture method, and
    • two methods that return true or false depending on whether or not a potential move or capture is “legal”. These last two methods will be virtual, so that each pieces class (Pawn.cs, Bishop.cs, King.cs, etc.) can override them with their own unique rulesets.
  • Six unique piece scripts that hold their own rulesets and inherit from Piece.cs (named after the six pieces). We can go ahead and attach these to our piece sprites and save them all as prefabs.
  • A square-shaped game object that we can use to represent one square of the board. I will be using Unity’s generic quad, but it may be a better idea to use a lighter-weight sprite instead of the the quad’s mesh components. This square should be turned into a prefab so that we can refer to it like a blueprint when creating our board. It will also be nice to have a Tile.cs script attached to this prefab, so that we can reference the positions of pieces a little easier
  • Two materials that we can assign to the tiles (light or dark)
  • A Main Camera that is positioned well enough to see the board clearly (use your own judgement here. Mine is set to a position of (4, 3.5, -10) with a black background
  • An empty game object called “Game Manager” that has the following scripts:
    • GameManager.cs (useful for setting up the game, relaying inputs, and monitoring time/score)
    • Board.cs, the class responsible for setting up the tiles and pieces at the start of the game

 

Piece Sheet

I drew a mock-up of the six main pieces in Piskel, a relatively lightweight, low barrier-of-entry pixel art editor and animation tool. I’d highly recommend looking into the desktop app if you are in need of quick temp-art for your 2D projects. Anyway, the pieces shown in this image are, from right-left and top-bottom:

  • Pawn
  • Knight
  • Bishop
  • Rook
  • Queen
  • King

We can create empty game objects for each of these pieces, import these sprites with the Sprite Editor into our Assets folder, and then assign each sprite to the matching game object. Then, we can create prefabs out of these pieces and delete them from the hierarchy. Be sure to adjust the pixels per unit field in the Inspector when importing the sprites, so that their widths and heights are 1×1 Unity units. Mine are set to 256:

PieceSheet.PNG

Note that I made twelve sprites, six in white and six in black. The black sprites are unnecessary, since we will color the sprites with materials a bit later. This also enables us to re-skin our pieces later, if we choose to do so.

Let’s check our Assets folder to make sure we’re on the same page:

ChessPrefabs

We can also create four materials using the Sprites/Default shader, and choose the colors that we want to represent our squares and pieces:

Materials.PNG

We have everything set up now! Let’s open up Board.cs and get our chessboard up and running. We will need a reference to our square prefab, as well as references to all of our piece prefabs. Finally, we need to reference our four materials. Let’s take a look:

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

public class Board : MonoBehaviour {

    public GameObject tilePrefab;

    public GameObject pawnPrefab, knightPrefab, bishopPrefab, rookPrefab, queenPrefab, kingPrefab;

    public Material whiteMat, blackMat;
    public Material whitePieceMat, blackPieceMat;
}

In the inspector, let’s attach our prefabs to the associated fields within the Game Manager:

GM.PNG

Alright, back in the Board script, let’s think about how we want to arrange the tiles. We know that the chessboard is a grid, and that means our tiles will need to be arrange along 2D (x, y) coordinates. Since we’ve set the position of all of our game objects to (0, 0), we can let that point be our A1 square, and work up to H8 at (7, 7). We can use a 2D array of squares to represent this:

public GameObject[,] squares = new GameObject[8, 8];

Then, we can create a method called CreateBoard that instantiates these squares at every integer position of the grid:

public void CreateBoard() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                squares [i, j] = Instantiate (tilePrefab, new Vector3 (i, j, 0), Quaternion.identity);
            }
        }
    }

In order to test our code, we need to call the CreateBoard() method somewhere. Let’s turn to the GameManager script quickly and give it a reference to the board:

public class GameManager : MonoBehaviour {
    Board board;
    void Start() {
        board = gameObject.GetComponent<Board> ();
        board.CreateBoard ();
    }
}

Now when we run the game, we see an 8×8 grid of squares, but… the squares are all the same color! Back in Board.cs, we can add some logic to this method to assign light or dark materials to the squares, depending on their location. For a checkerboard pattern, dark squares appear at coordinates that are either both even (4,2), or both odd (3,7). White squares appear at mismatched, even-odd coordinates like (1, 2). Let’s look at this logic in action:

public void CreateBoard() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
               
                // if the square is double odd or double even, its black
                if (i % 2 != 0 && j % 2 != 0 || i % 2 == 0 && j % 2 == 0) {
                    squares [i, j].GetComponent<Renderer> ().material = blackMat;
                } else {
                    squares [i, j].GetComponent<Renderer> ().material = whiteMat;
                }
            }
        }
    }

Wonderful! It’s starting to look a lot like a chess board. I’d like to clean up the hierarchy though. We can assign all of the newly created squares as children of the Game Manager, and give them all proper names. Since chess squares are named after their file and rank, we can make a static array of letters ‘a’ through ‘h’ to help us:

[HideInInspector]
    public static string[] alphabet = new string[] {a, b, c, d, e, f, g, h};

    public void CreateBoard() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
               
                squares [i, j].transform.SetParent (gameObject.transform);
                squares [i, j].name = alphabet [i] + (j + 1);
            }
        }
    }

Great, the board itself is good-to-go. Onto the pieces… let’s make an array of Game Objects called “pieceArrangement”, as well as a new method called SetupPieces() that fill out the array with the pieces in the correct order:

GameObject[] pieceArrangement;

    public void SetupPieces() {
       
        pieceArrangement = new GameObject[8] {
            rookPrefab,        // R = 0
            knightPrefab,      // N = 1
            bishopPrefab,      // B = 2
            queenPrefab,       // Q = 3
            kingPrefab,        // K = 4
            bishopPrefab,      // B = 5
            knightPrefab,      // N = 6
            rookPrefab         // R = 7
        };       
    }

Since pieces are arranged by rank, we can use a single for loop to contain our logic, and iterate by file along the ranks we choose. Since our game is using (0,0) as the position of the A1 square, our ranks will be arranged from 0 to 7, not the tradition 1 to 8. Don’t worry, fellow chess enthusiasts – this is only represented in the code itself!

Knowing this, the white pieces will be arranged on the 0th rank, followed by the white pawns on the 1st rank. On the other end of the board, the black piece get placed along the 7th rank, behind the black pawns on the 6th. Let’s see what this looks like in our SetupPieces() method:

public void SetupPieces() {
       …

        for (int i = 0; i < 8; i++) {

            // SETUP 1st RANK WHITE PIECES
            GameObject newWhitePiece = Instantiate (pawnPrefab, squares [i, 1].transform);
            newWhitePiece.gameObject.GetComponent<Piece> ().white = true;
            newWhitePiece.GetComponent<Renderer> ().material = whitePieceMat;

            // SETUP 2nd RANK WHITE PAWNS
            GameObject newWhitePawn = Instantiate (pieceArrangement [i], squares [i, 0].transform);
newWhitePawn.gameObject.GetComponent<Piece> ().white = true;
            newWhitePawn.GetComponent<Renderer> ().material = whitePieceMat;

            // SETUP 7th RANK WHITE PAWNS
            GameObject newBlackPawn = Instantiate (pawnPrefab, squares [i, 6].transform);
            newBlackPawn.gameObject.GetComponent<Piece> ().white = false;
            newBlackPawn.GetComponent<Renderer> ().material = blackPieceMat;

            // SETUP 8th RANK WHITE PIECES
            GameObject newBlackPiece = Instantiate (pieceArrangement [i], squares [i, 7].transform);
            newBlackPiece.gameObject.GetComponent<Piece> ().white = false;
            newBlackPiece.GetComponent<Renderer> ().material = blackPieceMat;
        }
    }

 

Let’s quickly call this method in the Start() method of GameManager.cs with:

    void Start() {
       
        board.SetupPieces ();
    }

 

Alright, we have classically arranged pieces decorating our chessboard now! Perhaps the player with the black pieces feels a little left out, and would like it if they could rotate the board. If they are reading this tutorial, then they are in luck! Let’s add a Rotate() method to Board.cs that looks like this:

public void Rotate() {
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                squares [i, j].gameObject.transform.localPosition = new Vector3 (
                    7  squares [i, j].gameObject.transform.localPosition.x,
                    7  squares [i, j].gameObject.transform.localPosition.y,
                    0);
            }
        }
        print (Board rotated);
    }

 

We can call this method in Update() whenever we press a button, but let’s do that in the GameManager:

void Update() {
        if (Input.GetKeyDown (KeyCode.Space)) {
            board.Rotate ();
        }
    }

 

Finally, it would fun if we could enjoy the game from starting arrangements other than the classic arrangement. Bobby Fischer, famed chess player and quasi- Cold War superstar, felt similarly when he came up with FischerRandom. FischerRandom, or Fischer960, is a chess variant that was made to eschew reliance on studying opening theories, and instead put a focus on finding moves and tactics through over-the-board analysis and intuition. FischerRandom chess is played identically to regular chess, with the exception that the positions of the starting pieces are randomized. If we can find a way to randomize the indexes of our array elements, we can just shuffle our pieceArrangement array before we place the pieces in SetupPieces(). Thankfully, another Fisher (no ‘c’ this time) partnered up with someone named Yates to create a rather efficient means of shuffling arrays, aptly named the Fisher-Yates Method, sometimes known as the Fisher-Yates / Knuth Shuffle. Let’s add this method into our Board script:

    public void RandomizeArrangement(GameObject[] pieces) {
for (int i = pieces.Length1; i > 0; i–) {
int randomIndex = Random.Range(0,i);
GameObject temp = pieces[i];
pieces[i] = pieces[randomIndex];
pieces[randomIndex] = temp;
}
}

In SetupPieces(), we can send our pieceArrangement array out to be shuffled by this method, just after we initialize it and just before we instantiate the pieces:

public void SetupPieces() {
        
        pieceArrangement = new GameObject[8] {
           
        };

        if (fischer) {
            RandomizeArrangement (pieceArrangement);
        }
            
        for (int i = 0; i < 8; i++) {
           
        }
    }

 

Don’t worry, let’s add in a boolean switch to the top of the script called “fischer” that controls the randomization of the pieces:

[SerializeField]
    bool fischer = false;

 

We can now select whether or not we want to use FischerRandom or Classical arrangements in the editor before we start our game!

Thus ends the first part of this tutorial on chess and array manipulation, and I hope to see you here again next time, when we cover the Piece.cs script and virtual methods. Thanks for reading!

Reinventing the Wheel with Pong, Cont.

If you haven’t checked out the first part of this tutorial, I’d recommend doing so. If you’re just interested in the “finished” code/project, you can find the assets and build files here. Note that you only need to open “index.html” in your web browser to play the current build.

Today we will continue our Pong tutorial, and hopefully find ways to integrate our ideas into our playable framework. Let’s go ahead and open Unity, as well as MonoDevelop (or your IDE of choice). Hopefully you saved your progress! We should have a serviceable pong clone, though not a very fun one. Now that we have the skeleton of a game, what features should it have? If you read the article that prefaced this tutorial, “How To Implement Your Ideas (pt. 1)“, you will know that the same ingredients can be used to make thousands of different recipes. More importantly, we don’t even need to describe the details of each ingredient to convey the meaning of what they represent as a whole.

Our Pong game is a skeleton, perhaps with some muscle fibers and basic organs for functionality. We could add some fur, some paws, and an adorable nose, and we would get… well, we would get… a pet? See, there is an inherit misunderstanding here. Is it a dog, or a cat, or something else entirely? Wouldn’t it have been simpler to reference one of these animals from the start? If I take my dog to the vet, the veterinarians will know the ways in which it is different from a cat, without me ever having to describe its uniqueness.

For our purposes, I could tell you my game has a health bar, environmental interaction, and adaptive enemy AI. This is not helpful, because your idea of my game is now being stretched and pulled by the thousands of tropes and genres that define so many popular games – FPS, RPG, platformer, etc… It may even cause you exclude certain genres that typically lack these features – visual novels, sandbox-creation games, and board games, to name a few. A game is not just it’s skeleton. It’s the combination of each of its structural and functional components that creates an experience. You can play 20 hex-grid, turn-based, historical grand strategy games and walk away with wildly different experiences not only from each game, but from each play session! My point is that your game could be another basic pong tutorial, but the ideas you implement on top of that framework can make it unique and engaging nonetheless.

All of that said, the game I mentioned previously with a health bar, environmental interaction, and adaptive enemy AI, is our Pong game! Well, not yet, we have some more work to do. First, let’s think about how these feature could factor into our existing framework of two paddles, a puck, and a few walls


 

Health Bar

how do we measure the health of two rectangles when there are no incoming laser beams or punches to dodge? Taken one step further, what is health? Can we abstract it to something relevant to our game? Possibly! In fighting games, health is a measure of your performance. If your performance is good, your health remains high. The more mistakes you make, the lower your health gets, until… poof, you’re gone! Not forever, of course. You’ll probably respawn somewhere with some amount of your total health gifted back to you.

Classic 2D fighting games saw many variations to this formula. In some, health could be recovered if certain actions were taken quickly after damage was dealt. In others, both players regained their full health at the start of each round. In other genres, health can be found or earned by defeating opponents or exploring the environment. So already, the idea of a health bar in and of itself is a little unhelpful. We have points in Pong, much like in tennis, but what does it matter? The players remain unchanged for their efforts, and each new round feels like the last.

Let’s change this. Most health bars represent linear progressions from 100% to 0%, so let’s make our paddles living health bars. Every time a player hits the puck, the puck “chips off” a bit of the paddle, shrinking the paddle’s height by a tiny amount. As the volley goes on, the difficult ramps up, with players rushing to move a smaller and smaller paddle to reach trajectories of a faster and faster moving puck. Of course, we should cap our shrinkage so that the players will have at least some fighting chance in longer volleys. Lastly, at the end of the volley, we want the players to return to a normal size. Perhaps we can borrow from fighting games here and restore only a percentage of the paddle’s size, so that players start with 100% size on the first volley, but on consecutive volleys, their max health will only be restore to a percent matching the total average health of the walls behind the players. Last time, we set the “health” of each wall to 3, so if Player One has scored one point, and Player Two has score one point, this average percentage would be (2 + 2) / (3 + 3) = 67%. Once a player scores 3 points, the walls reset their “health”, the players fully reset their size, and a new round begins.

We can start in the Paddle script by creating methods for shrinking and resetting the paddle size each round. The puck can call the shrink method every time it collides with the paddle, and the game manager can control when the paddles need to reset, and by what percentage. For this reason, the resetting method will need to take in some floating point value for the percentage average of the current wall health. Finally, we will need variables to represent the original height of the paddle (for when we reset it), the current height of the paddle, the minimum size the paddle is allowed to shrink to, and the amount that the paddle shrinks every time it is hit. Let’s look at what we’ve got so far:

 


[SerializeField][Tooltip(What is the starting height of the paddle?)][Range(4f, 8f)]
    protected float originalHeight = 6f;

    protected float currentHeight;

[SerializeField][Tooltip(How small can the paddle get before it stops shrinking?)][Range(0.5f, 4f)]
    protected float minimumSize = 1.5f;

[Tooltip(Every hit will reduce the paddle height by this amount:)][Range(0f, 2f)]
    public float shrinkModifierConstant = 0.2f;

 

    protected virtual void Start() {
        body = gameObject.GetComponent<Rigidbody2D> ();
        originalPosition = body.transform.position;
        gameObject.transform.localScale = new Vector3 (gameObject.transform.localScale.x, originalHeight, gameObject.transform.localScale.z);
        currentHeight = originalHeight;
    }

 

    public void ResetSize(float proportion) {
       
    }

    public void Shrink() {
       
    }


 

We will serialize the original height of the paddle, so we can adjust it via the script as opposed to the transform field in the editor. We will also limit the height to a reasonable size, so that the paddles aren’t cramped up between the top and bottom walls. Similarly, our minimum size should be within a reasonable range, and it should be adjustable in the editor for balancing purposes. We will set the shrink modifier “constant” to 0.2, which allows for decent pacing each volley. Finally, our methods are now named and the ResetSize method is parameterized with a float called proportion. Let’s add our code:


    public void ResetSize(float proportion) {
        if (currentHeight < originalHeight * proportion ) {
            currentHeight = originalHeight * proportion;
            gameObject.transform.localScale = new Vector3 (gameObject.transform.localScale.x, currentHeight, gameObject.transform.localScale.z);
        }
    }

    public void Shrink() {
        if (gameObject.transform.localScale.y >= minimumSize) {
            gameObject.transform.localScale -= new Vector3 (0, shrinkModifierConstant, 0);
            currentHeight = gameObject.transform.localScale.y;
        }
    }


 

Okay, so how will we call these methods? I think the Puck should call Shrink() every time it collides with the paddle. We should do this in the OnCollisionEnter2D method:

 


void OnCollisionEnter2D (Collision2D other) {
        if (other.gameObject.tag == Player || other.gameObject.tag == AI) {

           

            if (other.gameObject.tag == Player) {
                other.gameObject.GetComponent<PlayerController> ().Shrink ();
            } else {
                other.gameObject.GetComponent<PlayerAI> ().Shrink ();
            }
        }
    }


 

This code will work, but we can do better. That being said, I will not demonstrate how in this tutorial. I challenge you to fix it! We have been talking about abstraction, so see if you can find a more concise way to call the Shrink() method with the Puck. Keep in mind the relationships between the PlayerController, the PlayerAI, and the Paddle scripts.

Moving on, we will also need a way to refer to the ResetSize() method. Let’s do that in the game manager, every time we score a point, using Countdown():


IEnumerator Countdown() {
        
        yield return new WaitForSeconds (1);

        float progressPercentage =
            (float)(leftWall.health + rightWall.health) /
            (float)(leftWall.maxHealth + rightWall.maxHealth);
        leftPlayer.GetComponent<Paddle> ().ResetSize (progressPercentage);
        rightPlayer.GetComponent<Paddle> ().ResetSize (progressPercentage);

        …


        text.text = ;
        text.enabled = false;
        background.enabled = false;
    }


 

Perfect! Now when we play, our paddles will gradually shrink, with each hit, and recover some of their size after each volley until the end of the round. I think it would be more satisfying if the reset wasn’t so abrupt, though. I want it to look like the paddles are growing in chunky, pixely steps, where one might imagine the Mario mushroom sound effect playing in the back of their minds as it happens. If you remember from the last lesson, we can have a method execute over time by changing its return type to IEnumerator, and calling the method via MonoBehavior’s StartCoroutine() method. Let’s do this, and try to make the change happen gradually:


    public void ResetSize(float proportion) {
        StartCoroutine (GradualReset (proportion));
    }

    private IEnumerator GradualReset(float proportion) {
        if (currentHeight < originalHeight * proportion ) {
            float heightDifference = originalHeight * proportion  currentHeight;
            while (heightDifference > 0) {
                currentHeight += shrinkModifierConstant;
                gameObject.transform.localScale = new Vector3 (gameObject.transform.localScale.x, currentHeight, gameObject.transform.localScale.z);
                heightDifference -= 0.2f;
                yield return new WaitForSeconds (0.1f);
            }
        }
    }


 

Now, whenever the ResetSize method is called, it calls another method that can use “yield return new WaitForSeconds (0.1f)” to space out little bursts of growth by a tenth of a second. Furthermore, we don’t have to change our game manager script to call the new GradualReset method, because ResetSize does it for us! This is much more satisfying to watch, in my opinion, and it will be even better if we could add some sound effects later (hint hint). By the way, if you have been paying close attention to the paddles, you might notice that their x position in the editor has been changing by incredibly small amounts every time the puck collides with them. Unity’s physics can be all too real sometimes, despite our efforts to reduce the mass of the puck and freeze the x positions of both paddles. We can fix this quickly by adding a method in Paddle called ResetXPosition, and call it every time we start a new round through the game manager:


    public void ResetXPosition() {
        gameObject.transform.position = originalPosition;
    }


In the Game Manager:

    void FixedUpdate() {

        if (leftWall.isBroken || rightWall.isBroken) {           

           …

            leftPlayer.GetComponent<Paddle> ().ResetXPosition ();
            rightPlayer.GetComponent<Paddle> ().ResetXPosition ();
            print(New Round);
    }


 

Environmental Interaction

Our pong game is boring. It might occupy your attention for a couple rounds, but the monochromatic aesthetic and lack of interaction aren’t doing the game any favors. Perhaps we could add some color to the mix? We have already imagined that the puck “chips away” at each paddle upon impact. What if each hit imparted the puck with a little bit of color from the paddle’s material? Perhaps the paddles are just rectangular blocks of color that sacrifice a bit of themselves every time they hit away the puck, defending their territory from other invasive colors. How can we show the puck “absorbing” the colors of the paddles? I think adding a trail to the puck would be a cool way of displaying its momentum, direction, and history. Maybe the puck will remain white, but its trail will change colors to that of the last paddle that it hit.

First, select the Puck prefab, and in the inspector, click “Add Component”. We will add two components to our puck, a Particle System and a Trail Renderer, both found in the “Effects” category. You can play around with the seemingly limitless options these components have, but if you are intimated by the sheer amount of choices available to you, fret not! I have arrange the components for our project as follows:

 

 

* as a note, if any of the in-editor options are confusing, Unity’s website and API documentation are great resources for learning the ins and outs of the engine.

When we play the game, a white trail now follows the puck around. I think the trail should start out white, but then change to either a red or blue color, depending on which paddle was most recently hit. Let’s reference these additions in our Puck script with:

    private TrailRenderer trail;

In Initialize(), we should declare this trail as the one on our game object by adding:

trail = gameObject.GetComponent<TrailRenderer> ();

In the Puck’s IEnumerator ResetPosition() method, we should make sure to reset the color of the trail to white:

    trail.startColor = Color.white;

Finally, we can add a method called ColorChange that takes in a new color and sets the trail’s starting color to the new color:

    public void ColorChange(Color playerColor) {
        trail.startColor = playerColor;
    }

Great! Now we can use the game manager to check where the puck is headed, and change/reset its color accordingly. In the GameManager’s FixedUpdate(), add:


    void FixedUpdate() {

      

        if (puck.body.velocity.x > 0 && puck.initialCollision) {
            puck.ColorChange (leftPlayer.GetComponent<Renderer> ().material.color);
        } else if (puck.body.velocity.x < 0 && puck.initialCollision) {
            puck.ColorChange (rightPlayer.GetComponent<Renderer> ().material.color);
        } else {
            puck.ColorChange (Color.white);
        }
    }


 

So this is kind of cheating. Instead of directly changing color based on the last paddle hit, the game manager changes the puck’s color depending on its direction. It is serviceable nonetheless, although is does require a bool that I have not yet mentioned. Let’s add it, and then explain why this implementation needs such a thing. Open the Puck script, and add the following:


    [HideInInspector]
    public bool initialCollision;

    void OnCollisionEnter2D (Collision2D other) {
        if (other.gameObject.tag == Player || other.gameObject.tag == AI) {

            initialCollision = true;



        }

    }

public IEnumerator ResetPosition() {
       
        newSpeed = originalSpeed;


        initialCollision = false;


        float direction = Random.Range (0, 2);
       
    }


 

We need to monitor the initial collision, because if the puck hasn’t collided with a paddle, then it shouldn’t take any new colors. Since we are changing colors based on the puck’s direction, this initialCollision bool acts as a toggle to keep the puck’s trail white for the duration of the “serve” (the puck’s initial velocity at the start of the volley), at least until it hits a paddle.

Alright, our paddle now inherits the color of the last paddle to hit it. This alone is a cool effect, but let’s give this a bit of in-game significance and gravitas. I want something to happen when the puck hits a wall to make the moment satisfying for the scoring player, and tense for the defending player. What if our puck, which has thematically been “wiping paint” off of the paddles, then paints the walls with the color it currently holds? If the red player scores, the blue player should have a visible reminder of the effects of poor defense (and vice versa). This could also provide visual feedback for the progress of the round, since we currently cannot tell if the round has just started, or if there is a 2-2 tiebreak volley coming up.

In order to do this, we need to make our BreakableWall scripts aware of their wall’s Renderer components. We should initialize the Renderer in Start(), and we should remember to reset the color of the renderer to white at the start of each new round via ResetHealth():


public class BreakableWall : MonoBehaviour {

    private Renderer render;


   

    void Start() {
        render = gameObject.GetComponent<Renderer> ();
       
    }

   

    public void ResetHealth() {
       
        render.material.color = Color.white;
    }
}


 

Alright, now we should write a method to actually change the color of the wall, depending on the color of the incoming puck’s trail. Let’s think for a moment… how are we going to change the wall’s color? Will we simply make it a one-to-one copy of the puck’s trail color? That might be a powerful visual mark, but it doesn’t serve the purpose of showing us the progress of the game. We need a method that takes an input color, mixes it with the current color, and returns the result. So if our puck’s trail color is red, the first time it hits the white wall, the wall will turn pink. The second hit will produce a color that is a mix between pink and red, and then pink-red + red, and so on. The ratio of white to red each hit should be as follows:

  • 100 / 0
  • 50 / 50
  • 25 / 75
  • 12.5 / 87.5
  • etc.

So, how can we add/subtract colors using math? Every color that passes through your computer can be interpreted using some variation of four individual components. RGBA use 0 – 255 value sliders for the amounts of red, green, and blue in a color, plus a fourth value slider for the color’s apha, or transparency. The RGBA color [0, 255, 0, 255] is pure, opaque green. The RGBA color [100, 0, 100, 50] gives a slightly transparent purple color. If we could add the individual values of these four components together, we could potentially mix two colors. We can make a new script called ColorMixer to do this. ColorMixer will be a static class, meaning it doesn’t associate with any one particular object.

To conceptualize static classes, imagine a school wants to keep track of the average GPA of all of its students. To do this, it needs to convert A-B-C-D-F grades or 0-100 point grades into a 0.0 – 4.0 GPA scale. The administrators and teachers decide to make a handy conversion sheet, where any style of grade can be input into the sheet, and the sheet will convert the input into GPA. One teacher who uses an A-B-C system can type “A” into the sheet, and the student’s grade will be recorded as 4.0. Another teacher can type 25/100, and that poor student’s grade will be recorded as 1.0.

Thinking about programming, static classes can be extremely useful as “tool” or “helper” classes. More importantly, static classes, like the GPA calculator, don’t “belong” to any particular object, or student. As such, they only use static variables and methods, and they don’t need to be instanced before they are used. By instanced, I am referring to what we do when we write code like this:


using System.Collections;
using UnityEngine;

public class MakeAThing : MonoBehaviour {

    public GameObject thing;

    void Start() {
        thing = new GameObject(Something);
    }


 

Static classes cannot be instantiated, or “made”, like this thing was made. Back to our project, let’s write our code for the class ColorMixer:


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

public class ColorMixer : MonoBehaviour {

    public static Color ColorMix (Color colorOne, Color colorTwo) {

        float r = 0;
        float g = 0;
        float b = 0;
        float a = 0;

        r = (colorOne.r + colorTwo.r) / 2;
        g = (colorOne.g + colorTwo.g) / 2;
        b = (colorOne.b + colorTwo.b) / 2;
        a = (colorOne.a + colorTwo.a) / 2;

        return new Color(r,g,b,a);
    }
}


 

Alright, we simply take the four values of each color supplied to the method, and return a color that is composed of the averages of those four value pairs. Let’s use our new static class in BreakableWall by calling it a new public method called ColorMark:

    public void ColorMark(Color puckColor) {
        render.material.color = ColorMixer.ColorMix (render.material.color, puckColor);
    }

We will then let Puck execute this method every time it collides with a breakable wall. Specifically, we will make this a trigger event, meaning that we will use OnTriggerEnter2D instead of OnCollisionEnter2D. We should check the “trigger” boxes in the inspector for the box collider components of both walls, if we haven’t done so already. Turning these colliders into triggers means that the walls will no longer detect physical collisions or interact physically with the puck, but rather they will send some message to the script for custom interactions. This means that once the wall is hit, the ball will continue to move through it as though the wall doesn’t exist. This is fine, and I explain this choice in a moment. Let’s add our wall-painting effect to the OnTriggerEnter2D method in Puck:

    void OnTriggerEnter2D (Collider2D other) {
        if (other.gameObject.tag == Left Wall || other.gameObject.tag == Right Wall) {
            other.gameObject.GetComponent<BreakableWall> ().damage();
            other.GetComponent<BreakableWall> ().ColorMark (trail.startColor);
            isDestroyed = true;
        }
    }

If everything goes well, we should be able to see the impact of our puck on the walls, and the walls should reset their colors at the end of each round. However, it’s still a little strange to see the puck float through the wall, and the trail effect is now acting strange! We can fix this by resetting the trail at the start of each volley, as well as the puck’s sprite:


public class Puck : MonoBehaviour {

   
    private SpriteRenderer sprite;
    private TrailRenderer trail;

   

    public void Initialize() {
        body = gameObject.GetComponent<Rigidbody2D> ();
        sprite = gameObject.GetComponent<SpriteRenderer> ();
        trail = gameObject.GetComponent<TrailRenderer> ();
       
    }

   

    void OnTriggerEnter2D (Collider2D other) {
        sprite.enabled = false;
        StartCoroutine(TrailKill());
        if (other.gameObject.tag == Left Wall || other.gameObject.tag == Right Wall) {
           
            other.GetComponent<BreakableWall> ().ColorMark (trail.startColor);
        }
    }

    IEnumerator TrailKill() {
        // wait a little bit so the trail can finish sinking into the point of impact before resetting it         yield return new WaitForSeconds (trail.time + 0.1f);
        trail.enabled = false;
    }

    public IEnumerator ResetPosition() {
        gameObject.GetComponent<Collider2D> ().enabled = false;
        yield return new WaitForSeconds (timer * 0.5f + 1); // one for a breather and then the countdown (1/2 second for each tick)
        gameObject.GetComponent<Collider2D> ().enabled = true;
        body.velocity = Vector2.zero;
        gameObject.transform.position = Vector2.zero;
        sprite.enabled = true;
        trail.enabled = true;
        trail.startColor = Color.white;
       
    }

   
}


 

We disable the sprite upon hitting a wall, and wait just a bit longer to disable the trail. This gives the effect of the trail “sinking into” the wall, imbuing it with color. We then enable those components once more in the ResetPosition() method.

The final touches of our visual effects should involve the top and bottom walls, I think. After all, the puck interacts with them way more than it does with the left and right walls. Instead of mixing colors with these walls, it would be cool if the puck could instead cause the top and bottom walls to flash the color of the puck, like pin-ball machine bumpers. If you haven’t already, give the top and bottom walls a plain white material (you can copy or use the same material that the side walls have). We will also give them a script called ColorFlash, which needs to do a few things for us:

  • reference the wall’s material, so we can change it’s color
  • reference two colors: the natural wall color, and whatever color the incoming puck brings when it hits the wall
  • A flash time, or the total time it takes for the wall to flash from white -> puck’s trail color -> white
  • A variable for the current time that acts as a control timer. We will transition from white -> puck’s trail color -> white, only if our control timer has reached a certain point
  • A variable that represents half of the flash time. For the first half of the flash time, we will transition from white to the puck’s trail color. For the second half of this time, we will change the color back to white.
  • We will let the Update() method handle the flash itself, because Update runs according to the in-game time. However, the puck will access this class via a public Flash() method, which “sets the timer” and assigns the puck’s trail color to the second Color variable we referenced.
  • Finally, we might want a Reset() method to change the wall’s color to white at the start of each volley, just in case

 


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

public class ColorFlash : MonoBehaviour {

    private Renderer render;
    private Color originalColor = Color.white;
    private Color puckColor = Color.white;

    [HideInInspector]
    public bool impacted;
    private bool cooldown;
    [SerializeField]
    private float flashTime = 0.1f;
    private float limit;
    private float controlTime = 0f;

    void Start() {
        render = gameObject.GetComponent<Renderer> ();
        originalColor = render.material.color;
        limit = flashTime / 2;
    }
    void Update() {
        if (controlTime > limit  0.05f && impacted) {
            impacted = false;
            cooldown = true;
            controlTime = 0f;
            render.material.color = puckColor;
        }
        if (controlTime < limit && impacted) {
            render.material.color = Color.Lerp (render.material.color, puckColor, controlTime);
            controlTime += Time.deltaTime * 2;
        }
        if (controlTime > limit  0.05f && cooldown) {
            cooldown = false;
            controlTime = 0f;
            render.material.color = originalColor;
        }
        if (controlTime < limit && cooldown) {
            render.material.color = Color.Lerp (render.material.color, originalColor, controlTime);
            controlTime += Time.deltaTime;
        }
    }
    public void Flash (Color hitColor) {
        puckColor = hitColor;
        impacted = true;
        controlTime = 0f;
    }
    public void Reset() {
        render.material.color = Color.white;
        Debug.Log (Resetting Top Wall and Bottom Wall colors);
    }
}


 

Dealing with this in update is a little messy, and it requires two boolean variables to act as toggles, but it works. I challenge those of you following along with this tutorial with a second “homework” assignment: see if you can rewrite this code using a switch statement. Anyway, we have completed out final visual effect! Let’s reference these walls in our Game Manager (don’t forget to drag the game objects from the scene view into the proper inspector fields!):


public class GameManager : MonoBehaviour {

   
    [SerializeField]
    ColorFlash topWall, bottomWall;
   

    void Start() {
       
        topWall = GameObject.FindGameObjectWithTag(Top Wall).GetComponent<ColorFlash>();
        bottomWall = GameObject.FindGameObjectWithTag (Bottom Wall).GetComponent<ColorFlash>();

    }

    void FixedUpdate() {

        if (leftWall.isBroken || rightWall.isBroken) {
           
            topWall.Reset ();
            bottomWall.Reset ();
            print(New Round);
        }
    }


 

To actually see this effect, we’ll remind our Puck script to send the appropriate message to any objects with ColorFlash that it bumps into:

    void OnCollisionEnter2D (Collision2D other) {
       
        if (other.gameObject.GetComponent<ColorFlash> ()) {
            other.gameObject.GetComponent<ColorFlash> ().Flash (trail.startColor);
        }
    }


 

Balancing and Tweaking

Only a few more small changes left to go! The first is more of a gripe – I hate when the ball gets “trapped” in a near-vertical trajectory, bouncing off the walls at 90 degree angles and taking forever to reach the other player. Let’s give ourselves the benefit of the doubt that we won’t cheat, and add in a “level out” button that resets the velocity of the puck mid-match. We obviously only want this button to work if the puck is traveling up and down at such a steep angle that it would get “stuck”. Let’s add a method in our Puck class that checks if this is the case, and returns the result as a bool variable:

    public bool verticalOrientation() {
        float angle = Mathf.Atan2(body.velocity.y, body.velocity.x) * Mathf.Rad2Deg;
        if ((angle > 80 && angle < 100) || (angle > 170 && angle < 190)) {
            return true;
        }
        return false;
    }

Then in the GameManager, we can add another input in our Update() method, just after our pause button:

    void Update() {

        …

        if (Input.GetKeyDown (KeyCode.Space) && puck.verticalOrientation()) {
            float yDir = 0;
            if (puck.body.velocity.y > 0) {
                yDir = puck.originalSpeed / 2;
            } else {
                yDir = puck.originalSpeed / 2;
            }
            puck.body.velocity = new Vector2 (puck.originalSpeed, Random.Range(0, yDir));
        }
    }

There, no more excruciatingly long waits between intense volleys just because the puck gets stuck! What else can we improve upon? I think we could make the AI player a little more adaptive, so that the more we win against it, the faster and more difficult it becomes. Conversely, if our Pong skills are not up to par, the AI should have pity on us and slow down a bit while we get our bearings. In GameManager, we can add the following code to FixedUpdate():


    void FixedUpdate() {

        if (leftWall.isBroken || rightWall.isBroken) {
            
            if (leftWall.isBroken) {
                print (Player 2 has won the round!);
                if (rightPlayer.GetComponent<PlayerAI> ()) {
                    rightPlayer.GetComponent<PlayerAI> ().speed–;
                    print (The AIs speed has decreased to  + rightPlayer.GetComponent<PlayerAI> ().speed);
                }
            }

            if (rightWall.isBroken) {
                print (Player 1 has won the round!);
                if (rightPlayer.GetComponent<PlayerAI> ()) {
                    rightPlayer.GetComponent<PlayerAI> ().speed++;
                    print (The AIs speed has increased to  + rightPlayer.GetComponent<PlayerAI> ().speed);
                }
            }
                
           
        }

       
    }


 

Another cool feature we could add is screen shake, say whenever a wall is hit. Screen shake, when used sparingly, can add impact and feedback to significant moments during play. Let’s add a script to the Main Camera called ScreenShake, and give it the following code:


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

public class ScreenShake : MonoBehaviour {

    private Vector2 currentPosition;
    private Vector3 initialPosition;
    [HideInInspector]
    public float time;
    [Tooltip(How hard do you want the camera to shake?)][Range(0, 10)]
    public float intensity;
    [Tooltip(How quickly should the camera cooldown?)][Range(0, 10)]
    public float cooldownRate;

    void Start() {
        currentPosition = gameObject.transform.localPosition;
        initialPosition = currentPosition;
    }

    void Update() {
        if (time > 0) {
            currentPosition = initialPosition + Random.insideUnitSphere * intensity;
            time -= Time.deltaTime * cooldownRate;
        }
        else {
            time = 0f;
            currentPosition = initialPosition;
        }
    }

}


 

By default, the shaking time is 0. We can add a little time, maybe half a second, in BreakableWall, whenever the wall takes damage. That time will tick down in the screen shake script, and cause shaking until it reaches 0 again. In BreakableWall:


   
    private ScreenShake screen;

    void Start() {
        screen = GameObject.FindObjectOfType<ScreenShake> ();
       
    }

    public void damage() {
       
        screen.time = 0.5f;
    }
}


 

The final addition to our game will be… dun-du-du-dun… sound! Create a new script called SoundManager, and attach it to the Game Manager prefab. We also want to add two Audio Source components to the Game Manager. These will be our audio channels for sound effects (SFX) and music, respectively. Let’s add a reference to both in our SoundManager script, as well as a few methods of playing sound. Our first method will take a single audio clip as a parameter and play it as a sound effect. The second method will take an array of audio clips and play one randomly, at a slightly random pitch for variety:


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

public class SoundManager : MonoBehaviour {

    public AudioSource fxSource;
    public AudioSource musicSource;

    public void PlaySingle (AudioClip clip) {
        fxSource.clip = clip;
        fxSource.Play ();
    }

    public void PlayRandom (AudioClip[] clips) {
        int randomSound = Random.Range (0, clips.Length);
        float randomPitch = Random.Range (0.95f, 1.05f);

        fxSource.pitch = randomPitch;
        fxSource.clip = clips [randomSound];
        fxSource.Play ();
    }
}


 

Let’s test out our sound manager by giving a sound to the countdown text at the start of each volley. Open GameManager, and add a reference to the SoundManager. Then, add a sound clip of your choice and play the clip through the SoundManager in the Countdown() method:


public class GameManager : MonoBehaviour {


    SoundManager soundManager;
    [SerializeField]
    private AudioClip countdownClip;
   

    void Start() {
       
        soundManager = GameObject.FindObjectOfType<SoundManager> ();
        canvas = GameObject.FindObjectOfType<Canvas>();
       
    }

    IEnumerator Countdown() {
       
        for (int i = puck.timer; i > 0; i) {
           
            soundManager.PlaySingle (countdownClip);
            yield return new WaitForSeconds (0.5f);
        }
       
    }
}


 

I chose to make a sound clip with the resource BFXR. Save your clips in an Audio folder, and drag the Countdown Clip to the proper field in the inspector:

GMSound

Also, we can add a main theme song to our game by opening the second audio source (our music source) and adding the AudioClip song of our choosing. I’m rather fond of a track I found from the Free Music Archive, “Electro Swing” by Creo. Be sure to select “Loop” so that the song repeats for extended play sessions. I’ve also pitched the song down a bit in the editor and lowered the volume, for a less bombastic presence in the final game.

Music.PNG

 

Go ahead a make a few more sounds with BFXR or some other software, and return to Unity when you’ve finished. We’ll add all of those sounds into an array in the Puck class, so that every time the puck hits something, a random sound from its audio list will play. We can also choose one particularly powerful sound to trigger every time a wall is hit.



public class Puck : MonoBehaviour {

   
    private SoundManager soundManager;
    public AudioClip[] hitSounds;
    public AudioClip scoreSound;
   

    public void Initialize() {
       
        soundManager = GameObject.FindObjectOfType<SoundManager> ();
    }

    void OnCollisionEnter2D (Collision2D other) {
       
        soundManager.PlayRandom (hitSounds);
    }

    void OnTriggerEnter2D (Collider2D other) {
       
        soundManager.PlaySingle (scoreSound);
    }

   

}


 

Let’s drag our clips to the appropriate fields in the Puck prefab:

PuckSounds

 

One final adjustment. Let’s add a few volume options in the GameManager so that we can adjust the volume of the sounds or mute the music:


    

void Start() {
       
        print (W/S to move player 1. Up/Down Arrows to move player 2. M mutes music. Period increases volume by 10%. Comma decreases volume by 10%. Escape pauses the game);
    }

    void Update() {

        …

        if (Input.GetKeyDown (KeyCode.M)) {
            soundManager.musicSource.mute = !soundManager.musicSource.mute;
        }

        if (Input.GetKeyDown (KeyCode.Period)) {
            soundManager.musicSource.volume += 0.1f;
            soundManager.fxSource.volume += 0.1f;
        }

        if (Input.GetKeyDown (KeyCode.Comma)) {
            soundManager.musicSource.volume -= 0.1f;
            soundManager.fxSource.volume -= 0.1f;
        }
    }


 

There you have it! We’ve finished implementing our ideas onto the classic game of Pong, and we’ve created a pretty fun, unique experience in the process. Of course, that’s not to say that these ideas haven’t been seen before, but rather that we managed to realize them in unfamiliar and exciting ways. I hope you will take the time to accept the challenges I gave you in this lesson, and even add your own ideas into this project. Feel free to use any resources from this lesson in your own projects, and if you liked this tutorial, please check out this site regularly for new content! If you have any comments, questions, or suggestions, please leave a comment below or contact me directly. Thanks for reading, and I’ll see you next time!

PUBG: MOBA or RPG?

Player Unknown’s Battlegrounds, or PUBG, is shaking up Twitch streams and eSports competitions alike with it’s surprisingly inventive take on the online battle arena. It has even overtaken Blizzard’s Overwatch in player count recently, with an all time peak of a little under one million simultaneous players! What could have been thrown out as yet another half-baked MOBA starring faceless soldiers was instead treated with a handful of nuanced design choices that allowed it to thrive. By blending the arena mechanics of a MOBO genre with the tactical, squad-based approach of military shooters, PUBG has managed to find a sweet spot of strategy amidst a tense rush of action.

It should be point out that the aforementioned genres have all been skewing themselves towards more traditional markets in the past few years. Games like Overwatch, Gigantic, and Paladins have tried to provide family-friendly answers to the well-established, jargon-heavy, genre juggernauts League of Legends and DotA 2. Similarly, the survival genre became surprising ubiquitous when Minecraft exploded onto the scene a few years ago, warranting a buy-out from Microsoft, global distribution, and wide platform availability. This genre, once known for its unforgiving, strategy-mandating gameplay, has managed to tend toward the casual consumer market ever since.

Despite all of this, PUBG has found a vast swell of playership by dragging these genre’s back through the sticky-sweet mud of their popularity to a more hardcore,  ever accessible era. Even the setting feels like a call back to earlier games – Soviet buildings and a lack of laser guns (sorry, FPS genre) harken back to a time where video game’s relied on their mechanics to carry an experience. If Solitaire, Pong, and Tetris can be played and enjoyed to this day on a handful of mechanics alone, then graphics and content should be viewed as condiments over the main course. None of this is to say that PUBG is lacking in any of the areas mentioned – for all of it’s flaws and bugs as a mod-inspired indie-effort, it’s truly well put together and addictive to its core.

So, which mechanics does PUBG boast that are “freshening up” the survival or MOBA genres? For starters, you are literally parachuted onto the game area at the beginning of each match. This is a dual purpose enhancement over the team spawns of games like Call of Duty or the predictable spawn areas in games like League of Legends. PlayerUnknown is giving players an opportunity to appreciate the world from a perspective infrequently offered by survival games. This bird’s eye view of the world is both a prideful demonstration of PUBG’s accomplishments, as well as an essential choice to every player in the aircraft.

“Where will my story be told?”

Do we drop near the abandoned cargo site to contest other players for valuable resources? Do we lay low and hide out near the farmlands, kept alive not by gear, but by distance from those who would try to hunt us? Any number of potential situations could arise from a decision to drop on the outskirts of the map or right near its center, and all of these are influenced by the choices of other players. Assuming the player base doesn’t converge on a particular meta strategy that abandons some areas in favor of others, this leaves an almost desk-shuffling sized probability of new experiences every round. With one fell swoop of cinematic and design genius, PUBG sprints past predictability and paces itself next to the classics.

Of course, what talk on PUBG’s design would leave out the circle of death? In the game, a blue, electrical field surrounds the map, providing constant damage over time to players unlucky or unskilled enough to be caught in its area of effect. Thankfully, this feature escaped a life of “gimmickiness” (that’s a fun word to say) by shrinking, with smaller circle randomly being placed within one another until the final moments of the game see the handful of players left tensely dueling mere feet from one another. No matter where the players land at the start of the game, they are all either brought together or killed by its end.

Gifted RPG designers spend a particularly long amount of time and energy on balancing true role playing with the desire to tell a story. J.E. Sawyer in particulr does a remarkable job of demonstrating this balance as the systems balancing designer on games like Fallout New Vegas and Pillars of Eternity. I’d highly recommend checking out his GDC talks on both games on Youtube. Miraculously, PUBG of all games manages to strike a balance here that, while not as deep as an old Obsidian PC classic, manages to be serviceable to a much wider audience. This, I think, is the beauty of a game like PUBG. At no point while playing are you told to be anything more a soldier looking out for his or her life, and yet the game still feels like it honors player choice more than any of its peers.

As always, thanks for reading, and let me know what you think about PUBG’s design in the comments!

Reinventing the Wheel with Pong

giphy

As over-saturated as Pong tutorial are, I feel that making one is almost an inaugural act, borne partly from necessity and partly from convenience. However, I don’t want to bore you with details long beaten to death by thousands of other coders and developers. Rather, I thought we could use Pong as a template for practicing how to implement our ideas effectively. By the end of this tutorial, we will have created a Frankenstein’s monster of small game mechanics, effects, and balancing features over the skeleton of a classic 70’s arcade game. We will use a method of development here that involves having a functional, presentable prototype at each step of the process. I believe this to be good practice when developing your projects, for a few reasons:

  1. A working prototype = immediate feedback. You won’t spend hours and hours working on many different pieces of a project, only to realize at testing time that those pieces don’t play well together. Instead, you can develop, test, and iterate whenever you want.
  2. Ideas change. When we get into to the tutorial, you’ll see one script named “Breakable Wall”. Nowhere in the game that we’ll make is this wall ever broken, but at the time, I had an idea that it could be. I left the name in the project to remind you that it’s not finished. The perfectionists among you may sympathize with the idea that few projects ever are.
  3. Time is a challenger. It might take you a couple hours to write the code for a platformer. It might take days to understand and add splat-mapping to your game (think Splatoon, or the incredible indie platformer Ink). If your deadlines are set by AAA publishers, busy lives, or the desire to escape a Ramen-only lifestyle by getting your game to market ASAP, time can begin to feel quite antagonistic. It’s more important to have a working product that can be updated, patched, or expanded upon later than it is to release a half-baked, buggy mess of a product because you spent all your time perfecting a single mechanic.
Splatoon.gif

Retexturing the environment of Splatoon with splat-mapping. Before this core mechanic was added, the developers at Nintendo had to create basic character movement and level mock-ups in order to test later  additions they made to the game, including level-painting.

Ink 2.gif

Ink, made by ZackBellGames, would be perfectly serviceable as a difficult, minimalistic indie-platformer in its own right. Fast paced, responsive movement, intuitive level design, and a challenging difficulty curve could have been enough. Even so, the most brilliant piece of Ink‘s design is its coloring mechanic. Every jump, environmental collision, and satisfyingly “popped” enemy sprays the level with paint, elucidating platforms and encouraging careful navigation and exploration.

Without further ado, let’s try to practice what I’ve been preaching and make Pong already! I’ve posted all of the assets used in this project here on GitHub. Feel free to get Unity and follow along, or use any of this code in your own projects.  If you have Unity Web Player installed, you can even play the game from you browser!

(Note: I’ve encountered some issues with getting the Unity Player working via WordPress, so I went ahead and uploaded the build files to GitHub here. Just download the index.html file and open it in your browser to play the game)

A fair warning, I’m testing a method of tutorializing this project that involves reviewing, rewriting, and resetting now-complete scripts and scenes. While I’ve tried hard to prevent any moments of confusion, suffice it to say that there are better ways of formatting and presenting these tutorials that I will be exploring for future posts. Thanks for bearing with me! Without further ado…

First things first, let’s create a new 2D project in Unity. We’ll start by going into the Assets folder and organizing our project-to-be with a few folders. Create a folder for “Scripts”, “Materials”, “Prefabs”, and “Audio”. You can also create a folder for “Scenes”, but I’ve just left our scene in the main Assets folder, because we’ll only have one. Oh, that reminds me: let’s save our scene and save our project. You should do this as often as you can!

AssetsFolder

If we look in the Hierarchy, we’ve got a Main Camera and not much else. We can keep the camera, but we’ll need to add a few more things before our project looks Pong-like. Let’s start with a puck:

  1. From the menu, select GameObject / 2D Object / Sprite and rename it “Puck”
  2. We want a circular puck, so let’s find a circular sprite image. Under the Sprite Renderer tab, add Unity’s default “Knob” sprite (you could always draw and import your own sprite image, if you felt so inclined)
  3. Set the Transform’s position to (0, 0, 0) and the scale to (3, 3, 1). Whenever we create a puck, we want it to start out in the center of the screen, and we want it to be big enough to be followed during particularly tense, fast-paced volleys.
  4. Speaking of movement, Unity provides a physics engine for use, but all things that use Unity physics require a Rigidbody. Let’s add a Rigidbody2D component using Add Component / Physics2D / Rigidbody2D.
  5. Let’s change the Mass of our puck to 0.1, and cut the linear drag, angular drag, and gravity scales down to 0. Our puck will now be unaffected by gravity, air resistance, or inertia (for the most part).
  6. Change the Collision Detection to “Continuous” and the Interpolate to “Interpolate”, to remind the physics engine to pay extra attention to the puck’s interactions with its environment. We’ll also check “Freeze Z rotation” (not necessary, but if you’re working on a 2D platformer, checking this box may become good habit, as it prevents your characters from tipping over onto their faces).
  7. Go to Add Component / Physics2D / CircleCollider2D and add a circle collider to the puck. Rigidbody might help Unity’s physics engine detect the puck, but a collider will communicate with the other objects in our game (the paddles and walls) that our puck has just bumped into them.
  8. Change the radius until it snugly covers the puck (I found 0.09 to be a good radius for this sprite).
  9. Create a new Material in the Materials folder called “Puck”. Set the Shader field to Sprites/Default and the tint to white. Drag it over the puck in the scene view.
  10. Hmm… we can make our sprite bouncy using Unity’s physics material component. In your Materials folder, right click and create a new Physics Material 2D. Name it whatever you like, but in the Inspector, change the Friction value to 0 and the Bounciness value to 1. This will add a bit of pluckiness to our puck’s movement, and allow it effortlessly glide around the field, bouncing off of other collider-wearing objects. Our puck should look like this now:

Puck1

Now that we have a puck, let’s add a couple paddles. For the sake of ease, we can add a rectangular object to our scene with GameObject / 3D Object / Quad. This is by no means an ideal solution, and keeping the quad’s mesh in mind adds a little more overhead cost to our performance, but for the purposes of our tiny game, it’s irrelevant. Name it something like “Player One”.

  1. Let’s set the paddle’s position to (-6, 0, 0) so that it’s on the left side of the screen, and set its scale to (0.3, 4, 0).
  2. Next, let’s add on our Rigidbody2D. We’ll set the mass to 1 (10x heavier than the puck, for posterity), as well as the linear and angular drag.
  3. We’ll freeze the X position so that the paddle can’t move itself left or right, and again freeze the Z rotation so it doesn’t windmill around every time it hits the puck (though that might be a fun *spin* on the Pong formula that you could implement yourself).
  4. Finally, change the collision detection to Continuous again, so that the physics engine remains ever-vigilant in monitoring our hits.
  5. Of course, we still need a collider. Instead of a Box Collider 2D, which would work perfectly fine for what we’re planning on doing, let’s try something a little different and add a Capsule Collider 2D to our paddle. The rounded corners of the capsule collider may provide smoother hits with our circular puck than the sharp edges of the box collider.
  6. Perhaps we should add some color to our paddle. Let’s create a new Material in our Materials folder. We’ll set the Shader field to Sprites/Default, and we’ll change the Tint to a nice blue color. Drag this material over to the Player One object in the game view or hierarchy, and now we have a nice blue paddle.

As Harry Nilsson famously wrote, “one is the loneliest number”, so let’s right click our player and duplicate it, renaming it to “Player Two”. While we’re at it, give both paddles the tag “Player” – we’ll use this a little later to help the puck tell the difference between our paddles and the walls. We’ll set the position of this second paddle to (6, 0, 0) to place it on the opposite side of the field. Let’s also copy our Player One material, rename it, and change the tint of the new material to a red color. Slap this red material onto Player Two, and your scene should look something like this:

SetUpPaddles.PNG

Finally, let’s add some walls to our scene. We’ll need four quads, and we’ll have to replace their Mesh Colliders with Box Collider 2D components. We can create an empty game object in the Hierarchy by right clicking and selecting Create Empty. Let’s do so, rename it “Walls”, and move our four quads into this “folder” object. Make sure the empty parent object “Walls” has a transform position of (0, 0, 0)! Next, let’s resize our quads to make the pong arena. I used to the following Position/Scales:

  • Left Wall
    • Position: (-8.5, 0, 0)
    • Scale: (1, 8.5, 1)
  • Right Wall
    • Position: (8.5, 0, 0)
    • Scale: (1, 8.5, 1)
  • Top Wall
    • Position: (4.75, 0, 0)
    • Scale: (18, 1, 1)
  • Bottom Wall
    • Position: (-4.75, 0, 0)
    • Scale: (18, 1, 1)

Also, we can make a new material called “Wall” by copying the Puck material, renaming it, and dropping it on each quad. Finally, let’s change the Background Color of our Main Camera to black, so we can clearly see our paddles and contrast the puck with the background. Our scene should begin to look familiar:

Field.PNG

*Don’t worry about the PlayerAI or UI Canvas prefabs , we’ll add those in later.

We can try to play this, but… nothing happens. Of course, we need scripts to tell our objects what to do! Let’s make three scripts to start out. Right click the Assets menu, and navigate to Create / C# Script. Name the scripts “Puck”, “Paddle”, and “GameManager” (don’t leave spaces in the names of your scripts!). Drag them all into the Scripts folder. Attach the Puck script to the puck, and the Paddle scripts to both paddles. As for the GameManager, create a new empty object in the hierarchy, name it “Game Manager”, set it’s position to (0, 0, 0), and tack on our new GameManager script to it. Now let’s open up all three files in Monodevelop so we can start editing them.

First, let’s get the paddles moving up and down. We have a few initial requirements:

  • We need to introduce our Paddle script to the paddle’s Rigibody2D
  • We need some way of getting input from the keyboard…
  • And we need a way to translate that input into movement
  • It might also help if we could use the Unity Editor to adjust our movement speed, so that we can tweak it if need be

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

public class Paddle : MonoBehaviour {

    private Rigidbody2D body;

    private float verticalAxis;
    public string verticalAxisName;

    public float speed = 10f;

    void Start() {
        body = gameObject.GetComponent<Rigidbody2D> ();
    }

    void FixedUpdate() {
        verticalAxis = Input.GetAxis (verticalAxisName);
        body.velocity = new Vector2 (0, verticalAxis * speed);
    }

}


Instead of using the traditional Vertical Axis provided by the Unity Input Editor, we’ll create our own axes and refer to them via the string “verticalAxisName”. Let’s make a couple different axes by going to Edit / Project Setting / Input. Right click on “Vertical” and select “Duplicate Array Element”. You can name these axes “PlayerOne” and “PlayerTwo” if you’d like, but I’m going to call them “VerticalWASD” and “VerticalArrow”. Change the Negative Button and Positive Button inputs to whatever you’d like each player to use in game. My axes look like this:

input.png

Now that we have that figured out, all we have to do is go in the Inspector and assign those axis names to our Vertical Axis Name field:

Axis.PNG

After we assign the input to our verticalAxis float, we can affect the velocity of our paddles using the line:

        body.velocity = new Vector2 (0, verticalAxis * speed);

I’ve set the paddle speed to 10, but this variable is public so that you can adjust it in the editor. We can now move our paddles up and down! Barring any major catastrophes, they’ll stop moving when they hit the walls. Also, it’s worth noting that all code affecting Rigidbody velocities and other aspects of Unity’s PhysX physics engine will written in FixedUpdate() as opposed to Update(). Update runs every frame, while FixedUpdate runs every time the in-game physics updates. If your game involves millions of physics-enable components interacting with one another, the physics updates might happen quite slowly. However, if your physics engine is only keeping track of a few objects at a time, it might update hundreds of times a second. That’s way faster than the 50-60fps we’d expect from Update. If, for some reason, our game was running slowly AND we put our physics functions in Update(), our puck might phase through the paddles like it had a quantum grudge against Newtonian physics!

Let’s see if we can get the puck to move now, too. Again, let’s list our requirements:

  • We need a Rigidbody2D body for the script to move around
  • We need an initial speed that the puck starts with
  • It would help if our puck shot out at a random angle at the start, which will presumably be the center of the screen at (0, 0, 0)
  • When the puck hits a paddle,  we want it to bounce back, maybe even a little faster than it arrived. Remember, because a Rigidbody has mass in Unity’s physics engine, the puck will naturally just thud against the paddle, lowering its velocity significantly. Instead, we want to circumvent this by giving it a new normalized speed. There are plenty of ways to make pong, and while this one might not be the most efficient, it’s purpose is to introduce some math concepts that you might need when dealing with game physics.

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

public class Puck : MonoBehaviour {

    [HideInInspector]
    public Rigidbody2D body;

    public float originalSpeed;

    public float speedIncrement;

    private float newSpeed;

    void Start() {
        body = gameObject.GetComponent<Rigidbody2D> ();
        newSpeed = originalSpeed;

        float direction = Random.Range (0, 2);
        float yDir = direction  1;
        if (direction >= 1) {
            body.velocity = new Vector2(originalSpeed, yDir);
        } else {
            body.velocity = new Vector2(originalSpeed, yDir);
        }
    }

    void OnCollisionEnter2D (Collision2D other) {
        if (other.gameObject.tag == Player) {

            float newY = (transform.position.y  other.transform.position.y) / other.collider.bounds.size.y;
            float newX = transform.position.x  other.transform.position.x;

            body.velocity = new Vector2(newX, newY).normalized * newSpeed;    
            newSpeed += speedIncrement;
        }
    }
}


Okay,  let’s break this down. We have three variables for controlling the speed of the puck over time, and a public RigidBody2D. We’ve added the [HideInInspector] tag because we only want this component to be visible by other scripts, and not in our beautiful editor.

In Start, we’ve set the initial trajectory of the puck to a random value, and given it a 50% chance to shoot either right or left, at a slightly random angle. Then, we’ve added the OnCollisionEnter2D method, which tracks every other collider the puck hits. If the puck hits something tagged with “Player”, it’ll know to readjust its speed in the opposite direction, and also go a little faster. To get the new Y component of the velocity, we can divide the difference in height between the center of the puck and the paddle by the total height of the paddle’s collider. This will allow the puck’s reflection to be controlled a bit by the paddle’s position, like in the original pong. Now if the puck hits the top of the paddle, it should reflect upwards, regardless of its angle of impact. We can set the body’s velocity to this new vector, normalize it, and multiply it by the puck’s speed. Finally, we add the speed increment to the newSpeed, so the pace of the game gets faster over time.

Before we dig into the GameManager script, let’s take our existing objects and turn them into prefabs. We can use the prefabs like blueprints, so that scripts can read them and create the objects they represent. Once the object is moved from the hierarchy to the assets folder, the object names should turn blue:

prefabs.PNG

Now let’s open up the GameManager script. What do we want to accomplish?

  • It would be nice if the GameManager (GM) was in charge of “setting the scene”, so to speak. That is to say, we should be able to delete everything in the scene editor and rely on the GM to organize our paddle positions, pucks, and walls correctly by using prefabs. This is also good practice when creating games that involve restarting, saving, or serializing data. You don’t want an extra puck when you restart your game, so let’s clean up that messy scene editor!
  • We’ll need a way for the GM to track if someone scores a point. Let’s create a new script called “BreakableWall”, and attach it to our left and right walls. We’ll also tag those walls “Left Wall” and “Right Wall”. We’ll edit it later, but for now, we’re going to treat that script like a goalie of sorts. Don’t worry if Mono can’t find the variables and methods here, they haven’t been created yet.
  • We should be able to pause our game, in case something quirky happens and we want to have a freeze-frame look at everything.
  • We can rename the Start() method in Puck.cs to Initialize(), and call it when the GM first creates the puck.
  • Finally, if we score a point, we need to let our puck know that it’s needed back at the center of the screen.We’ll refer to a currently non-existent method called ResetPosition() and a currently non-existent bool called isDestroyed that controls it, both of which we can add to Puck later.

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

public class GameManager : MonoBehaviour {

    [SerializeField]
    BreakableWall leftWall, rightWall;
    [SerializeField]
    GameObject leftPlayer, rightPlayer;
    [SerializeField]
    Puck puck;

    void Start() {
        
        leftWall = GameObject.FindGameObjectWithTag(Left Wall).GetComponent<BreakableWall>();
        rightWall = GameObject.FindGameObjectWithTag (Right Wall).GetComponent<BreakableWall>();

        leftPlayer = Instantiate(leftPlayer, new Vector3 (6, 0, 0), Quaternion.identity);
        rightPlayer = Instantiate(rightPlayer, new Vector3 (6, 0, 0), Quaternion.identity);

        puck = Instantiate (puck, Vector3.zero, Quaternion.identity);

        puck.Initialize ();
    }
        
    void Update() {
        
        if (Input.GetKeyDown (KeyCode.P) || Input.GetKeyDown (KeyCode.Escape)) {
            if (Time.timeScale == 1) {
                Time.timeScale = 0;
            } else {
                Time.timeScale = 1;
            }    
        }
    }

    void FixedUpdate() {

        if (leftWall.isBroken || rightWall.isBroken) {
            leftWall.ResetHealth ();
            rightWall.ResetHealth ();
            print(New Round);
        }
            
        if (puck.isDestroyed) {
            puck.isDestroyed = false;
            StartCoroutine (puck.ResetPosition ());
        }
    }
}


In the scene view, delete the paddles and the puck. Drag the prefabs from the asset menu into the matching fields in the Game Managers GameManager Script component in the inspector. We’ll leave all of the walls in the scene view, because we won’t plan on moving those around for the rest of the project.

Now let’s look at that code. We use Instantiate to “build” the players and the puck from their respective prefabs.  We also set a reference to the BreakableWall scripts on the left and right walls by searching for their tags in the scene. Note that we didn’t have to use the [SerializeField] tag on the leftWall and rightWall objects. You can assign objects in the inspector or through scripting, whichever you prefer. Monobehavior’s FindGameObjectWithTag method is actually pretty inefficient, but for a small project like this, it’s convenient enough.

In Update, we’re checking every frame to see if the P or Escape buttons are pressed, so either player can pause the game. Our pause feature works by literally stopping time within the game. Funny enough, if this is done in FixedUpdate, the game will freeze! Because FixedUpdate runs every time the physics engine updates, a call to stop time will also stop time in the physics engine, meaning that the game will never even notice your “unpause” command.

Remember, every change to a rigidbody or use of the PhysX physics engine should run in FixedUpdate, so that’s where we will monitor scores and reset the puck on collisions/triggers.

Now, to get this to work, we need to make BreakableWall functional. For our pong game, we’ll allow each wall to get damaged three times before the round is over. We’ll also add a method that will allow us the reset the “health” of the wall.



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

public class BreakableWall : MonoBehaviour {

    [HideInInspector]
    public int health;
    [Tooltip(How many points are needed to win a round?)][Range(1, 20)]
    public int maxHealth = 3;
    [HideInInspector]
    public bool isBroken = false;

    void Start() {
        ResetHealth ();
    }

    public void damage() {
        if (health  1 <= 0) {
            isBroken = true;
        } else {
            health -= 1;
        }
        print(gameObject.name +  damaged ( + health + / + maxHealth +  health remaining));
    }
        
    public void ResetHealth() {
        health = maxHealth;
        isBroken = false;
        print(Resetting Health of  + gameObject.name +  to  + maxHealth + / + maxHealth);
    }

}


 

Nothing too crazy here, except we added a tooltip and range tag to our maxHealth, and a couple of print statements to help us check in on the condition of our walls. In the editor, check the box for “Trigger” on the colliders of both the left and right walls. This will let the puck know when it hits the wall. Let’s return to Puck.cs and let it know we’ve been busy with BreakableWalls:


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

public class Puck : MonoBehaviour {

    [HideInInspector]
    public Rigidbody2D body;

    [Tooltip(Set the initial speed of the puck at the start of each round)]
    public float originalSpeed;
    [Tooltip(How much should the velocity increase from each bounce off of a paddle?)]
    public float speedIncrement;
    private float newSpeed;

    [HideInInspector]
    public bool isDestroyed;

    public void Initialize() {
        body = gameObject.GetComponent<Rigidbody2D> ();
        isDestroyed = false;
        newSpeed = originalSpeed;
    }

    void OnCollisionEnter2D (Collision2D other) {
        if (other.gameObject.tag == Player || other.gameObject.tag == AI) {

            float newY = (transform.position.y  other.transform.position.y) / other.collider.bounds.size.y;
            float newX = transform.position.x  other.transform.position.x;

            body.velocity = new Vector2(newX, newY).normalized * newSpeed;    
            newSpeed += speedIncrement;
        }
    }

    void OnTriggerEnter2D (Collider2D other) {
        if (other.gameObject.tag == Left Wall || other.gameObject.tag == Right Wall) {
            other.gameObject.GetComponent<BreakableWall> ().damage();
            isDestroyed = true;
        }
    }

    public IEnumerator ResetPosition() {
        body.velocity = Vector2.zero;
        gameObject.transform.position = Vector2.zero;
        newSpeed = originalSpeed;

        float direction = Random.Range (0, 2);
        float yDir = direction  1;
        if (direction >= 1) {
            body.velocity = new Vector2(originalSpeed, yDir);
        } else {
            body.velocity = new Vector2(originalSpeed, yDir);
        }
    }
}


 

We added some tooltips to clarify our speed variables in the editor. We also added a new bool called isDestroyed and set it to false in the Initialize method. This bool will act as a control, to let the puck know when it’s time to stay active in the scene, and when it’s time to reset itself at the start of a new point. To find the walls, our puck script will use a Monobehavior method called OnTriggerEnter2D. Remember how we set the triggers on our left and right walls earlier? The puck can now call the damage() function through the wall’s script whenever it enters the trigger area. Finally, we moved the random starting direction to a new method called ResetPosition(), afte setting the puck’s position and velocity back to their starting values.

Note that this method is an IEnumerator. Why? Well, Monobehavior uses smethign called a coroutine to call IEnumerator methods when we want to run a method and include a wait time. So, the next logical step would be to add a countdown timer, so that we can have a breather between points. Once our time ticks to 0, we want the puck to also finish resetting its position. So we’ll call both methods with Coroutines, and add a WaitForSeconds time to each so that they start and stop exactly when we want them to.

For our Countdown to be effective, we’ll want to see it. Let’s add a Canvas to the scene by going to GameObject / UI / Canvas. We’ll add a Panel child to the Canvas, and a Text child to the Panel. Drag it all into the prefabs folder.

First, we’ll add an image to the Panel. Use the UISprite image provided by Unity, and set the transform as follows:

  • Left: 325
  • Top: 160
  • Right: 325
  • Bottom: 160

UI.PNG

Next, adjust the size of the text so that it fits over the image. I’ve got the text size set to 80. Leave the text field blank, we’ll adjust what it says in the GameManager script.

Text.PNG

 

Alright, we’re all set up to go back into the GameManager script. Every time a point is scored and at the very start of the game, we want to run a text countdown from some number set by the Puck. This variable will be called timer, so let’s add that into Puck really quickly:

    [Tooltip(Adjust countdown seconds)][Range(1, 10)]
    public int timer;

We also need to reset the puck’s position. So we’ll call two Coroutines in Initialize, and every time the puck is destroyed in FixedUpdate:

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour {

    [SerializeField]
    BreakableWall leftWall, rightWall;
    [SerializeField]
    GameObject leftPlayer, rightPlayer;
    [SerializeField]
    Canvas canvas;

    Text text;

    [SerializeField]
    Puck puck;

    void Start() {

        leftWall = GameObject.FindGameObjectWithTag(Left Wall).GetComponent<BreakableWall>();
        rightWall = GameObject.FindGameObjectWithTag (Right Wall).GetComponent<BreakableWall>();

        canvas = GameObject.FindObjectOfType<Canvas>();

        text = canvas.GetComponentInChildren<Text> ();

        leftPlayer = Instantiate(leftPlayer, new Vector3 (6, 0, 0), Quaternion.identity);
        rightPlayer = Instantiate(rightPlayer, new Vector3 (6, 0, 0), Quaternion.identity);

        puck = Instantiate (puck, Vector3.zero, Quaternion.identity);

        puck.Initialize ();

        StartCoroutine(puck.ResetPosition());
        StartCoroutine (Countdown ());
    }
        
    void Update() {
        if (Input.GetKeyDown (KeyCode.P) || Input.GetKeyDown (KeyCode.Escape)) {
            if (Time.timeScale == 1) {
                Time.timeScale = 0;
            } else {
                Time.timeScale = 1;
            }    
        }
    }

    void FixedUpdate() {

        if (leftWall.isBroken || rightWall.isBroken) {
            leftWall.ResetHealth ();
            rightWall.ResetHealth ();
            print(New Round);
        }

        if (puck.isDestroyed) {
            puck.isDestroyed = false;
            StartCoroutine (puck.ResetPosition ());
            StartCoroutine (Countdown ());
        }
    }
        
    IEnumerator Countdown() {
        
        yield return new WaitForSeconds (1);

        Image background = canvas.GetComponentInChildren<Image> ();
        background.enabled = true;
        text.enabled = true;
        for (int i = puck.timer; i > 0; i) {
            text.text = i.ToString();
            yield return new WaitForSeconds (0.5f);
        }
        text.text = ;
        text.enabled = false;
        background.enabled = false;
    }

}


 

Remember to drag your UI prefab into the correct GameManager field in the inspector! Now we have to synchronize our puck’s ResetPosition timer to match the new countdown:

    public IEnumerator ResetPosition() {
        gameObject.GetComponent<Collider2D> ().enabled = false;
        yield return new WaitForSeconds (timer * 0.5f + 1); // one for a breather and then the countdown (1/2 second for each tick)
        gameObject.GetComponent<Collider2D> ().enabled = true;

We also disable the collider during this time, so that the puck doesn’t continue to interact with triggers or other objects.  Great! Assuming I haven’t made a huge ovesight in rewriting this code, this should bea playable game for two people. However, most of us don’t have a spare pong-enthusiast partner to play with, so let’s add an AI!

The only thing we really need the AI to do differently from the player is come up with its own vertical input. Instead of copying our Paddle script, let’s make two new scripts that inherit from Paddle. Make a PlayerController script and a PlayerAI script. We’ll cut anything that the AI can’t use in Paddle and move it to the new PlayerController script. So, Paddle should now look like this:


public class Paddle : MonoBehaviour {

    protected Rigidbody2D body;
    protected float verticalAxis;

    protected virtual void Start() {
        body = gameObject.GetComponent<Rigidbody2D> ();
    }


 

And our new PlayerController script should inherit from Paddle and hold onto all of the things we cut out from it. Replace the Paddl script components of both the Player One and Player Two prefabs with this new PlayerController script:


public class PlayerController : Paddle {

    [Tooltip(Use *VerticalWASD* or *VerticalArrow* to specify control scheme)]
    public string verticalAxisName;

    [Tooltip(Adjust the movement speed of the paddle)][Range(5, 20)]
    public float speed = 10f;

    void FixedUpdate() {
        verticalAxis = Input.GetAxis (verticalAxisName);
        body.velocity = new Vector2 (0, verticalAxis * speed);
    }

}


 

Alright, the PlayerAI should inherit from Paddle, too. It needs a movement speed, a method for moving up, and a method for moving down. It also needs some logic in FixedUpdate to tell it when to call these methods. That logic will depend on the position of the puck, so we should add a reference to the puck, too. We can override the Paddle’s Start method to include a reference to the puck, and then run the base Start method after. I’ll explain the resulting math in a bit, but first, the code:


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

public class PlayerAI : Paddle {

    [Range(1, 12)]
    public float speed = 7f;

    private float distanceRatio;
    private float lerpSpeed;
    private float resetSpeed;

    protected Puck puck;

    Vector2 paddlePosition;
    Vector2 puckPosition;

    protected override void Start() {
        puck = GameObject.FindObjectOfType<Puck> ();
        base.Start ();
    }

    void FixedUpdate() {
        
        paddlePosition = gameObject.transform.position;
        puckPosition = puck.transform.position;

        // the closer the distance between the puck and paddle, the closer to 1 the ratio gets
        // the further the distance between the puck and paddle, the closer to 0 the ratio gets

        distanceRatio = 1 / Mathf.Abs (puckPosition.x  paddlePosition.x);

        lerpSpeed = Mathf.Pow (speed, Mathf.Abs (paddlePosition.y  puck.body.position.y));
        resetSpeed = Mathf.Pow(2, Mathf.Abs(body.position.y));

        if (puckPosition.x < paddlePosition.x && puckPosition.x > paddlePosition.x) {            // inside bounds

            if (puckPosition.y > paddlePosition.y) {
                MoveUp (lerpSpeed);
            } else if (puckPosition.y < paddlePosition.y) {
                MoveDown (lerpSpeed);
            }

        } else if (puckPosition.x > paddlePosition.x || puckPosition.x < paddlePosition.x) {        // outside bounds
            
            if (0 > paddlePosition.y) {
                MoveUp (resetSpeed);
            } else if (0 < paddlePosition.y) {
                MoveDown (resetSpeed);
            }
        }
    }

    void MoveUp(float speed) {
        body.velocity = Vector2.Lerp (body.velocity, new Vector2 (0, 1) * speed, distanceRatio + 0.01f);
    }
    void MoveDown(float speed) {
        body.velocity = Vector2.Lerp (body.velocity, new Vector2 (0, 1) * speed, distanceRatio + 0.01f);
    }
}


 

The AI depends on a distance ratio – how far away the puck is from the AI’s paddle. The closer the puck is, the slower the paddle moves. This allows for the paddle to move smoothly, without jittering when the puck heads straight for it at a y=0 horizontal path. The AI also needs to transition its speed from where it is to where it can hit the puck. The lerpSpeed variable adjusts for the y difference between the two objects, and linearly interpolates between those values. This speed is passed to the move methods when the puck is within range of the paddle. When the puck is far into the enemy’s side of the field, or when it manages to slip past the AI, the AI will reposition itself to the center of the screen, using a resetSpeed that lerps between the AI and y = 0.

In order to play against the AI, we can just duplicate the Player Two prefab and replace it’s PlayerController script with the PlayerAI script. Drag the new PlayerAI prefab into the GameManager field for the player on the right side of the screen, and you’re good to go! (Don’t forget to set the AI’s speed, or it won’t move!)

 


 

Whew, we did it! We made pong! Of course, the story isn’t over. Using Pong as a base, we can practice implementing our own ideas. We will continue this lesson in the next post, where we will add color mechanics, screen-shake, music, sound effects, and some fun game balancing mechanics. You can get a head start by studying the finished project on GitHub, but I encourage you to add two or three of your own ideas and mechanics into the game.  If not, take a break, relax, and tune in next time for part two!

How to Implement Your Ideas (pt. 1)

Have you ever found yourself stuck in front of a monitor or notepad, contorting your mind and contriving overly complex, head-ache inducing solutions to simple problems? Have you ever asked a question that seemingly no resource in the world was equipped to answer? Maybe you’ve felt like the pop caricature of Thomas Edison, at the point in his life where he was inventing all of the failed light-bulb predecessors. In any case, my condolences – we’ve all been there at some point.

Implementing an idea is often the most difficult part of the creative process. Without the proper technique and approach, this process can become harmful to your efficiency, your mental health, and even the idea itself. Too often are good and unique ideas left half-finished or abandoned due to improper implementation. It’s a shame, really, that often the more novel and game-changing an idea is, the harder it will be to realize. After all, if the world has never before seen the likes of your idea, you’ll be hard-pressed to find resources on it.

While it may seem that no library, internet forum, or think tank could help you develop and debug your groundbreaking new adaptive-AI or game mechanic, consider for a moment that example about the invention of the light-bulb (historically accurate or not, we’ll roll with the colloquially told story). You can create 99 prototypes and succeed on the 100th attempt, but I think we can do better than that. First, let’s take an abstraction of our desired product.

Abstraction is the process we use for simplifying things to their base elements, so that those things can be understood easier. For example, if you ask me what I want for lunch, I might respond with an abstraction, “Italian”. You get the general idea of what I want to eat (maybe pizza, pasta, mozzarella, fresh tomatoes, etc.), even though “Italian” isn’t really an edible thing. What I want is directly tied to the abstraction of “Italian”, but the abstraction does not care about the details of my appetite. If instead I gave you the details and told you that I wanted chicken, green peppers, and onions, you wouldn’t know if I was referring to Italian, Mexican, or even Asian food. The details of my appetite are less informative and less helpful than the abstraction of my appetite when a request is made for where to eat.

So, if we’re to make a light-bulb for the first time in human history, we’re going to need it to do a few things:

  1. Create light
  2. Scale to create more or less luminous light
  3. Improve upon the duration of the contemporary torch or candle
  4. Be relatively portable
  5. Be relatively easy to install
  6. Be relatively easy to operate

This might not be the best abstraction of a light-bulb, but that’s okay. The point is that we now have goals to work towards. The emboldened words are common functions of many modern inventions, apps, and refinements, so feel free to re-contextualize this example in the present day as you read. Let’s start with creating light.

Creating and doing are often the most common functions of an idea. A toaster creates toast, a telephone “does” a call, and so on. Keep in mind that creation can be just as much about the product as it is about the experience. A game “creates” an experience as its product, whereas a restaurant creates a dish. To the right person, one product can be just as tangible as the other.

First, we should ask: has light ever been created before? Can I even imagine the existence of light? If the answer is yes, we’re all set to move on. If not, you should review that requirement and find another way of defining it. If you wanted to write an app that took every “Ort” in your phone and sent it to your contacts, you’d be stuck once you got to defining whatever an “Ort” is. As for light, that’s simple: Fire! Electricity! Bioluminescence!  We don’t need to care about the applications of these things. Torches and candles make fire, bed-sheets and thunderstorms make electricity, and scary fish are bioluminescent, but we aren’t hear to make any of those things. We want our own light source! However, we may have to borrow one of those three light-producing mechanisms…

Let’s look at fire. Can we manipulate fire in such a way that fulfills our requirements? It can create light, it can scale light, maybe we could improve the duration, it can be portable, it can be easy to install, and it can be easy to operate. Fantastic! Well… not really. See, fire may meet all of those requirements, but not at the same time. A fire that scales to create more light becomes bigger, hotter, and less manageable than a small. This means that the portability, ease of use, and ease of operation of fire-produced light are dependent on its scale. These aspects of fire are coupled, tied together in such a way that if one piece changes, the rest of the solution changes. This is messy, and while it worked a century ago, we can do better. Instead, let’s focus on finding a cohesive solution to our problem, wherein every requirement we have can be dealt with without modifying or changing the parameters of another requirement.

The classic movie Home Alone is whimsical and really quite fun. We can look to Macaulay Culkin’s character Kevin for inspiration on implementing cohesive solutions to our problems. In the movie, Kevin’s house is invaded by a couple of goofy criminals called the “Wet Bandits”. Kevin sets booby traps all throughout the house, all installed with the single abstract purpose of keeping people out. The booby traps are location specific (one above the door, one by the stairwell, one by the window, etc.), and have their own associated entrances to protect. The trap above the door does not depend on the trap in front of the door, but both work together harmoniously to provide plenty of laughs when the Wet Bandits open the door.

Compare Kevin’s booby-trapped house to Rube Goldberg’s famously complex and intertwined inventions, and you’ll begin to understand that layers upon layers of codependency and coupling can quickly add up to a needlessly complex, error-prone system for problem solving.

So we’ve taken what we know, applied it to our requirements, and found that it added in extra rules and requirements that we didn’t necessarily want. We could run this same experiment on bioluminescence, and discover the many problems associated with applying it’s light production to our requirements. That’s okay. Keep reaching for abstractions until you find what you are truly looking for.

Perhaps Edison started with fire, bioluminescence, lightning, and static electricity from his socks as possible methods of light production. Since lighting and static electricity are both electrical, we can abstract them down to plain ol’ electricity. In fact, we can abstract everything down to energy, if we truly wanted. Many scientists and engineers working under a microscope treat all things as some combination of mass and energy, because the problems they are trying to solve are so complex that such abstraction is not only convenient, but necessary!

If you’re ever stuck troubleshooting or in your creative process, try to take a step back and see if the room you’re standing in is even equipped with the things you need. If not, examine the house, and then the neighborhood, and the city, and so forth – eventually, you’ll be staring at every solution in the world, including yours. If you’re worried that “pulling a Descartes” and claiming cogito ergo sum about your problems is reductive, that’s okay. At the very least, it means the solution exists. At the most, your new vantage point can put old problems into fresh new perspectives. Keep reaching for abstractions until you find what you are truly looking for.

Check in later this week for pt. 2, where we will talk about cohesive, adaptable, and interdependent methods for tackling our list of problems and implementing our ideas. Thanks for reading!

Procedural Generation with Catlike Coding

 

I’ve been working through the tutorials on the website Catlike Coding, an incredible Unity resource created by Jasper Flick. The lessons assume you have some basic experience with C# and the Unity Editor, and pick up with basic script communication, editor customization, UI construction, basic tool creation, and object pooling. I’d recommend anyone interested in Unity3D try these initial projects. If you like them, great! There are 57 lessons here, not counting projects from older versions of Unity, enough to extensively cover editor familiarity for Unity initiates and dust off the minds of experienced Unity developers alike. In this post, I’ll take a brief look at one of the first fully-fledged projects in the series, the Maze Game. (My attempt at this project is on GitHub here, if you want to see the completed project in Unity)

 

 

To begin with, we’ll start with a procedurally generated maze. So, what does that mean?

Procedural: you’ve probably heard of this term in one of two gaming-related contexts, procedural generation and procedural programming.

Procedurally oriented programming (POP) is a style of problem solving that involves subdividing the problem into more manageable parts called functions until the problem can be solved. This might sound familiar – don’t we do this already with objects in Unity? Well, yes, in fact.

C# is an Object Oriented Programming (OOP) language much like Java, and both are used in Unity because it’s convenient to conceptualize a checkpoint or a player as an object. An object, for all intents and purposes, is just a box that holds a bunch of related methods and data. These boxes can interact, or be put inside one another, sure. But at its core, OOP is just a bunch of related POP functions (methods) and data collected into boxes called objects.

The benefits of POP are less resource intensive code, more interactivity with other languages and libraries, and often simpler code. OOP is generally less interactive (C# uses .NET, which mitigates this issue), but it is highly reusable and easier to update.

Procedural generation is the act of creating something using procedural rules. For example, asking a classroom of students to draw a picture of a person by starting with the feet, legs, body, arms, and finally the head, is an act of procedural generation. You are establishing a set of rules, or procedures, that must be followed by the program in order to produce something (20 crudely drawn stick figures, in this case).

For gaming, this sort of process most often appears in environmental design. Designers of large, open world games benefit by using procedural generation to create generic landscapes, before manually going in and painting their world with details. Replayable games like roguelikes and survival-types rely on procedural generation to create uniquely random levels each time you play… Well, almost random. It’s very difficult to create wholly unique environments only using code. Instead, they create a list of things that a level-generator script can use to assemble a new level. These things can be whole room prefabs down to little variations in pots and crates.

This is what we will be using to creating a maze. We’ll have a floor piece, a few variants of walls, and a door. Our level generator will add these prefabs to its palette and paint our level with them using the instructions we give it. We’ll only be looking at the Maze.cs script from the tutorial to get an idea of procedural level generation. I’ll be excluding most of the variable declarations from this post, as the original author of the tutorial did a good enough job naming them for readability,

 

Let’s look at the generator, tucked away inside the class “Maze”:

public IEnumerator Generate() {
        WaitForSeconds delay = new WaitForSeconds (generationStepDelay);
        cells = new MazeCell[size.xsize.z];
        List<MazeCell> activeCells = new List<MazeCell> ();
        DoFirstGenerationStep (activeCells);
        while (activeCells.Count > 0) {
            yield return delay;
            DoNextGenerationStep (activeCells);
        }
        for (int i = 0; i < rooms.Count; i++) {
            rooms [i].Hide ();
        }
    }

 

The first method is called Generate, and it lays out a timeline for maze construction. Maze construction is done by creating a grid of cells during runtime and filling in those cells with prefabs of things like walls, doors, and paintings. This means that the environment of this game does not appear in the editor (only after you begin the game), so you can’t manually adjust pieces of the maze. This also means that you will get a new maze each time! The first cell is handled by a DoFirstStep method, while the rest are dealt with via a DoNextStep method, both of which will be described in a bit.

This script also includes an adjustable WaitForSeconds time that delays each newly created piece of the Maze, so we can see if our generation is working as intended. This method works with Unity’s StartCoroutine() feature, and so returns an IEnumerator. Finally, the tutorial includes a cool room-hiding feature that we refer to here. It looks at every room in the maze and hides the cells within, both for the sake of performance and to make the maze feel more intriguing when looking on the minimap.

    private void DoFirstGenerationStep (List<MazeCell> activeCells) {
        MazeCell newCell = CreateCell (RandomCoordinates);
        newCell.Initialize (CreateRoom (-1));
        activeCells.Add (newCell);
    }

    private void DoNextGenerationStep (List<MazeCell> activeCells) {
        int currentIndex = activeCells.Count – 1;
        MazeCell currentCell = activeCells [currentIndex];
        if (currentCell.IsFullyInitialized) {
            activeCells.RemoveAt(currentIndex);
            return;
        }
        MazeDirection direction =currentCell.RandomUninitializedDirection;
        IntVector2 coordinates = currentCell.coordinates + direction.ToIntVector2();
        if (ContainsCoordinates(coordinates)) {
            MazeCell neighbor = GetCell(coordinates);
            if (neighbor == null) {
                neighbor = CreateCell(coordinates);
                CreatePassage(currentCell, neighbor, direction);
                activeCells.Add(neighbor);
            }
            else if (currentCell.room.settingsIndex == neighbor.room.settingsIndex) {
                CreatePassageInSameRoom(currentCell, neighbor, direction);
            }
            else {
                CreateWall(currentCell, neighbor, direction);
            }
        }
        else {
            CreateWall(currentCell, null, direction);
        }
    }

DoFirstGenerationStep is functionally similar to DoNextGenerationStep, but it doesn’t need to worry about complex cell interactions. The first cell is like the first book in the bookshelf. Sure, it might have neighbors pushed against it, laying on top of it, and otherwise cramming into its personal space, but it shouldn’t have to worry about re-positioning. After all, it was here first! Anyway, I should mention that when the first cell is created, it’s placed in the location RandomCoordinates. This is the randomized version of our IntVector2 struct, a custom data type that holds two integer values, much like a Vector2.

Back to it, DoNextGenerationStep continues by restricting newly created cells to the limits of the of the maze size. If the new cell is outside of the limitations, a wall is put up. These walls form the outer border of the maze. However, if the new location coordinates of the cell-to-be are empty, the class is given the go-ahead to make a passageway. The new cell is added back to a list in Generate that acts as control for the loop that runs this code, making sure that the process will repeat for each new passage that is made, until there are no more empty spaces left.

There are two other cases to consider, however. If the next cell is already occupied, we can throw up a wall and split the two cells. Furthermore, if the current cell shares the same “room” as an occupied space it wants to expand into, it will instead just create a floor there to connect the two part of the room. We’ll talk about this a little later. For now, we have the rules of creations. So, what are we working with?

 

    private MazeCell CreateCell (IntVector2 coordinates) {
        MazeCell newCell = Instantiate(cellPrefabas MazeCell;
cells [coordinates.x, coordinates.z] = newCell;
newCell.coordinates = coordinates;
newCell.name = Maze Cell  + coordinates.x + ,  + coordinates.z;
newCell.transform.parent = transform;
newCell.transform.localPosition = new Vector3
(coordinates.x – size.x * 0.5f + 0.5f0f, coordinates.z – size.z * 0.5f + 0.5f);
return newCell;

    }

 

First and foremost, a basic cell is assigned the standard floor tile, a location on the grid, and a name. It’s tucked away in the hierarchy under a Maze(clone) object, and that’s that. We can also create two types of passages. The first is standard:

 

private void CreatePassage (MazeCell cell, MazeCell otherCell, MazeDirection direction) {
        MazePassage prefab = Random.value < doorProbability ? doorPrefab : passagePrefab;
        MazePassage passage = Instantiate(prefab) as MazePassage;
        passage.Initialize(cell, otherCell, direction);
        passage = Instantiate(prefab) as MazePassage;

        if (passage is MazeDoor) {
            otherCell.Initialize (CreateRoom (cell.room.settingsIndex));
        } else {
            otherCell.Initialize (cell.room);
        }
    
        passage.Initialize(otherCell, cell, direction.GetOpposite());
    }

 

Here, we want to first allow our passage the slight probability of being a door, for the sake of variety. Then, we’ll go ahead and create the passage via Instantiate and place it on the grid by relating the directions of the last cell generated and the cell-to-be using an Initialize method. Also, if we got lucky, we’ll have a door. We should go ahead and create a new room using a randomly rolled, predetermined room setting (think carpet and wallpaper selection). Either way, we have to Initialize the passage twice – once from this cell to the new cell, and once in reverse, from the new cell back.

 

private void CreatePassageInSameRoom (MazeCell cell, MazeCell otherCell, MazeDirection direction) {
        MazePassage passage = Instantiate(passagePrefab) as MazePassage;
        passage.Initialize(cell, otherCell, direction);
        passage = Instantiate(passagePrefab) as MazePassage;
        passage.Initialize(otherCell, cell, direction.GetOpposite());
        if (cell.room != otherCell.room) {
            MazeRoom roomToAssimilate = otherCell.room;
            cell.room.Assimilate(roomToAssimilate);
            rooms.Remove(roomToAssimilate);
            Destroy(roomToAssimilate);
        }
    }

 

If we’re about to bump into a similarly carpeted and wallpaper-ed cell, we can just Initialize in both directions. If we run into a new room accidentally, we can simply call upon the new room’s Assimilate method to tear up its old carpet and install our own brand inside. Conquest!

But what if we want some privacy, or at least a challenge? It’s a maze after all, we should have some walls:

 

    private void CreateWall (MazeCell cell, MazeCell otherCell, MazeDirection direction) {
        MazeWall wall = Instantiate(wallPrefabs[Random.Range(0, wallPrefabs.Length)]) as MazeWall;
        wall.Initialize(cell, otherCell, direction);
        if (otherCell != null) {
            wall = Instantiate(wallPrefabs[Random.Range(0, wallPrefabs.Length)]) as MazeWall;
            wall.Initialize(otherCell, cell, direction.GetOpposite());
        }
    }

 

We already asked DoNextGenerationStep to handle the logic of when and why to install walls. This method simply handles the how. Note that the newly created wall is chosen from an array of wall prefabs. These include blank walls and walls with various picture frames. This is the part of procedural generation that allows artistically minded developers to flourish.

The final method in our “Maze” script makes rooms:

 

    private MazeRoom CreateRoom (int indexToExclude) {
        MazeRoom newRoom = ScriptableObject.CreateInstance<MazeRoom>();
        newRoom.settingsIndex = Random.Range(0, roomSettings.Length);
        if (newRoom.settingsIndex == indexToExclude) {
            newRoom.settingsIndex = (newRoom.settingsIndex + 1) % roomSettings.Length;
        }
        newRoom.settings = roomSettings[newRoom.settingsIndex];
        rooms.Add(newRoom);
        return newRoom;
    }

 

Each room is assigned a random setting (color scheme) and added to the list of rooms. There are a few pieces of code here that interact with other elements of the project, but for now, I’ll leave you with this brief overview of the code from Catlike Coding’s Maze Game tutorial.

 


 

I thought this tutorial was excellently crafted, and there are quite a few tips to be taken from the site’s design and layout. Embedded link to the Unity API documentation give this tutorial a new layer of teach-ability and interaction, as well as the FAQ style tool tips by the side of the content.

I found myself playing around with the camera for quite some time, and I still haven’t managed to get the main camera renderer treat the minimap background as transparent. As far as I understand, the Alpha value slider of the camera background doesn’t affect the image in-game, but rather objects rendered by the image. The tutorial’s solution of clearing only depth flags didn’t work for me, despite some time spent in documentation and on web forums. I’ll have to revisit this again to fix that issue.

 

I hope you’ve enjoyed the review. If you have any questions, comments, suggestions, or critiques, feel free to message me here or in the comment section below. Cheers!