Devlog #08 - Implementing Spectator Player


Introduction:

As per the next plan, the most unique aspect of our board game is to let the spectator player play as well during an ongoing battle. This is the key to forming alliances while the game lasts. We were quite focused on the balance aspect, hence we are currently not letting players who spectate do damage to the arena players,  although we wanted something that could be advantageous enough to give value to forming alliances. Hence, we will currently be giving a movement debuff of 40 percent, and we will mostly alter this value during the playtesting phase.

Setting up a model:

Since we are developing a base model that will be replaced by the player's avatar and we are focusing on developing the mechanics of the game. I decided to reuse as much things as possible, So I could develop the mechanics as fast as possible. The model reuses aspects of the Arena player.

Spectator Player hierarchy
 

Here we have 2 empty game objects, FireStartingPoint, which is where we spawn our bullet or projectile, and AimTarget, which is where our bullet is supposed to go. AimTarget is new in this model because we decided to make the projectile shoot towards the sky and fall down on the player, hence avoiding as many obstacles, since the spectators can't move. So we needed this target to calculate the amount of distance to cover, and hence calculate the velocity that needs to be applied. Also, we attached a crosshair to this point to let players be more accurate about where they are shooting. There is also one more advantage to this approach, which is that  we could easily incorporate controller support since we use a crosshair in-game, which is already done


This script is done in SpectatorPlayerFire.cs
 

Visual Feedback:

In this game, we also have a projectile preview script that uses the 2 empty child objects to follow the same calculation and get arc points. These points are then passed to the line renderer component to draw a projectile trajectory to aid players in their accuracy. This component is used to disable the line renderer when the projectile is not ready to fire, hence acting as a visual indicator of when a player can shoot.

void Update() {
    if (firePoint == null || aimTarget == null) {
        lr.enabled = false;
        return;
    }
    Vector3 start = firePoint.position;
    Vector3 end = aimTarget.position;
    if (Vector3.Distance(start, lastFirePointPos) < 0.01f &&
        Vector3.Distance(end, lastAimTargetPos) < 0.01f) {
        return;
    }
    lastFirePointPos = start;
    lastAimTargetPos = end;
    // Check if target is too close
    float distance = Vector3.Distance(new Vector3(start.x, 0, start.z), new Vector3(end.x, 0, end.z));
    if (distance < minDistance) {
        lr.enabled = false;
        return;
    }
    lr.enabled = true;
    Vector3[] points = new Vector3[resolution];
    // Clamp target Y to prevent extreme downward aim
    if (end.y < start.y - 1f) {
        end.y = start.y - 1f;
    }
    float gravity = Mathf.Abs(Physics.gravity.y);
    // Calculate direction and vertical distance
    Vector3 direction = new Vector3(end.x - start.x, 0f, end.z - start.z);
    float horizontalDistance = direction.magnitude;
    float verticalDistance = end.y - start.y;
    // Calculate time to apex and descent
    float timeToApex = Mathf.Sqrt(2 * arcHeight / gravity);
    float descendHeight = Mathf.Max(arcHeight - verticalDistance, 0.1f);
    float timeFromApex = Mathf.Sqrt(2 * descendHeight / gravity);
    float totalTime = Mathf.Clamp(timeToApex + timeFromApex, 0.1f, 5f);
    // Calculate velocities
    Vector3 horizontalVelocity = direction.normalized * (horizontalDistance / totalTime);
    float verticalVelocity = Mathf.Sqrt(2 * gravity * arcHeight);
    Vector3 initialVelocity = horizontalVelocity + Vector3.up * verticalVelocity;
    // Generate arc points
    for (int i = 0; i < resolution; i++) {
        float t = (i / (float)(resolution - 1)) * totalTime;
        Vector3 point = start + initialVelocity * t + 0.5f * Physics.gravity * t * t;
        points[i] = point;
    }
    lr.positionCount = resolution;
    lr.SetPositions(points);
}
This script is done in ProjectileArcPreview.cs

The next visual feedback is in the MortarProjectile Script, which uses a trail renderer component to render a trail that acts as a visual indicator of the impending debuff coming your way.


This script is done in MortarProjectile.cs
 

The MortarProjectile script also stores the slow amount and the duration, which is processed and accordingly applied to the arena player. And when the collision is triggered, a small particle effect is played to let the player know they are being debuffed.

Additionally, I have made changes to the ArenaPlayerRPC script to process this slow effect and have a blue tint on the character to act as a visual indicator of the player being slowed

Final Output:

Hence, using these steps, we have successfully implemented the spectator player mechanics that could be controlled using both keyboard and mouse, and a controller.

References:

[1] Unity Trail Renderer component reference: https://docs.unity3d.com/Manual/class-TrailRenderer.html

[2] Unity.Line Renderer component reference: https://docs.unity3d.com/Manual/class-LineRenderer.html

[3] Use of Generative AI: 

prompt - "What are the mathematical calculations to find the velocity to be applied for an object to go from point A to point B, after reaching a certain apex height?"

Leave a comment

Log in with itch.io to leave a comment.