Jake Rowland
Tools/Engine Programmer
Neon_Feature_Image.png

Neon

Change Colors To Explore

logo_team.png

Position

Lead Programmer

Team Size

6 Students

Engine

Unity

Time Frame

3 Months

Release (Internal)

Dec 2019

Platform

Galaxy Tab A


About

Neon is a relaxed platform-puzzle game for the Galaxy Tab A. Players adventure through a color changing synth-wave world where the music and the colors are constantly changing to provide a wide array of emotions from exciting bursts of fun and relaxing wanderings through this neon world. The game is targeted towards players looking for a new-age synth-wave journey that only takes up a commute’s worth of time.

Six Levels

Double Jump, Wall Climb, Dash

Virtual Joystick

Collectables

Multiple Paths


Acceleration Based Player Movement

When developing the movement for Neon, we wanted to have a very satisfying movement curve. Instead of simply setting the velocity to move left and right, I developed a system to set accelerations and deceleration with a maximum velocity. The vertical movement has some similar tweaks including adjusting gravity during different points in the players arc. The player also has some extra control over the jump on the height similar to Mario games.

  • Virtual Joystick with programmable dead zones

  • Highly ability to modify acceleration and deceleration values

  • Variable height jump based on player input

  • Focused on movement feeling

      
          // Update is called once per frame
      public void UpdateJoystick()
      {
          if( Input.touchCount > 0 && m_IsJoystickActive )
          {
              foreach(Touch t in Input.touches)
              {
                  JoystickCalculation(t);
              }
          }

          if(Input.touchCount == 0)
          {
              m_IsJoystickActive = false;
              m_Magnitude = 0f;
              m_MagnitudeNoCurve = 0f;
              m_JoystickPosition = Vector2.zero;
              m_Angle = 0;
          }
      }

      // Determines if the touch should be calculated
      private void JoystickCalculation(Touch touch)
      {
          if(touch.fingerId != m_JoystickFingerId) { return; }

          Vector2 joystickDisplacement = Vector2.ClampMagnitude(touch.position - m_JoystickCenter, m_MaximumJoystickDistance);

          DeadzoneCorrection(joystickDisplacement);
      }

      // Deadzone correction
      private void DeadzoneCorrection(Vector2 rangeToMax)
      {
          float pureRadius = rangeToMax.magnitude * 1f / m_MaximumJoystickDistance;
          float correctedRadius = pureRadius.Remap(m_InnerDeadzone, m_OuterDeadzone, 0f, 1f).Clamp(0f, 1f);

          if( correctedRadius > 0)
          {
              m_Angle = Mathf.Rad2Deg * Mathf.Atan2(rangeToMax.y, rangeToMax.x);
              if(m_Angle < 0)
              {
                  m_Angle = 360 + m_Angle;
              }

              m_MagnitudeNoCurve = correctedRadius;

              // Magnitude Curve
              if(correctedRadius < .333f)
              {
                  m_Magnitude = .333f;
              }
              else if (correctedRadius < .666f)
              {
                  m_Magnitude = .666f;
              }
              else if(correctedRadius < 1f)
              {
                  m_Magnitude = 1f;
              }

              m_Magnitude = correctedRadius;
              m_JoystickPosition = new Vector2(m_Magnitude * Mathf.Cos(m_Angle * Mathf.Deg2Rad),
                  m_Magnitude * Mathf.Sin(m_Angle * Mathf.Deg2Rad));
          }
          else
          {
              m_Magnitude = 0f;
              m_MagnitudeNoCurve = 0f;
              m_JoystickPosition = Vector2.zero;
              m_Angle = 0f;
          }
      }
      
  

Neon Shader Effect

One of the major challenges with Neon was the visuals and how to get quality neon glow effect on the relatively low specs of our devices. I spent quite a long time investigating alternatives but eventually decided that I needed a custom solution. I started with a simple blur effect and inserted it into the middle of Unity’s render pipeline. I made sure to only blur the objects we wanted the effect on. Finally, I combined the scene with the blurred texture such that the resulting alpha follows a similar function: f(x) = -|x| + 1 centered around the edges of the objects.

  • Custom blur shader for the glowing objects

  • Can modify the shader from Unity Editor

  • Performant on low device specs

  • Adds unique look to the environment

  	
    	// Customization from Unity Engine
    	#if _SAMPLES_LOW
            #define SAMPLES 20
        #elif _SAMPLES_MEDIUM
            #define SAMPLES 50
        #elif _SAMPLES_HIGH
            #define SAMPLES 100
        #endif
    
    	// Horizontal Blur Pass
        fixed4 frag( v2f i ): SV_Target
        {
            fixed4 col = 0;

            // Get 10 averages on the vertical
            for ( float index = 0; index < SAMPLES; ++index )
            {
                float2 uv = i.uv + float2(0, (index / (SAMPLES - 1) - .5) * _BlurSize);

                col += tex2D( _MainTex, uv );
            }

            // Average the colors
            col = col / SAMPLES;
            return col;
        }
        
        // Vertical Blur Pass
        fixed4 frag( v2f i ): SV_Target
            {
            float inverseAspect = _ScreenParams.y / _ScreenParams.x;
            fixed4 col = 0;

            // Get 10 averages on the vertical
            for ( float index = 0; index < SAMPLES; ++index )
            {
                float2 uv = i.uv + float2((index / (SAMPLES - 1) - 0.5) * _BlurSize * inverseAspect, 0);

                col += tex2D( _MainTex, uv );
            }

            // Average the colors
            col = col / SAMPLES;
            return col;
        }
        
        // Addative result (Scene + Blur Texture)
        sampler2D _Background;
        sampler2D _Additive;
        float _AlphaAdditiveCutoff;

        fixed4 frag( v2f i ): SV_Target
        {
            fixed4 colBack = tex2D( _Background, i.uv );
            fixed4 colAdd = tex2D( _Additive, i.uv );

            if ( _AlphaAdditiveCutoff < colAdd.a )
            {
                return colBack + colAdd * (1 - colAdd.a);
            }
            return colBack + colAdd;
        }
    
  

Saved Games

During development, once the collectables were implemented we realized that we needed a way to save and load the players progression through play sessions. To achieve that, I used Newtonsoft to serialize and de-serialize the list of level progressions to store on device. This allowed the player to exit the game and save their current progress. Whenever the game was paused in android, I would quickly write out the current data to the device. When the game was loaded, I would query that file. If the file was gone, I would create a new list of level states that would be used for the remainder of the game, being saved back to disk when the game exited.

  • Persistent progression data

  • Saved and loaded based on application state

  • Used third party serialization library

	
    // Initialize the game
    public void Initialize()
    {
        AudioManager.Instance.SetupMixer();
        AudioManager.Instance.SetupSounds();

        gameObject.tag = "GameController";
        m_IsInitialized = true;

        bool noInitilization = true;

        // Load the data from disc
        m_LevelStoragePath = Application.persistentDataPath + "/LevelData.json";
        if (File.Exists(m_LevelStoragePath))
        {
            UnityEngine.Debug.Log("Loading file from data");
            StreamReader fileReader = new StreamReader(m_LevelStoragePath);
            m_LevelDatas = JsonConvert.DeserializeObject<List<LevelData>>(fileReader.ReadToEnd());
            if (m_LevelDatas != null)
            {
                noInitilization = false;
            }
            else
            {
                UnityEngine.Debug.LogError("ERROR: Data failed to load");
            }
            fileReader.Close();
        }

        // Verify that the file loaded
        if (noInitilization)
        {
            UnityEngine.Debug.Log("No file Found/Failed to initialize");
            m_LevelDatas = new List<LevelData>();
            for (int i = 0; i < NumberOfLevels; i++)
            {
                m_LevelDatas.Add(new LevelData(i));
            }
        }
    }
    
    // Called when the application is minimized
    private void OnApplicationPause()
    {
        UnityEngine.Debug.Log("Writing file to disc");
        UnityEngine.Debug.Log("Storing data at " + m_LevelStoragePath);
        StreamWriter fileWriter = new StreamWriter(m_LevelStoragePath, false);
        string temp = JsonConvert.SerializeObject(m_LevelDatas);
        fileWriter.Write(temp);
        fileWriter.Close();
    }
      
  

Production and Scrum

Of the team members originally on our team, I was that one that had the most professional experience with the scrum process and software development. During the early sprints, I helped guide our team through and answer any questions. However, as the team became more confident, I became more hands off. We chose to swap scrum masters every sprint to allow each of our team members the opportunity. During sprint planning, I really tried to drive the other team members to very granular tasks and helped them break up tasks that would be too large for our specific timeline. As the team improved, they became more self-reliant, including taking on some very major production tasks without being required of them.

  • Scrum development cycle

  • Sprint planning and retros

  • Rotating scrum master


Retrospective

What Went Well

  • Team composition worked well from the beginning

  • Cross discipline communication - the moment a asset was ready, we would announce to the awaiting discipline its completion

  • Very fine task breakdowns

What Went Wrong

  • Task estimation needed improvement going through the sprints

  • Under estimated time/hours available during a sprint (alpha sprint)

  • Used sprint time for meetings that were not allocated/exceeded time box

Even Better Ifs

  • Schedule additional time for meetings and guaranteed tasks such as bug fixing

  • Schedule weekly meetings to refine game design and discuss the state of the game