I am writing a game where a car drives and makes jumps. When a jump is landed, the player is rewarded if they land all four wheels either at the same time, or near to the same time. If they don't, they are penalised.
I am concerned my solution is not well abstracted (although I am open to all topics of feedback), and particularly that it will not be very extensible in the future should I come to add additional things to check for (such as bonuses for good air time, or tricks) on landing.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
/// <summary>
/// Checks if the car has made a good landing or not.
/// </summary>
[RequireComponent(typeof(CollisionEvent))] //Unity engine code to ensure a CollisionEvent is always attached to the same object as this script.
public class GoodLandingChecker : MonoBehaviour
{
/// <summary>
/// Gets or sets the bad landing threshold.
/// When a bad landing occurs, the player is penalised.
/// </summary>
/// <value>
/// The bad landing threshold.
/// </value>
public float BadLandingThreshold
{
get { return badLandingThreshold; }
set { badLandingThreshold = value; }
}
/// <summary>
/// Gets or sets the good landing threshold.
/// When a good landing occurs, the player is rewarded.
/// </summary>
/// <value>
/// The good landing threshold.
/// </value>
public float GoodLandingThreshold
{
get { return goodLandingThreshold; }
set { goodLandingThreshold = value; }
}
/// <summary>
/// Gets or sets the great landing threshold.
/// When a great landing occurs, the player is rewarded greatly.
/// </summary>
/// <value>
/// The great landing threshold.
/// </value>
public float GreatLandingThreshold
{
get { return greatLandingThreshold; }
set { greatLandingThreshold = value; }
}
/// <summary>
/// Gets or sets the minimum flying time before a landing will be considered.
/// </summary>
/// <value>
/// The minimum flying time.
/// </value>
public float MinimumFlyingTime
{
get { return minFlyingTime; }
set { minFlyingTime = value; }
}
/// <summary>
/// Gets or sets the wheels used to calculate when landings happen.
/// </summary>
/// <value>
/// The wheels.
/// </value>
public List<Transform> Wheels
{
get { return wheels; }
set { wheels = value; }
}
/// <summary>
/// The bad landing threshold
/// </summary>
[SerializeField] //Unity code to make this private field show up in Unity's
//"Inspector" where its value is set by the designer. This is used because the
//Inspector cannot display properties.
private float badLandingThreshold;
/// <summary>
/// Flying checker is a separate class that simply checks if every wheel is touching the ground.
/// </summary>
[SerializeField]
private FlyingChecker flyingChecker;
/// <summary>
/// The time spent in the air since the last landing.
/// </summary>
private float flyingTime = 0f;
/// <summary>
/// The good landing threshold
/// </summary>
[SerializeField]
private float goodLandingThreshold;
/// <summary>
/// The great landing threshold
/// </summary>
[SerializeField]
private float greatLandingThreshold;
/// <summary>
/// Whether the car is flying right now or not.
/// </summary>
private bool isFlying = false;
/// <summary>
/// The minimum flying time before a landing will be considered.
/// </summary>
[SerializeField]
private float minFlyingTime;
/// <summary>
/// The wheels
/// </summary>
[SerializeField]
private List<Transform> wheels;
/// <summary>
/// Checks the angle of the car.
/// If it's fairly flat, a good landing is awarded.
/// </summary>
/// <param name="wheel">The wheel that landed.</param>
public void WheelLanded(Transform wheel)
{
if (isFlying && flyingTime >= minFlyingTime)
{
isFlying = false;
//Get height of other wheel
float wheelHeightDifference = Wheels.Where(x => x != wheel).Sum(x => Mathf.Abs(x.position.y - wheel.position.y));
if (wheelHeightDifference <= GreatLandingThreshold)
{
Debug.Log("Great!");
//Give the car a *huge* forward push
rigidbody.AddForce(transform.forward * 500000);
}
else if (wheelHeightDifference <= GoodLandingThreshold)
{
Debug.Log("Good");
//Give the car a forward push
rigidbody.AddForce(transform.forward * 200000);
}
else if (wheelHeightDifference >= BadLandingThreshold)
{
Debug.Log("Awful!");
//Give the car a backward push
rigidbody.AddForce(-transform.forward * 200000);
}
else
{
Debug.Log("Ok");
//Normal landing, ignore
}
}
flyingTime = 0f;
}
/// <summary>
/// Handles the CollisionEntered event of the GoodLandingChecker control.
/// This occurs when something has hit something.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Game.View.CollisionEventArgs"/> instance containing the event data.</param>
private void GoodLandingChecker_CollisionEntered(object sender, Game.View.CollisionEventArgs e)
{
//Check whether any wheels hit the ground.
var wheel = Wheels.FirstOrDefault(w => e.Collision.contacts.Select(x => x.thisCollider).Any(x => w.collider == x));
if (wheel != null)
{
WheelLanded(wheel);
}
}
/// <summary>
/// Called when this object is destroyed by Unity.
/// </summary>
private void OnDestroy()
{
GetComponent<CollisionEvent>().CollisionEntered -= GoodLandingChecker_CollisionEntered;
}
/// <summary>
/// Called by Unity when the game starts.
/// </summary>
private void Start()
{
GetComponent<CollisionEvent>().CollisionEntered += GoodLandingChecker_CollisionEntered;
}
/// <summary>
/// Called by Unity every frame.
/// </summary>
private void Update()
{
//Flying checker is a separate class that simply checks if every wheel is touching the ground
isFlying = isFlying || flyingChecker.IsFlying();
if (isFlying)
{
flyingTime += Time.deltaTime;
}
}
}