UNIVERSIDAD AUTONOMA DE NUEVO LEON
FACULTAD DE CIENCIAS FISICO MATEMATICO
DOCUMENTACION DE PROYECTO
PRIMER AVANCE
Jazil Rebeca Guerrero González………1898899
Montserrat Ortega García………2076203
Esteban Alan Hinojosa González……….1966534
Miguel Cuellar Quintero………..1950524
María Yolanda Escudero Guzmán……..1656843
FPS Microgame (Juego de Shooting)
Para este proyecto, decidimos hacer un videojuego en 3D al que fuera sencillo
asignarle un haptico, en este caso, decidimos hacer un juego de shooting. UnityLearn
cuenta con muchas plantillas que pueden ser utilizadas por sus usuarios para
proyectos de práctica, así que después de investigar, decidimos utilizar el FPS
Microgame. UnityLearn cuenta con todos los materiales para crear un videojuego con
diferentes niveles y además proveen un manual de cómo hacerlo y programarlo. La
temática de este juego es sencilla, tu POV se encuentra en primera persona, te
encuentras en el espacio y es necesario dispararles a los enemigos para ganar. Para
este avance solo creamos un nivel sencillo con un enemigo y un boss para que el demo
sea rápido y fácil, ganas cuando matas a los enemigos, pierdes si ellos te matan a ti, y
siempre inicias en el mismo lugar.
PROCESO.
Para crear este videojuego, lo primero fue crear un proyecto vacio 3D en Unity, una vez
hecho esto, se descarga dentro del proyecto la carpeta con los componentes a utilizar
(Modelos 3D, estructuras, texturas, sonidos, etc.) Despues, el primer paso es
asignarles los prefabs (movimientos) a tu personaje. Al ser este un POV de primera
persona, no hay un personaje como tal, solamente un objeto transparente al que se le
asignan los movimientos, lo único visible es la pistola, que será nuestro haptico.
Después de tener al “personaje” en la carpeta de Prefabs, lo siguiente es asignarle un
Script (programar sus comandos en C#). Después de terminar de programarlo, hay que
asegurarse que el Prefab se haya guardado correctamente, después de esto, el modelo
quedara listo para jugar. Lo que sigue es crear el escenario donde el personaje se
moverá.
Esto se repite con los enemigos, los posicionamos en alguna parte del mapa y después
les asignamos su respectivo Script.
Este paquete de unity ya cuenta con escenas pre cargadas para que el usuario
simplemente la modifique a como desee y aprenda en el proceso, ya que también tiene
tutoriales incluidos. Para este avance, nosotros decidimos usar la default scene para
ver como funcionaria en el proyecto final, pero planeamos modificarla para el segundo
avance para que esta se vea diferente y existan más enemigos.
Nuestro juego es bastante sencillo pero divertido, y facilita la creación del haptico ya
que este será la pistola visualizada durante todo el juego.
El juego cuenta con dos pantallas, cuando se pierde y cuando se gana.
No importa si gane o pierda, el jugador siempre hará respawn en el mismo lugar de
inicio.
HAPTICO.
Al ser este un juego de shooting, nuestro haptico será la pistola que se muestra al
jugar. Para crearlo, utilizaremos el MPU6050 para el movimiento de la cámara, un
joystick para el movimiento del jugador y los disparos, leds decorativos, y un buzzer
que sonara cuando se le haga presión al joystick.
SCRIPTS.
Movimiento de Jugador.
using [Link];
using UnityEngine;
using [Link];
namespace [Link]
[RequireComponent(typeof(CharacterController), typeof(PlayerInputHandler),
typeof(AudioSource))]
public class PlayerCharacterController : MonoBehaviour
[Header("References")] [Tooltip("Reference to the main camera used for the
player")]
public Camera PlayerCamera;
[Tooltip("Audio source for footsteps, jump, etc...")]
public AudioSource AudioSource;
[Header("General")] [Tooltip("Force applied downward when in the air")]
public float GravityDownForce = 20f;
[Tooltip("Physic layers checked to consider the player grounded")]
public LayerMask GroundCheckLayers = -1;
[Tooltip("distance from the bottom of the character controller capsule to test
for grounded")]
public float GroundCheckDistance = 0.05f;
[Header("Movement")] [Tooltip("Max movement speed when grounded (when
not sprinting)")]
public float MaxSpeedOnGround = 10f;
[Tooltip(
"Sharpness for the movement when grounded, a low value will make the
player accelerate and decelerate slowly, a high value will do the opposite")]
public float MovementSharpnessOnGround = 15;
[Tooltip("Max movement speed when crouching")] [Range(0, 1)]
public float MaxSpeedCrouchedRatio = 0.5f;
[Tooltip("Max movement speed when not grounded")]
public float MaxSpeedInAir = 10f;
[Tooltip("Acceleration speed when in the air")]
public float AccelerationSpeedInAir = 25f;
[Tooltip("Multiplicator for the sprint speed (based on grounded speed)")]
public float SprintSpeedModifier = 2f;
[Tooltip("Height at which the player dies instantly when falling off the map")]
public float KillHeight = -50f;
[Header("Rotation")] [Tooltip("Rotation speed for moving the camera")]
public float RotationSpeed = 200f;
[Range(0.1f, 1f)] [Tooltip("Rotation speed multiplier when aiming")]
public float AimingRotationMultiplier = 0.4f;
[Header("Jump")] [Tooltip("Force applied upward when jumping")]
public float JumpForce = 9f;
[Header("Stance")] [Tooltip("Ratio (0-1) of the character height where the
camera will be at")]
public float CameraHeightRatio = 0.9f;
[Tooltip("Height of character when standing")]
public float CapsuleHeightStanding = 1.8f;
[Tooltip("Height of character when crouching")]
public float CapsuleHeightCrouching = 0.9f;
[Tooltip("Speed of crouching transitions")]
public float CrouchingSharpness = 10f;
[Header("Audio")] [Tooltip("Amount of footstep sounds played when moving
one meter")]
public float FootstepSfxFrequency = 1f;
[Tooltip("Amount of footstep sounds played when moving one meter while
sprinting")]
public float FootstepSfxFrequencyWhileSprinting = 1f;
[Tooltip("Sound played for footsteps")]
public AudioClip FootstepSfx;
[Tooltip("Sound played when jumping")] public AudioClip JumpSfx;
[Tooltip("Sound played when landing")] public AudioClip LandSfx;
[Tooltip("Sound played when taking damage froma fall")]
public AudioClip FallDamageSfx;
[Header("Fall Damage")]
[Tooltip("Whether the player will recieve damage when hitting the ground at
high speed")]
public bool RecievesFallDamage;
[Tooltip("Minimun fall speed for recieving fall damage")]
public float MinSpeedForFallDamage = 10f;
[Tooltip("Fall speed for recieving th emaximum amount of fall damage")]
public float MaxSpeedForFallDamage = 30f;
[Tooltip("Damage recieved when falling at the mimimum speed")]
public float FallDamageAtMinSpeed = 10f;
[Tooltip("Damage recieved when falling at the maximum speed")]
public float FallDamageAtMaxSpeed = 50f;
public UnityAction<bool> OnStanceChanged;
public Vector3 CharacterVelocity { get; set; }
public bool IsGrounded { get; private set; }
public bool HasJumpedThisFrame { get; private set; }
public bool IsDead { get; private set; }
public bool IsCrouching { get; private set; }
public float RotationMultiplier
get
if (m_WeaponsManager.IsAiming)
return AimingRotationMultiplier;
return 1f;
}
}
Health m_Health;
PlayerInputHandler m_InputHandler;
CharacterController m_Controller;
PlayerWeaponsManager m_WeaponsManager;
Actor m_Actor;
Vector3 m_GroundNormal;
Vector3 m_CharacterVelocity;
Vector3 m_LatestImpactSpeed;
float m_LastTimeJumped = 0f;
float m_CameraVerticalAngle = 0f;
float m_FootstepDistanceCounter;
float m_TargetCharacterHeight;
const float k_JumpGroundingPreventionTime = 0.2f;
const float k_GroundCheckDistanceInAir = 0.07f;
void Awake()
ActorsManager actorsManager = FindObjectOfType<ActorsManager>();
if (actorsManager != null)
[Link](gameObject);
}
void Start()
// fetch components on the same gameObject
m_Controller = GetComponent<CharacterController>();
[Link]<CharacterController,
PlayerCharacterController>(m_Controller,
this, gameObject);
m_InputHandler = GetComponent<PlayerInputHandler>();
[Link]<PlayerInputHandler,
PlayerCharacterController>(m_InputHandler,
this, gameObject);
m_WeaponsManager = GetComponent<PlayerWeaponsManager>();
[Link]<PlayerWeaponsManager,
PlayerCharacterController>(
m_WeaponsManager, this, gameObject);
m_Health = GetComponent<Health>();
[Link]<Health,
PlayerCharacterController>(m_Health, this, gameObject);
m_Actor = GetComponent<Actor>();
[Link]<Actor,
PlayerCharacterController>(m_Actor, this, gameObject);
m_Controller.enableOverlapRecovery = true;
m_Health.OnDie += OnDie;
// force the crouch state to false when starting
SetCrouchingState(false, true);
UpdateCharacterHeight(true);
void Update()
// check for Y kill
if (!IsDead && [Link].y < KillHeight)
m_Health.Kill();
HasJumpedThisFrame = false;
bool wasGrounded = IsGrounded;
GroundCheck();
// landing
if (IsGrounded && !wasGrounded)
// Fall damage
float fallSpeed = -[Link](CharacterVelocity.y,
m_LatestImpactSpeed.y);
float fallSpeedRatio = (fallSpeed - MinSpeedForFallDamage) /
(MaxSpeedForFallDamage - MinSpeedForFallDamage);
if (RecievesFallDamage && fallSpeedRatio > 0f)
float dmgFromFall = [Link](FallDamageAtMinSpeed,
FallDamageAtMaxSpeed, fallSpeedRatio);
m_Health.TakeDamage(dmgFromFall, null);
// fall damage SFX
[Link](FallDamageSfx);
else
// land SFX
[Link](LandSfx);
// crouching
if (m_InputHandler.GetCrouchInputDown())
SetCrouchingState(!IsCrouching, false);
}
UpdateCharacterHeight(false);
HandleCharacterMovement();
void OnDie()
IsDead = true;
// Tell the weapons manager to switch to a non-existing weapon in order to
lower the weapon
m_WeaponsManager.SwitchToWeaponIndex(-1, true);
[Link]([Link]);
void GroundCheck()
// Make sure that the ground check distance while already in air is very
small, to prevent suddenly snapping to ground
float chosenGroundCheckDistance =
IsGrounded ? (m_Controller.skinWidth + GroundCheckDistance) :
k_GroundCheckDistanceInAir;
// reset values before the ground check
IsGrounded = false;
m_GroundNormal = [Link];
// only try to detect ground if it's been a short amount of time since last
jump; otherwise we may snap to the ground instantly after we try jumping
if ([Link] >= m_LastTimeJumped + k_JumpGroundingPreventionTime)
// if we're grounded, collect info about the ground normal with a
downward capsule cast representing our character capsule
if ([Link](GetCapsuleBottomHemisphere(),
GetCapsuleTopHemisphere(m_Controller.height),
m_Controller.radius, [Link], out RaycastHit hit,
chosenGroundCheckDistance, GroundCheckLayers,
[Link]))
// storing the upward direction for the surface found
m_GroundNormal = [Link];
// Only consider this a valid ground hit if the ground normal goes in
the same direction as the character up
// and if the slope angle is lower than the character controller's limit
if ([Link]([Link], [Link]) > 0f &&
IsNormalUnderSlopeLimit(m_GroundNormal))
IsGrounded = true;
// handle snapping to the ground
if ([Link] > m_Controller.skinWidth)
{
m_Controller.Move([Link] * [Link]);
void HandleCharacterMovement()
// horizontal character rotation
// rotate the transform with the input speed around its local Y axis
[Link](
new Vector3(0f, (m_InputHandler.GetLookInputsHorizontal() *
RotationSpeed * RotationMultiplier),
0f), [Link]);
// vertical camera rotation
// add vertical inputs to the camera's vertical angle
m_CameraVerticalAngle += m_InputHandler.GetLookInputsVertical() *
RotationSpeed * RotationMultiplier;
// limit the camera's vertical angle to min/max
m_CameraVerticalAngle = [Link](m_CameraVerticalAngle, -89f,
89f);
// apply the vertical angle as a local rotation to the camera transform
along its right axis (makes it pivot up and down)
[Link] = new
Vector3(m_CameraVerticalAngle, 0, 0);
// character movement handling
bool isSprinting = m_InputHandler.GetSprintInputHeld();
if (isSprinting)
isSprinting = SetCrouchingState(false, false);
float speedModifier = isSprinting ? SprintSpeedModifier : 1f;
// converts move input to a worldspace vector based on our character's
transform orientation
Vector3 worldspaceMoveInput =
[Link](m_InputHandler.GetMoveInput());
// handle grounded movement
if (IsGrounded)
{
// calculate the desired velocity from inputs, max speed, and current
slope
Vector3 targetVelocity = worldspaceMoveInput * MaxSpeedOnGround
* speedModifier;
// reduce speed if crouching by crouch speed ratio
if (IsCrouching)
targetVelocity *= MaxSpeedCrouchedRatio;
targetVelocity =
GetDirectionReorientedOnSlope([Link], m_GroundNormal) *
[Link];
// smoothly interpolate between our current velocity and the target
velocity based on acceleration speed
CharacterVelocity = [Link](CharacterVelocity, targetVelocity,
MovementSharpnessOnGround * [Link]);
// jumping
if (IsGrounded && m_InputHandler.GetJumpInputDown())
// force the crouch state to false
if (SetCrouchingState(false, false))
// start by canceling out the vertical component of our velocity
CharacterVelocity = new Vector3(CharacterVelocity.x, 0f,
CharacterVelocity.z);
// then, add the jumpSpeed value upwards
CharacterVelocity += [Link] * JumpForce;
// play sound
[Link](JumpSfx);
// remember last time we jumped because we need to prevent
snapping to ground for a short time
m_LastTimeJumped = [Link];
HasJumpedThisFrame = true;
// Force grounding to false
IsGrounded = false;
m_GroundNormal = [Link];
// footsteps sound
float chosenFootstepSfxFrequency =
(isSprinting ? FootstepSfxFrequencyWhileSprinting :
FootstepSfxFrequency);
if (m_FootstepDistanceCounter >= 1f / chosenFootstepSfxFrequency)
m_FootstepDistanceCounter = 0f;
[Link](FootstepSfx);
}
// keep track of distance traveled for footsteps sound
m_FootstepDistanceCounter += [Link] *
[Link];
// handle air movement
else
// add air acceleration
CharacterVelocity += worldspaceMoveInput * AccelerationSpeedInAir
* [Link];
// limit air speed to a maximum, but only horizontally
float verticalVelocity = CharacterVelocity.y;
Vector3 horizontalVelocity =
[Link](CharacterVelocity, [Link]);
horizontalVelocity = [Link](horizontalVelocity,
MaxSpeedInAir * speedModifier);
CharacterVelocity = horizontalVelocity + ([Link] *
verticalVelocity);
// apply the gravity to the velocity
CharacterVelocity += [Link] * GravityDownForce *
[Link];
// apply the final calculated velocity value as a character movement
Vector3 capsuleBottomBeforeMove = GetCapsuleBottomHemisphere();
Vector3 capsuleTopBeforeMove =
GetCapsuleTopHemisphere(m_Controller.height);
m_Controller.Move(CharacterVelocity * [Link]);
// detect obstructions to adjust velocity accordingly
m_LatestImpactSpeed = [Link];
if ([Link](capsuleBottomBeforeMove,
capsuleTopBeforeMove, m_Controller.radius,
[Link], out RaycastHit hit,
[Link] * [Link], -1,
[Link]))
// We remember the last impact speed because the fall damage logic
might need it
m_LatestImpactSpeed = CharacterVelocity;
CharacterVelocity = [Link](CharacterVelocity,
[Link]);
// Returns true if the slope angle represented by the given normal is under
the slope angle limit of the character controller
bool IsNormalUnderSlopeLimit(Vector3 normal)
return [Link]([Link], normal) <= m_Controller.slopeLimit;
}
// Gets the center point of the bottom hemisphere of the character controller
capsule
Vector3 GetCapsuleBottomHemisphere()
return [Link] + ([Link] * m_Controller.radius);
// Gets the center point of the top hemisphere of the character controller
capsule
Vector3 GetCapsuleTopHemisphere(float atHeight)
return [Link] + ([Link] * (atHeight -
m_Controller.radius));
// Gets a reoriented direction that is tangent to a given slope
public Vector3 GetDirectionReorientedOnSlope(Vector3 direction, Vector3
slopeNormal)
Vector3 directionRight = [Link](direction, [Link]);
return [Link](slopeNormal, directionRight).normalized;
void UpdateCharacterHeight(bool force)
{
// Update height instantly
if (force)
m_Controller.height = m_TargetCharacterHeight;
m_Controller.center = [Link] * m_Controller.height * 0.5f;
[Link] = [Link] *
m_TargetCharacterHeight * CameraHeightRatio;
m_Actor.[Link] = m_Controller.center;
// Update smooth height
else if (m_Controller.height != m_TargetCharacterHeight)
// resize the capsule and adjust camera position
m_Controller.height = [Link](m_Controller.height,
m_TargetCharacterHeight,
CrouchingSharpness * [Link]);
m_Controller.center = [Link] * m_Controller.height * 0.5f;
[Link] =
[Link]([Link],
[Link] * m_TargetCharacterHeight * CameraHeightRatio,
CrouchingSharpness * [Link]);
m_Actor.[Link] = m_Controller.center;
// returns false if there was an obstruction
bool SetCrouchingState(bool crouched, bool ignoreObstructions)
{
// set appropriate heights
if (crouched)
m_TargetCharacterHeight = CapsuleHeightCrouching;
else
// Detect obstructions
if (!ignoreObstructions)
Collider[] standingOverlaps = [Link](
GetCapsuleBottomHemisphere(),
GetCapsuleTopHemisphere(CapsuleHeightStanding),
m_Controller.radius,
-1,
[Link]);
foreach (Collider c in standingOverlaps)
if (c != m_Controller)
return false;
}
m_TargetCharacterHeight = CapsuleHeightStanding;
if (OnStanceChanged != null)
[Link](crouched);
IsCrouching = crouched;
return true;
}
ENEMIGOS.
using [Link];
using [Link];
using UnityEngine;
using [Link];
using [Link];
namespace [Link]
[RequireComponent(typeof(Health), typeof(Actor), typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
[[Link]]
public struct RendererIndexData
public Renderer Renderer;
public int MaterialIndex;
public RendererIndexData(Renderer renderer, int index)
Renderer = renderer;
MaterialIndex = index;
}
[Header("Parameters")]
[Tooltip("The Y height at which the enemy will be automatically killed (if it
falls off of the level)")]
public float SelfDestructYHeight = -20f;
[Tooltip("The distance at which the enemy considers that it has reached its
current path destination point")]
public float PathReachingRadius = 2f;
[Tooltip("The speed at which the enemy rotates")]
public float OrientationSpeed = 10f;
[Tooltip("Delay after death where the GameObject is destroyed (to allow for
animation)")]
public float DeathDuration = 0f;
[Header("Weapons Parameters")] [Tooltip("Allow weapon swapping for this
enemy")]
public bool SwapToNextWeapon = false;
[Tooltip("Time delay between a weapon swap and the next attack")]
public float DelayAfterWeaponSwap = 0f;
[Header("Eye color")] [Tooltip("Material for the eye color")]
public Material EyeColorMaterial;
[Tooltip("The default color of the bot's eye")] [ColorUsageAttribute(true,
true)]
public Color DefaultEyeColor;
[Tooltip("The attack color of the bot's eye")] [ColorUsageAttribute(true, true)]
public Color AttackEyeColor;
[Header("Flash on hit")] [Tooltip("The material used for the body of the
hoverbot")]
public Material BodyMaterial;
[Tooltip("The gradient representing the color of the flash on hit")]
[GradientUsageAttribute(true)]
public Gradient OnHitBodyGradient;
[Tooltip("The duration of the flash on hit")]
public float FlashOnHitDuration = 0.5f;
[Header("Sounds")] [Tooltip("Sound played when recieving damages")]
public AudioClip DamageTick;
[Header("VFX")] [Tooltip("The VFX prefab spawned when the enemy dies")]
public GameObject DeathVfx;
[Tooltip("The point at which the death VFX is spawned")]
public Transform DeathVfxSpawnPoint;
[Header("Loot")] [Tooltip("The object this enemy can drop when dying")]
public GameObject LootPrefab;
[Tooltip("The chance the object has to drop")] [Range(0, 1)]
public float DropRate = 1f;
[Header("Debug Display")] [Tooltip("Color of the sphere gizmo representing
the path reaching range")]
public Color PathReachingRangeColor = [Link];
[Tooltip("Color of the sphere gizmo representing the attack range")]
public Color AttackRangeColor = [Link];
[Tooltip("Color of the sphere gizmo representing the detection range")]
public Color DetectionRangeColor = [Link];
public UnityAction onAttack;
public UnityAction onDetectedTarget;
public UnityAction onLostTarget;
public UnityAction onDamaged;
List<RendererIndexData> m_BodyRenderers = new
List<RendererIndexData>();
MaterialPropertyBlock m_BodyFlashMaterialPropertyBlock;
float m_LastTimeDamaged = [Link];
RendererIndexData m_EyeRendererData;
MaterialPropertyBlock m_EyeColorMaterialPropertyBlock;
public PatrolPath PatrolPath { get; set; }
public GameObject KnownDetectedTarget =>
[Link];
public bool IsTargetInAttackRange =>
[Link];
public bool IsSeeingTarget => [Link];
public bool HadKnownTarget => [Link];
public NavMeshAgent NavMeshAgent { get; private set; }
public DetectionModule DetectionModule { get; private set; }
int m_PathDestinationNodeIndex;
EnemyManager m_EnemyManager;
ActorsManager m_ActorsManager;
Health m_Health;
Actor m_Actor;
Collider[] m_SelfColliders;
GameFlowManager m_GameFlowManager;
bool m_WasDamagedThisFrame;
float m_LastTimeWeaponSwapped = [Link];
int m_CurrentWeaponIndex;
WeaponController m_CurrentWeapon;
WeaponController[] m_Weapons;
NavigationModule m_NavigationModule;
void Start()
m_EnemyManager = FindObjectOfType<EnemyManager>();
[Link]<EnemyManager,
EnemyController>(m_EnemyManager, this);
m_ActorsManager = FindObjectOfType<ActorsManager>();
[Link]<ActorsManager,
EnemyController>(m_ActorsManager, this);
m_EnemyManager.RegisterEnemy(this);
m_Health = GetComponent<Health>();
[Link]<Health,
EnemyController>(m_Health, this, gameObject);
m_Actor = GetComponent<Actor>();
[Link]<Actor,
EnemyController>(m_Actor, this, gameObject);
NavMeshAgent = GetComponent<NavMeshAgent>();
m_SelfColliders = GetComponentsInChildren<Collider>();
m_GameFlowManager = FindObjectOfType<GameFlowManager>();
[Link]<GameFlowManager,
EnemyController>(m_GameFlowManager, this);
// Subscribe to damage & death actions
m_Health.OnDie += OnDie;
m_Health.OnDamaged += OnDamaged;
// Find and initialize all weapons
FindAndInitializeAllWeapons();
var weapon = GetCurrentWeapon();
[Link](true);
var detectionModules = GetComponentsInChildren<DetectionModule>();
[Link]<DetectionModule,
EnemyController>([Link], this,
gameObject);
[Link]<DetectionModule,
EnemyController>([Link],
this, gameObject);
// Initialize detection module
DetectionModule = detectionModules[0];
[Link] += OnDetectedTarget;
[Link] += OnLostTarget;
onAttack += [Link];
var navigationModules = GetComponentsInChildren<NavigationModule>();
[Link]<DetectionModule,
EnemyController>([Link],
this, gameObject);
// Override navmesh agent data
if ([Link] > 0)
m_NavigationModule = navigationModules[0];
[Link] = m_NavigationModule.MoveSpeed;
[Link] = m_NavigationModule.AngularSpeed;
[Link] = m_NavigationModule.Acceleration;
foreach (var renderer in GetComponentsInChildren<Renderer>(true))
for (int i = 0; i < [Link]; i++)
if ([Link][i] == EyeColorMaterial)
m_EyeRendererData = new RendererIndexData(renderer, i);
if ([Link][i] == BodyMaterial)
m_BodyRenderers.Add(new RendererIndexData(renderer, i));
}
}
m_BodyFlashMaterialPropertyBlock = new MaterialPropertyBlock();
// Check if we have an eye renderer for this enemy
if (m_EyeRendererData.Renderer != null)
m_EyeColorMaterialPropertyBlock = new MaterialPropertyBlock();
m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor",
DefaultEyeColor);
m_EyeRendererData.[Link](m_EyeColorMaterialPropertyBlo
ck,
m_EyeRendererData.MaterialIndex);
void Update()
EnsureIsWithinLevelBounds();
[Link](m_Actor, m_SelfColliders);
Color currentColor = [Link](([Link] -
m_LastTimeDamaged) / FlashOnHitDuration);
m_BodyFlashMaterialPropertyBlock.SetColor("_EmissionColor",
currentColor);
foreach (var data in m_BodyRenderers)
[Link](m_BodyFlashMaterialPropertyBlock,
[Link]);
m_WasDamagedThisFrame = false;
void EnsureIsWithinLevelBounds()
// at every frame, this tests for conditions to kill the enemy
if ([Link].y < SelfDestructYHeight)
Destroy(gameObject);
return;
void OnLostTarget()
[Link]();
// Set the eye attack color and property block if the eye renderer is set
if (m_EyeRendererData.Renderer != null)
m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor",
DefaultEyeColor);
m_EyeRendererData.[Link](m_EyeColorMaterialPropertyBlo
ck,
m_EyeRendererData.MaterialIndex);
void OnDetectedTarget()
[Link]();
// Set the eye default color and property block if the eye renderer is set
if (m_EyeRendererData.Renderer != null)
m_EyeColorMaterialPropertyBlock.SetColor("_EmissionColor",
AttackEyeColor);
m_EyeRendererData.[Link](m_EyeColorMaterialPropertyBlo
ck,
m_EyeRendererData.MaterialIndex);
}
public void OrientTowards(Vector3 lookPosition)
Vector3 lookDirection = [Link](lookPosition -
[Link], [Link]).normalized;
if ([Link] != 0f)
Quaternion targetRotation = [Link](lookDirection);
[Link] =
[Link]([Link], targetRotation, [Link] *
OrientationSpeed);
bool IsPathValid()
return PatrolPath && [Link] > 0;
public void ResetPathDestination()
m_PathDestinationNodeIndex = 0;
public void SetPathDestinationToClosestNode()
if (IsPathValid())
{
int closestPathNodeIndex = 0;
for (int i = 0; i < [Link]; i++)
float distanceToPathNode =
[Link]([Link], i);
if (distanceToPathNode <
[Link]([Link], closestPathNodeIndex))
closestPathNodeIndex = i;
m_PathDestinationNodeIndex = closestPathNodeIndex;
else
m_PathDestinationNodeIndex = 0;
public Vector3 GetDestinationOnPath()
if (IsPathValid())
{
return
[Link](m_PathDestinationNodeIndex);
else
return [Link];
public void SetNavDestination(Vector3 destination)
if (NavMeshAgent)
[Link](destination);
public void UpdatePathDestination(bool inverseOrder = false)
if (IsPathValid())
// Check if reached the path destination
if (([Link] - GetDestinationOnPath()).magnitude <=
PathReachingRadius)
// increment path destination index
m_PathDestinationNodeIndex =
inverseOrder ? (m_PathDestinationNodeIndex - 1) :
(m_PathDestinationNodeIndex + 1);
if (m_PathDestinationNodeIndex < 0)
m_PathDestinationNodeIndex += [Link];
if (m_PathDestinationNodeIndex >= [Link])
m_PathDestinationNodeIndex -= [Link];
void OnDamaged(float damage, GameObject damageSource)
// test if the damage source is the player
if (damageSource && ![Link]<EnemyController>())
// pursue the player
[Link](damageSource);
onDamaged?.Invoke();
m_LastTimeDamaged = [Link];
// play the damage tick sound
if (DamageTick && !m_WasDamagedThisFrame)
[Link](DamageTick, [Link],
[Link], 0f);
m_WasDamagedThisFrame = true;
void OnDie()
// spawn a particle system when dying
var vfx = Instantiate(DeathVfx, [Link],
[Link]);
Destroy(vfx, 5f);
// tells the game flow manager to handle the enemy destuction
m_EnemyManager.UnregisterEnemy(this);
// loot an object
if (TryDropItem())
Instantiate(LootPrefab, [Link], [Link]);
}
// this will call the OnDestroy function
Destroy(gameObject, DeathDuration);
void OnDrawGizmosSelected()
// Path reaching range
[Link] = PathReachingRangeColor;
[Link]([Link], PathReachingRadius);
if (DetectionModule != null)
// Detection range
[Link] = DetectionRangeColor;
[Link]([Link],
[Link]);
// Attack range
[Link] = AttackRangeColor;
[Link]([Link],
[Link]);
public void OrientWeaponsTowards(Vector3 lookPosition)
{
for (int i = 0; i < m_Weapons.Length; i++)
// orient weapon towards player
Vector3 weaponForward = (lookPosition -
m_Weapons[i].[Link]).normalized;
m_Weapons[i].[Link] = weaponForward;
public bool TryAtack(Vector3 enemyPosition)
if (m_GameFlowManager.GameIsEnding)
return false;
OrientWeaponsTowards(enemyPosition);
if ((m_LastTimeWeaponSwapped + DelayAfterWeaponSwap) >= [Link])
return false;
// Shoot the weapon
bool didFire = GetCurrentWeapon().HandleShootInputs(false, true, false);
if (didFire && onAttack != null)
{
[Link]();
if (SwapToNextWeapon && m_Weapons.Length > 1)
int nextWeaponIndex = (m_CurrentWeaponIndex + 1) %
m_Weapons.Length;
SetCurrentWeapon(nextWeaponIndex);
return didFire;
public bool TryDropItem()
if (DropRate == 0 || LootPrefab == null)
return false;
else if (DropRate == 1)
return true;
else
return ([Link] <= DropRate);
void FindAndInitializeAllWeapons()
{
// Check if we already found and initialized the weapons
if (m_Weapons == null)
m_Weapons = GetComponentsInChildren<WeaponController>();
[Link]<WeaponController,
EnemyController>(m_Weapons.Length, this,
gameObject);
for (int i = 0; i < m_Weapons.Length; i++)
m_Weapons[i].Owner = gameObject;
public WeaponController GetCurrentWeapon()
FindAndInitializeAllWeapons();
// Check if no weapon is currently selected
if (m_CurrentWeapon == null)
// Set the first weapon of the weapons list as the current weapon
SetCurrentWeapon(0);
}
[Link]<WeaponController,
EnemyController>(m_CurrentWeapon, this,
gameObject);
return m_CurrentWeapon;
void SetCurrentWeapon(int index)
m_CurrentWeaponIndex = index;
m_CurrentWeapon = m_Weapons[m_CurrentWeaponIndex];
if (SwapToNextWeapon)
m_LastTimeWeaponSwapped = [Link];
else
m_LastTimeWeaponSwapped = [Link];
}
RECURSOS.
[Link]