Showing posts with label fps. Show all posts
Showing posts with label fps. Show all posts

Tuesday, November 5, 2019

Unity : FPS Microgame new feature and gameplay.

Finally I added a Tracer's blink skill to the player.



Implement blink was really easy.

// tracer blink.
if (isGrounded && m_InputHandler.GetTracerBlink())
{
characterVelocity *= 10;

// make blink effect
GameObject obj = GameObject.Instantiate(trailPrefab,
transform.position, Quaternion.identity);
obj.GetComponent<VFX_Trail>().SetFollowObject(gameObject, 0.7f);
}


For trail effect I added trail effect object and 0.7 later it will be destroyed. As you can see there is a variable characterVelocity. That's a character's velocity and blink skill just boost the current velocity 10 times faster.

I added portal, weapon tuning, new blink skills and tried to play this game to make it look fun.








That is it! I hope you enjoyed this series of article. :)


Sunday, November 3, 2019

Unity : FPS Microgame Analysis -2-


Class: ProjectileBase

As you can see, ProjectileStandard has a member of ProjectileBase. ProjectileBase has a UnityAction variable, which is onShoot and it is a delegate. onShoot will be called in Shoot method. Actual logic related with Projectile is in ProjectileStandard class.

ProjectileStandard component is attached into Projectile_[WeaponName]. Let’s see how Projectile_Blaster prefabs look like.

<ProjectileStandard Component on Projectile_Blaster prefab>

<Projectile_Blaster>

Projectile has a radius for represent collision detection. You can see red sphere in the above image. That is a radius for collision detection.

Tuning for Torbjorn’s gun
If you know Overwatch (Blizzard’s game), there is a character Torbjorn. A little change makes feel different. I have just tuned some values to make a projectile to be same as Torbjorn primary gun.

<Change a scale to 1.7 from 10>

<Projectile looks much smaller than before>

Next, change Speed and Gravity Down Acceleration like down below.


<Change Speed and Gravity Down Acceleration>

If you change, the values like above then movements of projectile looks similar as Torbjorn’s primary gun projectile. In addition, WeaponControl’s Delay Between Shots value needs to be 0.5 from 0.1.

<original.gif>

<tune.gif>

Register callback
OnEnable method, we register OnShot method to the ProjectileBase’s onShot delegate. When somebody calls Shoot method in ProjectileBase then OnShot callback in ProjectileStandard is called.

Initial process for firing

<Firing Sequence>

When user fire the gun, several functions are called and onShot callback is called via delegate. In OnShot, we set shootTime, velocity, m_LastRootPosition and so on. Especially m_LastRootPosition is for tracking a position and it is used in collision detection.

Prevent firing a gun in front of the wall
User is able to fire a gun in front of the wall. When user is really close to the wall, bullet can go through walls. We need to prevent this.

if (Physics.Raycast(playerWeaponsManager.weaponCamera.transform.position, cameraToMuzzle.normalized, out RaycastHit hit, cameraToMuzzle.magnitude, hittableLayers, k_TriggerInteraction))
{
if (IsHitValid(hit))
    {
        OnHit(hit.point, hit.normal, hit.collider);
}
}


Collision Detection
Most important thing in projectile is collision detection in my opinion. Last time I mentioned that this class has a member ‘radius’. For collision detection, we use sphere shape. As you know bullet could be really fast which means it can go through the wall or enemy even player character. So just sphere vs plane or sphere vs sphere collision detection is not enough. We have to consider the time lapse.

<Time t can be really big if bullets are so fast>

In Unity has built-in functions for this and we can use it.

// Sphere cast
Vector3 displacementSinceLastFrame = tip.position - m_LastRootPosition;
RaycastHit[] hits = Physics.SphereCastAll(m_LastRootPosition, radius, displacementSinceLastFrame.normalized, displacementSinceLastFrame.magnitude, hittableLayers, k_TriggerInteraction);

foreach (var hit in hits)
{
if (IsHitValid(hit) && hit.distance < closestHit.distance)
{
foundHit = true;
closestHit = hit;
}
}

There exist multiple hits and we need to consider the closest hit.

Store m_LastRootPosition
At the end of Update method, we should store m_LastRootPosition like down below to track the last position of the projectile.

m_LastRootPosition = root.position;

Explosion and damage process
In OnHit method, we create particle, play SFX and destroy by itself. For damage process, it uses Damageable class. Using GetComponent, we get Damageable comp and then call InflictDamage function. If the projectile type is area (like bomb), calls InflictDamageInArea.

Class: EnemyController

When we talk about enemy (for AI programmer), usually enemy class has its AI states, behaviors. For instance, idle, move, attack, dead states. In this example, EnemyMobile and EnemyTurret has its own movement behavior and AI and controls Enemy with EnemyController.

EnemyController has no connection to EnemyMobile and EnemyTurret. Instead, EnemyMobile and EnemyTurret uses EnemyController. EnemyController has patrolPath, other utility functions that is common logic for both EnemyMobile and EnemyTurret.

Class: EnemyMobile

EnemyMobile is an AI class for Enemy HoverBot. EnemyMobile has 3 states which is Patrol, Follow, Attack.

Patrol data is come from the patrol game object.


PatrolPath class has pathNode. Enemy will follow the paths when the state is Patrol. As soon as enemy see the player then it change his state.

Adjusting sound pitch for movement
When the enemy moves, we change a pitch of audio based on the speed.

m_AudioSource.pitch =
Mathf.Lerp(PitchDistortionMovementSpeed.min, PitchDistortionMovementSpeed.max, moveSpeed / m_EnemyController.m_NavMeshAgent.speed);

Transitions
There are many different ways to implement FSM(Finite State Machine). By definition each state has its connections, and then connections has its condition.

If the state has no match connections then state execute his logic based on the state something like (entering, updating, exiting). In this bot example, transitions and update logics are separated to 2 functions which is UpdateAIStateTransitions and UpdateCurrentAIState. Personally, separating a transition and update logics are good idea.

void UpdateAIStateTransitions()
{
    // Handle transitions
    switch (aiState)
    {
        case AIState.Follow:
            // Transition to attack when there is a line of sight to the target
            if (m_EnemyController.isSeeingTarget && m_EnemyController.isTargetInAttackRange)
            {
                aiState = AIState.Attack;
                m_EnemyController.SetNavDestination(transform.position);
            }
            break;
        case AIState.Attack:
            // Transition to follow when no longer a target in attack range
            if (!m_EnemyController.isTargetInAttackRange)
            {
                aiState = AIState.Follow;
            }
            break;
    }
}

void UpdateCurrentAIState()
{
    // Handle logic
    switch (aiState)
    {
        case AIState.Patrol:
            m_EnemyController.UpdatePathDestination();
            m_EnemyController.SetNavDestination(m_EnemyController.GetDestinationOnPath());
            break;
        case AIState.Follow:
m_EnemyController.SetNavDestination(
m_EnemyController.knownDetectedTarget.transform.position);
m_EnemyController.OrientTowards(
m_EnemyController.knownDetectedTarget.transform.position);
            break;
        case AIState.Attack:                if(Vector3.Distance(m_EnemyController.knownDetectedTarget.transform.position, m_EnemyController.detectionSourcePoint.position) >= (attackStopDistanceRatio * m_EnemyController.attackRange))
{                   
m_EnemyController.SetNavDestination(
m_EnemyController.knownDetectedTarget.transform.position);
        }
            else
            {
                m_EnemyController.SetNavDestination(transform.position);
            }

m_EnemyController.OrientTowards(
m_EnemyController.knownDetectedTarget.transform.position);
              
m_EnemyController.TryAtack((m_EnemyController.knownDetectedTarget.transform.position - m_EnemyController.weapon.transform.position).normalized);
            break;
    }
}

Class: EnemyTurret

EnemyTurret has 2 AI states. Idle and Attack. It is too simple to analysis so I will skip it.

Class: Damageable

There is a Health class, which represent health of Player or Bot. To decrease player/bot health, there are two ways.

1.     Call TakeDamage method of health class.
2.     Through Damageable, call TakeDamage method of health class.

Number 2 used for projectiles. Number 1 used for player itself. (get damage by falling/environment or other reasons)


Add more feature

I will write it next article! 

Appendix

UnityAction
In this example uses UnityAction object. This is a delegate. If you haven’t heard about delegate, what about callback? Delegate has same idea like callback. Most different thing of delegate is it can call back many functions. Normally callback function is a single. Delegate we can register/add a callback functions like receiver/subscriber.


Final UML Diagram.
I noticed that this UML Diagram doesn't show everything of FPS microgame. When you are investigating/analyzing something, you don't need to look at everything. Concentrate on what you are looking/wanting. 



Thursday, October 31, 2019

Unity : FPS Microgame Analysis -1-

Unity released very good FPS example for people and I decided to analysis how they make this. Personally I wanted to show you how I analysis the games.

Class: Player Input Handler


What Player Input Handler class does is controlling a player. It reference to GameFlowManager and get the state of current game to ignore user input when game is finished.

To Change Weapon
If user press 1, 2, 3 and so on, we can get positive number and it is used in PlayerWeaponsManager class.

Class: Player Character Controller

PlayerCharacterController class controls player’s character. Many properties are adjustable to game feel better. It uses PlayerWeaponsManager to change weapon and Health Class. Health Class is managing health for player and bots.(Both uses Health Class)

Class: PlayerWeaponsManager

At the beginning of the game player should have weapon. Variable ‘startingWeapons’ contains initial weapon list.

Weapon camera
Weapon has its own camera to render the weapon on top of main camera. Default position of weapon uses DefaultWeaponPosition. When User Press zoom (which is aiming), it will use AimingWeaponPosition. Its x value is 0 which means aligned to 0 of x.

Aiming
When user press right button, Aiming is activated. As soon as aiming is activated then all of camera’s FOV(field of view) is changed. This effect will make zoom in. FOV Animation is code driven which means changing FOV value is running by code.

void UpdateWeaponAiming()
{
    if (m_WeaponSwitchState == WeaponSwitchState.Up)
    {
        WeaponController activeWeapon = GetActiveWeapon();
        if (isAiming && activeWeapon)
        {
            m_WeaponMainLocalPosition = Vector3.Lerp(m_WeaponMainLocalPosition, aimingWeaponPosition.localPosition + activeWeapon.aimOffset, aimingAnimationSpeed * Time.deltaTime);

            SetFOV(Mathf.Lerp(m_PlayerCharacterController.playerCamera.fieldOfView, activeWeapon.aimZoomRatio * defaultFOV, aimingAnimationSpeed * Time.deltaTime));
        }
        else
        {
            m_WeaponMainLocalPosition = Vector3.Lerp(m_WeaponMainLocalPosition, defaultWeaponPosition.localPosition, aimingAnimationSpeed * Time.deltaTime);
            SetFOV(Mathf.Lerp(m_PlayerCharacterController.playerCamera.fieldOfView, defaultFOV, aimingAnimationSpeed * Time.deltaTime));
        }
    }
}
In here, we use aimingWeaponPosition and defaultWeaponPosition.


To display crosshair hud
At the end of update function, raycast and set the isPointingAtEnemy bool variable. This variable is used by CrosshairManager which will display crosshair hud based on isPointingAtEnemy.

<Raycast fail and no enemy>

<Raycast success then CrosshairManager will show different hud>

Weapon Change Animation
When user change the weapon, current weapon position moves down and changed 3d model (actually hide current weapon and show new weapon) and then move up. In this case, we use DownWeaponPosition.


 

Class: Jetpack

After user unlock Jetpack, user press spacebar then player can fly. A relationship between PlayerCharacterController and Jetpack is like down below.


Player’s movement uses characterVelocity. In every update frame of Jetpack checks and applying additional velocity into characterVelocity.

<<Gameplay Idea: Implement a skill ‘Relentless Pursuit’ of Lucian in LOL or Tracer’s Blink>>

Class: Actor


When Actor is activated, we register itself to ActorsManager actors container. ActorsManager doesn’t have any other logic. Actor also has no logic at all but it has aimPoint variable. This variable is for aiming. For instance bot is targeting a player then bot should have specific target. The target will use aimPoint of Actor class.

Class: WeaponController

Every weapon objects has WeaponController component. WeaponController represent weapon itself. Weapon type, decide which projectile should use, bulletSpreadAngle (for shotgun), aimZoomRatio and so on.

Zoom for sniper
For instance, we have sniper rifle then we probably has zooming function more than pistol. In that case, we can use aimZoomRatio. If we set animZoomRatio to be 0.3 for instance, then zoom is way bigger than 0.7

<aimZoomRatio 0.7>

<aimZoomRatio 0.3>

Firing
In WeaponController, we instantiate projectile prefabs. Function flow like down below.

HandleShootInputs -> TryShoot -> HandleShoot

Bullet position
Every generated bullets position set by weaponMuzzle. In addition, muzzle flash effect is generated.

Prevent firing too quickly
If we call HandleShoot function really quickly, bullets will be generated. If we call HandleShoot every single time when user press the button then bullets will be generated really quickly if user has lighting speed hand! We have to prevent it. There is a variable m_LastTimeShoot which will prevent calling a function really quickly. This is timing logic.


Class: EnemyController


Class: Damageable

There is a Health class, which represent health of Player or Bot. To decrease player/bot health, there are two ways.

1.     Call TakeDamage method of health class.
2.     Through Damageable, call TakeDamage method of health class.

Number 2 used for projectiles. Number 1 used for player itself. (get damage by falling/environment or other reasons)



That is it for today. Next time I'll look how projectile works (probably collision detection), add more features such as spawner for bot, user's new skills (tracer blink!)





Task in UnrealEngine

 https://www.youtube.com/watch?v=1lBadANnJaw