Home Artists Posts Import Register

Content

As you might have seen, I've remade two of my old Flash games on Unity, you can find them on my new itch.io account. Since video games are unquestionably art, I thought I might as well share with you some devlogs, explaining how the algorithms work. I know many of you might not know much about C#, so I'll keep it in easier to read. First of all, let's go over some very basic stuff.

A function is a piece of code that does a specific task and can be called at any time within a computer program. Most programs are, in fact, just some functions calling each other. For instance, Unity codes rely mainly on a few functions that update the application at particular times, the main two being Start, which prepares a scene at the beginning of its execution, and Update, which changes whatever needs to be changed on every frame. These are the only two Unity functions I used for both U235 and 2mino.

A class is a piece of code that defines an object, which is an entity with its own internal data and internal functions (called methods). Classes are very useful when your program contains a lot of items which share some behavior. For instance, in U235 every beam emitter (the little boxes with numbers) were defined as classes, since they all behave in the same way. 2mino however didn't need any classes, since there are only two boards with different behaviors and their cells were too simple to require methods.

Lastly, Unity programs need graphics, which it either handles as 2D images on the user interface (called canvas) or 3D objects located in 3D space. The latter are made of meshes, which are groups of connected triangles that form a more complex shape. A simple square would contain just two triangles, a moving character might contain thousands of triangles. Normally, these meshes are built in a different application, such as Blender or Maya, and then imported onto Unity. However, since I'm a psycho, I prefer to do it the old-school way: I calculate every vertex by hand and write down their coordinates one by one. Even if the apps are 2D, I use 3D graphics because they give me full control over where everything is. 

Let's focus on U235 for now. Its Start function creates the frame around the board, as a mesh that stretches whenever the size of the board is updated; the background grid as a single 3D rectangle, but its texture is adjusted to show as many squares as the difficulty level allows; and the ending gates that close when the board is solved, which are kept hidden until they're needed. Then, Start calls the adjustSize function, which was created separately so it can be called whenever the user changes the difficulty level or rotates the device.

adjustSize first checks the orientation of the device, in case we're playing on a mobile phone, and calculates the number of cells in the game board, along with their size. The difficulty level the user gives is the number of cells along the shorter side of the screen; the number of cells on the other side is calculated proportionally to that value. With these numbers, we can update the sizes of the frame, the background and the ending gates. Then, adjustSize either calls a populate function, if the difficulty level was changed, or a transpose function, if the device was rotated. populate will also be called whenever the user requests a new board without changing its size.

Now here comes the fun part. First of all, populate creates a matrix (a grid that stores objects or values) of whatever size we have chosen for our game, where we will store numbers that describe the arrangement of the board: any matrix cell that contains a positive number (including zero) means that there is an emitter at that spot, which can produce that many energy units; negative values are treated as codes: -1 means EMPTY (nothing there), -2 is UP (there is a beam going up from an emitter in that same column), -3 is DOWN (same, but the beam is going down), -4 is LEFT and -5 is RIGHT. At the beginning, we set the whole matrix as EMPTY.

Now we fill up that matrix. The game only ends when there are no empty cells left, so we will keep adding emitters to empty cells at random until the matrix is full. Every time a new emitter is added, we stretch its beams a random length, making sure they don't go past the edges of the board or through any other bars or emitters that had been placed before. The energy of the emitter is calculated as the sum of the lengths of its beams.

Now, this should be enough to fill up the whole matrix, but it can cause a lot of emitters to have zero energy, if they arrive too late to the party. So whenever we notice that a new emitter has no beams, we'll attempt to fuse it to a previous one: if any cell around the emitter contains the end of a beam pointing towards it, we let the beam "eat" the emitter and add +1 to the total energy of its own emitter. If there are no such beams around the emitter, then it just remains at zero.

So far it's all just numbers on a matrix, now we have to build the actual emitters as objects of that class we created before - I won't go into detail about this class because it's pretty much all about graphics (how to draw the number, the beams, the tiny gates when the energy reaches zero...). We create enough emitters for all the cells where there should be one (recycling as many as possible from previous games, to keep memory usage efficient) and we give them their values.

Their beams are fully retracted at first, since it's the player's job to pull those out, but we do show how much energy they store on their displays. At the same time, we clean up the matrix so we can use it during the game - the emitters will check this matrix like a map, to know how far their beams can be stretched without hitting other emitters or beams that may be in the way, and will also write down which cells are occupied by their own beams so other emitters can see them. With that, the populate function is done. 

The transpose function is really simple in comparison, it just adjusts the board if the user rotates the phone. However, it doesn't rotate the board. Transposing means mirroring horizontally and vertically - the result isn't the same, but it's much easier to program and it works just as well. The function transposes the matrix (UP becomes RIGHT, DOWN becomes LEFT, and vice versa) along with the position and beam lengths of every emitter. There is also a cleanUp function, which is called whenever the user asks to reset their game. This one is also trivial, all it does is retracting all beams back to zero and updating the matrix accordingly (all UP, DOWN, LEFT and RIGHT values become EMPTY).

And finally, there's the Update function which updates the game on every frame. We're going to have to deal with mouse dragging, so we must save the position where the dragging starts, whenever the player clicks or taps on the screen, and follow the cursor on every frame until the button or tap is released. We can only drag in the direction of a beam, so if we clicked a beam, we must remember which one it was; otherwise, if the player clicked on an emitter, we wait for them to start dragging to see in which direction they want to go. The length of the corresponding beam is updated to the distance between the emitter and the cursor, as long as it doesn't cross other beams, emitters or the board edges. Once we detect that all emitters are out of energy, the game ends and the ending gates close. The Update function also calls adjustSize whenever the device orientation changes.

And that's it. About 800 lines of code, counting both the emitter class and the main program. Let me know in the comments if you have any questions about the program, or if you really don't care about devlogs and would prefer that I don't post them.

Comments

No comments found for this post.