- Home /
Changing 2 dependent variable at the same time
Hi everybody,
I'm new to Unity and new to this forum. Right now I'm modelling the physic calculation for a drag racing game. And I'm thinking about some thing that might be a problems. So 2 have 2 variables that are depending on each other: engineSpeed(rpm) and wheelSpeed. The formula (I don't remember the exact one, but they depend on each other, from what I've heard): engineSpeed gearRatio = wheelSpeed wheelSpeed = (engineSpeed) ( tireDiameter*pi)/60 I need the engineSpeed and gearRatio to figure out the final motorTorque to send to WheelCollider, while also need the WheelCollider.rpm (the wheelSpeed) to calculate the engineSpeed when shifting (the engine rpm jumps back an amount when shifting) If I put them together in Update(), would they automatically update each other? Other do I have to put them in separate functions and call them in specific order? I hope that you guys can understand because English is not my first language. Thank you
Answer by Cynikal · Sep 27, 2016 at 09:20 PM
Here is an example of a engine script found on the wiki (http://wiki.unity3d.com/index.php?title=JCar) :
/**
* A simple car physics script using wheel colliders.
* Jaap Kreijkamp [jaap] at [ctrl-j.com.au]
*
* orientation should be that front of car is in direction of
* the 'blue arrow' in Unity, the roof should be in direction of
* the green angle. Thus with rotation 0, 0, 0, adding 1 to Z
* will move car 1m forward, adding 1 to Y will move car 1m
* upward. The wheels should be children of the car object this
* script is added to and connected to the wheelFL, wheelFR, ...
* variables.
*
* Please modify script and do whatever you like with it,
* in it's current state it should give a working car,
* but by no means perfect (or even close) behavior.
* It's my first attempt and don't really need in my current
* project so haven't put too much effort into it to perfect
* it. As often people are looking for help to getting
* a car working with wheel colliders, I'd appreciate when
* improvements are posted back on the unity forums.
*
* Lastly, thanks to all the people helping me on forums (especially
* the order of initialisation problem and other example
* code that helped me much in learning how to do stuff like
* this).
*/
using UnityEngine;
using System.Collections;
public enum JWheelDrive {
Front = 0,
Back = 1,
All = 2
}
public class JCar : MonoBehaviour {
// if connected the controls will block if object not active
// (for example steer only if car camera is active).
public GameObject checkForActive;
public Transform wheelFR; // connect to Front Right Wheel transform
public Transform wheelFL; // connect to Front Left Wheel transform
public Transform wheelBR; // connect to Back Right Wheel transform
public Transform wheelBL; // connect to Back Left Wheel transform
public float suspensionDistance = 0.2f; // amount of movement in suspension
public float springs = 1000.0f; // suspension springs
public float dampers = 2f; // how much damping the suspension has
public float wheelRadius = 0.25f; // the radius of the wheels
public float torque = 100f; // the base power of the engine (per wheel, and before gears)
public float brakeTorque = 2000f; // the power of the braks (per wheel)
public float wheelWeight = 3f; // the weight of a wheel
public Vector3 shiftCentre = new Vector3(0.0f, -0.25f, 0.0f); // offset of centre of mass
public float maxSteerAngle = 30.0f; // max angle of steering wheels
public JWheelDrive wheelDrive = JWheelDrive.Front; // which wheels are powered
public float shiftDownRPM = 1500.0f; // rpm script will shift gear down
public float shiftUpRPM = 2500.0f; // rpm script will shift gear up
public float idleRPM = 500.0f; // idle rpm
public float fwdStiffness = 0.1f; // for wheels, determines slip
public float swyStiffness = 0.1f; // for wheels, determines slip
// gear ratios (index 0 is reverse)
public float[] gears = { -10f, 9f, 6f, 4.5f, 3f, 2.5f };
// automatic, if true car shifts automatically up/down
public bool automatic = true;
public float killEngineSoundTimeout = 3.0f; // time until engine sound is cut off (in s.)
// table of efficiency at certain RPM, in tableStep RPM increases, 1.0f is 100% efficient
// at the given RPM, current table has 100% at around 2000RPM
float[] efficiencyTable = { 0.6f, 0.65f, 0.7f, 0.75f, 0.8f, 0.85f, 0.9f, 1.0f, 1.0f, 0.95f, 0.80f, 0.70f, 0.60f, 0.5f, 0.45f, 0.40f, 0.36f, 0.33f, 0.30f, 0.20f, 0.10f, 0.05f };
// the scale of the indices in table, so with 250f, 750RPM translates to efficiencyTable[3].
float efficiencyTableStep = 250.0f;
int currentGear = 1; // duh.
// shortcut to the component audiosource (engine sound).
AudioSource audioSource;
// every wheel has a wheeldata struct, contains useful wheel specific info
class WheelData {
public Transform transform;
public GameObject go;
public WheelCollider col;
public Vector3 startPos;
public float rotation = 0.0f;
public float maxSteer;
public bool motor;
};
WheelData[] wheels; // array with the wheel data
// setup wheelcollider for given wheel data
// wheel is the transform of the wheel
// maxSteer is the angle in degrees the wheel can steer (0f for no steering)
// motor if wheel is driven by engine or not
WheelData SetWheelParams(Transform wheel, float maxSteer, bool motor) {
if (wheel == null) {
throw new System.Exception("wheel not connected to script!");
}
WheelData result = new WheelData(); // the container of wheel specific data
// we create a new gameobject for the collider and move, transform it to match
// the position of the wheel it represents. This allows us to do transforms
// on the wheel itself without disturbing the collider.
GameObject go = new GameObject("WheelCollider");
go.transform.parent = transform; // the car, not the wheel is parent
go.transform.position = wheel.position; // match wheel pos
// create the actual wheel collider in the collider game object
WheelCollider col = (WheelCollider) go.AddComponent(typeof(WheelCollider));
col.motorTorque = 0.0f;
// store some useful references in the wheeldata object
result.transform = wheel; // access to wheel transform
result.go = go; // store the collider game object
result.col = col; // store the collider self
result.startPos = go.transform.localPosition; // store the current local pos of wheel
result.maxSteer = maxSteer; // store the max steering angle allowed for wheel
result.motor = motor; // store if wheel is connected to engine
return result; // return the WheelData
}
// Use this for initialization
void Start () {
// 4 wheels, if needed different size just modify and modify
// the wheels[...] block below.
wheels = new WheelData[4];
// setup wheels
bool frontDrive = (wheelDrive == JWheelDrive.Front) || (wheelDrive == JWheelDrive.All);
bool backDrive = (wheelDrive == JWheelDrive.Back) || (wheelDrive == JWheelDrive.All);
// we use 4 wheels, but you can change that easily if neccesary.
// this is the only place that refers directly to wheelFL, ...
// so when adding wheels, you need to add the public transforms,
// adjust the array size, and add the wheels initialisation here.
wheels[0] = SetWheelParams(wheelFR, maxSteerAngle, frontDrive);
wheels[1] = SetWheelParams(wheelFL, maxSteerAngle, frontDrive);
wheels[2] = SetWheelParams(wheelBR, 0.0f, backDrive);
wheels[3] = SetWheelParams(wheelBL, 0.0f, backDrive);
// found out the hard way: some parameters must be set AFTER all wheel colliders
// are created, like wheel mass, otherwise your car will act funny and will
// flip over all the time.
foreach (WheelData w in wheels) {
WheelCollider col = w.col;
col.suspensionDistance = suspensionDistance;
JointSpring js = col.suspensionSpring;
js.spring = springs;
js.damper = dampers;
col.suspensionSpring = js;
col.radius = wheelRadius;
col.mass = wheelWeight;
// see docs, haven't really managed to get this work
// like i would but just try out a fiddle with it.
WheelFrictionCurve fc = col.forwardFriction;
fc.asymptoteValue = 5000.0f;
fc.extremumSlip = 2.0f;
fc.asymptoteSlip = 20.0f;
fc.stiffness = fwdStiffness;
col.forwardFriction = fc;
fc = col.sidewaysFriction;
fc.asymptoteValue = 7500.0f;
fc.asymptoteSlip = 2.0f;
fc.stiffness = swyStiffness;
col.sidewaysFriction = fc;
}
// we move the centre of mass (somewhere below the centre works best.)
rigidbody.centerOfMass += shiftCentre;
// shortcut to audioSource should be engine sound, if null then no engine sound.
audioSource = (AudioSource) GetComponent(typeof(AudioSource));
if (audioSource == null) {
Debug.Log("No audio source, add one to the car with looping engine noise (but can be turned off");
}
}
void Update() {
if (Input.GetKeyDown("page up")) {
ShiftUp();
}
if (Input.GetKeyDown("page down")) {
ShiftDown();
}
}
float shiftDelay = 0.0f;
// handle shifting a gear up
public void ShiftUp() {
float now = Time.timeSinceLevelLoad;
// check if we have waited long enough to shift
if (now < shiftDelay) return;
// check if we can shift up
if (currentGear < gears.Length - 1) {
currentGear ++;
// we delay the next shift with 1s. (sorry, hardcoded)
shiftDelay = now + 1.0f;
}
}
// handle shifting a gear down
public void ShiftDown() {
float now = Time.timeSinceLevelLoad;
// check if we have waited long enough to shift
if (now < shiftDelay) return;
// check if we can shift down (note gear 0 is reverse)
if (currentGear > 0) {
currentGear --;
// we delay the next shift with 1/10s. (sorry, hardcoded)
shiftDelay = now + 0.1f;
}
}
float wantedRPM = 0.0f; // rpm the engine tries to reach
float motorRPM = 0.0f;
float killEngine = 0.0f;
// handle the physics of the engine
void FixedUpdate () {
float delta = Time.fixedDeltaTime;
float steer = 0; // steering -1.0 .. 1.0
float accel = 0; // accelerating -1.0 .. 1.0
bool brake = false; // braking (true is brake)
if ((checkForActive == null) || checkForActive.active) {
// we only look at input when the object we monitor is
// active (or we aren't monitoring an object).
steer = Input.GetAxis("Horizontal");
accel = Input.GetAxis("Vertical");
brake = Input.GetButton("Jump");
}
// handle automatic shifting
if (automatic && (currentGear == 1) && (accel < 0.0f)) {
ShiftDown(); // reverse
}
else if (automatic && (currentGear == 0) && (accel > 0.0f)) {
ShiftUp(); // go from reverse to first gear
}
else if (automatic && (motorRPM > shiftUpRPM) && (accel > 0.0f)) {
ShiftUp(); // shift up
}
else if (automatic && (motorRPM < shiftDownRPM) && (currentGear > 1)) {
ShiftDown(); // shift down
}
if (automatic && (currentGear == 0)) {
accel = - accel; // in automatic mode we need to hold arrow down for reverse
}
if (accel < 0.0f) {
// if we try to decelerate we brake.
brake = true;
accel = 0.0f;
wantedRPM = 0.0f;
}
// the RPM we try to achieve.
wantedRPM = (5500.0f * accel) * 0.1f + wantedRPM * 0.9f;
float rpm = 0.0f;
int motorizedWheels = 0;
bool floorContact = false;
// calc rpm from current wheel speed and do some updating
foreach (WheelData w in wheels) {
WheelHit hit;
WheelCollider col = w.col;
// only calculate rpm on wheels that are connected to engine
if (w.motor) {
rpm += col.rpm;
motorizedWheels++;
}
// calculate the local rotation of the wheels from the delta time and rpm
// then set the local rotation accordingly (also adjust for steering)
w.rotation = Mathf.Repeat(w.rotation + delta * col.rpm * 360.0f / 60.0f, 360.0f);
w.transform.localRotation = Quaternion.Euler(w.rotation, col.steerAngle, 0.0f);
// let the wheels contact the ground, if no groundhit extend max suspension distance
Vector3 lp = w.transform.localPosition;
if (col.GetGroundHit(out hit)) {
lp.y -= Vector3.Dot(w.transform.position - hit.point, transform.up) - col.radius;
floorContact = floorContact || (w.motor);
}
else {
lp.y = w.startPos.y - suspensionDistance;
}
w.transform.localPosition = lp;
}
// calculate the actual motor rpm from the wheels connected to the engine
// note we haven't corrected for gear yet.
if (motorizedWheels > 1) {
rpm = rpm / motorizedWheels;
}
// we do some delay of the change (should take delta instead of just 95% of
// previous rpm, and also adjust or gears.
motorRPM = 0.95f * motorRPM + 0.05f * Mathf.Abs(rpm * gears[currentGear]);
if (motorRPM > 5500.0f) motorRPM = 5500.0f;
// calculate the 'efficiency' (low or high rpm have lower efficiency then the
// ideal efficiency, say 2000RPM, see table
int index = (int) (motorRPM / efficiencyTableStep);
if (index >= efficiencyTable.Length) index = efficiencyTable.Length - 1;
if (index < 0) index = 0;
// calculate torque using gears and efficiency table
float newTorque = torque * gears[currentGear] * efficiencyTable[index];
// go set torque to the wheels
foreach (WheelData w in wheels) {
WheelCollider col = w.col;
// of course, only the wheels connected to the engine can get engine torque
if (w.motor) {
// only set torque if wheel goes slower than the expected speed
if (Mathf.Abs(col.rpm) > Mathf.Abs(wantedRPM)) {
// wheel goes too fast, set torque to 0
col.motorTorque = 0;
}
else {
//
float curTorque = col.motorTorque;
col.motorTorque = curTorque * 0.9f + newTorque * 0.1f;
}
}
// check if we have to brake
col.brakeTorque = (brake)?brakeTorque:0.0f;
// set steering angle
col.steerAngle = steer * w.maxSteer;
}
// if we have an audiosource (motorsound) adjust pitch using rpm
if (audioSource != null) {
// calculate pitch (keep it within reasonable bounds)
float pitch = Mathf.Clamp(1.0f + ((motorRPM - idleRPM) / (shiftUpRPM - idleRPM) * 2.5f), 1.0f, 10.0f);
audioSource.pitch = pitch;
if (motorRPM > 100) {
// turn on sound if it's not playing yet and RPM is > 100.
if (!audioSource.isPlaying) {
audioSource.Play();
}
// how long we should wait with engine RPM <= 100 before killing engine sound
killEngine = Time.time + killEngineSoundTimeout;
}
else if ((audioSource.isPlaying) && (Time.time > killEngine)) {
// standing still, kill engine sound.
audioSource.Stop();
}
}
}
public void OnGUI() {
if (checkForActive.active) {
// calculate actual speed in Km/H (SI metrics rule, so no inch, yard, foot,
// stone, or other stupid length measure!)
float speed = rigidbody.velocity.magnitude * 3.6f;
// message to display
string msg = "Speed " + speed.ToString("f0") + "Km/H, " + motorRPM.ToString("f0") + "RPM, gear " + currentGear; // + " torque " + newTorque.ToString("f2") + ", efficiency " + table[index].ToString("f2");
GUILayout.BeginArea(new Rect(Screen.width -250 - 32, 32, 250, 40), GUI.skin.window);
GUILayout.Label(msg);
GUILayout.EndArea();
}
}
}
I don't know quite what you're wanting to do... You don't need to simultaneously update 2 variables for the engine script to work. You can do it in order.
Another great resource is: http://www.asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html which contains a lot of math formulas and information.
Your answer
![](https://koobas.hobune.stream/wayback/20220612093407im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Its not playing the Audio? 2 Answers
how to allow the key to only open 1 door rather than all of them? 0 Answers
Scan for a variable based upon an integer 2 Answers
Moving a ScrollRect child via script to focus on some point. 0 Answers
Why when using get; set; in one script i'm getting null in other script that use it ? 1 Answer