Introduction

Choate offers Directed Studies to students who have completed all of the courses offered in the regular curriculum for a given department. This project is part of a joint Honors Directed Study in Computer Science with my friend Alan Luo titled Interactive Media.

If you're looking for a detailed overview of my project, you'll find that here.

Official Course Description

As computers continue to experience a constant revolution over the years, graphical processors become more and more powerful, allowing for machine-level optimizations to be replaced by higher-level interactive loops such as animations. This has given computers the capability to create cohesive interactive experiences and boundless complexity in art. With computers only getting more powerful and algorithms only getting more sophisticated, interactive media finds itself in many forms, particularly in online interactive installations and the booming games industry. Companies as large as Google, Activision, and Sony, are major investors and producers in the industry, demonstrating the promise that interactive media holds for the future. In particular, projects like agar.io and Place demonstrate the power of the internet to bring together millions of people to create media installations.

Our stretch goal for the final project will to bring our final product to PAX East, a consumer expo with thousands of attendees for media and games. We will aim to have a booth to present our product.

This directed study aims to:

  1. Understand techniques in interactive media.

    Interactive media installations require many techniques in order to be run properly. For instance, any online project requires a good understanding of asynchronously running a server from multiple browsers and netcode. In addition, any project requires optimization for specific hardware.

  2. Learn how to manage projects and deadlines

    We will aim to bring our project to a large consumer expo. In the last few weeks, we will have to deal with marketing and polish.

  3. Demonstrate mastery of concepts through projects

    Programming is not something that can be properly taught through tests and quizzes. Instead, we will do mini-projects each week to practice and demonstrate mastery of each concept. For the midterm and final, we will endeavor to create a larger project that can be presented as an individual product.

My Project

For my final project, I've decided to make a video game - by the end of the term my goal is to have a presentable and polished game by "early access standards," meaning that the game doesn't have to be perfect, but should be playable and enjoyable without any major flaws.

I believe that video games represent an incredible conglomeration of creativity and media. In combining art, engineering and writing in an interactive environment, I believe that video games are easily the best example of interactive and digital media while being one of the newest and most groundbreaking art-forms.

The staple of good indie game is new and innovative ideas and mechanics, as proven by so many games - Minecraft, Papers Please and Undertale to name a few. My project, which is as of yet unnamed, will be a turn-based multiplayer shooter - I'll elaborate a lot more on this in the beginning of my first devblog.

Battle Blocks - A Summary

The term has ended, and this is my final recap of what I've worked on. I plan to continue work on this project going forward, fixing the few minor bugs that remain and adding in a ton of new content. I'll try to document this work as best I can in devblogs in the future.

Table of Contents

Video Demo

This video shows one round of the game playing out, where the enemy wins the round.

Summary

Battle Blocks is a melding of the traditional turn-based style of board and card games with the modern shooter video game genre.

Each player is given 20 seconds to play their turn, where they control their character just like a regular game and each of their movements are recorded, but neither player can see what the other player is doing. After 20 seconds, or once both players have voluntarily ended their turns, both characters' turns are played back simultaneously for both of them.

This creates a very interesting dynamic of having to predict your opponent's thoughts, moves and positions. When played in a LAN environment where both players are actually in a room together, it makes for really cool psychological gameplay.

Networking

Having dealt with both uNet and Photon in the past, I decided that Unity's standard networking solutions were simply unecessary for my game. I have no need for latency-free real-time multiplayer in a turn-based game, and I decided that instead of trying to adapt these generic, complex networking solutions to my unique, simple game, I'd be better off simply writing my own networking solution.

I chose nodejs and socket.io for their ease of use and also because I'm familiar with both and have used them in the past. I use the Socket.IO for Unity plugin to communicate between the Unity client and my node.js server.

Networking Challenges

Node.js and Socket.IO use JavaScript, while Unity uses C#. JavaScript is a dynamic language, while C# is a static language, this means that C# is much more structured and strict than JavaScript, while JavaScript lets you get away with a lot more. In JavaScript, I can write a function that accepts a variable as an argument, while in C#, the argument must be an int, string, bool, etc. Because of this difference in languages, there's a lot of decoding to be done on the Unity end. Luckily, Unity does have built-in JSON support, so that helps bridge the gap somewhat and simplify the issue.

The biggest networking hurdle that I had to deal with was one that I've seen a few complaints about on the internet, but I actually couldn't find a single solution. I did, however, manage to figure one out myself.

The issue was extremely weird and circumstantial. Some socket.io messages from the server to the client caused the client to disconnect and continue rapidly reconnecting and disconnecting until either the server or client was closed. After looking through each socket.io message being sent, I isolated that messages sending JS objects were fine, but messages that sent individual variables, like strings or ints, outside of an object, crashed the Unity end. This was a very simple fix once I discovered what the problem was, just changing var myVar = 5 to var myVar = {x:5}.

In a broader sense, I would say that challenges stemming from the multiplayer nature of the game were by far the most time-consuming and frustrating aspects of this project. Having to build and launch multiple instances of the game each time I needed to test some minute change became extremely frustrating.

The first time I tested my game between two computers, it broke spectacularly. Strange issues from different resolution screens, and especially different aspect ratio screens, made UI creation and fullscreen settings quite difficult, while random and unexplained bugs on other computers that I simply couldn't replicate on my own machine became incredibly frustrating.

Managers, Singletons and GameObjects: The Unity End

Unity works on a basis of scenes, GameObjects and scripts.

GameObjects

Unity is based around GameObjects. GameObjects can take the form of UI elements, sprites, empties, lights, cameras and shapes.

GameObjects can have attached scripts, which tell the object what to do and when to do it - these are where most of the work goes.

Scenes

A Scene is a loadable portion of the game that's almost entirely isolated on its own; my game consists of 3 scenes:

  • Main Menu
  • Lobby
  • Game
Managers

To organize everything in my game, I developed a system of "Managers" - GameObjects that control the scene and hold important values that many GameObjects need to access. My game has a Manager for each scene, an overall Manager and a NetworkManager.

The Manager and NetworkManager are something called singletons. This means that only one can exist at a time, it isn't destroyed when a new scene is loaded, and it is accessible universally by every script.

Manager handles things like the Options menu, which is unified throughout every scene; music, which loops consistently and continually throughout the game, and handles the loading and checking of scenes.

Shooting

Local

In Unity, hitting the spacebar instantiates a 'Projectile' GameObject - just a purple square that moves forward when you tell it to (Surprise: we tell it to). It's fired with a velocity of (10x , 0), the x being either 1 or -1 depending on where the player is facing.

A 'Shot' C# object is also created to record the properties of the shot - this is later sent to the server as part of the turn. The 'Shot' object holds values like what frame the shot was fired on, its initial speed (10x , 0) and its type (if I choose to put in different types in the future). The necessity for the 'Shot' object is elaborated on in the next section:

Networking

This is where things get tricky. Before this week, the way turns were networked was simply that the player's movements were recorded into an array of Vector2's ([x,y]) and that array was sent to the enemy player when the turns were played.

Initially, I thought to approach shooting the same way, and actually got pretty far with my plan before realizing its glaring problems. I created a 'TurnFrame' object that holds two Vector2 arrays - one for the player and one for the player's shot. This method has a multitude of flaws, like lots of unnecessary data and a lack of versatility in the future.

So, I thought, if projectiles behave the exact same way on both clients, don't I just need to send the frame that the player fired along with the original turn Vector2 array, and then when the replay of the turn reaches that frame it fires? This is a big improvement, but there are two issues in that the projectile doesn't know which direction to go (the enemy player object on the client-side is just a red box that gets moved according to the Vector2 array - it has no properties so we don't know where it's facing).

So I settled for a compromise between the two - for now, the 'Turn' object that is sent to the server has two parts: a Vector2 array for the players positions, and the 'Shot' C# object.

Collision

Collision is actually one of the main reasons I chose to go with a well-developed engine like Unity instead of a more from-scratch approach. Collision can be incredibly easy or remarkably complicated, depending on how efficient and effective you want it to be. Here's an example of a really simple collision detection check between two rectangles:

if (rect1.x < rect2.x + rect2.width  &&
    rect1.x + rect1.width > rect2.x  &&
    rect1.y < rect2.y + rect2.height &&
    rect1.height + rect1.y > rect2.y) {
    //collision detected
}

While this works just fine, it's very inefficient, since the function is being run every single frame regardless of any other factors. Even if the two objects are on the opposite side of the screen, we're still checking every frame whether they're colliding, which is unnecessary and inefficient. Unity's collision is developed and advanced to an excellent point of efficiency and effectiveness.

Challenges

One challenge with using Unity's collision is that I haven't used any of Unity's other physics mechanics. Player movement and physics are handled by the custom script discussed in devblog 1. Unity's collision, however, is made to work with something called a RigidBody, a component of a GameObject that handles physics. This created some weird issues when I tried to use a collider without a RigidBody.

After playing with a test project I made in Unity for a while, I figured out an effective and simple solution that had little effect on my game's other mechanics. Adding a RigidBody only to the Projectile and then using Unity's Trigger mechanics instead of its traditional collision mechanics, I could make the projectile the only RigidBody. I then set the Projectile's RigidBody type to kinematic, telling it to sit still and not try any physics stuff.

Art & Design

First things first: starting the artwork in the last week was a big mistake. Luckily, I'm really happy with the results. The first thing I did was open up Adobe Illustrator and did my best to make some characters and terrain:

Triangulation

I wasn't super happy with the terrain, and had no idea what to do for a background - so I turned to my favorite art style: low-poly triangulation. I found a few online resources for automatic triangulation like Trianglify Generator, Halftone Pro and DMesh, my favorite for its versatility.

So, using these tools, I was able to turn this:

into this:

This kind of processing allowed me to really easily vary my sprites. I followed suit with each of my terrain sprites:

Ultimately, this triangulation made for a really cool, unified style for my game:

I also made simple gradient triangulations for the home screen and lobby backgrounds:

Overall, I'm incredibly happy with the way the artwork turned out. I've never really worked on an independent graphic design project, and I've always struggled artistically, but this project allowed me to work with a very simple art-style and still make something that I'm genuinely very proud of.

Map

One of my favorite scripts, partially because its functionality is genuinely really cool and partly because it was the only script that actually worked properly on my first try, is my map generation script.

When I started implementing my artwork into my game, I realized that materials are really complicated in Unity and designed to work with 3D games. It's sprites that are designed for 2D. So, after porting everything else in my game over to a Sprite-system, I needed to make individual GameObjects for every square sprite in my terrain. The easiest way to do this was using a script that allowed me to write out my map in a text file, and then have it generate the map on being loaded. This saved me from having to drag and drop individual blocks into place and calculate their positions to line up, and also made map creation and customization an absolute breeze. Since it's a relatively short and reader-friendly script, I'll just show it here:

public List<TextAsset> maps;
public int map = 0;
public GameObject grassPrefab;
public GameObject dirtPrefab;
public GameObject stonePrefab;
public GameObject tiltedDirtPrefab;
public GameObject tiltedStonePrefab;
public List<Vector2> spawnPoints;

public int colNo = 51;
public int rowNo = 28;

void Start()
{
    string[] rows = maps[map].text.Split('\n');
    if (rows.Length != rowNo)
    {
        Debug.LogError("Map Corrupted!");
        return;
    }
    for (int r = 0; r < rowNo; r++)
    {
        string[] row = rows[r].Split(' ');
        for (int c = 0; c < colNo; c++)
        {
            string item = row[c];
            Vector2 coords = new Vector2(c - (colNo / 2), (rowNo-r) - (rowNo / 2));
            switch (item)
            {
                case "1":
                    PlaceTile(grassPrefab, coords);
                    break;
                case "2":
                    PlaceTile(dirtPrefab, coords);
                    break;
                case "3":
                    PlaceTile(stonePrefab, coords);
                    break;
                case ">":
                    PlaceTiltedTile(tiltedDirtPrefab, coords, 1,1);
                    break;
                case "<":
                    PlaceTiltedTile(tiltedDirtPrefab, coords, -1,1);
                    break;
                case "[":
                    PlaceTiltedTile(tiltedStonePrefab, coords, -1, -1);
                    break;
                case "]":
                    PlaceTiltedTile(tiltedStonePrefab, coords, 1, -1);
                    break;
                case "8":
                    spawnPoints.Add(coords);
                    break;
                case "9":
                    spawnPoints.Add(coords);
                    break;
            }
        }
    }
    if (spawnPoints.Count == 2)
    {
        Manager.Instance.gameManager.SetSpawnpoints(spawnPoints.ToArray());
    }
}

public GameObject PlaceTile(GameObject prefab, Vector2 loc)
{
    GameObject tile = Instantiate(prefab, gameObject.transform);
    tile.transform.position = loc;
    return tile;
}

public void PlaceTiltedTile(GameObject prefab, Vector2 loc, float xS, float yS)
{
    GameObject tile = PlaceTile(prefab, loc);
    tile.transform.localScale = new Vector3(xS, yS, tile.transform.localScale.z);
}

And here's what a map.txt file looks like:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . < 1 1 > . . . . . . . . . < 1 1 > . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . < 2 2 2 2 1 1 1 1 1 1 1 1 1 2 2 2 2 > . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 . . . . . . . . . . . . . . . .
. . 1 1 1 > . . . . . . . . . . . . [ 3 3 3 3 3 3 3 3 3 3 3 3 3 ] . . . . . . . . . . . . < 1 1 1 . .
. . 2 2 2 2 1 1 > . . . . . . . . . . . . [ 3 3 3 3 3 3 3 ] . . . . . . . . . . . . < 1 1 2 2 2 2 . .
. . [ 3 3 3 3 2 2 > . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . < 2 2 3 3 3 3 ] . .
. . . . [ 3 3 3 3 ] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . [ 3 3 3 3 ] . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . 1 1 1 1 1 1 1 1 . . . . . . . . . . . 1 1 1 1 1 1 1 1 . . . . . . . . . . . .
. . . . . . . . . . . . [ 3 3 3 3 3 3 ] . . . . . . . . . . . [ 3 3 3 3 3 3 ] . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . 1 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1 . . .
. . . 2 1 1 1 1 1 1 1 1 > . . . . . . . . . . . . . . . . . . . . . . . . . < 1 1 1 1 1 1 1 1 2 . . .
. . . [ 3 3 2 2 2 2 2 2 2 1 1 1 1 > . . . . . . . . . . . . . . . < 1 1 1 1 2 2 2 2 2 2 2 3 3 ] . . .
. . . . [ 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 2 2 2 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 ] . . . .
. . . . . . [ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ] . . . . . .
. . . . . . . . . [ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ] . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Sprite variance

The way I put my very varied sprites to good use was with a simple sprite randomizer script attached to the prefabs that were instantiated by the MapMaker script above. When each GameObject is instantiated, it chooses a random sprite from the array given to it.

public List<Sprite> sprites;
public int index;
void Start () {
    index = (int)(Random.value * (sprites.Count));
    GetComponent<SpriteRenderer>().sprite = sprites[index];
}

Music

I'm going to conclude with music because it represents the only failure of my project. While the final product does incorporate a really cool music loop, it was mostly worked on by my friend, Ben Dreier. When I sat down with Bosca Ceoil, a simple music making software, it didn't take long to realize that making music is hard. Having absolutely no background in music whatsoever and with time running out, I asked my friend Ben for help with making the music; he ended up doing a bulk of the work on the music, and I can't really take credit for it.

Here's a sample of the background loop of music for the game:

Reflection

^ Click that link to see my concluding reflection on my Directed Study.

Reflection

Last year, in Ms. Healey’s AP Computer Science class, I realized my passion for coding. Before last year, I had never completed a single full programming project. I went into this directed study with the goal of furthering my coding knowledge and experience in a fun way - game development. Unexpectedly, I ended this directed study with an entirely new way of looking at and approaching programming projects.

Throughout my Computer Science class last year, every assignment, including my final project, had hundreds of guides, tutorials and help on the internet and from my friends. While I definitely still learned a lot from this class, I found myself constantly relying on others for help with my projects.

This year, my Interactive Media directed study allowed me to break through this reliance, and gain an immense, newfound confidence in myself and my own abilities. I came up with a completely original idea for a new game, used sparsely documented plugins and frameworks, and was forced to work through each problem on my own.

This may sound hyperbolic or cheesy, but this experience has genuinely and significantly changed the way I look at independent projects, and released a previously hidden creativity within myself. Before, ideas were simply ideas; sometimes they were cool, and maybe I’d share them with a friend. Now, ideas are a foundation for new projects. Every time a thought pops into my head, I now autonomously begin to work on it in my own mind.

This self-confidence is not exclusive to programming. In this project, I was challenged with creating visual art and music for my design. While the music part of things was incredibly challenging and I ultimately ran out of time, I have most definitely found a new confidence in my graphical design abilities.

Over this coming break and the next ones, I have decided to continue working on this project, allowing it to grow and morph with each of the new ideas that are now flowing freely from my mind. Not only would I like to continue this project, but the knowledge and confidence that I’ve gained from this project have inspired me to pick up work on failed past projects, and to press through to the future as an independent, self-reliant and confident developer. This project has helped me immeasurably in making steps towards realizing my dreams of a future in Computer Science.

Finally, for any other students looking to work on a similar project, I have one word of advice: prioritize. I spent weeks on unnecessary mechanics and ideas, many of which never came to fruition, and ignoring some of the most essential parts of my project. While I was able to eventually complete the project, the art, music, user-interaction and polishing began way too late. This procrastination and lack of prioritization lead to incredible stress during the last week of the year specifically, and could have been easily avoided by better prioritizing my time near the beginning of the year. Also, I would tell them to finish at least a week early. When I finally finished my project the night before it was due, I played with a friend and discovered a few other bugs. While I was able to iron most out, this ate out of my time to work on other classwork and was incredibly stressful to deal with.

Devblog 1 - 09/24/2017

Since this is the first weekly devblog for a game that I've been working on for much more than a week, it'll serve as a sort of introduction and be much longer than these will be in the future.

Introduction

Welcome to the [insert clever and catchy game title here] developer blog! Here's the basic premise of the game:

This project explores a combination of the turn-based strategy of classic games like Chess and video games like Pokémon and Hearthstone with the generic "shoot 'em up" genre that has dominated gaming since its inception.

Game Design and Mechanics

Okay…, you say, cool idea

I agree…

but how does it work?

There is a 30-45 second period (I haven't decided yet) for each player to play their turn. During this period, neither player can see what the other is doing. Each turn is 300-600 (again, haven't decided) frames recorded and played at 60 FPS (5-10 seconds). If you decide you don't like your turn, you can reset and re-record it at any point during the 30-45 second prep period. Once either the time runs out or both players voluntarily end their prep periods, both turns will be played simultaneously for both players. The timers reset and this process repeats itself over and over.

Okay……

oh no, that's more dots than before

but how does a player win?

So this is one I haven't thought quite as much about. Obviously, in accordance with the "shoot 'em up" genre, you win by killing the other player. The problem with this concept is the same problem that's plagued the world for millenia: there are a lot of ways to kill people. The simplest solution and the one that I'm going with for the moment is that each player gets 1 shot per turn and if a player is hit they die immediately, increasing the killer's score and resetting the round.

In the future, however, there are a lot more possibilities to explore:

  • An hp system, where it takes multiple shots to kill someone. Hitting certain body parts like headshots would deal more damage
  • An armor system, where you have to shoot off the enemy's body armor before you can hit them
  • Different guns
    • A sniper, which deals more damage (if using an hp system) and is very accurate but can only fire once every two turns.
    • A shotgun, which fires a random spray of bullets that deal little damage each.
    • An assault rifle, which fires in bursts of 3 or so, but has recoil and is less accurate
    • An SMG, which full-auto fires a volley of somewhat innacurate, low damage bullets
    • A machine gun that takes a turn to be set up and needs to be set up again if moved, but fires many rounds per turn and still hits hard
    • Could also be a stationary item in the map that can be 'mounted' (for lack of a better word) Something like the shotgun would be ridiculously strong in the 1-hit-kill setup, and the sniper would be too weak, but the machine gun and assault rifle could be interesting.

Another thing to think about is the appropriateness of the game. While games like Battlefield and Call of Duty are bestsellers, their markets are severely limited by the high age-rating on their games. Games like Splatoon are "shoot 'em up" style games that are specifically targeted at attracting that younger audience that isn't allowed to play more mature games. There are also some middle-of-the-road games, like the incredibly successful Overwatch, which has human characters and shows them dying, but never shows any blood or actually uses the words "die" or "kill," preferring words like "eliminate." Right now, I'm leaning towards the side of a more appropriate, less violent game - expecially since this is a school project. I'm not sure I'd like to go as far as Splatoon specifically for the low maturity audience, and I feel like something just a little below Overwatch on the scale of appropriateness would be great, as I think Splatoon's ridiculousness takes away from the game a bit.

Player Movement

Instead of using Unity's built-in player controller scripts, I've decided to adapt mine from the simpler and more customizable scripts from Sebastian Lague's 2D Platformer Tutorial Series. I chose this controller for it's effectiveness in dealing with climbing and descending slopes, as well as it's customizability, which has enabled me to make numerous adaptations and changes to Sebastian's robust base. I won't go into too much detail on this point as you can get all the information you need from the link above.

(The red lines are just for debugging collision, they will not appear in the final product)

Networking

Having dealt with both uNet and Photon in the past, I decided that Unity's standard networking solutions were simply unecessary for my game. I have no need for latency-free real-time multiplayer in a turn-based game, and I decided that instead of trying to adapt these standard, complex networking solutions to my unique, simple game, I'd be better off simply writing my own networking solution.

I chose nodejs and socket.io for their ease of use and also because I'm familiar with both and have used them in the past. I use the Socket.IO for Unity plugin to communicate between the Unity client and my node.js server.

Challenges

I very quickly realized that the most prominent issue in my networking plan was the communication between Node.js (JavaScript) and Unity (C#). JavaScript is a dynamic language, while C# is a static language. C# is a much more structured and strict language than JavaScript, while JavaScript lets you get away with a lot more. In JavaScript, I can write a function that accepts a variable as an argument, while in C#, the argument must be an int, string, bool, etc. Because of this difference in languages, there's a lot of decoding to be done on the Unity end. Luckily, Unity does have built-in JSON support, so that helps bridge the gap somewhat and simplify the issue.

Goals

The server really only serves to accomplish a few basic tasks:

  • Exchange information between the players (usernames, plays, etc.)
  • Start and stop the game and individual turns appropriately
  • Keep track of score
  • Light anti-cheat functionality

The most advanced and difficult of these tasks by far is the anti-cheat. The server should be able to spot suspicious activity (too many frames in a turn, player moving too fast, only one client registered bullet hit, etc.) and react appropriately, either by booting the offending player from the game or simply remedying the effects of their cheating. The issue of bullet hit registration is probably the trickiest in this category. One of the advantages of using uNet would have been that it uses Unity's engine and can calculate collisions authoritatively. Hopefully, since my game is only 2D, I should be able to do some basic calculations on the server-end should something go wrong, but ideally both clients would report the bullet hit and the server wouldn't have to do any extra work.

Graphics, UI and other Artwork

Art and Design is an integral part of any software - regardless of how great your code is, if the user can't effectively interact with it, it's useless. Unfortunately, I have little to no experience with art and design of any kind, and really don't know where to even start here. This will probably be the most challenging aspect of this project for me.

Challenges

I don't know the first thing about graphic design. I also don't know the first thing about artwork. I don't even have an idea for the aesthetic or theme of my game's art! At this point, it could be a sci-fi shooter with plasma rifles, and it could just as easily become a cowboy-western game with shotguns and revolvers. Hell, I could have the characters not be human at all.

Goals

My goal is to have a polished, responsive UI and unique, custom artwork and visuals. I want to create all of the art myself and try to be as unique as possible - that is, not tracing other people's sprites from DeviantArt or downloading characters from the Unity Asset Store.

Devblog 2 - 10/01/2017

Networking

Functionality

I spent an exorbitant amount of time working on this project early on in the week and got the basic functionality of the server almost completely finished. Here's a demo:

What you see above is two players connecting to a lobby, both declaring themselves ready and starting a game, then playing out their turns. When both players have finished their turns, their turn is sent to their opponent and both turns are played simultaneously for each client.

Note that both clients and the server were all running on the same machine, so there is no network latency in this demo.

Challenges

When I first started on this project, server-authored countdowns were one of the first things I tried to do. Every time I tried to test it, all clients would disconnect from the server for seemingly no reason. I had no idea if this was node kicking them off, Unity disconnecting or something in between. I canned that functionality for a later date - this week was that later date. I reached the point where I realized that something was going to have to be done about this if I was going to have a server-authored time limit for turns.

After a couple hours of questioning my own sanity, I realized that this issue stemmed from my arch-nemesis throughout this project: JavaScript to C# miscommunication. C# can't handle JavaScript variables, so when I was sending through an int for the time, my Unity end of things just died. I figured out that the only way to send data from node.js to Unity was if it was contained within an object. This was a very simple fix once I discovered what the problem was, just changing var lobbyCountdown = 5 to var lobbyCountdown = {t:5}. I'm extremely glad I figured this out early on, as this would have been a very prominent issue throughout the rest of the project.

Housekeeping

warning: lots of possibly boring code ahead; if you don't care about code condensing and housekeeping, skip it, don't worry, I'll understand :'(

My node server is very messy - let's clean it up a bit.

First order of business: these extremely repetitive "checking" functions:

function checkReady() {
    var ready = true;
    for (i = 0; i < sockets.length; i++) {
        if (!sockets[i].ready) {
            ready = false;
        }
    }
    return gameReady;
}
function checkLoaded() {
    var ready = true;
    for (i = 0; i < sockets.length; i++) {
        if (!sockets[i].gameLoaded) {
            ready = false;
        }
    }
    return ready;
}
function checkTurnEnds() {
    var ready = true;
    for (i = 0; i < sockets.length; i++) {
        if (!sockets[i].turnEnded) {
            ready = false;
        }
    }
    return ready;
}
function checkNextTurnRequests() {
    var ready = true;
    for (i = 0; i < sockets.length; i++) {
        if (!sockets[i].nextTurn) {
            ready = false;
        }
    }
    return ready;
}

Using the power of JavaScript, we should be able to condense all of these down into a single function that takes the boolean name in the form of a string as an argument:

function check(bool) {
    var ready = true;
    for (i = 0; i < sockets.length; i++) {
        if (!sockets[i][bool]) {
            ready = false;
        }
    }
    return ready;
}

The second aberration of repetitiveness is the handling of all my countdowns; this one will take a bit more work. Here's the original code:

function loadGame() {
    lobbyCountdownTimer = setInterval(function() {
        console.log('   game loads in ' + lobbyCountdown.t);
        for (i = 0; i < sockets.length; i++) {
            sockets[i].emit('lobbycountdown', lobbyCountdown);
        }
        lobbyCountdown.t--;
        if (lobbyCountdown.t < 1) {
            for (i = 0; i < sockets.length; i++) {
                console.log(sockets[i].number + ': sending gameload information');
                sockets[i].emit('loadgame');
            }
            resetLobbyCountdown();
        }
    }, 1000);
}
function startGame() {
    gamestartCountdownTimer = setInterval(function() {
        console.log('   game starts in ' + gamestartCountdown.t);
        for (i = 0; i < sockets.length; i++) {
            sockets[i].emit('gamestartcountdown', gamestartCountdown);
        }
        gamestartCountdown.t--;
        if (gamestartCountdown.t < 1) {
            for (i = 0; i < sockets.length; i++) {
                console.log(sockets[i].number + ': starting game');
                sockets[i].emit('startturn');
            }
            console.log('   Game On!');
            resetGameStartCountdown();
        }
    }, 1000);
}
function startTurn() {
    turnCountdownTimer = setInterval(function() {
        console.log('   turn has started');
        for (i = 0; i < sockets.length; i++) {
            sockets[i].emit('turncountdown', turnCountdown);
        }
        turnCountdown.t--;
        if (turnCountdown.t < 1) {
            console.log('  turn over!');
            for (i = 0; i < sockets.length; i++) {
                sockets[i].emit('turnover');
            }
        }
    });
}
function resetLobbyCountdown() {
    for (i = 0; i < sockets.length; i++) {
        sockets[i].emit('lobbycountdown-cancel');
    }
    clearInterval(lobbyCountdownTimer);
    lobbyCountdown.t = 5;
}

function resetGameStartCountdown() {
    clearInterval(gamestartCountdownTimer);
    gamestartCountdown.t = 5;
}

function resetTurnCountdown() {
    clearInterval(turnCountdownTimer);
    turnCountdown = 45;
}

This one was a little bit more complicated but I was effectively able to cram all these functions into these two:

function setTimer(name, time, message) {
    var interval = setInterval(function() {
        console.log('   Timer ' + name + ': ' + time.t);
        for (i = 0; i < sockets.length; i++) {
            sockets[i].emit(name + 'countdown', time);
        }
        time.t--;
        if (time.t < 1) {
            console.log('  Timer ' + name + ': emitting ' + message);
            for (i = 0; i < sockets.length; i++) {
                sockets[i].emit(message);
                resetTimer(time, interval);
            }
        }
    }, 1000);
    return interval;
}

function resetTimer(time, interval) {
    clearInterval(interval);
    time.t = time.o;
}

Future Plans

Projectiles

My most immediate task is to add projectile shooting - a simple task with a few issues to think about (I'll flesh these out a lot more in my next devblog:

  • Controls and Functionality
  • Turn Recording
  • Rules/Limitations

Art/Design/UI/Sound/Music

Lingering in the back of my mind as I lay awake at night is the horror that soon I'll have to start working on gaspart. As I mentioned last devblog, I have no idea where to even start with this and have no meaningful experience or talent of any kind in this area. But wait! There's More! I completely forgot that music and sound is also incredibly important, so now I have two terrifying challenges ahead of me, I can't wait!

Disclaimer: ^ that wasn't sarcasm; I've been wanting to try to hone my artistic abilities for a while now, I'm actually really excited to get to work on this since up until now I've just been doing code.

Devblog 3 - 10/08/2017

Shooting

This week I worked on shooting mechanics - they're just a little bit important to a shooter game.

Oh yeah, and I also fixed that pesky bug seen at the end of the demo from the last devblog where players float in the air if their turn ends while they are falling - now the turn can exceed 300 frames, but only to complete actions started within the 300, like falling; player control is disabled when the 300 frames are exhausted.

Approach

In my mind, there are two fundamentally different ways of approaching the issue of shooting:

  • [Spacebar] shoots straight in the direction you're facing (jump is rebound to [W]/[up])

  • [Mouseclick] shoots where your cursor is pointing

The second option would give the player more control, but more isn't always better. I really like the simplicity of the first option and the idea that this can be a mouse-less game - there are too few games that can be played solely on a keyboard.

I plan on revisiting this in the future, especially if the special weapons from my first devblog end up being implemented. For now, though, I'm going with option 1 for its elegance and simplicity.

Implementation

Local

In Unity, hitting the spacebar instantiates a 'Projectile' GameObject - just a purple square that moves forward when you tell it to (Surprise: we tell it to). It's fired with a speed vector of (10x , 0), the x being either 1 or -1 depending on where the player is facing. A 'Shot' object is also created to record the properties of the shot - this is later sent to the server as part of the turn. The 'Shot' object holds values like what frame the shot was fired on, its initial speed (10x , 0) and its type (if I choose to put in different types in the future). The necessity for the 'Shot' object is elaborated on in the next section:

Networking

This is where things get tricky. Before this week, the way turns were networked was simply that the player's movements were recorded into an array of Vector2's ([x,y]) and that array was sent to the enemy player when the turns were played.

Initially, I thought to approach shooting the same way, and actually got pretty far with my plan before realizing its stupidity. I created a 'TurnFrame' object that holds two Vector2 arrays - one for the player and one for the player's shot. This method has a multitude of flaws:

  • It doubles the amount of data sent to and from the server for turn communications
  • There's no effective way of handling if the player decided not to shoot
  • There's no effective way of implementing different weapons and bullets in the future

So, I thought, if projectiles behave the exact same way on both clients, don't I just need to send the frame that the player fired along with the original turn Vector2 array, and then when the replay of the turn reaches that frame it fires? This is a big improvement, but there are two issues in that the projectile doesn't know which direction to go (the enemy player object on the client-side is just a red box that gets moved according to the Vector2 array - it has no properties so we don't know where it's facing).

So I settled for a compromise between the two - for now, the 'Turn' object that is sent to the server has two parts: a Vector2 array for the players positions, and the 'Shot' object. There's probably a more efficient way of handling this, but this method works great for now.

Devblog 4 - 11/05/2017

For the past month, I've been working simultaneously on a lot of different parts of the project, so the past three weekends have not produced a presentable product in any way.

Long story short, I've done a ton of work all over the place and am very close to the artwork-phase. Here's an example of a round so far:

As you can see, shooting works better, collision with players and the world has been implemented, a score system has been added as well as server-side handling of resetting player positions when a round is started.

There are also quite a few bugs that you can see in the video, like the projectile being shot randomly in the bottom right for the right-side player and how the score goes up by two instead of 1 - I'm working on tracking down what's causing these.

I've also worked a lot on optimizing both the server and client-end and worked on the foundation of a spectator system - but that's a little hard to test as I need more than two computers.

Collision

The most important thing I worked on this month was collision. It took way longer than it had any right to but I'm very happy with the result.

Approach

Collision is actually one of the main reasons I chose to go with a well-developed engine like Unity instead of a more from-scratch approach. Collision can be incredibly easy or remarkably complicated, depending on how efficient and effective you want it to be. Here's an example of a really simple collision detection check between two rectangles:

if (rect1.x < rect2.x + rect2.width  &&
    rect1.x + rect1.width > rect2.x  &&
    rect1.y < rect2.y + rect2.height &&
    rect1.height + rect1.y > rect2.y) {
    //collision detected
}

While this works just fine, it's very inefficient, since the function is being run every single frame regardless of any other factors. Even if the two objects are on the opposite side of the screen, we're still checking every frame whether they're colliding, which is unnecessary and inefficient. Unity's collision is developed and advanced to an excellent point of efficiency and effectiveness.

Challenges

One challenge with using Unity's collision is that I haven't used any of Unity's other physics mechanics. Player movement and physics are handled by the custom script discussed in devblog 1. Unity's collision, however, is made to work with something called a RigidBody, a component of a GameObject that handles physics. This created some weird issues when I tried to use a collider without a RigidBody.

After playing with a test project I made in Unity for a while, I figured out an effective and simple solution that had little effect on my game's other mechanics. Adding a RigidBody only to the Projectile and then using Unity's Trigger mechanics instead of its traditional collision mechanics, I could make the projectile the only RigidBody. I then set the Projectile's RigidBody type to kinematic, telling it to sit still and not try any physics stuff.

Shooting, part 2

While I worked out most of the actual shooting mechanics in my last devblog, there was still some work to be done.

Turn Timing

One of the first issues I noticed with shooting was that it messed with the turn timing pretty badly both in the recording and the replaying stage, as both have their end triggered when each player runs out of moves. This means that, if both players complete their movements, the turn would reset and the projectiles wouldn't fully run their course.

To rectify this, I placed obstacles, the same GameObject type that the platformer map is made of, and placed them just out of view of the camera as a border that the projectile would destroy itself on. I then set the end of a turn to also require that all projectiles in the scene had been destroyed, so that all projectiles could run their course until they reach the edge of the game.

Another avenue I considered was Unity's OnBecameInvisible instead of putting borders around the camera, but ultimately decided that the borders would stand to help solve issues in the future like players or other objects falling off the map, and are generally simpler and less likely to cause issues than checking whether the projectiles are being rendered or not.

Housekeeping

When I was working on the shooting, I tried to remain consistent with my other player control functions like movement and have the Player object handle shooting. This resulted in a lot of communication to and from the gameManager object to determine stuff like whether the scene was replaying or recording and sending information back and forth about the projectiles.

This became especially complicated once I started working on collision and shooting during the replay, and I decided that it was better to have the gameManager handle all shooting and keep track of all projectiles.

Spectating

This is a pretty minor feature that might fall by the wayside in the name of other more important features, especially because of how difficult it will be to test. Nonetheless, I think it'd be cool for players who join a server that already has 2 players in it will be able to observe only the replay periods.

I've started work on some of the framework for this on the server-side, but with 2 weeks left until the end of the term, I think that my time will be better spent on more significant parts of the game, like artwork.

(The remaining devblogs will be consolidated into the summary)

Downloads

Instructions