Game Preview
Source code download at:
The code is very easy to understand and self explanatory.
The kart object
The reflection probe is to give the car a nice metallic feel.
Particle systems: Boosting fires, drifting sparks on the tiers, etc.
The animator component is on StandartKart gameobject
Tires
The model’s pivot is not in the center of the model, so we added the model to the parent gameobject called FrontLeft, FrontLeft gameobject is at the center of the model. We will rotate the FrontLeft gameobject when the car is running.
Movement (Back and forth)
PlayerScripts.cs is the most important script in this project.
If you want to know what each function does, just comment out other functions first. Like the first function move();
FixedUpdate() {
move();
//tireSteer();
//steer();
//groundNormalRotation();
//drift();
//boosts();
}
In move function:
if (Input.GetKey(KeyCode.Space)) {
CurrentSpeed = Mathf.Lerp(CurrentSpeed, MaxSpeed, Time.deltaTime * 0.5f);
} else if (Input.GetKey(KeyCode.S)) {
CurrentSpeed = Mathf.Lerp(CurrentSpeed, -MaxSpeed / 1.75f, 1f * Time.deltaTime);
} else {
CurrentSpeed = Mathf.Lerp(CurrentSpeed, 0, Time.deltaTime * 1.5f);
}
Vector3 vel = transform.forward * CurrentSpeed;
vel.y = rb.velocity.y; //gravity
rb.velocity = vel;
Here is the doc for Math.Lerp: https://docs.unity3d.com/ScriptReference/Mathf.Lerp.html
CurrentSpeed = Mathf.Lerp(CurrentSpeed, MaxSpeed, Time.deltaTime * 0.5f);
In this line of code, 0.5f means that the kart will reach max speed in 1/0.5=2 seconds.
Don't forget to add vel.y=rb.velocity.y
Obviously we don’t want the gravity effect to get erased.
Car Body Steering
Rotate around the y axis.
In steer() function:
since handling is supposed to be stronger when car is moving slower, we adjust steerAmount depending on the real speed of the kart, and then rotate the kart on its y axis with steerAmount
steerAmount = RealSpeed > 30 ? RealSpeed / 4 * steerDirection : steerAmount = RealSpeed / 1.5f * steerDirection;
Tire steer and rotation
Note that only front tires can rotate left and right. Back tires won’t.
if (Input.GetKey(KeyCode.LeftArrow))
{
frontLeftTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 155, 0), 5 * Time.deltaTime);
frontRightTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 155, 0), 5 * Time.deltaTime);
}
else if (Input.GetKey(KeyCode.RightArrow))
{
frontLeftTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 205, 0), 5 * Time.deltaTime);
frontRightTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 205, 0), 5 * Time.deltaTime);
}
else
{
frontLeftTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 180, 0), 5 * Time.deltaTime); // when moving straight forward
frontRightTire.localEulerAngles = Vector3.Lerp(frontLeftTire.localEulerAngles, new Vector3(0, 180, 0), 5 * Time.deltaTime);
}
The default rotation of the Y axis is 180, when the car is moving straight forward.
Ground Normal Rotation
There is a little bug here we need to fix:
As you can see in the picture above, when on the slope, the body is not parallel to the ground. We need to rotate the car body to achieve the effect of the vehicle climbing a hill.
Just rotate the car to the rotation of the raycast’s hit point’s normal direction.
private void groundNormalRotation()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, -transform.up, out hit, 0.75f))
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.FromToRotation(transform.up * 2, hit.normal) * transform.rotation, 7.5f * Time.deltaTime);
touchingGround = true;
} else {
touchingGround = false;
}
}
Drifting
We need two flags: driftRight and driftLeft
if(steerDirection > 0)
{
driftRight = true;
driftLeft = false;
}
else if(steerDirection < 0)
{
driftRight = false;
driftLeft = true;
}
steerDirection is controlled by Horizontal input axis
steerDirection = Input.GetAxisRaw("Horizontal");
Accumulating drift time:
if (Input.GetKey(KeyCode.V) && touchingGround && CurrentSpeed > 40 && Input.GetAxis("Horizontal") != 0)
{
driftTime += Time.deltaTime;
Conditions of drifting: touching ground, speed>40, hold left or right key, hold V the drift key.
According to the drift time, set a different color for the particle system. The longer the drift, the stronger the color. This way we give users visual feedback.
Before drifting there will be a hop animation:
Boosts
The boost time is determined by the total of drift time
//give a boost
if (driftTime > 1.5 && driftTime < 4) {
BoostTime = 0.75f;
}
if (driftTime >= 4 && driftTime < 7)
{
BoostTime = 1.5f;
}
if (driftTime >= 7)
{
BoostTime = 2.5f;
}
When boosting, maxspeed will be set to boostspeed:
if(BoostTime > 0) {
for(int i = 0; i < boostFire.childCount; i++)
{ // boost fire particles
if (! boostFire.GetChild(i).GetComponent<ParticleSystem>().isPlaying) {
boostFire.GetChild(i).GetComponent<ParticleSystem>().Play();
}
}
MaxSpeed = boostSpeed;
CurrentSpeed = Mathf.Lerp(CurrentSpeed, MaxSpeed, 1 * Time.deltaTime);
} else {
for (int i = 0; i < boostFire.childCount; i++) {
boostFire.GetChild(i).GetComponent<ParticleSystem>().Stop();
}
MaxSpeed = boostSpeed - 20;
}
Camera Follow
The CameraFollow.cs script is attached on PlayerLookat gameobject
The Main Camera gameobject is the child of PlayerLookat object.
Note that the relative position is 0,0.95,-5.5 and the relative rotation on the X axis is 10 to make it look down a bit.
Also, in the game, when we get a boost, the camera moves slightly back. But it's not the actual PlayerLookat object that's moving back, it’s Main Camera gameobject that is moving back. The relevant code below:
In CameraFollow.cs:
if (playerScript.BoostTime > 0) {
transform.GetChild(0).localPosition = Vector3.Lerp(transform.GetChild(0).localPosition, boostCamPos, 3 * Time.deltaTime);
} else {
transform.GetChild(0).localPosition = Vector3.Lerp(transform.GetChild(0).localPosition, origCamPos, 3 * Time.deltaTime);
}
When the kart is turning or drifting,
PlayerLookat gameobject will gradually rotate to the direction the kart is rotating.
Because we want to see the side of the car, so we made the camera’s rotation value slowly interpolate approaching the kart’s steering rotation.
otherwise we can only see the back of the car the whole time like this:
How to achieve this in code:
In CameraFollow.cs
transform.rotation = Quaternion.Slerp(transform.rotation, player.rotation, 3 * Time.deltaTime);
The camera's rotation is approaching player’s rotation slowly.
Gliding
Preview:
How to trigger gliding:
The extra collider is above the panel, this extra collider is set to Trigger.
In PlayerScript.cs:
private void OnTriggerExit(Collider other)
{
if(other.gameObject.tag == "GliderPanel")
{
GLIDER_FLY = true;
gliderAnim.SetBool("GliderOpen", true);
gliderAnim.SetBool("GliderClose", false);
}
}
When exit the trigger, GLIDER_FLY will be true.
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.tag == "Ground" || collision.gameObject.tag == "OffRoad")
{
GLIDER_FLY = false;
gliderAnim.SetBool("GliderOpen", false);
gliderAnim.SetBool("GliderClose", true);
}
}
When hit the ground or off road, we will end the gliding by set GLIDER_FLY = false;
We will use the GLIDER_FLY boolean flag to decide the gravity factor and speed:
In PlayerScript.cs move() function:
if (!GLIDER_FLY) {
Vector3 vel = transform.forward * CurrentSpeed;
vel.y = rb.velocity.y; //gravity
rb.velocity = vel;
} else {
Vector3 vel = transform.forward * CurrentSpeed;
vel.y = rb.velocity.y * 0.6f; //gravity with gliding
rb.velocity = vel;
}
Also, when gliding, the kart will rotate around the z axis instead of y axis that the kart rotates around when the kart is on the ground.
//glider movements
if (Input.GetKey(KeyCode.LeftArrow) && GLIDER_FLY) //left
{
transform.rotation = Quaternion.SlerpUnclamped(transform.rotation, Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, 40), 2 * Time.deltaTime);
} // left
else if (Input.GetKey(KeyCode.RightArrow) && GLIDER_FLY) //right
{
transform.rotation = Quaternion.SlerpUnclamped(transform.rotation, Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, -40), 2 * Time.deltaTime);
} //right
else //nothing
{
transform.rotation = Quaternion.SlerpUnclamped(transform.rotation, Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, 0), 2 * Time.deltaTime);
} //nothing
Animation of gliding:
TO BE CONTINUED:
Comments