J29 Unleashed

Defend your forest from the invading turrets

Overview



Key Features




Project Goals




Background

This personal project has been a passion of mine to make ever since playing Airfix Dogfighter. 

Over the past four weeks, I've focused on gameplay programming, specifically creating a player controller and weapon systems using Unity's Rigidbody for a semi-realistic feel. 

This project highlights my strengths in gameplay programming and creative thinking.

Working alone has allowed me to explore new ideas independently. 

However, I find that collaborating in a team environment enhances my creativity and allows for valuable feedback on my work. 

I approach each project with an open mind, balancing simplicity with effective programming solutions.

Please take a moment to explore my page for J29 Unleashed!

Terrain Deformation

Using Unity's own terrain tool I created a script that deforms it based it's heightmap and position, I used this for the bombs, missile and when you explode.

IEnumerator CreateCraterCoroutine(Vector3 position, float radius, float depth)

{

        Terrain terrain = Terrain.activeTerrain;

        if (terrain == null) yield break;


        TerrainData terrainData = terrain.terrainData;

        Vector3 terrainPos = terrain.transform.position;


        int heightmapWidth = terrainData.heightmapResolution;

        int heightmapHeight = terrainData.heightmapResolution;


        int xBase = (int)((position.x - terrainPos.x) / terrainData.size.x * heightmapWidth);

        int zBase = (int)((position.z - terrainPos.z) / terrainData.size.z * heightmapHeight);

        int craterRadiusInHeightmap = (int)(radius / terrainData.size.x * heightmapWidth);


        int xStart = Mathf.Clamp(xBase - craterRadiusInHeightmap, 0, heightmapWidth);

        int xEnd = Mathf.Clamp(xBase + craterRadiusInHeightmap, 0, heightmapWidth);

        int zStart = Mathf.Clamp(zBase - craterRadiusInHeightmap, 0, heightmapHeight);

        int zEnd = Mathf.Clamp(zBase + craterRadiusInHeightmap, 0, heightmapHeight);


        int width = xEnd - xStart;

        int height = zEnd - zStart;


        float[,] heights = terrainData.GetHeights(xStart, zStart, width, height);


        for (int x = 0; x < width; x++)

        {

            for (int z = 0; z < height; z++)

            {

                int xOffset = x + xStart - xBase + craterRadiusInHeightmap;

                int zOffset = z + zStart - zBase + craterRadiusInHeightmap;


                float distance = Vector2.Distance(new Vector2(xOffset, zOffset), 

                new Vector2(craterRadiusInHeightmap, craterRadiusInHeightmap));


                if (distance < craterRadiusInHeightmap)

                {

                    float proportionalDistance = distance / craterRadiusInHeightmap;

                    float heightAdjustment = (1 - proportionalDistance) * (depth / terrainData.size.y);

                    heights[z, x] -= heightAdjustment;

                }

            }

            if (x % 10 == 0)

            {

                yield return null;

            }

        }


        terrainData.SetHeights(xStart, zStart, heights);

    }

Missile Controller

Using the Quadratic formula I calculate the optimal trajectory for intercepting a moving target based on their positions and velocities. MoveToTarget method utilizes this course to adjust the missile's velocity, ensuring it steers to intercept the target effectively creating a seeking missile, this interception calculation is also used for the ground turrets to know where to shoot.

Vector3 CalculateInterceptCourse(Vector3 _tPos, Vector3 _tSpeed, Vector3 _iPos, float _iSpeed)

{

    Vector3 targetDir = _tPos - _iPos;


    float iSpeedSquared = _iSpeed* _iSpeed;

    float tSpeedSquared = _tSpeed.sqrMagnitude;

    float forwardDot = Vector3.Dot(targetDir, _targetSpeed);

    float targetDist = targetDir.sqrMagnitude;

    float d = (forwardDot * forwardDot) - targetDist * (tSpeedSquared - iSpeedSquared);


    if (d < 0.0f)

        return targetDir.normalized;


    float sqrt = Mathf.Sqrt(d);

    float S1 = (-forwardDot - sqrt) / targetDist;

    float S2 = (-forwardDot + sqrt) / targetDist;


    float S = Mathf.Max(S1, S2);

    return (targetDir * S + _tSpeed).normalized;

}

MoveToTarget()

{

    outOfAngleTimer = 0f;


    Vector3 targetPosition = target.transform.position;

    Rigidbody targetRigidbody = target.GetComponent<Rigidbody>();


    if (targetRigidbody != null)

    {

        Vector3 targetVelocity = targetRigidbody.velocity;


        Vector3 interceptDirection = CalculateInterceptCourse(targetPosition,

        targetVelocity, transform.position,

        rb.velocity.magnitude);


        rb.velocity = Vector3.RotateTowards(rb.velocity,

        interceptDirection * currentSpeed + playerVelocity,

        maxTurnSpeed * Time.deltaTime, 0.0f);

    }

}

Flight Controller

Making a method for lift ensures the ability to be able to glide with the plane.
Aligning the gameobject's forward with it's rigibody's forward vector was crucial to remove any drifting.

ApplyLift()

{

    float forwardSpeed = Vector3.Dot(rb.velocity, transform.forward);

    float gravitationalForce = rb.mass * Physics.gravity.magnitude;

    float liftForceMagnitude = liftMultiplier * airDensity * forwardSpeed * forwardSpeed * wingArea * liftCoefficient;


    float speedFactor = Mathf.Clamp01((forwardSpeed - minSpeedForLift) / minSpeedForLift);


    liftForceMagnitude = Mathf.Min(liftForceMagnitude, gravitationalForce);

    liftForceMagnitude *= speedFactor;


    Vector3 liftDirection = transform.up;

    liftForce = liftDirection * liftForceMagnitude;


    rb.AddForce(liftForce);

}

AlignVelocityWithForwardDirection()

{

    float gravitationalForce = rb.mass * Physics.gravity.magnitude;


    bool isLiftSufficient = liftForce.magnitude / 2.0f >= gravitationalForce;


    float forwardSpeed = Vector3.Dot(rb.velocity, transform.forward);

    float alignmentSpeed = isLiftSufficient ? maxAlignmentSpeed : minAlignmentSpeed;


    Vector3 alignedVelocity = Vector3.Lerp(rb.velocity, transform.forward * forwardSpeed, alignmentSpeed);


    rb.velocity = alignedVelocity;

}