This is an old revision of the document!
Table of Contents
Prototyping Zombie Minigame
Player Prototyping
ZombiePlayerController Script
using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using Micosmo.SensorToolkit; using System; using Sirenix.OdinInspector; using UnityEngine.Events; using HighlightPlus; public class ZombiePlayerController : MonoBehaviour { [BoxGroup("GameObject Serialization")] public GameObject cross; [BoxGroup("GameObject Serialization")] public Transform charater; [BoxGroup("DoLookAt Fine Tune")] public float lookAtSpeed = 1f; [BoxGroup("Movement Fine Tune")] public float movementSpeed = 1f; [BoxGroup("Movement Fine Tune")] public float movementSprintSpeed = 1f; [BoxGroup("Movement Fine Tune")] public float jumpHeight = 1f; [BoxGroup("Movement Fine Tune")] public float groundGravity = 1f; [BoxGroup("Movement Fine Tune")] public float runStamina = 15f; [BoxGroup("Private Variable")] [ShowInInspector] private bool _groundedStatus; [BoxGroup("Private Variable")] [ShowInInspector] private Vector3 _finalMoveValue; [BoxGroup("Private Variable")] [ShowInInspector] private float _fowardDirection; [BoxGroup("Private Variable")] [ShowInInspector] private float _righDirection; [BoxGroup("Private Variable")] [ShowInInspector] private float _velocity; [BoxGroup("Private Variable")] [ShowInInspector] private float _storedRunStamina; private CharacterController _characterController; private ZombieHealth _zombieHealth; private HighlightEffect _highlight; [HideInEditorMode] public UnityEvent<float> sendEventToUI; [HideInEditorMode] public UnityEvent onPlayerDied; private void Awake() { Initialization(); } private void Start() { _storedRunStamina = runStamina; _zombieHealth.sendEventOnMinusHealth.AddListener(ReceivedEventOnPlayerHit); SendEventToUI(); } private void Initialization() { _characterController = this.GetComponent<CharacterController>(); _zombieHealth = this.GetComponent<ZombieHealth>(); _highlight = this.GetComponent<HighlightEffect>(); } private void Update() { if (_zombieHealth.IsDead()) { onPlayerDied.Invoke(); return; } _velocity = GetVelocity(); SetRightFowardDirection(); PlayerLookAtCross(); //this will update _movement value that will be called by MovePlayer(_movement); SetFinalMoveValue(); MovePlayer(_finalMoveValue); } #region INPUT private Vector3 InputRelativeToCamera() { //reading the input: float horizontalAxis = Input.GetAxis("Horizontal"); float verticalAxis = Input.GetAxis("Vertical"); //assuming we only using the single camera: var camera = Camera.main; //camera forward and right vectors: var forward = camera.transform.forward; var right = camera.transform.right; //project forward and right vectors on the horizontal plane (y = 0) forward.y = 0f; right.y = 0f; forward.Normalize(); right.Normalize(); //this is the direction in the world space we want to move: var desiredMoveDirection = (forward * verticalAxis + right * horizontalAxis).normalized * movementSpeed; SprintController(); if (Input.GetKey(KeyCode.LeftShift) && runStamina >= 0) { return desiredMoveDirection * movementSprintSpeed; } return desiredMoveDirection; } private void SprintController() { SendEventToUI(); if(GetVelocity() > 5) { runStamina -= Time.deltaTime; return; } if (runStamina < _storedRunStamina && GetVelocity() == 0) runStamina += Time.deltaTime; } private float _jumpDuration = 0; private float InputMovementJump() { if (!_groundedStatus) { _jumpDuration -= Time.deltaTime * groundGravity; } else { _jumpDuration = Input.GetAxis("Jump"); } return _jumpDuration; } #endregion #region MONOBEHAVIOUR private void SetRightFowardDirection() { //This method will take dot value to detect character moving direction in local axis (so kalau axis tu rotate dia akan rotate sama) _righDirection = Vector3.Dot(transform.right, new Vector3(_finalMoveValue.x, 0f, _finalMoveValue.z)); _fowardDirection = Vector3.Dot(transform.forward, new Vector3(_finalMoveValue.x, 0, _finalMoveValue.z)); } private void PlayerLookAtCross() => charater.DOLookAt(cross.transform.position, lookAtSpeed, AxisConstraint.Y); private void SetFinalMoveValue() { Vector3 _moveDirectionAtXZ = InputRelativeToCamera(); float _jumpDuration = InputMovementJump(); _finalMoveValue = new Vector3(_moveDirectionAtXZ.x, _jumpDuration * jumpHeight, _moveDirectionAtXZ.z); } private void MovePlayer(Vector3 _finalMoveValue) => _characterController.Move(_finalMoveValue * Time.deltaTime); #endregion #region SET METHOD public void SetWhenGrounded() => _groundedStatus = true; public void SetWhenNotGrounded() => _groundedStatus = false; #endregion #region GET METHOD public bool IsGroundedStatus() => _groundedStatus; public bool GetCursor() => Cursor.visible; public float GetFowardDiretion() => _fowardDirection; public float GetRightDirection() => _righDirection; public float GetVelocity() => _characterController.velocity.magnitude; #endregion #region EVENT private void InitializeEvent() { if (sendEventToUI == null) sendEventToUI = new UnityEvent<float>(); SendEventToUI(); } private void SendEventToUI() { sendEventToUI.Invoke(runStamina); } private void ReceivedEventOnPlayerHit() { _highlight.HitFX(); } # endregion }
ZombieGunController Script
using System.Collections; using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; using HellTap.PoolKit; using UnityEngine.Events; using SkyConsole.Utils; public class ZombieGunController : MonoBehaviour { [BoxGroup("GameObject Initialization")] public Spawner gunBarrelPoolSpawner; [BoxGroup("Weapon Porperties")] public float bulletSpeed; [BoxGroup("Weapon Porperties")] public float shootCooldown = 1f; [BoxGroup("Weapon Porperties")] private int bullet = 10; [BoxGroup("Weapon Porperties")] public AudioClip shootSound; [BoxGroup("Weapon Porperties")] public AudioClip emptyShootSound; [BoxGroup("Weapon Porperties")] public AudioSource gunSoundSource; [HideInEditorMode] public UnityEvent<int> sendEventToUI; private void Start() { gunSoundSource = this.GetComponent<AudioSource>(); InitializeEvent(); } private void Update() { BulletShootHandler(); } [SerializeField] private float timer = 0; private void BulletShootHandler() { if (timer > 0) { timer -= Time.deltaTime; return; } if (Input.GetKey(KeyCode.Mouse0)) { timer = shootCooldown; if (bullet == 0) { PlaySound(emptyShootSound); return; } if (gunBarrelPoolSpawner != null) { gunBarrelPoolSpawner.Play(); PlaySound(shootSound); bullet -= 1; SendEventToUI(); } } } private void PlaySound(AudioClip sound) { gunSoundSource.PlayOneShot(sound); } #region EVENT private void InitializeEvent() { if (sendEventToUI == null) sendEventToUI = new UnityEvent<int>(); SendEventToUI(); } private void SendEventToUI() { sendEventToUI.Invoke(bullet); } #endregion public void addBullet(int bulletCount) { bullet += bulletCount; SendEventToUI(); } }
ZombieCameraController Script
using System.Collections; using System.Collections.Generic; using UnityEngine; using Micosmo.SensorToolkit; using DG.Tweening; using System; using Sirenix.OdinInspector; public class ZombieCameraController : MonoBehaviour { [BoxGroup("Serialize GameObject")] public GameObject cross; [BoxGroup("Serialize GameObject")] public GameObject crossCylinder; [BoxGroup("Serialize GameObject")] public GameObject player; [BoxGroup("Cross Fine Tuning")] public float crossSpeed = 1f; [BoxGroup("Camera Porperties")] public float zTilt; [BoxGroup("Camera Porperties")] public float xTilt; private Camera _camera; private RaySensor _sensor; private RangeSensor _crossRangeSensor; private void Awake() { _sensor = this.GetComponent<RaySensor>(); _camera = this.GetComponent<Camera>(); _crossRangeSensor = cross.GetComponentInChildren<RangeSensor>(); } private void Update() { MoveRaycastToMousePosition(); CameraMovement(); SetCrossCylinderActive(); } private void MoveRaycastToMousePosition() { var ray = _camera.ScreenPointToRay(Input.mousePosition); _sensor.Direction = ray.direction; MoveCrossToRaycastObstructionRayHit(); } private void MoveCrossToRaycastObstructionRayHit() { float x = _sensor.GetObstructionRayHit().Point.x; float z = _sensor.GetObstructionRayHit().Point.z; if (_sensor.IsObstructed) cross.transform.DOMove(new Vector3(x, cross.transform.position.y, z), crossSpeed); } private void CameraMovement() { this.transform.DOLookAt(player.transform.position, 1f); float x = player.transform.position.x + xTilt; float z = player.transform.position.z + zTilt; this.transform.DOMove(new Vector3(x, this.transform.position.y, z), 1f); } private void SetCrossCylinderActive() { if (_crossRangeSensor.GetNearestDetection() != null) { crossCylinder.SetActive(true); } else { crossCylinder.SetActive(false); } } }
ZombieBullet Script
using Micosmo.SensorToolkit; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using HellTap.PoolKit; using Sirenix.OdinInspector; using SkyConsole.Utils; public class ZombieBullet : MonoBehaviour { public float bulletSpeed = 25f; public ParticleSystem bulletCollideParticle; private RangeSensor _rangeSensor; private MeshRenderer _meshRenderer; private Pool _pool; private bool canMove = true; private void Awake() { _rangeSensor = this.GetComponent<RangeSensor>(); _meshRenderer = this.GetComponent<MeshRenderer>(); _pool = FindObjectOfType<Pool>(); _rangeSensor.OnSomeDetection.AddListener(ReceivedListenerOnCollided); } private void Update() { moveBullet(canMove); } private void moveBullet(bool canMove) { if(canMove == true) transform.position += transform.forward * Time.deltaTime * bulletSpeed; } private IEnumerator DespawnGameObject(float duration) { yield return new WaitForSeconds(duration); canMove = true; _pool.Despawn(this.gameObject); } #region Called From Event & Outside private void ReceivedListenerOnCollided() { canMove = false; bulletCollideParticle.Play(); StartCoroutine(DespawnGameObject(bulletCollideParticle.main.duration)); } private void OnDisable() { _rangeSensor.enabled = false; } private void OnEnable() { _rangeSensor.enabled = true; } #endregion }
NPC Prototyping
ZombieNpcMovingNashMeshController Script
using Sirenix.OdinInspector; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class ZombieNpcMovingNashMeshController : MonoBehaviour { [BoxGroup("Debug Value")] [SerializeField] private string d_tranfsormName; [BoxGroup("Debug Value")] [SerializeField] private float d_remainingDistance; [BoxGroup("Debug Value")] [SerializeField] private float d_velocity; [BoxGroup("Movement Tweak")] [SerializeField] private float _walkSpeed = 1; [BoxGroup("Movement Tweak")] [SerializeField] private float _runSpeed = 3; [SerializeField] private List<Transform> _checkPoint; private int checkPointIndex = 0; private int currentIndex = 0; private NavMeshAgent _navMeshAgent; private ZombieAnimationController _zombieAnimationController; [SerializeField] private Transform movePositionTransform; private void Awake() { _navMeshAgent = GetComponent<NavMeshAgent>(); _zombieAnimationController = GetComponent<ZombieAnimationController>(); _navMeshAgent.updateRotation = false; } private void Update() { if (_navMeshAgent.remainingDistance <= _navMeshAgent.stoppingDistance) { currentIndex = checkPointIndex == _checkPoint.Count - 1 ? checkPointIndex = 0 : checkPointIndex += 1; } MoveAgentToDestination(_checkPoint[currentIndex]); } private void MoveAgentToDestination(Transform transform) { _navMeshAgent.destination = transform.position; d_tranfsormName = transform.gameObject.name; d_remainingDistance = _navMeshAgent.remainingDistance; d_velocity = _navMeshAgent.velocity.magnitude; if (_navMeshAgent.velocity.sqrMagnitude > Mathf.Epsilon) { this.transform.rotation = Quaternion.LookRotation(_navMeshAgent.velocity.normalized); } } [Button] public void MakeCharacterWalk() { _navMeshAgent.speed = _walkSpeed; } [Button] public void MakeCharacterRunning() { _navMeshAgent.speed = _runSpeed; } [Button] public void MakeCharacterPunch() { _zombieAnimationController.PlayAction(_zombieAnimationController.attack); } }
ZombieAnimationController Script
using Animancer; using Sirenix.OdinInspector; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class ZombieAnimationController : MonoBehaviour { private AnimancerComponent _Animancer; [SerializeField] public ClipTransition idle; [SerializeField] public ClipTransition moveFoward; [SerializeField] public ClipTransition runFoward; [SerializeField] public ClipTransition attack; [SerializeField] private AvatarMask _baseBody; [SerializeField] private AvatarMask _upperBody; private AnimancerLayer _baseLayer; private AnimancerLayer _upperBodyLayer; //private CharacterController _characterController; private NavMeshAgent _navMeshAgent; private void Awake() { Initialization(); InitializationAnim(); } private void Initialization() { //_characterController = GetComponent<CharacterController>(); _navMeshAgent = GetComponent<NavMeshAgent>(); _Animancer = GetComponentInChildren<AnimancerComponent>(); } private void InitializationAnim() { _baseLayer = _Animancer.Layers[0]; _upperBodyLayer = _Animancer.Layers[1]; _baseLayer.SetMask(_baseBody); _upperBodyLayer.SetMask(_upperBody); attack.Events.OnEnd = OnActionEnd; } private void Update() { //if(_characterController.velocity.magnitude > 1f) if (_navMeshAgent.velocity.magnitude > 1f) { _baseLayer.Play(moveFoward, 1f); } //if (_characterController.velocity.magnitude > 2f) if(_navMeshAgent.velocity.magnitude > 2f) { _baseLayer.Play(runFoward, 1f); } } bool _canBePlayed = true; public void PlayAction(ClipTransition clip) { if (!_canBePlayed) return; _upperBodyLayer.Play(clip); StartCoroutine(PlayActionDelay(clip.Clip.length)); } private IEnumerator PlayActionDelay(float length) { _canBePlayed = false; yield return new WaitForSeconds(length); _canBePlayed = true; } private void OnActionEnd() { _upperBodyLayer.StartFade(0, 1f); } }
NPC Agro prototype
Agro
There's basically three layers of detection - outer layer when zombie heard shoots they will go agro, and this layer player need to escape from it to stop the agro. - first inner when player move close to zombie. - nearest layer for zombie detect when can it hit player.


