Unity & Visual Studio
Okay- this is what drove me to seek out GameDev.tv’s 2D Unity course in the first place. I didn’t know how to code and I really wanted to start on that piece of the puzzle. So, I dove in head-first! The first thing to say: the more I learn, the more I realize how little I still know.
I was instantly hooked and found a desire to really learn as much as possible. However, I often found myself getting distracted with other pieces: design, art, music, etc… and I “accidentally” took extended breaks from coding. Don’t do this if you can avoid it- you may lose some of what you learned.
This section could easily be several Blog posts on its own, so I’m going to have to be selective and limit myself to a small handful of lessons I found most helpful.
Alright, there are differing theories on this, but I have a pretty clear opinion from my limited experience: KEEP YOUR SCRIPTS FOCUSED!!!
This is the first video game I’ve ever made, so keep that in mind, but I did try to research this as thoroughly as a beginner can. Generally, I find that scripts work better when they have a limited specific intention.
Of course, this doesn’t always mean a script is any particular length or even short. However, the single most problematic script in my entire project WITHOUT ANY CONTEST is my player script, which tries to cover many jobs for the player ship (it’s also between 600-700 lines).
What?! You can't read my near 700 line monstrosity? This my player script- it has too many functions and purposes to list! You don't need to read it. I can barely read it- it's a convoluted mess! Ha!
When I first started realizing the script was a problem, I was not confident enough that I could change it without tearing down large parts of the project. Looking back- I absolutely could have broken it out without much trouble! Most of my scripts are now somewhere around 25-40 lines or 60-80 lines and have a single focused purpose. It would still benefit this project to divide and conquer, making this into several pieces.
Okay- you can read these. This script is for an "aether portal" that (1) looks at each frame to see how close the player ship is and (2) pulls them closer at a certain force if they are within a certain distance.
The spawning placement and timing are dealt with in a separate script. So are collision, damage, etc... In comparison to my player script this is very discrete in scope. Certainly, it could be even better. I am still a novice, but I hope I'm making the point...
I realize coding/scripting isn’t this black & white- but it’s ultimately about functionality, efficiency, and organization from my perspective. I’m sure there are many projects for which some longer scripts are appropriate.
For reference, my project has 99 scripts and 7,745 lines of code (give or take). This doesn’t include scripts generated by tools such as the newer Unity Input System.
PLAN YOUR WORKFLOW WHEN BUILDING:
I’m not a grizzled veteran of the tabletop industry or anything, but I have been in charge of designing/writing/organizing/editing 7 interconnected tabletop books, most of which are between 150-250 pages each. With every one of those, planning out the project ahead of time was totally necessary.
So why didn’t I do the same here? Well, part of it was that game projects change a ton throughout development so it’s nearly impossible to know exactly where you’re going from the very beginning.
I have to say, though- I REALLY wish I had written down the specific goals for gameplay mechanics, UI, and scope ahead of time. Even this very short list below (which is still not enough at all...) would have saved me a ton of time and kept me focused:
Ultimately, I really wish I had written a more detailed outline from the outset.
SCRIPTABLE OBJECTS SAVED MY BACON
I fell in love with scriptable objects during this project. Here are a few reasons why:
- They are largely memory efficient
- Easy to customize
- Plays Well with Others: can receive callbacks just like any normal MonoBehaviour
- Work well with the Unity editor
- Easy to change/swap data values
For my game, almost all of my enemy wave spawn systems are done with a single scriptable object. It’s extremely easy to modify in the editor:
- Wave paths
- Entry/exit points
- Types of enemy/friendly crafts in waves
- Speed of waves
- Where waves stop/restart (and IF they stop)
- Amount of time in between waves
I also have test waves spawners for each level so I can immediately run sequences in the middle of levels with minimal effort.
Scriptable Objects were a huge win for me in terms of (1) efficiency and (2) ease of iterating level design. I’m already planning on using them for two major components of my next project.
LEARN TO USE THE PROFILER
Alright- an argument could be made that the profiler is total overkill for a small student project. I would say, though, learning to use Unity’s built-in tool that tracks performance frame-by-frame is a habit and skill worth learning.
The profiler definitely helped me. My game may be small, but there are A LOT of game objects on-screen during much of the game. I started to notice drops in frame rate and the profiler ended up being my answer to fix that issue.
The pic above shows the profiler in action. I would encourage you to read the documentation on unity and find tutorials as necessary:
Once I started using the profiler on my game, I found some of the biggest offenders that caused my game to slow down.
GameObject.Find(), FindObjectOfType(), GetComponent() are all incredibly useful and necessary tools. They do, however, come at a cost of some efficiency. If you’re accessing something multiple times throughout a play session, I would strongly consider storing/recording that data in a variable (caching it).
The pic above shows examples of caching variables in Awake(). Of course, it's also creating a new Vector2 each frame. There's always room for improvement!
I’m putting this here to make you aware of the process more than anything else. I HAVE SO MUCH MORE TO LEARN ON THIS TOPIC AND MEMORY USAGE DURING GAMEPLAY IN GENERAL. As I understand it, though, garbage collection is a way of reclaiming memory that was once referenced but is now unused. Then, that reclaimed memory can be used again (oversimplified… but that’s the basic idea).
You want to reduce that rate at which garbage collection needs to happen when you can. According to the documentation below, here are a few of bigger offenders:
- Heavy use of Update()
- Accessing transforms (cache these when possible)
When you can find other ways to make your game work consider it.
UNITY INPUT SYSTEM
Let’s talk about a great example of a Love-Hate Relationship! This one is definitely my fault in part. IF YOU ARE GOING TO CHANGE FROM THE OLD TO THE NEW INPUT SYSTEM IN THE MIDDLE OF A PROJECT… PROBABLY DON’T!
I should have known better, and I did back up my whole project first. However, there are some things about the new Unity input system that are just not quite right yet in terms of implementation. Here are some pros & cons:
- Much easier support for multiple input devices (eg: controller & keyboard)
- Multiple calls (2 or 3) from a single button press (also a "Con" in its current state- see below)
- Pretty easy to learn how to implement in script and editor
- Multiple calls (2 or 3) from a single button press (see below)
- Options in the editor can be misleading in what they actually do
Above is a picture of the Input System in the Editor. If you look at the top left of the window, you'll see I have two separate "action maps" for different sets of player controls: (1) during general Gameplay and (2) navigating UI menus.
My main problem with this system is the lack of access to take control over what happens when you actually press a button/key.
Essentially, when a button/key is pressed a single time with the new input system it is actually called 2-3 times: once when started, once just after it is performed, and once when the action is canceled.
At first, it appears there are ways to fix this easily in the Unity editor, but they just don’t work for all cases. I spent a fair amount of time fixing this in scripts. I would definitely read the documentation below and consult forums.
What you’ll see below is my solution to multiple calls of an action through button presses. In this case, we’re looking at code for when the Player tries to fire a weapon called an “aether bolt.” This resulted in the bolt firing twice every time.
TLDR Code Explanation 😊:
1. In the first image below, the Player Controls are enabled. Lines 148-149 are set to handle both when the Launch() method is both “performed” and “canceled.”
2. You can see the Launch() method below takes InputAction.CallbackContext. This is a reference to whether the method is being called when the button press is “performed” or “canceled.” (Both of these happen each time the button is pressed.)
3. Also in the image below, an if statement makes sure an aether bolt is only fired when the button press is “performed.”
4. The Boolean launchPressed simply keeps the method from calling LaunchAether() [which actually fires the aether bolt] until the first button press is completed.
Unity Input System Documentation:
The Input System is really great, it truly is! However, it also really takes some work and forethought to integrate with many of Unity's other components.
There are also issues relating to both Prefabs and Inactive GameObjects. I haven’t looked into it in great detail myself, but again, I’d recommend reading the unity docs and possibly some Unity forum posts before trying it out.
Unity & Visual Studio Summary
What went well:
- I learned a ton. I am so much more confident in both Unity Editor and Visual Studio skills. There is so much still to learn, but it always feels manageable now.
- Overall, my game runs efficiently on the several machines I have tested it on. Of course, I don’t have a mobile build... I suspect I would have some more work to do to get the game to run consistently at a good frame rate, but maybe not!
- I was able to implement each game design idea I wanted to through scripting. I didn’t have to compromise my vision of the mechanics or gameplay.
Things I would do differently/continue to improve:
- Code every day, even if it’s just a little bit: Of course, more time is better, but even if you have only a few minutes, it will keep what you learned in your head
- Object Pools: This is probably the next thing I will choose to educate myself on. I’m guessing I would need it for an efficient mobile build of this game. This little game instantiates A LOT of objects. 😊 Efficiency, efficiency, efficiency!
- Continue to make code more efficient and organized
- RNG in Visual Studio [Random.Range()] isn’t truly random. I was able to rig up some code to help improve this, but my power-up spawns still get into repeating cycles.
- I wouldn’t switch input systems in the middle of design… oops!
Next Time in Part 3:
Thanks for reading!
Next time I'll talk about what was easily the most challenging part of the whole process for me: Pixel Art!
Spoiler: Hire a Professional!
Below is a link to itch.io where you can download Aether Run for free.
You can also check out my "in progress" website link below if you're curious about some of my past projects or the upcoming puzzle game I'm working on. There isn't much info yet on the latter but there will be much more when the alpha build is released (TBD October 2021).