Learning Game Dev #2: Apple ID, Unity Ads, Ghost scripts, Awake vs Start, level switcher, and scope.

Apple publishing process, and getting an app ID

This process was a bit convoluted, but overall took around an hour to be completed. In order to share my app with other users for testing via TestFlight, or put it in the Apple store (even if is for free) I need an app ID, and this only comes once your part of Apple Developer Program.

As a member of the Apple Developer Program, you’ll get access to App Store Connect , which is used to submit and manage apps, invite users to test with TestFlight, add tax and banking information, access sales reports, etc, …

Then you need to create a bundle certificate. You will need to set up digital certificates to develop and distribute iOS apps. To install any app on a development device or submit it to the App Store, it must first be signed with an Apple-issued certificate, and certificates allow the system to identify who signed the app. For this you’ll be creating a CSR file Certificate Signing Request (CSR) file on your local machine.

Once the keychain certificate has been generated, you can register a Bundle ID. A Bundle ID is the identifier of an App ( the App ID is not, the App ID is the connection between the App and a provisioning profile). For example, in my case these will be the Bundle ID and App ID:

  • Bundle ID: blog.tilcode.ngu.ios
  • App ID: Team ID (generated automatically by Apple) + the previous Bundle ID

One question that came up and still haven’t found a straight answer is: Can the App name be changed later in production? I’m still not sure and seems that’s not possible, so as I’m using a codename Ngu for the project but won’t be the final name, I guess I can always submit a different app for production, keeping the beta as well.

Once all of this is done, you’re good to go. Seems that the first submission requires a revision from Apple end and takes around 72 hours, for the moment I haven’t submitted anything as the Alpha is not ready for beta testing, but I expect it will be at the end of this week, and see how the complete process works.

Integrating Unity Ads

This was way easier, basically is a button that you enable/disable from Unity Services. Once the service is enabled you need to add programmatically when will the ads be shown, as well as other nuances like what happens if the ad is skipped, if is closed, if cannot be closed, if fails, the type of ad, etc, …

Unity makes this very easy with methods that are already accesible, like Advertisement.IsReady() or ShowResult.Skipped, to give some examples. This is the code snippet I’m using at the moment:

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

namespace Ngu
{
    public class AdManager : MonoBehaviour
    {

        public bool isAdRunning = true;

        public void PlayAdvertising()
        {

            var options = new ShowOptions { resultCallback = HandleShowResult }; // TODO: (?)

            if (Advertisement.IsReady("video")) //placement ID parameter, can be others from https://operate.dashboard.unity3d.com/ Overview > Monetization > Placements, pe: rewardedvideos cannot be skipped.
            {
                Advertisement.Show("video", options);
                Time.timeScale = 0; // Pauses game while the add is running
            }
        }

        private void HandleShowResult(ShowResult result)
        {
            switch (result)
            {
                // TODO: Seems that in TEST MODE, if I click on "close", the ad result is shown. Test this on real to see if is the same case.
                case ShowResult.Finished:
                    Debug.Log("The ad was successfully shown.");

                    // YOUR CODE TO REWARD THE GAMER

                    break;
                case ShowResult.Skipped:
                    Debug.Log("The ad was skipped before reaching the end.");
                    break;
                case ShowResult.Failed:
                    Debug.LogError("The ad failed to be shown.");
                    break;
            }
            Time.timeScale = 1; // Restarts game
            isAdRunning = false; // Game returns to normal after we have an ad result
        }
    }

}

/* Calling the script: This part is added within the GameManager, or when it should be called specifically */

AdManager adManagerScript;

adManagerScript = GetComponent<AdManager>();
adManagerScript.PlayAdvertising();

The GIST can be found here: https://gist.github.com/iamgabrielma/834e03ca8592074d784ad96818f5acfa

As you can see is still on the works, with some TODO’s to finish in order to be sure that everything is in place, if something changes I’ll update the GIST above.

At this point during development Unity Ads are completely optional when you die or level up, this activates an Score Multiplier for the next round, which make easier to reach un-lockable content. However these are never forced into you and are 100% optional. I’m not sure if will remain like this as has a basic flaw: When the player has unlocked everything, if they want to keep playing, then there’s no benefit of watching ads, which is not a long term sustainable solution for a free game.

One possible solution in the future is offer to deactivate these via payment, but still get the Score multiplier on die/reset to reach higher score on a leaderboard. Needs some thought.

Awake vs Start

I thought I knew the difference between these two initialization functions but this week I discovered that is not the case. wWhen I was working through my Parallax manager (which is what handles the different sprites that move at different speeds, as well as in different layers, to generate the background) the vectors for these GameObjects were being reset to (0,0,0), messing the whole thing up. Imagine the following example:

v3InitialPosition = new Vector3(10, -10, 0);
degreesPerSecond = 30.0f;
this.transform.Rotate(v3InitialPosition * degreesPerSecond * Time.deltaTime);

Calling these functions on Awake() was resetting the v3InitialPosition to (0,0,0), for some reason that I still don’t fully comprehend (possibly is the default position when the GameObjects are generated for first time, before being assigned to its new Vectors?), making all sprites to be generated on the same spot, which broke the Parallax effect completely, as well as the screen alignment.

Unity docs do a less-than-good-of-a-job explaining these differences, but after some testing I discovered that moving this logic into Start() worked perfectly.

Ghost scripts attached to inactive GameObjects

When I was working through the UI and menus I realized that some scripts where not working, this is because these were attached to menus that were not currently active in the scene. That is for example when you move from the Main Menu to the Options Menu, the first one will be disabled, and the second one enabled, if you attached scripts to the first one these will stop working because the GameObject is inactive, which makes sense but at the time can be a bit of a headache to find out why things don’t work as expected.

Lesson learned, at the moment I use a GameObject that acts as a placeholder for all menus, and from where all these settings are controlled, instead of going granular into each screen.

Cannot modify the return value of “X” because is not a variable. Access variables via Reference vs via Value

I’m still wrapping my head about this, and while I understand the basics, I haven’t dig deeper. Check the following example:

enemyObject.transform.localPosition.x = 10; // ERROR: Cannot modify the return value of x because is not a variable

When you access the localPosition.x property of enemyObject we’re accessing a copy of the value held by the class, not the value itself as you would with a reference type (class), so if you set the localPosition.x property on it then you’re setting the property on the copy and then discarding it, leaving the original value unchanged. That’s not what we want, it can be solved by assigning a new Vector3 to the localPosition:

int _newX = 10;
enemyObject.transform.localPosition = new Vector3(_newX, y, z);

Changing levels via SceneManager was an overkill in this game, moved to switch case scenarios.

The initial implementation of how the player move through levels implied 1 different scene per level, I didn’t thought this at all when I created the first world, but when I wanted to create the second world I stopped for a moment: Do I have to recreate all of this again? The map generator, the lighting, the cameras, the controls, replicate the GameManager, etc, … ?

Yes, I promise that’s not the definite art that will go into the game 🙂

In this case was a bit overkill to rely on SceneManager to change between levels, as all levels are the same on its root, a rectangle of 20×34 squares, where stuff is generated on the fly based on probability.

I already had setup some basic logic in place for this so I just put everything together in a better way: Now my Parallax Manager checks which level should we be at, loads the correct sprites to “change the level”, and that’s all about it, the rest of events still happens within an EventGenerator (like different types of enemies based on the level, difficulty, item chance, and such).

Static variables, PlayerPrefs, and accessing information globally

Careful with using static variables and PlayerPrefs, specially when you’re setting or hardcoding values for testing, or during any point at execution, as can really mess your project. In my case I spent hours debugging an issue where the player couldn’t unlock new levels after levelling up, which resulted on a static variable being set to 1 on Start(), this leaded to the following chain of events:

Player levels up, let’s say level 2 > We check current level == 2 (static variable) > New content for level 2 is unlocked > We go back to the World Selection screen and clear level references to start a new one > As we started a new one, the static variable returns to be = 1, as was set as initial value on Start() > We’re back to level one, and content remains locked.

Thinking about optimizing

I haven’t gone into optimization yet, as I don’t want to spend time pre-optimizing things that may change later on, or may not be necessary, but definitely try to build the game with this in mind. For example avoid using things like FindGameObjectWithTag in Update() as it’s very expensive, or use .png vs .jpg sprites correctly.

This will need some time dedicated to it, I’ll plan a few days or a week to optimize after the alpha is ready for playing. This takes even more importance when I’m planning to release on mobile only, not only so the game can run decently on multiple devices, but also there’s some research that states that every +6MB your app has, your customer base is reduced by 1%, so definitely something to look into.

Closing scope

Sooner than later you need to decide what will go into the game, and limit the scope. I initially planned to have an Alpha up and running by June 1st, that’s 3 days from now, and I still need to implement the unlock system correctly, as well as enemy behaviour, and variety of enemies. While I believe is alright to drag this a bit in order to have a quality product, is pretty easy to fall into the scope trap and keep adding stuff to the game, something that must be avoided at all costs if you ever want to launch.

Personally I though the game was scope closed 2 weeks ago but that was not the case, as wasn’t “fun” enough, but after a few gameplay changes these last weeks the game has taken a good turn, after these few things are implemented that’s it, scope closed and time to move to the polish phase.

Keep your scope tight and under control, even if you’re flexible with what goes in or out.

Next week:

For next week I should have the whole scope closed (everything that will go into the final game must be completed and working), fix current bugs, and start seriously with the art for each level. I also plan to move it to TestFlight so I can start external testing, if you want to join the Alpha testing, let me know in the comments, via email, or whatever 🙂

Feel free to join my Subscription list for updates:

Comments

Leave a Reply

Discover more from Home

Subscribe now to keep reading and get access to the full archive.

Continue reading