Joe Barber

Software Developer

About Me

Enthusiastic, adaptable, productive software developer.

Algonquin College graduate of
Computer Engineering - Computing Science

One year in successful co-op work experience:
CSDC - (SQL) Data Migration
Genband - (Python) Disk Space Management
Genband - (HTML/CSS/JS) WebRTC Application
WindRiver - (Python) Web Automated Testing and Configuration

Hard working, passionate programmer and gamer.
Excellent communication skills and friendly team player.

I'm eager to join a software development team that allows me to use and extend my skills.

Unity

Projects In The Works


1. 2.5D RPG adventure

Completed Unity Projects

These Unity Projects were created with a mindset of a minimal viable product.
I took basic concepts and mechanics to create these projects below.

Jump To Code Section


3D Unity - Zelda

This project shows 3rd person 3D movement with a gamepad/controller. It also demonstrates targeting NPC and enemy objects.

Bounce Away Unity Project

This project shows first person 3D movement with keyboard and mouse. It also demonstrates bouncing off platforms using rigidbody force.

Mario Unity Project

This project shows 2D movement with keyboard controls. It also demonstrates 2D animations and simple 2D physics.

Pokemon Battle Unity Project


This project shows turn based combat between a player and an NPC. The NPC waits for the player to take their turn to attack. The player then also must wait for the NPC to take their turn.

Code

Here are the Player Controller classes from the 4 Unity projects above.



            // Rigibody_Movement requires the GameObject to have a Rigidbody component
            [RequireComponent (typeof (Rigidbody))]

            /// summary
            /// This class controls a players movement applying forces to a Rigidbody component
            /// This class can be used for 3D 3rd person gamepad/controller based games
            /// summary
            public class Rigidbody_3rdPerson_Gamepad_Movement : MonoBehaviour {

                Rigidbody rb; // Rigidbody component reference
                Camera cam; // Camera component reference
                Animator anim; // Animator component reference

                // Private Serialized variables to allow tweaking in Unity inspector
                [SerializeField] float rbDrag = 1f; // Rigidbody drag
                [SerializeField] float speed = 25.0f; // Player speed
                [SerializeField] float maxSpeed = 8.0f; // Player max speed
                [SerializeField] float turnSpeed = 3.0f; // Player turn speed

                // Private variables
                float dampTime = 0.1f;
                float controllerHorizontalCalibration = 0.4f;
                float maxSpeedLTargetV = 8.0f;
                float maxSpeedLTargetH = 7.5f;
                bool attacking = false;

                /// summary
                /// Start is called first
                /// The function initializes all variables and components
                /// summary
                void Start () {
                    rb = GetComponent Rigidbody ();
                    anim = GetComponent Animator();
                    rb.drag = rbDrag;
                    cam = Camera.main;
                }

                /// summary
                /// FixedUpdate is called every physics step
                /// The function gets input from the user and adds a force to the Rigidbody component
                /// summary
                void FixedUpdate () {
                    Vector3 forward;
                    if (attacking)
                        return;
                    if (ZeldaMechanics.lTargetEnabled) {
                        // Get input
                        float h = speed * Input.GetAxis ("JoystickXAxis");
                        float v = speed * Input.GetAxis ("JoystickYAxis");
                        forward = cam.transform.TransformDirection (Vector3.forward);
                        Vector3 right = cam.transform.TransformDirection (Vector3.right);
                        // Move a bit faster with weapon equipped
                        if (ZeldaMechanics.weaponEquiped) {
                            h = Mathf.Clamp (h, -maxSpeedLTargetH, maxSpeedLTargetH);
                            v = Mathf.Clamp (v, (-maxSpeedLTargetV), (maxSpeedLTargetV + 2));
                        } else {
                            h = Mathf.Clamp (h, -maxSpeedLTargetH, maxSpeedLTargetH);
                            v = Mathf.Clamp (v, -maxSpeedLTargetV, maxSpeedLTargetV);
                        }
                        // Small fix for controller calibration
                        if (Mathf.Abs (h) < controllerHorizontalCalibration)
                            h = 0;
                        // Play animations
                        anim.SetFloat ("HorizontalTL", h, dampTime, Time.deltaTime);
                        anim.SetFloat ("Forward", v, dampTime, Time.deltaTime);
                        if (h == 0 && v > h || v < 0) { // Vertical movement
                            rb.AddForce (forward * v); // Add vertical force
                        } else {
                            rb.AddForce (right * h); // Add horizontal force
                        }
                    } else {
                        // Get input
                        transform.Rotate (0f, Input.GetAxis ("Horizontal") * turnSpeed, 0f);
                        forward = transform.TransformDirection (Vector3.forward);
                        float verticalSpeed = speed * Input.GetAxis ("Vertical");
                        verticalSpeed = Mathf.Clamp (verticalSpeed, -maxSpeed, maxSpeed);
                        // Play animations
                        anim.SetFloat ("Forward", verticalSpeed, dampTime, Time.deltaTime);
                        anim.SetFloat ("HorizontalTL", 0f, dampTime, Time.deltaTime);
                        rb.AddForce (forward * verticalSpeed);
                    }
                }
            }
        


            /// summary
            /// Character controls. 
            /// This class controls the character checking grounded, movement, jumping, and bouncing.
            /// summary
            public class CharacterControls : MonoBehaviour {

                // Public variables
                public float speed = 10.0f;
                public float gravity = 10.0f;
                public float maxVelocityChange = 10.0f;
                public bool canJump = true;
                public float jumpHeight = 2.0f;

                // Public static variables
                public static bool grounded = false;
                public static bool isBouncing = false;
                public static bool onWall = false;

                // Private variables
                private Rigidbody rb;
                private Camera cam;
                private float distToGround;

                /// summary
                /// Awake is called first. 
                /// The function initializes all variables.
                /// summary
                void Awake () {
                    distToGround = GetComponent< Collider >().bounds.extents.y;
                    Cursor.visible = false;
                    cam = GetComponentInChildren< Camera > ();
                    rb = GetComponent< Rigidbody > ();
                    rb.freezeRotation = true;
                    rb.useGravity = false;
                }

                /// summary
                /// Update is called every frame. 
                /// Checks for userinput 'c'. 
                /// summary
                void Update () {
                    if (Input.GetButtonUp ("Checkpoint")) {
                        GameManager gm = GameObject.Find ("GameManager").GetComponent< GameManager > ();
                        gm.GotoCheckPoint ();
                    }
                }

                /// summary
                /// FixedUpdate is called every physics step. 
                /// The function calculates the player velocity and applies the forces to the character.
                /// If character is onWall, stop user input for velocity.
                /// summary
                void FixedUpdate () {
                    if (onWall) {
                        // Don't take user input
                    } else {
                        Vector3 velocityChange = CalculateTargetVelocity ();
                        rb.AddForce (velocityChange, ForceMode.VelocityChange);
                    }

                    if (IsGrounded ()) {
                        onWall = false;
                        // Jump
                        if (canJump && Input.GetButton ("Jump") && !isBouncing) {
                            rb.velocity = new Vector3 (rb.velocity.x, CalculateJumpVerticalSpeed (), rb.velocity.z);
                        }
                    }

                    // Apply gravity manually for more tuning control
                    rb.AddForce (new Vector3 (0, -gravity * rb.mass, 0));

                    grounded = false;
                }

                /// summary
                /// Raises the collision enter event.
                /// The function checks ground 'type'. Bounce platforms or the rest of the level.
                /// summary
                /// param name="other">Other.param
                void OnCollisionEnter (Collision other) {
                    if (other.collider.CompareTag ("Untagged")) {
                        isBouncing = false;
                    }
                }

                /// summary
                /// Determines whether this instance is grounded.
                /// summary
                /// returns true if this instance is grounded; otherwise, false
                private bool IsGrounded() {
                    return Physics.Raycast(transform.position, -Vector3.up, distToGround + 0.1f);  
                }

                /// summary
                /// Calculates the jump vertical speed.
                /// summary
                /// returns The jump vertical speed
                private float CalculateJumpVerticalSpeed () {
                    // From the jump height and gravity, deduce the upwards speed 
                    // for the character to reach at the apex.
                    if (isBouncing)
                        return rb.velocity.y;
                    return Mathf.Sqrt(2 * jumpHeight * gravity);
                }

                /// summary
                /// Raises the GU event.
                /// Creates simple crosshair.
                /// summary
                void OnGUI(){
                    GUI.Box(new Rect(Screen.width/2,Screen.height/2, 10, 10), "");
                }

                /// summary
                /// Calculates the target velocity.
                /// summary
                /// returns The target velocity
                private Vector3 CalculateTargetVelocity(){
                    // Calculate how fast we should be moving
                    Vector3 targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical")); // Get input
                    targetVelocity = cam.transform.TransformDirection (targetVelocity); // Which direction is the player (camera in this case) facing
                    targetVelocity *= speed;

                    // Apply a force that attempts to reach our target velocity
                    Vector3 velocity = rb.velocity; // current velocity
                    Vector3 velocityChange = (targetVelocity - velocity);
                    velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
                    velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
                    velocityChange.y = 0;
                    return velocityChange;
                }

                /// summary
                /// Disables the control.
                /// summary
                public void DisableControl () {
                    rb.isKinematic = true;
                    rb.velocity = Vector3.zero;
                    rb.angularVelocity = Vector3.zero;
                }

                /// summary
                /// Enables the control.
                /// summary
                public void EnableControl () {
                    rb.isKinematic = false;
                }
            }
        


            /// summary
            /// Player control.
            /// This class controls the character checking grounded, walking, running, and jumping.
            /// summary
            public class PlayerControl : MonoBehaviour
            {
                // Public Hidden variables 
                [HideInInspector] public bool facingRight = true;
                [HideInInspector] public bool jump = false;
                [HideInInspector] public bool run = false;
                [HideInInspector] public GameObject instance; 

                // Public variables
                public float moveForce = 365f;
                public float maxSpeed = 5f;
                public float runSpeed = 100f;
                public float jumpForce = 1000f;
                public Transform groundCheck;
                public Text livesText;

                // Public static variables
                public static int lives = 3;

                // Private variables
                private bool grounded = false;
                private Animator anim;
                private Rigidbody2D rb2d;
                private AudioSource audioPlayer;
                private AudioClip[] audioPlayerclips;

                /// summary
                /// Awake is called first.
                /// The function initializes all variables.
                /// summary
                void Awake () {
                    anim = GetComponent< Animator > ();
                    rb2d = GetComponent< Rigidbody2D > ();
                    if (lives == 1)
                        livesText.text = "Last Life!";
                    else
                        livesText.text = "Lives: " + lives;
                    audioPlayer = GetComponent< AudioSource > ();
                    audioPlayerclips = Resources.LoadAll < AudioClip > ("Sounds/Player");
                }

                /// summary
                /// Update is called every frame.
                /// Checks for grounded, run, and jump.
                /// summary
                void Update () {
                    grounded = Physics2D.Linecast (transform.position, groundCheck.position, 1 << LayerMask.NameToLayer ("Ground"));

                    if (Input.GetButtonDown ("Jump") && grounded) { // Must be grounded to jump
                        jump = true;
                    } else if (Input.GetButton ("Run") && grounded && Mathf.Abs(rb2d.velocity.x) > maxSpeed) { // Must be walking to run and also grounded
                        run = true;
                    }
                }

                /// summary
                /// FixedUpdate is called every physics step.
                /// The function calculates the player velocity and applies the forces to the character.
                /// summary
                void FixedUpdate () {

                    float h = Input.GetAxisRaw ("Horizontal");

                    if (h * rb2d.velocity.x < maxSpeed) {
                        rb2d.AddForce (Vector2.right * h * moveForce); // Not moving
                    }


                    if (Mathf.Abs (rb2d.velocity.x) > maxSpeed) {
                        anim.SetTrigger ("isWalking");
                        rb2d.velocity = new Vector2 (Mathf.Sign (rb2d.velocity.x) * maxSpeed, rb2d.velocity.y); // Moving
                    }

                    if (h > 0 && !facingRight) {
                        Flip ();
                    } else if (h < 0 && facingRight) {
                        Flip ();
                    }

                    if (jump) {
                        audioPlayer.clip =  audioPlayerclips[0];
                        audioPlayer.Play();
                        anim.SetTrigger ("isJumping");
                        if (run)
                            rb2d.velocity += new Vector2 (Mathf.Sign (rb2d.velocity.x) * runSpeed, 5f); // Running Jump
                        rb2d.AddForce (new Vector2 (0f, jumpForce));
                        jump = false;
                    }

                    if (run) {
                        anim.SetTrigger ("isRunning");
                        rb2d.velocity += new Vector2 (Mathf.Sign (rb2d.velocity.x) * runSpeed, 0f); // Running
                        run = false;
                    }
                        
                }

                /// summary
                /// Flip the player sprite.
                /// summary
                void Flip () {
                    facingRight = !facingRight;
                    Vector3 theScale = transform.localScale;
                    theScale.x *= -1;
                    transform.localScale = theScale;
                }

                /// summary
                /// Disables the control of the player.
                /// summary
                public void DisableControl () {
                    rb2d.velocity = Vector3.zero;
                    rb2d.angularVelocity = 0f;
                    rb2d.isKinematic = true;
                }

                /// summary
                /// Enables the control of the player.
                /// summary
                public void EnableControl () {
                    rb2d.isKinematic = false;
                }

                /// summary
                /// Reset player variables.
                /// summary
                public void Reset () {
                    lives = 3;
                    livesText.fontSize = 30;
                    livesText.alignment = TextAnchor.UpperCenter;
                }

                /// summary
                /// Player is dead.
                /// Disable all player controls, and edit text values.
                /// summary
                public void PlayerDead () {
                    DisableControl ();
                    anim.Stop ();
                    livesText.fontSize = 70;
                    livesText.alignment = TextAnchor.MiddleCenter;
                    livesText.text = "GAME OVER";
                }
            }
        


            /// summary
            /// Player.
            /// This class controls the player's actions. Checking turn, attacking, and changing pokemon.
            /// Both the player and NPC get 3 random pokemon from a list of 10.
            /// summary
            public class Player : MonoBehaviour {

                // Public variables
                public bool myTurn = false;
                public Text PokemonName;
                public Text PokemonHealthPoints;
                public Text Turn;

                // Private variables
                private float healthPoints = 100f;
                private float attack = 25f;
                private int currentPokemon = 0;
                private bool pokemonList = false;
                private bool isPanelActive = false;
                private bool isCoroutineRunning = false;
                private bool isCoroutineRunningSpriteFlicker = false;
                private ArrayList pokemon;
                private Pokemon pk;

                // Private game variables
                private NPC npc;
                private GameManager gm;
                private Button buttonAttack;
                private Button buttonPokemon;
                private GameObject panel;
                private Sprite[] playerSprites;
                private SpriteRenderer sr;
                private Slider slider;
                private AudioSource audioPlayer;
                private AudioClip[] audioPlayerclips;


                /// summary
                /// Start is called on first frame.
                /// The function initializes all variables.
                /// The function also adds two onclick listeners.
                /// summary
                void Start () {
                    npc = GameObject.Find ("NPC").GetComponent< NPC >(); // Instance of NPC script
                    gm = GameObject.Find ("GameManager").GetComponent< GameManager >(); // Instance of GameManager script
                    buttonAttack = GameObject.FindGameObjectWithTag("Player_Attack").GetComponent< Button >();
                    buttonPokemon = GameObject.FindGameObjectWithTag("Player_Pokemon").GetComponent< Button >();
                    panel = GameObject.FindGameObjectWithTag ("Panel");
                    sr = GetComponent< SpriteRenderer > ();
                    sr.transform.localPosition = new Vector3 (-3, -3, 0);
                    sr.transform.localScale = new Vector3 (5,5,0);
                    slider = GameObject.Find ("PlayerSlider").GetComponent< Slider > ();
                    audioPlayer = GetComponent< AudioSource > ();
                    Init ();
                    panel.SetActive (false);
                    buttonAttack.onClick.AddListener (delegate() {
                        Attack();
                    });
                    buttonPokemon.onClick.AddListener (delegate() {
                        ShowPokemon();
                        audioPlayer.clip =  audioPlayerclips[0];
                        audioPlayer.Play();
                    });
                }
                
                /// summary
                /// Update is called every frame.
                /// Checks for gameover, all pokemon dead, and player's turn.
                /// summary
                void Update () {
                    Pokemon temp = (Pokemon)pokemon [currentPokemon];
                    if (gm.gameOver) {
                        DisableMenu ();
                        return;
                    }
                    else if (temp.HealthPoints == 0) {
                        // Switch to next pokemon with health != 0
                        AllPokemonDead();
                        ShowPokemon();
                        temp.HealthPoints--; // Stops spam of showing menus
                    } else {
                        if (myTurn) {
                            // Wait for button click Player_Attack
                            buttonAttack.enabled = true;
                        }
                    }
                }

                /// summary
                /// Raises the GU event.
                /// Creates player pokemon list.
                /// summary
                void OnGUI () {
                    if (pokemonList) {
                        // Loop to create buttons based on pokemon with HP left
                        float buttonShift = 0.10f;
                        for (int i = 0; i < pokemon.Count; i++) {
                            Pokemon p = (Pokemon)pokemon [i];
                            if (p.HealthPoints > 0) {
                                if (GUI.Button (new Rect (Screen.width* (0.45f), Screen.height* (0.6f  + buttonShift), 200, 50), p.Name)) {
                                    ChangePokemon (p, i);
                                    buttonShift = 0.10f;
                                    return;
                                }
                                buttonShift += 0.05f;
                            }
                        }
                        buttonShift = 0.10f;
                        myTurn = false;
                    }
                }

                /// summary
                /// Init called inside Start() to create player's pokemon.
                /// Init also sets all UI text for the player's first pokemon.
                /// summary
                private void Init () {
                    // Get sprites
                    playerSprites = Resources.LoadAll < Sprite > ("Sprites/pokePlayer");
                    // Get audioPlayer clips
                    audioPlayerclips = Resources.LoadAll < AudioClip > ("Sounds/Player");
                    pokemon = new ArrayList();
                    for (int i = 0; i < 3; i++) {
                        int randomIndex = Random.Range (1, 10);
                        // Choose pokemon name
                        Pokemon p = (Pokemon)gm.pokemon[randomIndex];
                        // Create new pokemon
                        switch(p.Type){
                        case "Starter": {
                                for (int j = 0; j < playerSprites.Length; j++) {
                                    if (playerSprites [j].name.Contains (p.Name.ToLower())) {
                                        pokemon.Add (new Pokemon (p.Name, 250, 50, p.Type, playerSprites[j]));
                                        break;
                                    }
                                }
                                break;
                            }
                        case "Basic": {
                                for (int j = 0; j < playerSprites.Length; j++) {
                                    if (playerSprites [j].name.Contains (p.Name.ToLower())) {
                                        pokemon.Add (new Pokemon (p.Name, 100, 25, p.Type, playerSprites[j]));
                                        break;
                                    }
                                }
                                break;
                            }
                        case "Useless": {
                                for (int j = 0; j < playerSprites.Length; j++) {
                                    if (playerSprites [j].name.Contains (p.Name.ToLower())) {
                                        pokemon.Add (new Pokemon (p.Name, 100, 0, p.Type, playerSprites[j]));
                                        break;
                                    }
                                }
                                break;
                            }
                        default: {
                                break;
                            }
                        
                        }
                    }

                    // Assign attack and hp from first pokemon
                    Pokemon temp = (Pokemon)pokemon [0];
                    healthPoints = temp.HealthPoints;
                    attack = temp.AttackDamage;
                    // Set sprite
                    sr.sprite = temp.Image;
                    // Set health slider
                    switch (temp.Type) {
                    case "Starter":
                        { slider.maxValue = Pokemon.STARTERHP; break; }
                    case "Basic":
                        { slider.maxValue = Pokemon.BASICHP; break; }
                    case "Useless":
                        { slider.maxValue = Pokemon.USELESSHP; break; }
                    }
                    slider.value = healthPoints;
                    Turn.text = "Player's TURN NOW";
                    PokemonName.text = temp.Name;
                    PokemonHealthPoints.text = "HP: " + temp.HealthPoints;
                }

                /// summary
                /// Shows the pokemon panel.
                /// summary
                private void ShowPokemon () {
                    // Enable panel
                    if (isPanelActive) {
                        panel.SetActive (false);
                        isPanelActive = false;
                        pokemonList = false;
                    } else {
                        isPanelActive = true;
                        panel.SetActive (true);
                        pokemonList = true;

                    }
                }

                /// summary
                /// Checks if all pokemon are dead.
                /// summary
                private void AllPokemonDead () {
                    int count = 0;
                    for (int i = 0; i < pokemon.Count; i++) {
                        Pokemon p = (Pokemon)pokemon [i];
                        if (p.HealthPoints > 0) {
                            // Found Pokemon with health
                            // Update values
                            healthPoints = p.HealthPoints;
                            attack = p.AttackDamage;
                            // Update text on screen
                            PokemonName.text = p.Name;
                            PokemonHealthPoints.text = "HP: " + p.HealthPoints;
                            // Change index to current Pokemon
                            currentPokemon = i;
                            break;
                        }
                        // Count pokemon with 0 hp
                        if (p.HealthPoints <= 0) {
                            count++;
                            // All pokemon have 0 hp
                            if (count == pokemon.Count)
                                gm.GameOver ();
                        }
                    }
                }

                /// summary
                /// Disables the menu.
                /// summary
                private void DisableMenu () {
                    buttonAttack.enabled = false;
                    buttonPokemon.enabled = false;
                }

                /// summary
                /// Changes the pokemon.
                /// Changes all UI text to changed pokemon.
                /// summary
                /// param name="p"
                /// param name="index"
                private void ChangePokemon (Pokemon p, int index) {
                    audioPlayer.clip =  audioPlayerclips[0];
                    audioPlayer.Play();
                    // Update values
                    healthPoints = p.HealthPoints;
                    attack = p.AttackDamage;
                    // Set sprite
                    sr.sprite = p.Image;
                    // Set health slider
                    switch (p.Type) {
                    case "Starter":
                        { slider.maxValue = Pokemon.STARTERHP; break; }
                    case "Basic":
                        { slider.maxValue = Pokemon.BASICHP; break; }
                    case "Useless":
                        { slider.maxValue = Pokemon.USELESSHP; break; }
                    }
                    slider.value = healthPoints;
                    // Update text on screen
                    PokemonName.text = p.Name;
                    PokemonHealthPoints.text = "HP: " + p.HealthPoints;
                    // Change index to current Pokemon
                    currentPokemon = index;
                    // Hide Pokemon panel
                    panel.SetActive (false);
                    isPanelActive = false;
                    pokemonList = false;
                    Turn.text = "NPC's TURN NOW";
                    myTurn = false;
                    npc.myTurn = true;
                    buttonAttack.enabled = false;
                }

                /// summary
                /// Attack NPC pokemon.
                /// summary
                private void Attack () {
                    audioPlayer.clip =  audioPlayerclips[1];
                    audioPlayer.Play ();
                    // Pokemon attack animation
                    sr.transform.localPosition += new Vector3 (0.5f, 0, 0);
                    StartCoroutine(ResetSprite());
                    npc.TakeDamage (attack);
                    myTurn = false;
                    buttonAttack.enabled = false;
                    Turn.text = "NPC's TURN NOW";
                    npc.myTurn = true;
                }

                /// summary
                /// Resets the sprite.
                /// summary
                /// returns The sprite
                IEnumerator ResetSprite(){
                    if (isCoroutineRunning)
                        yield break;
                    isCoroutineRunning = true;
                    yield return new WaitForSeconds (0.15f);
                    sr.transform.localPosition -= new Vector3 (0.5f, 0, 0);
                    isCoroutineRunning = false;
                }

                /// summary
                /// Take damage from NPC pokemon.
                /// summary
                /// param name="damage"
                public void TakeDamage (float damage) {
                    Pokemon p = (Pokemon)pokemon [currentPokemon];
                    p.HealthPoints -= damage;
                    if (p.HealthPoints < 0)
                        p.HealthPoints = 0;
                    PokemonHealthPoints.text = "HP: " + p.HealthPoints;
                    slider.value = p.HealthPoints;
                    StartCoroutine(Spriteflicker());
                    buttonAttack.enabled = true;
                }

                /// summary
                /// Spriteflicker when taking damage.
                /// summary
                IEnumerator Spriteflicker(){
                    if (isCoroutineRunningSpriteFlicker)
                        yield break;
                    isCoroutineRunningSpriteFlicker = true;
                    sr.enabled = true;
                    yield return new WaitForSeconds (0.5f);
                    sr.enabled = false;
                    yield return new WaitForSeconds (0.1f);
                    sr.enabled = true;
                    yield return new WaitForSeconds (0.1f);
                    sr.enabled = false;
                    yield return new WaitForSeconds (0.1f);
                    sr.enabled = true;
                    isCoroutineRunningSpriteFlicker = false;
                }
            }
        

Contact Joe

If employment opportunities arise please contact me at your convenience @ joeyanbarber@gmail.com