Search Unity

How in the world were the physics in F-zero X done?

Discussion in 'Scripting' started by Warrior1424, Oct 12, 2013.

  1. Warrior1424


    Sep 30, 2010
    I was playing F-Zero X today and it boggles my mind how they achieved the car physics.
    The game was released in 1998 on the N64.
    I tried raycasting down from empty objects at different points on the vehicle and applying force as necessary, but it wasn't smooth and you could fly straight through the track even at lower speeds since it relied on rigidbodies.

    I'm trying to figure out how to re-create that magnetic car effect in Unity WITHOUT using the physics engine, as I don't think it would work well at high speeds and wouldn't be needed if the same effect was made on a N64.

    If you've never played it, here's a good example of the kinds of tracks in it

    I have this weird thing where I have to know how stuff works, and this is really bugging me.
    Any ideas?
  2. Jacksendary


    Jan 31, 2012
    I have this weird Idea that it may work as a magnet, so certain objects are attracted to the game object, in this case the racing track and the cars/gliders are attracted. for the tracks that is only one sided I think they either use "Normal" physics or "directional" physics (only attracts objects from one direction, eg normal physics is from up to down.)

    Bear in mind this may be COMPLETELY wrong, but It's my Idea how it may work :)
  3. Nevak


    Sep 27, 2012
    I tried this once just to see what I was able to come up with.

    My first approach was pretty much like the one you said, however it didn't turn out nicely so I decided to forget about physics engine stuff aswell.

    What I tried next was to use a single raycast from the center of the ship and in the direction of its local down axis. With the info from this raycast you can know the distance to the track and also the normal of the track's surface at your ship's location. Then I smoothly lerped the ship's rotation so that its up axis matched the direction of the track's normal.
    As for keeping the ship hovering you can try a similar approach: you check your current distance to the ground (given by the raycast) and compare it to your desired hovering height to adjust your height accordingly (probably using a lerp aswell so that it looks smooth).

    I will try to find my code to post it here to make things clearer.:)
  4. hpjohn


    Aug 14, 2012
    If you wanted a perfectly round track, the position of the car could probably be expressed as a distance away and angle relative to the spline that defines the road, to move the car forward, just sample the spline some distance further along, at the same relative angle
  5. Warrior1424


    Sep 30, 2010
    I was thinking of that, but totally forgot about lerping it to make it smoother lol
    I'm gonna try to whip up some code using that idea.

    "Then I smoothly lerped the ship's rotation so that its up axis matched the direction of the track's normal. "
    How does one do such a thing?
    Last edited: Oct 12, 2013
  6. Nevak


    Sep 27, 2012
    Ok, I managed to put some new code together since I couldn't find my old script. I rewrote it by heart so maybe it doesn't work 100% and might need some tweaking. Here it is:

    Code (csharp):
    2. using UnityEngine;
    3. using System.Collections;
    5. public class ShipTest : MonoBehaviour
    6. {
    7.     /*Ship handling parameters*/
    8.     public float fwd_accel = 100f;
    9.     public float fwd_max_speed = 200f;
    10.     public float brake_speed = 200f;
    11.     public float turn_speed = 50f;
    13.     /*Auto adjust to track surface parameters*/
    14.     public float hover_height = 3f;     //Distance to keep from the ground
    15.     public float height_smooth = 10f;   //How fast the ship will readjust to "hover_height"
    16.     public float pitch_smooth = 5f;     //How fast the ship will adjust its rotation to match track normal
    18.     /*We will use all this stuff later*/
    19.     private Vector3 prev_up;
    20.     public float yaw;
    21.     private float smooth_y;
    22.     private float current_speed;
    25.     void Update ()
    26.     {
    27.         /*Here we get user input to calculate the speed the ship will get*/
    28.         if (Input.GetKey(KeyCode.W))
    29.         {
    30.             /*Increase our current speed only if it is not greater than fwd_max_speed*/
    31.             current_speed += (current_speed >= fwd_max_speed) ? 0f : fwd_accel * Time.deltaTime;
    32.         }
    33.         else
    34.         {
    35.             if (current_speed > 0)
    36.             {
    37.                 /*The ship will slow down by itself if we dont accelerate*/
    38.                 current_speed -= brake_speed * Time.deltaTime ;
    39.             }
    40.             else
    41.             {
    42.                 current_speed = 0f;
    43.             }
    44.         }
    46.         /*We get the user input and modifiy the direction the ship will face towards*/
    47.         yaw += turn_speed * Time.deltaTime * Input.GetAxis ("Horizontal");
    48.         /*We want to save our current transform.up vector so we can smoothly change it later*/
    49.         prev_up = transform.up;
    50.         /*Now we set all angles to zero except for the Y which corresponds to the Yaw*/
    51.         transform.rotation = Quaternion.Euler(0, yaw, 0);
    53.         RaycastHit hit;
    54.         if (Physics.Raycast(transform.position, -prev_up, out hit))
    55.         {  
    56.             Debug.DrawLine (transform.position, hit.point);
    58.             /*Here are the meat and potatoes: first we calculate the new up vector for the ship using lerp so that it is smoothed*/
    59.             Vector3 desired_up = Vector3.Lerp (prev_up, hit.normal, Time.deltaTime * pitch_smooth);
    60.             /*Then we get the angle that we have to rotate in quaternion format*/
    61.             Quaternion tilt = Quaternion.FromToRotation(transform.up, desired_up);
    62.             /*Now we apply it to the ship with the quaternion product property*/
    63.             transform.rotation = tilt * transform.rotation;
    65.             /*Smoothly adjust our height*/
    66.             smooth_y = Mathf.Lerp (smooth_y, hover_height - hit.distance, Time.deltaTime * height_smooth);
    67.             transform.localPosition += prev_up * smooth_y;
    68.         }
    70.         /*Finally we move the ship forward according to the speed we calculated before*/
    71.         transform.position += transform.forward * (current_speed * Time.deltaTime);
    72.     }
    73. }
    You can attach it to a sphere or something to test it first. Try changing the parameters to get it the way you want.
  7. Warrior1424


    Sep 30, 2010
    That works quite well, although it is important to note that all loops/twists must not be too sharp or the smoothing will cause the vehicle to fail to keep up.
    I'm gonna make a new test track that isn't so sharp and see how that works out.
    Other than that sick job dude!
  8. Nevak


    Sep 27, 2012
    Yea, you are right. The track geometry needs to be smooth enough.

    I actually had a look at wip3out game for the PSX (quite similar to FZero) in an emulator so I could see it in wireframe mode. I found out that they used some kind of "Level of detail" trick so that only the section of the track you are in is actually smooth while the rest is really low poly. However I don't know how that could affect the AI ships since the geometry they "see" would not be smooth at all...

    It is amazing how simple these games look at first sight and how complex they can get in the inside. That's why I loved the idea :D
  9. Warrior1424


    Sep 30, 2010
    I made a decent sized track, now there's the next issue:
    How would the programming be done for the side collisions if the vehicle uses no colliders?
    Raycasts I guess?

    New Issue,
    Vehicle doesn't function well upside down.
    I have a tube track where you drive on the outside, and if I go down the side too far it falls off and snaps to the ground way down below.

    Also, if you watch the video in the original post, you can see that the vehicle is unaffected by the low poly tube it drives across. There has to be another way to do this.

    BTW Thanks Nevak for all the input :)
    Last edited: Oct 12, 2013
  10. Marsupilami


    Sep 22, 2013

    See my video and code at the bottom of that url. Pretty much the same concept. Use Sphere cast of appropriate sizes 50-100 % radii of vehicle for single cast 25-50 % for multiple casts. You can also code a backup rotation position in case all of the casts fail.

    I would use colliders personally. Put barriers in a different layer or tag them and use the colliders or raycast to the sides and check the tag or layer and code the response accordingly.

    Handle your own gravity and have a sufficiently large downforce. Look at the link above to see a discussion and my example video and code for help. Using a larger sphere radius than the vehicle for a sphere cast can eliminate fall off issues.

    Using multiple sphere cast at locations similar to wheels and averaging them together then Lerp or Slerp with appropriate damping would handle just about any surface.

    You gotta remember that games like that took many, many hours of tweaking and trying to get things to work that way. I hope I helped :p

    Look at my video and see how using a sphere cast can make even angles equal to or sharper than -90° and 90° still work.
    Last edited: Oct 12, 2013
  11. Marsupilami


    Sep 22, 2013
    Not really an F-Zero example but some hovercraft code I had laying around using 4 raycast at the corners used as hover thrusters. Then smoothing the rotation to the ground normal below the craft. One could combine the Magic Boots and hover code to create an F-Zero like game.

    Code (csharp):
    2.         for(int i = 0; i < stabilizers.Length; i++)
    3.         {
    4.             if (Physics.Raycast(stabilizers[i].position, -stabilizers[i].up, out hit))
    5.             {
    6.                 thrustRatio = Mathf.Clamp(hit.distance, 0.1f, 5.0f);
    7.                 rigidbody.AddForceAtPosition(stabilizers[i].up * ((hoverForce + thrust) / thrustRatio), stabilizers[i].position);
    8.             }
    9.         }
    11.         if (Physics.Raycast(transform.position, -transform.up, out hit))
    12.         {
    13.             thrustRatio = Mathf.Clamp(hit.distance, 0.1f, 5.0f);
    14.             rigidbody.AddForce(transform.up * ((hoverForce + thrust) / thrustRatio));
    15.             if (hit.distance < 2.0f)
    16.             {
    17.                 rotationVector = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hit.normal), hit.normal), Time.deltaTime * 7.5f);
    18.             }
    19.             else
    20.             {
    21.                 rotationVector = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.Cross(transform.right, hit.normal), hit.normal), Time.deltaTime * 3.5f);
    22.             }
    23.             transform.rotation = rotationVector;
    24.         }
    26.         transform.rotation = new Quaternion(Mathf.Clamp(transform.rotation.x, -0.35f, 0.35f), transform.rotation.y, Mathf.Clamp(transform.rotation.z, -0.35f, 0.35f), transform.rotation.w);
    28.         transform.rigidbody.velocity *= 0.99f;
    29.         transform.rigidbody.angularVelocity *= 0.99f;
  12. Warrior1424


    Sep 30, 2010
    So after playing F-Zero some more, I noticed that the front of the vehicle clipped through the track when starting a small loop.
    That gave me the idea of having an invisible cube running the code by Nevak without any rotation smoothing, and having the actual vehicle set it's position to the cube's and lerp the rotation.
    It almost worked.
    I put this code on the vehicle
    Code (csharp):
    1. #pragma strict
    2. var target : Transform;
    3. var speed = 15;
    6. function Update () {
    7. transform.position = target.transform.position;
    8. var anglex : float = Mathf.MoveTowardsAngle (transform.eulerAngles.x, target.eulerAngles.x, speed * Time.deltaTime);
    9. var angley : float = Mathf.MoveTowardsAngle (transform.eulerAngles.y, target.eulerAngles.y, speed * Time.deltaTime);
    10. var anglez : float = Mathf.MoveTowardsAngle (transform.eulerAngles.z, target.eulerAngles.z, speed * Time.deltaTime);
    12. transform.eulerAngles = Vector3(anglex, angley, anglez);
    13. }
    I'm not too surprised it didn't work, as most things I code usually don't lol.

    I wanna use raycasting on the edges like in my initial attempt, but if you're going too fast and in one frame the vehicle is on the track, and in the next it is a little bit inside it, everything would goof up.
  13. rubixcube6


    Apr 17, 2013
    I have tried so many different things trying to get this to work I'm convinced it neither uses unity physics or raycasts. It probably uses splines to keep the car on the track. I noticed that on tube track, it has 6 sides. I replicated that in unity, and used raycasting to stay on track, and the angles were way too sharp to work. Even when I was going at a slow enough speed to stay on, it was not a smooth effect going around it no matter how much I mess with the tilt/pitch smoothing. In F-Zero X, the car always stays at the same distance around the track so it has to be rotating around a central point. and for such a point to exist, to move along a track would mean it needs some sort of path or curve to follow.


    If I were to move an object straight forward, it would travel straight along the z axis. If I wanted to move it along the spline then I would treat the distance of my point on the spline the same as I would the z axis. for normal track, your point on the spline also has a rotation so you would just use the x direction of your point as the ships local space x axis. For tube track, you would just rotate your ship around the spline.

    Walls and falling
    You could also set a min and max x distance for normal track. Then if you go beyond those limits, your ship would detach from the spline and fall. You could also simulate walls by not letting it go beyond your limits. No need for that on tube track. You could also set zones along the spline so that if you are going at a high enough speed you would detach from the spline like going off a ramp on normal track or a sharp curve on tube track. You could also re-attach to a spline by getting close enough to the track. you would just need to create an invisible mesh around your track to act as an attach zone.

    I haven't been able to test this theory yet, but I will soon.

    Here's how to make splines:
    Last edited: Oct 4, 2015
  14. Polymorphik


    Jul 25, 2014
    I used to play this game a lot...and by a lot I mean a lot....I am interested in giving this a try...
  15. tetriser016


    Jan 3, 2014
    Me also...

    Hoping to have a solution for this.
  16. Fajlworks


    Sep 8, 2014
    Perhaps F-Zero X wasn't made using the same effect, but you can achieve similar thing with Curved World asset.!/content/26165

    If I understood correctly, it is a shader, as it is stated in description "per mesh material shader effect". It basically curves your camera output, meaning your level is flat and you don't have to worry about those problems like curvature.

    I haven't tried it, but it seems the reviews are great. And it looks quite promising. Hope this helps!
  17. hippocoder


    Digital Ape Moderator

    Apr 11, 2010
    i'm pretty sure fzero keeps it simple with gravity always in the direction of the line segment, and distance to line segment + offset = floor height (for the tube bits).

    I really don't think it bothers with any form of raycasting on such limited hardware.
  18. bigmisterb


    Nov 6, 2010
    I did this entire thing before based on another test game in unity. As a matter of fact it operated almost exactly like this. I did 3 rays from the base of the ship and calculated down on whichever rays collided with the floor. if none of them did, then I assumed you were out in space so I applied gravity. other than that gravity was the direction of the calculation of rays. It looked exactly like what you have there.
  19. tetriser016


    Jan 3, 2014
    Sorry for reviving this dead topic, but are there any appropriate solutions yet?
  20. Akli-LT


    May 29, 2017
    I might be late to this. I am currently working on an anti gravity racer and figuring stuff out

    By interpolating between vertex normals you can get the movement pretty smooth.

    Now I am working on getting a constant height above the track

    Last edited: Feb 2, 2018
    Nevak, Ryiah, tetriser016 and 2 others like this.
  21. tetriser016


    Jan 3, 2014
    It has been over a year, are there any snippets or examples?