About the project

Project StarFall is the very first project that I worked on in a professional work enviroment as part of a hybrid learning program from the MediaCollege. This was a very informative experience where I have learned a lot. It feels like I am ready for what's to come at an internship. Excuse me for this messy project page! I'm working on making my portfolio cleaner and more clearer.

My Part In The Project

  Private
  • Movement(Walk & Jump)
  • Gun Shooting
  • Cinemachine Camera, Interesting Point Zoom
  • Item Pickup
  • Enemy Pathfinding & StateMachine
  • Early In-game screenshot

    Camera

    public class CameraController : MonoBehaviour
    {
    [Header("zoomData")]
    [SerializeField] private CinemachineTargetGroup playerTargets;
    [SerializeField] private float maxZoom;
    [SerializeField] private float minZoom;
    [SerializeField] private float defaultOffset;
    [SerializeField] private Vector2 zoomMultiplier;
    [SerializeField] private string interestPointTag;
    private Transform[] _furthestTargetTransforms = new Transform[4];
    private List<Collider2D> _interestingPointCollider = new List<Collider2D>();
    private float _targetCameraZoom = 0.0f;
    private const float TargetPointWeight = 0.5f;
    private void Awake()
    {
    for (int i = 0; i < 4; i++)
    {
    _furthestTargetTransforms[i] = new GameObject().transform;
    }
    }
    private void LateUpdate()
    {
    if (playerTargets.IsEmpty) return;
    _targetCameraZoom = GetZoom();
    _targetCameraZoom += defaultOffset;
    _targetCameraZoom = Mathf.Clamp(_targetCameraZoom, minZoom, maxZoom);
    _targetCameraZoom = -Mathf.Abs(_targetCameraZoom);
    GetComponent<CameraZoom>().UpdateZoom(_targetCameraZoom);
    }
    private void OnTriggerEnter2D(Collider2D other)
    {
    if (!other.gameObject.CompareTag(interestPointTag))
    {
    return;
    }
    _interestingPointCollider.Add(other);
    }
    private void OnTriggerExit2D(Collider2D other)
    {
    if (!other.gameObject.CompareTag(interestPointTag))
    {
    return;
    }
    RemoveInterestingPointTarget();
    _interestingPointCollider.Remove(other);
    }
    private float GetZoom()
    {
    if (_interestingPointCollider.Count == 0) return GetPlayerOnlyZoom();
    Vector3 center = GetInterestingPointsCenter();
    List<Vector3> corners = new List<Vector3>();
    Vector3[] colliderPoints = _interestingPointCollider[0].GetCornerPoints();
    corners.AddRange(colliderPoints);
    corners.AddRange(GetTargetPositions());
    Vector3 furthestPoint = center.GetFurthest(corners);
    if (Vector3.Distance(center, furthestPoint) < maxZoom)
    {
    UpdateTargetPoints(colliderPoints);
    return GetMultipliedDistance(center, furthestPoint);
    }
    RemoveInterestingPointTarget();
    return GetPlayerOnlyZoom();
    }
    private float GetPlayerOnlyZoom()
    {
    RemoveInterestingPointTarget();
    Vector3 center = GetDefaultCenter();
    List<Vector3> points = new List<Vector3>(GetTargetPositions());
    Vector3 furthest = center.GetFurthest(points);
    return GetMultipliedDistance(furthest, center);
    }
    private Vector3 GetDefaultCenter()
    {
    Vector3 center = new Vector3(0, 0, 0);
    foreach (var playerTargetsMTarget in playerTargets.m_Targets)
    center += playerTargetsMTarget.target.position;
    center /= playerTargets.m_Targets.Length;
    return center;
    }
    private Vector3 GetInterestingPointsCenter()
    {
    Vector3 center = new Vector3(0, 0, 0);
    foreach (var playerTargetsMTarget in playerTargets.m_Targets)
    center += playerTargetsMTarget.target.position;
    foreach (var box2DCornerPoint in _interestingPointCollider[0].GetCornerPoints())
    center += box2DCornerPoint;
    center /= playerTargets.m_Targets.Length + _interestingPointCollider[0].GetCornerPoints().Length;
    return center;
    }
    private void AddInterestingPointTarget()
    {
    var targets = new List<CinemachineTargetGroup.Target>(playerTargets.m_Targets);
    if (targets.Count(target => _furthestTargetTransforms.Contains(target.target)) > 0) return;
    for (var i = _furthestTargetTransforms.Length - 1; i >= 0; i--)
    {
    var pointTarget = new CinemachineTargetGroup.Target
    {
    radius = 1, weight = TargetPointWeight, target = _furthestTargetTransforms[i]
    };
    targets.Add(pointTarget);
    }
    playerTargets.m_Targets = targets.ToArray();
    }
    private void RemoveInterestingPointTarget()
    {
    List<CinemachineTargetGroup.Target> targets = new List<CinemachineTargetGroup.Target>(playerTargets.m_Targets);
    if (targets.Count(target => _furthestTargetTransforms.Contains(target.target)) == 0) return;
    for (var i = targets.Count - 1; i >= 0; i--)
    {
    if (_furthestTargetTransforms.Contains(targets[i].target)) targets.RemoveAt(i);
    }
    playerTargets.m_Targets = targets.ToArray();
    }
    private void UpdateTargetPoints(Vector3[] colliderPoints)
    {
    for (var i = colliderPoints.Length - 1; i >= 0; i--) _furthestTargetTransforms[i].position = colliderPoints[i];
    AddInterestingPointTarget();
    }
    private float GetMultipliedDistance(Vector3 from, Vector3 to)
    {
    var xDiff = (from.x - to.x) * zoomMultiplier.x;
    var yDiff = (from.y - to.y) * zoomMultiplier.y;
    return Mathf.Sqrt(xDiff * xDiff + yDiff * yDiff);
    }
    private Vector3[] GetTargetPositions()
    {
    Vector3[] positions = new Vector3[playerTargets.m_Targets.Length];
    for (var i = playerTargets.m_Targets.Length - 1; i >= 0; i--)
    {
    positions[i] = playerTargets.m_Targets[i].target.position;
    }
    return positions;
    }
    }
    public class CameraManager : MonoBehaviour
    {
    [SerializeField] private Camera orthoghrapicCamera;
    [SerializeField] private float followoffset;
    [SerializeField] private CinemachineVirtualCamera virtualCamera;
    [SerializeField] private CinemachineTransposer transposer;
    private void Awake()
    {
    transposer = virtualCamera.GetCinemachineComponent(CinemachineCore.Stage.Body).GetComponent<CinemachineTransposer>();
    }
    private void LateUpdate()
    {
    orthoghrapicCamera.orthographicSize = transposer.m_FollowOffset.z * -followoffset;
    }
    }
    public class CameraZoom : MonoBehaviour
    {
    [SerializeField] private CinemachineVirtualCamera virtualCamera;
    [SerializeField] private CinemachineTransposer transposer;
    [SerializeField] private float easeTime;
    private void Awake()
    {
    transposer = virtualCamera.GetCinemachineComponent(CinemachineCore.Stage.Body).GetComponent<CinemachineTransposer>();
    }
    public void UpdateZoom(float zoom)
    {
    transposer.m_FollowOffset.z += (zoom - transposer.m_FollowOffset.z) * easeTime;
    }
    }
    view raw CameraZoom.cs hosted with ❤ by GitHub
    public class CinamachineTargetGroupApi : CinemachineTargetGroup
    {
    public void AddTarget(Transform target)
    {
    List<Target> targets = new List<Target>(m_Targets);
    Target newTarget = new Target();
    newTarget.weight = 3;
    newTarget.radius = 1;
    newTarget.target = target;
    targets.Add(newTarget);
    m_Targets = targets.ToArray();
    }
    public void RemoveTarget(Transform target)
    {
    List<Target> targets = new List<Target>(m_Targets);
    if (targets.Count(aTarget => aTarget.target == target) == 0) return;
    targets.RemoveAt(targets.IndexOf(targets.First(aTarget => aTarget.target == target)));
    m_Targets = targets.ToArray();
    }
    }

    A storyboard of the tutorial that i wrote the code for

    Pickup

    public class Pickable : MonoBehaviour
    {
    [SerializeField] private string triggerTag;
    [SerializeField] private UnityEvent onPickup = new UnityEvent();
    [SerializeField] private bool destroyOnPickup;
    [SerializeField] private float destroyAfterSeconds = 1;
    private void OnTriggerEnter(Collider other)
    {
    if(other.gameObject.CompareTag(triggerTag))
    {
    onPickup?.Invoke();
    if (destroyOnPickup)
    {
    StartCoroutine(DestroyAfterTime());
    }
    }
    }
    IEnumerator DestroyAfterTime()
    {
    yield return new WaitForSeconds(destroyAfterSeconds);
    Destroy(this.gameObject);
    }
    }
    view raw Pickable.cs hosted with ❤ by GitHub

    The player can pick up coins

    Extra Info

    Engine:

  • Unity - Version 2019.2.15f1
  • C#


  • Enemy PathFinding

    public class EnemyAI : StateBehaviour
    {
    [SerializeField] private float speed = 500f;
    [SerializeField] private float mass = 1f;
    [SerializeField] private float nextWaypointDistance = 1f;
    [SerializeField] private UnityEvent onPathFinish = new UnityEvent();
    private Transform _currentTarget;
    private Path _path;
    private int _pathIndex = 0;
    private bool _reachedEndOfPath = false;
    private Seeker _seeker;
    private Rigidbody _rigidbody;
    public Transform CurrentTarget
    {
    get => _currentTarget;
    set => _currentTarget = value;
    }
    void Start()
    {
    _seeker = gameObject.GetOrAddComponent<Seeker>();
    _rigidbody = gameObject.GetOrAddComponent<Rigidbody>();
    StartPath();
    InvokeRepeating("UpdatePath", 0f, 0f);
    }
    private void UpdatePath()
    {
    if (_seeker.IsDone())
    {
    StartPath();
    }
    }
    private void OnPathComplete(Path path)
    {
    if (path.error) return;
    _path = path;
    _pathIndex = 0;
    onPathFinish?.Invoke();
    if (GetComponent<PatrolState>().enabled) {
    GetComponent<PatrolState>().OnPathComplete(path);
    }
    }
    void FixedUpdate()
    {
    if (_path == null || _currentTarget == null) return;
    if (_pathIndex >= _path.vectorPath.Count && !_reachedEndOfPath)
    {
    _reachedEndOfPath = true;
    StartPath();
    return;
    }
    if (_pathIndex >= _path.vectorPath.Count) return;
    _reachedEndOfPath = false;
    Vector3 currentWaypoint = _path.vectorPath[_pathIndex];
    Vector3 velocity = CalculateVelocity(currentWaypoint);
    _rigidbody.velocity = velocity;
    float distance = Vector3.Distance(_rigidbody.position, currentWaypoint);
    if (distance < nextWaypointDistance)
    {
    _pathIndex++;
    }
    FlipObject(velocity);
    }
    private Vector3 CalculateVelocity(Vector3 currentWaypoint)
    {
    Vector3 direction = currentWaypoint - _rigidbody.position;
    direction.Normalize();
    var currentVelocity = _rigidbody.velocity;
    Vector3 force = direction * speed * Time.deltaTime;
    var steeringForce = force - currentVelocity;
    var newVelocity = currentVelocity + (steeringForce / mass);
    return newVelocity;
    }
    public void StartPath()
    {
    if (_currentTarget == null || _seeker == null) return;
    _seeker.StartPath(_rigidbody.position, _currentTarget.position, OnPathComplete);
    }
    public GameObject GetClosestTarget(float length)
    {
    GameObject currentPlayer = null;
    List<GameObject> players = GameObject.FindGameObjectsWithTag("Player").OrderBy(player => Vector3.Distance(transform.position, player.transform.position)).ToList();
    foreach (GameObject player in players)
    {
    Vector3 direction = player.transform.position - transform.position;
    RaycastHit hit;
    Physics.Raycast(transform.position, direction, out hit, length);
    if (!IsHitValid(player, hit)) continue;
    currentPlayer = player;
    break;
    }
    return currentPlayer;
    }
    private bool IsHitValid(GameObject player, RaycastHit hit)
    {
    return hit.collider != null && hit.collider.gameObject != null && hit.collider.gameObject == player;
    }
    private void FlipObject(Vector3 force)
    {
    if (force.x > 0f)
    {
    transform.localScale = new Vector3(-1f, 1f, 1f);
    } else if (force.x < 0f)
    {
    transform.localScale = new Vector3(1f, 1f, 1f);
    }
    }
    }
    view raw EnemyAI.cs hosted with ❤ by GitHub
    public class EnemyExplodeState : PathfindingState
    {
    private GameObject _playerObject;
    [SerializeField] private GameObject _enemyObject;
    [SerializeField] private UnityEvent onEnemyExplode = new UnityEvent();
    [SerializeField] private float rangeDistance = 5f;
    private void Update()
    {
    onEnemyExplode?.Invoke();
    }
    public void DestroyEnemy()
    {
    Destroy(this.gameObject);
    }
    public override Transform GetCurrentTarget()
    {
    if (GameObject.FindWithTag("Player") == null) return null;
    _playerObject = EnemyAI.GetClosestTarget(rangeDistance);
    if (_playerObject == null) return null;
    return _playerObject.transform;
    }
    }
    public class FollowState : PathfindingState
    {
    private bool _foundTarget;
    private bool _isInExplodeRange = false;
    private GameObject _playerObject;
    [SerializeField] private float updateRate = 0.3f;
    [SerializeField] private float maxDistance = 25f;
    [SerializeField] private float rangeDistance = 5f;
    private float _currentUpdate;
    public override void OnEnter()
    {
    base.OnEnter();
    EnemyAI.CurrentTarget = GetCurrentTarget();
    EnemyAI.StartPath();
    }
    private void Update()
    {
    if (_playerObject == null) return;
    _currentUpdate += Time.deltaTime;
    if (_currentUpdate >= updateRate)
    {
    _currentUpdate = 0;
    EnemyAI.StartPath();
    }
    if (Vector3.Distance(transform.position, _playerObject.transform.position) >= maxDistance)
    {
    StateMachine.SetBool("outofRange", true);
    }
    if (Vector3.Distance(transform.position, _playerObject.transform.position) <= rangeDistance)
    {
    StateMachine.SetBool("isInExplodeRange", true);
    }
    }
    public override Transform GetCurrentTarget()
    {
    if (GameObject.FindWithTag("Player") == null) return null;
    _playerObject = EnemyAI.GetClosestTarget(maxDistance);
    if (_playerObject == null) return null;
    return _playerObject.transform;
    }
    }
    view raw FollowState.cs hosted with ❤ by GitHub
    public abstract class PathfindingState : StateBehaviour
    {
    [SerializeField] private EnemyAI enemyAI;
    [SerializeField] private StateMachine.StateMachine stateMachine;
    public virtual void OnPathComplete(Path path)
    {
    }
    public abstract Transform GetCurrentTarget();
    public EnemyAI EnemyAI => enemyAI;
    public StateMachine.StateMachine StateMachine => stateMachine;
    }
    public class PatrolState : PathfindingState
    {
    [SerializeField] private List<Transform> waypoints = new List<Transform>();
    private int _currentWaypointIndex = -1;
    [SerializeField] private float minDistance = 15f;
    public override void OnEnter()
    {
    base.OnEnter();
    EnemyAI.CurrentTarget = GetCurrentTarget();
    EnemyAI.StartPath();
    }
    private void Update()
    {
    GameObject playerObject = EnemyAI.GetClosestTarget(minDistance);
    if (playerObject != null)
    {
    if (Vector3.Distance(transform.position, playerObject.transform.position) <= minDistance)
    {
    StateMachine.SetBool("foundPlayer", true);
    }
    }
    }
    public override void OnPathComplete(Path path)
    {
    _currentWaypointIndex++;
    if (_currentWaypointIndex > waypoints.Count - 1)
    {
    _currentWaypointIndex = 0;
    waypoints.Reverse();
    }
    EnemyAI.CurrentTarget = GetCurrentTarget();
    }
    public override Transform GetCurrentTarget()
    {
    if (_currentWaypointIndex == -1)
    {
    return waypoints[0];
    }
    return waypoints[_currentWaypointIndex];
    }
    }
    view raw PatrolState.cs hosted with ❤ by GitHub

    The enemy has several states: Patrol, Follow and Explode in range

    Movement

    public void Walk(Vector2 dir)
    {
    animationManager.SetFloat(InputX, Math.Abs(dir.x));
    if (Mathf.Abs(dir.x) < deadzone)
    {
    onStopMoving?.Invoke();
    return;
    }
    if (!isActiveAndEnabled) return;
    if (_isGrounded)
    {
    onGroundWalk?.Invoke();
    }
    Vector3 deltaVelocity = new Vector3(dir.x, 0, 0);
    deltaVelocity = transform.TransformDirection(deltaVelocity);
    deltaVelocity *= speed;
    Vector3 velocity = _rigidbody.velocity;
    Vector3 velocityChange = (deltaVelocity - velocity);
    velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
    velocityChange.y = 0;
    velocityChange.z = 0;
    _rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
    CheckMoveDirection(dir);
    RotatePlayer(dir);
    onStartMoving?.Invoke();
    }
    private void Jump()
    {
    if (!isActiveAndEnabled) return;
    if (!_isGrounded) return;
    _isJumping = true;
    _isFalling = false;
    _rigidbody.velocity = Vector3.zero;
    _rigidbody.AddForce(new Vector3(_rigidbody.velocity.x, jumpForce, 0), ForceMode.VelocityChange);
    }
    public void JumpPrimer(InputAction.CallbackContext context)
    {
    if (!_isGrounded) return;
    _jumpPrimed = true;
    }
    public void RotatePlayer(Vector2 dir)
    {
    Vector3 localScale = transform.localScale;
    if (dir == Vector2.zero) return;
    transform.localScale = dir.x > 0
    ? transform.localScale = new Vector3(Math.Abs(localScale.x), localScale.y, localScale.z)
    : transform.localScale = new Vector3(-Math.Abs(localScale.x), localScale.y, localScale.z);
    }

    The player can Jump and Move left and right

    Gun Shooting

    public class Bullet : MonoBehaviour
    {
    public UnityEvent onHitEnter = new UnityEvent();
    public UnityEvent onHitEnterLeft = new UnityEvent();
    public UnityEvent onHitEnterRight = new UnityEvent();
    private void Start()
    {
    _startPosition = transform.position;
    }
    private void Update()
    {
    if (Vector3.Distance(_startPosition, transform.position) >= _maxDistance)
    {
    Destroy(gameObject);
    }
    }
    private void OnCollisionEnter(Collision other)
    {
    if (_hasHitObject) return;
    if (other == null) return;
    _hasHitObject = true;
    OnHit(other.gameObject);
    }
    public void SetMaxDistance(float maxDistance)
    {
    _maxDistance = maxDistance;
    }
    public Vector3 Direction
    {
    get => _direction;
    set => _direction = value;
    }
    public float Speed
    {
    get => _speed;
    set => _speed = value;
    }
    public void OnHit(GameObject hitObject)
    {
    if(hitObject.HasComponent<Destroyable>())
    {
    hitObject.GetComponent<Destroyable>().Hit();
    }
    onHitEnter.Invoke();
    _hasHitObject = false;
    if (this._direction.x > 0) onHitEnterRight?.Invoke();
    if (this._direction.x < 0) onHitEnterLeft?.Invoke();
    Destroy(gameObject);
    }
    }
    view raw Bullet.cs hosted with ❤ by GitHub
    public class Pistol : Weapon
    {
    public override void Use(InputAction.CallbackContext context)
    {
    Shoot(context);
    }
    public override void OnInput(InputAction.CallbackContext aContext)
    {
    Use(aContext);
    GetComponentInChildren<StateMachine.StateMachine>().SetBool("Attacking", true);
    }
    }
    view raw Pistol.cs hosted with ❤ by GitHub
    public abstract class Weapon : Ability
    {
    private void Start()
    {
    particle = new ParticleManager();
    }
    public void Shoot(InputAction.CallbackContext context)
    {
    if (_isOnCoolDown) return;
    onUseAbility?.Invoke();
    StartCoroutine(ShootBullet());
    }
    protected IEnumerator ShootBullet()
    {
    _isOnCoolDown = true;
    GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity);
    Bullet bulletComponent = bullet.GetComponent<Bullet>();
    bulletComponent.Direction = shootDir;
    bulletComponent.Speed = speed;
    _bulletRb = bullet.GetComponent<Rigidbody>();
    _bulletRb.velocity = shootDir * speed;
    bulletComponent.SetMaxDistance(maxShootDistance);
    yield return new WaitForSeconds(fireRate);
    _isOnCoolDown = false;
    }
    public void SetShootDirX(float newX)
    {
    shootDir.x = newX;
    }
    }
    view raw Weapon.cs hosted with ❤ by GitHub

    The player can shoot a laser gun