meta data for this page
Vehicles/VehicleScripts/VehicleMotor.lua
VehicleMotor implements a diesel engine together with a gearbox that powers the vehicle. This is the best option for all vehicles that are powered by any powertrain comparable to this setup, except if a very continuous speed level shall be achieved (in that case, head over to TrackedVehicle).
25 VehicleMotor = VehicleMotor or {};
VehicleMotor:load(dataTable)
Creates all functions required for VehicleMotor. As many of them require return values, only some can be created using VehicleManager:newFunction()
.
Afterwards, the function loads all relevant configuration parameters from the vehicle's data table.
31 function VehicleMotor:load(dataTable) 32 self.getMotorTorqueAtRpm = VehicleMotor.getMotorTorqueAtRpm; 33 self.getDrivingWheelsRpm = VehicleMotor.getDrivingWheelsRpm; 34 self.getCurrentMotorTorque = VehicleMotor.getCurrentMotorTorque; 35 self.getGearTransmissionRatio = VehicleMotor.getGearTransmissionRatio; 36 self.getMotorInertia = VehicleMotor.getMotorInertia; 37 self.getDrivingWheelsCount = VehicleMotor.getDrivingWheelsCount; 38 self.updateWheels = VehicleMotor.updateWheels; 39 self.setAutomaticDriveActive = VehicleMotor.setAutomaticDriveActive; 40 self.setIsMotorOn = VehicleManager:newFunction("setIsMotorOn"); 41 42 self.motorMinRpm = dataTable.motorMinRpm or 800; 43 self.motorMaxRpm = dataTable.motorMaxRpm or 5000; 44 self.motorInertiaMoment = dataTable.motorInertiaMoment or 0.5; 45 46 local speedPointerIndex = dataTable.speedPointerIndex or ""; 47 self.speedPointerWheelRadius = dataTable.wheelRadius or 0.5; 48 self.speedPointerMaxSpeed = dataTable.maxSpeed or 100; 49 if speedPointerIndex ~= "" then 50 self.speedPointerId = getChild(self.id, speedPointerIndex); 51 self.speedPointerMaxAngle = dataTable.speedPointerMaxAngle or 90; 52 self.speedPointerLastAngle = 0; 53 end; 54 55 local rpmPointerIndex = dataTable.rpmPointerIndex or ""; 56 self.rpmPointerMaxRpm = dataTable.maxRpm or 5000; 57 if rpmPointerIndex ~= "" then 58 self.rpmPointerId = getChild(self.id, rpmPointerIndex); 59 self.rpmPointerMaxAngle = dataTable.rpmPointerMaxAngle or 90; 60 self.rpmPointerLastAngle = 0; 61 end; 62 63 self.currentGear = 2; 64 65 self.gears = dataTable.gears or {}; 66 self.gearLabels = dataTable.gearLabels or {}; 67 68 if type(self.gears) ~= "table" then 69 print("Error while loading vehicle " .. tostring(self.templateName) .. "'s gears. Make sure you've set the key 'gears' within you data table to a table value containing each gear's transmission ratio."); 70 return; 71 end; 72 if type(self.gearLabels) ~= "table" then 73 print("Error while loading vehicle " .. tostring(self.templateName) .. "'s gear labels. Make sure you've set the key 'gearLabels' within you data table to a table value containing each gear's label as string value."); 74 end; 75 76 self.gearCount = #self.gears; 77 self.motorFinalDriveRatio = dataTable.motorFinalDriveRatio or 1; 78 for i, v in pairs(self.gears) do 79 if type(self.gearLabels[i]) ~= "string" then 80 self.gearLabels[i] = (i==1 and "R" or tostring(i-1)); 81 end; 82 end; 83 84 self.automaticDrive = getNoNil(dataTable.automaticDrive, true); 85 self.automaticDriveActive = self.automaticDrive; 86 self.upperLimitRpm = dataTable.automaticUpperLimitRpm or 2700; 87 self.lowerLimitRpm = dataTable.automaticLowerLimitRpm or 1600; 88 self.gearSwitchPace = dataTable.automaticGearSwitchPace or 0.3; 89 self.gearSwitchTimer = 100; 90 91 self.torqueCurve = dataTable.motorTorqueCurve; 92 -- !!TODO!! check if torque curve is valid 93 94 self.motorRpm = 0; 95 self.motorSmoothRpm = 0; -- smooth rpm used for digital rpm-meter 96 self.motorLastRpm = 0; 97 self.wheelRpm = 0; 98 self.currentSpeed = 0; 99 self.isMotorOn = false; 100 101 VehicleMotor.loadMotorLoadExhaust(self, dataTable); 102 103 self.customWheelScriptActive = true; 104 self.isReversing = false; 105 106 VehicleMotor.loadMotorSounds(self, dataTable); 107 108 -- finally apply the brake to our axles 109 for n, axle in pairs(self.axles) do 110 Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque); 111 Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque); 112 end; 113 end;
VehicleMotor.loadMotorLoadExhaust(self, dataTable)
115 function VehicleMotor.loadMotorLoadExhaust(self, dataTable) 116 -- exhaust load scale 117 self.exhaustLoadScale = dataTable.exhaustLoadScale or 2; -- three times as much exhaust in maximum load sound weight 118 119 if self.exhaustLoadScale > 0 then 120 for k, v in pairs(self.exhaustParticleSystems) do 121 v.emissionRate = ParticleSystem.getEmissionRateOverTime(v.id); 122 end; 123 end; 124 end;
VehicleMotor.updateMotorLoadExhaust(self, motorLoad)
Updates the exhaust particle spawn rate depending on motor load. Use the factor emissionRate
to have some black smoke particles if you press the accelerator.
128 function VehicleMotor.updateMotorLoadExhaust(self, motorLoad) 129 if self.exhaustLoadScale <= 0 then 130 return; 131 end; 132 133 motorLoad = motorLoad + 1; 134 135 for k, v in pairs(self.exhaustParticleSystems) do 136 ParticleSystem.setEmissionRateOverTime(v.id, motorLoad * v.emissionRate); 137 end; 138 end;
VehicleMotor.loadMotorSounds(self, dataTable)
Loads all motor sounds used for VehicleMotor. This makes use of the Vehicle:loadSingleSound()
function (see Vehicle).
142 function VehicleMotor.loadMotorSounds(self, dataTable) 143 144 if dataTable.sounds ~= nil then 145 self.motorSoundStart = self:loadSingleSound(dataTable.sounds.motorStart); 146 self.motorSoundStop = self:loadSingleSound(dataTable.sounds.motorStop); 147 148 if self.motorSoundStart ~= nil then 149 self.motorSoundStartLength = AudioSource.getLength(self.motorSoundStart); 150 end; 151 152 self.motorSoundIdle = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorIdle); 153 self.motorSoundLoad = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorLoad); 154 self.motorSoundRun = VehicleMotor.loadMotorSound(self, dataTable.sounds.motorRun); 155 156 if dataTable.sounds.motorConfiguration ~= nil then 157 local data = dataTable.sounds.motorConfiguration; 158 self.motorSoundMinRunRpm = data.minRunFadeRpm; 159 self.motorSoundMaxRunRpm = data.maxRunFadeRpm; 160 self.motorSoundMinRunSpeed = data.minRunFadeSpeed; 161 self.motorSoundMaxRunSpeed = data.maxRunFadeSpeed; 162 end; 163 end; 164 end;
VehicleMotor.loadMotorSound(self, data)
Loads a motor sound. Note that one motor sound can consist of different samples.
167 function VehicleMotor.loadMotorSound(self, data) 168 if data == nil then 169 return nil; 170 end; 171 172 local sound = {}; 173 174 if type(data) == "table" then 175 if data[1] ~= nil then 176 for k, v in pairs(data) do 177 table.insert(sound, VehicleMotor.rawLoadMotorSound(self, v)); 178 end; 179 else 180 table.insert(sound, VehicleMotor.rawLoadMotorSound(self, data)); 181 end; 182 end; 183 184 return sound; 185 end;
VehicleMotor.rawLoadMotorSound(self, data)
Loads a single motor sound sample. Each sample can have different volume and pitch settings, as well as many others. If you are in doubt about how the parameters exactly work, please have a look at the code implementation below.
- Use
baseRpm
along withbaseRpmScale
to control the pitch based on rpm OR - Use
pitchOffset
andpitchScale
to control the pitch based on custom pitch/rpm settings. In any case, trial-and-error provide best results. volumeScale
is multiplied with rpm to determine the volume.fadeInSpeed
allows you to avoid awkward jumps in volume.minFadeInRpm
andmaxFadeInRpm
specify whether and how fast the sound shall be faded in depending on rpm. At any rpm belowminFadeInRpm
, the sound will be inactive.minFadeOutRpm
andmaxFadeOutRpm
specify whether and how fast the sound shall be faded in depending on rpm. At any rpm abovemaxFadeOutRpm
, the sound will be inactive.interieurVolumeScale
allows to influence the sound volume in interior perspective.interiorLowpass
andexterieurLowpass
specify low-pass amounts depending on perspective. Cutoff frequency will be 2500 Hz by default.
197 function VehicleMotor.rawLoadMotorSound(self, data) 198 local sound = {}; 199 200 sound.sampleId = self:loadSingleSound(data, true); 201 202 sound.minPitch = data.minPitch or 0.5; 203 sound.maxPitch = data.maxPitch or 2; 204 sound.minVolume = data.minVolume or 0.2; 205 sound.maxVolume = data.maxVolume or 1.5; 206 207 -- load additional data for our motor sound 208 if data.baseRpm ~= nil then 209 sound.baseRpm = data.baseRpm; 210 sound.baseRpmScale = data.baseRpmScale or 1; 211 else 212 sound.pitchOffset = data.pitchOffset or 1; 213 sound.pitchScale = data.pitchScale or (sound.maxPitch - sound.minPitch) / (self.motorMaxRpm - self.motorMinRpm); -- 10% more pitch for 100 more rpm 214 end; 215 216 if data.volumeScale ~= nil then 217 sound.volumeScale = data.volumeScale; 218 end; 219 220 if data.fadeInSpeed ~= nil then 221 sound.fadeInSpeed = data.fadeInSpeed; 222 sound.lastVolume = 0; 223 end; 224 if data.fadeOutSpeed ~= nil then 225 sound.fadeOutSpeed = data.fadeOutSpeed; 226 sound.lastVolume = 0; 227 end; 228 if data.minFadeInRpm ~= nil then 229 sound.minFadeInRpm = data.minFadeInRpm; 230 sound.maxFadeInRpm = data.maxFadeInRpm or (sound.minFadeInRpm + 300); 231 end; 232 if data.minFadeOutRpm ~= nil then 233 sound.minFadeOutRpm = data.minFadeOutRpm; 234 sound.maxFadeOutRpm = data.maxFadeOutRpm or (sound.minFadeOutRpm + 300); 235 end; 236 237 if data.interieurVolumeScale ~= nil then 238 sound.interieurVolumeScale = data.interieurVolumeScale; 239 end; 240 if data.interieurLowpass ~= nil then 241 sound.interieurLowpass = data.interieurLowpass; 242 end; 243 if data.exterieurLowpass ~= nil then 244 sound.exterieurLowpass = data.exterieurLowpass; 245 end; 246 247 return sound; 248 end;
VehicleMotor.foreachMotorSound(self, sound, call, ...)
Calls the function call
(function) on each sound sample in sound
(table) while passing over all arguments in …
.
251 function VehicleMotor.foreachMotorSound(self, sound, call, ...) 252 if sound == nil or call == nil then return end; 253 254 for k, v in pairs(sound) do 255 call(v.sampleId, ...); 256 end; 257 end;
VehicleMotor:customLoadAxle(axle, v)
This hooks into Vehicle:loadAxles()
(see Vehicle for further details) to add further data to the axle configuration, which will be required later on in this script.
261 function VehicleMotor:customLoadAxle(axle, v) 262 -- read additional data from configuration 263 axle.radius = v.radius or 0.5 * (Wheel.getRadius(axle.leftWheel) + Wheel.getRadius(axle.rightWheel)); 264 axle.wheelMass = v.mass or 0.5 * (Wheel.getMass(axle.leftWheel) + Wheel.getMass(axle.rightWheel)); 265 axle.tractionControl = getNoNil(v.tractionControl, true); 266 axle.useABS = getNoNil(v.useABS, true); 267 end;
VehicleMotor:getMotorTorqueAtRpm(rpm)
Returns the motor torque at the rpm level rpm
(float) in Nm.
271 function VehicleMotor:getMotorTorqueAtRpm(rpm) 272 if #self.torqueCurve < 2 then 273 return 0; 274 end; 275 276 if rpm <= self.torqueCurve[1].rpm then 277 return self.torqueCurve[1].torque; 278 279 elseif rpm >= self.torqueCurve[#self.torqueCurve].rpm then 280 return self.torqueCurve[#self.torqueCurve].torque; 281 end; 282 283 for i=1, #self.torqueCurve-1, 1 do 284 285 local rpm1, rpm2 = self.torqueCurve[i].rpm, self.torqueCurve[i+1].rpm; 286 287 if rpm >= rpm1 and rpm <= rpm2 then 288 -- rpm is in this range 289 local torque1, torque2 = self.torqueCurve[i].torque, self.torqueCurve[i+1].torque; 290 291 return torque1 + (torque2 - torque1) * (rpm - rpm1) / (rpm2 - rpm1); 292 end; 293 end; 294 295 -- actually we should never arrive down here 296 return 0; 297 end;
VehicleMotor:update(dt)
Updates the current rpm, the gearbox and some controls. This will also refresh GUI values. Finally, it also updates motor sound.
300 function VehicleMotor:update(dt) 301 if not self.isActive then return end; 302 303 self.currentSpeed = 0.9 * self.currentSpeed + 0.1 * (self.wheelRpm / 30 * self.speedPointerWheelRadius * math.pi * 3.6); 304 self.motorSmoothRpm = 0.6 * self.motorSmoothRpm + 0.4 * self.motorRpm; 305 306 -- add fuel usage 307 if self.isMotorOn then 308 self:callSafe("addFuelUsage", dt, self.currentSpeed * dt); 309 310 if self:getHasRunOutOfFuel() then 311 self:setIsMotorOn(false); 312 end; 313 end; 314 315 316 -- update gear 317 local automaticActive = self.automaticDrive and self.automaticDriveActive; 318 if automaticActive then 319 self.gearSwitchTimer = self.gearSwitchTimer + dt; 320 321 if self.gearSwitchTimer > 0 and self.currentGear ~= 1 then 322 if self.motorSmoothRpm > self.upperLimitRpm then 323 -- switch one gear upwards 324 self:setGear(self.currentGear + 1); 325 326 elseif self.currentGear > 2 and self.motorSmoothRpm < self.lowerLimitRpm then 327 -- switch one gear upwards 328 self:setGear(self.currentGear - 1); 329 end; 330 end; 331 end; 332 333 -- make it possible to override input 334 if self:getIsInputActive() then 335 -- input help 336 337 if not self:getHasRunOutOfFuel() then 338 if not self.isMotorOn then 339 -- start motor again 340 g_GUI:addKeyHint(InputMapper.Vehicle_StartMotor, l10n.get("Input_Vehicle_StartMotor")); 341 end; 342 343 if InputMapper:getKeyDown(InputMapper.Vehicle_StartMotor) then 344 self:setIsMotorOn(not self.isMotorOn); 345 end; 346 end; 347 348 if self.currentGear == 1 then 349 if automaticActive then 350 -- show the message only for automatic gear 351 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_InverseDirection, l10n.get("Input_Vehicle_GearUp_Forward")); 352 end; 353 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_InverseDirection) then 354 self:setGear(self.currentGear + 1); 355 end; 356 357 elseif self.currentGear == 2 then 358 if automaticActive then 359 g_GUI:addKeyHint(InputMapper.VehicleSnowcat_InverseDirection, l10n.get("Input_Vehicle_GearDown_Reverse")); 360 end; 361 if InputMapper:getKeyDown(InputMapper.VehicleSnowcat_InverseDirection) then 362 self:setGear(self.currentGear - 1); 363 end; 364 end; 365 366 if not automaticActive then 367 if self.currentGear > 1 then 368 g_GUI:addKeyHint(InputMapper.Vehicle_GearDown, l10n.get("Input_Vehicle_GearDown")); 369 end; 370 if self.currentGear < self.gearCount then 371 g_GUI:addKeyHint(InputMapper.Vehicle_GearUp, l10n.get("Input_Vehicle_GearUp")); 372 end; 373 end; 374 375 if self.automaticDrive then 376 if automaticActive then 377 g_GUI:addKeyHint(InputMapper.Vehicle_AutomaticDrive, l10n.get("Input_Vehicle_AutomaticDrive1")); 378 379 else 380 g_GUI:addKeyHint(InputMapper.Vehicle_AutomaticDrive, l10n.get("Input_Vehicle_AutomaticDrive0")); 381 end; 382 383 if InputMapper:getKeyDown(InputMapper.Vehicle_AutomaticDrive) then 384 self:setAutomaticDriveActive(not self.automaticDriveActive) 385 end; 386 end; 387 388 if InputMapper:getKeyDown(InputMapper.Vehicle_GearDown) then 389 self:setGear(self.currentGear - 1); 390 391 elseif InputMapper:getKeyDown(InputMapper.Vehicle_GearUp) then 392 self:setGear(self.currentGear + 1); 393 end; 394 end; 395 396 if self:getIsActive() then 397 -- rpm pointer 398 if self.rpmPointerId ~= nil then 399 local newAngle = self.rpmPointerMaxAngle * clamp01(self.motorRpm / self.rpmPointerMaxRpm); 400 self.rpmPointerLastAngle = 0.9 * self.rpmPointerLastAngle + 0.1 * newAngle; 401 setRotationZ(self.rpmPointerId, self.rpmPointerLastAngle); 402 end; 403 if self.speedPointerId ~= nil then 404 local newAngle = self.speedPointerMaxAngle * clamp01(math.abs(self.currentSpeed / self.speedPointerMaxSpeed)); 405 self.speedPointerLastAngle = 0.95 * self.speedPointerLastAngle + 0.05 * newAngle; 406 setRotationZ(self.speedPointerId, self.speedPointerLastAngle); 407 end; 408 409 -- update reverse beep 410 self.isReversing = self:getGearTransmissionRatio() < 0; 411 end; 412 413 if self:getIsGUIActive() then 414 local absSpeed = math.abs(self.currentSpeed); 415 UI.setImageFillAmount(getChild(GUI.vehicleHUD, "SpeedBar/Filled"), 0.4 * clamp01(absSpeed / self.speedPointerMaxSpeed)); 416 UI.setImageFillAmount(getChild(GUI.vehicleHUD, "RpmBar/Filled"), 0.4 * clamp01(math.max(self.motorSmoothRpm, 900) / self.rpmPointerMaxRpm)); 417 418 419 if math.abs(self.currentSpeed) < 10 then 420 UI.setLabelText(getChild(GUI.vehicleHUD, "Speed"), string.format("%.1f", math.abs(self.currentSpeed))); 421 else 422 UI.setLabelText(getChild(GUI.vehicleHUD, "Speed"), string.format("%.0f", math.abs(self.currentSpeed))); 423 end; 424 425 UI.setLabelText(getChild(GUI.vehicleHUD, "Gear"), tostring(self.gearLabels[self.currentGear])); 426 setActive(getChild(GUI.vehicleHUD, "Icons2/Battery/dark"), self.isMotorOn); 427 setActive(getChild(GUI.vehicleHUD, "Icons2/Battery/bright"), not self.isMotorOn); 428 end; 429 430 if self.isMotorOn then 431 -- update sounds 432 local motorLerpValue = (self.motorSmoothRpm - self.motorMinRpm) / (self.motorMaxRpm - self.motorMinRpm); 433 local motorLoad = self.throttleValue; -- [0;1] 434 local runFade = 1; 435 436 if self.motorSoundMinRunRpm ~= nil and self.motorSoundMaxRunRpm ~= nil then 437 runFade = math.min(runFade, clamp01(inverseLerp(self.motorSoundMinRunRpm, self.motorSoundMaxRunRpm, self.motorSmoothRpm))); 438 end; 439 if self.motorSoundMinRunSpeed ~= nil and self.motorSoundMaxRunSpeed ~= nil then 440 runFade = math.min(runFade, clamp01(inverseLerp(self.motorSoundMinRunSpeed, self.motorSoundMaxRunSpeed, math.abs(self.currentSpeed)))); 441 end; 442 443 VehicleMotor.updateMotorLoadExhaust(self, motorLoad); 444 445 VehicleMotor.updateMotorSound(self, self.motorSoundIdle, dt, self.isInterieurCamera, self.motorSmoothRpm, motorLerpValue); 446 VehicleMotor.updateMotorSound(self, self.motorSoundLoad, dt, self.isInterieurCamera, self.motorSmoothRpm, motorLerpValue, motorLoad, runFade); 447 VehicleMotor.updateMotorSound(self, self.motorSoundRun, dt, self.isInterieurCamera, self.motorSmoothRpm, motorLerpValue, (1-motorLoad), runFade); 448 end; 449 end;
VehicleMotor.updateMotorSound(self, sound, ...)
Calls VehicleMotor.rawUpdateMotorSound
on each sample in this group of motor sounds.
453 function VehicleMotor.updateMotorSound(self, sound, ...) 454 if sound == nil then return end; 455 456 for k, v in pairs(sound) do 457 VehicleMotor.rawUpdateMotorSound(self, v, ...); 458 end; 459 end;
VehicleMotor.rawUpdateMotorSound(self, sound, dt, isInterieur, motorRpm, motorLerpValue, blendValue, blendScale)
This actually updates a motor sound, while making use of the parameters explained in VehicleMotor.rawLoadMotorSound
(above on this page).
462 function VehicleMotor.rawUpdateMotorSound(self, sound, dt, isInterieur, motorRpm, motorLerpValue, blendValue, blendScale) 463 464 local pitch, maxVolume = 1, 1; 465 466 if sound.baseRpm ~= nil then 467 pitch = self.motorSmoothRpm / sound.baseRpm; 468 469 if sound.baseRpmScale ~= nil then 470 pitch = 1 + (pitch - 1) * sound.baseRpmScale; 471 end; 472 else 473 pitch = sound.pitchOffset + sound.pitchScale * clamp(self.motorSmoothRpm - self.motorMinRpm, 0, self.motorMaxRpm); 474 end; 475 476 if sound.volumeScale ~= nil then 477 volume = sound.minVolume + sound.volumeScale * (self.motorSmoothRpm - self.motorMinRpm); 478 else 479 volume = lerp(sound.minVolume, sound.maxVolume, motorLerpValue); 480 end; 481 482 if blendValue ~= nil then 483 -- blend volume down to this value 484 local targetVolume = volume * blendValue; 485 486 if sound.lastVolume ~= nil then 487 if sound.lastVolume < targetVolume then 488 if sound.fadeInSpeed ~= nil then 489 volume = math.min(sound.lastVolume + sound.fadeInSpeed * dt, targetVolume); 490 else 491 volume = targetVolume; 492 end; 493 494 elseif sound.lastVolume > targetVolume then 495 if sound.fadeOutSpeed ~= nil then 496 volume = math.max(sound.lastVolume - sound.fadeOutSpeed * dt, targetVolume); 497 else 498 volume = targetVolume; 499 end; 500 else 501 volume = targetVolume; 502 end; 503 504 -- store current sound volume for next frame 505 sound.lastVolume = volume; 506 else 507 volume = targetVolume; 508 end; 509 510 else 511 -- don't clamp if blend value is applied 512 volume = clamp(volume * (blendScale or 1), sound.minVolume, sound.maxVolume); 513 end; 514 515 -- ramp volume up/down (depending on rpm) 516 if sound.minFadeInRpm ~= nil and sound.maxFadeInRpm then 517 if motorRpm < sound.minFadeInRpm then 518 volume = 0; 519 520 elseif motorRpm < sound.maxFadeInRpm then 521 volume = volume * clamp01(inverseLerp(sound.minFadeInRpm, sound.maxFadeInRpm, motorRpm)); 522 end; 523 end; 524 525 if sound.minFadeOutRpm ~= nil and sound.maxFadeOutRpm then 526 if motorRpm > sound.maxFadeOutRpm then 527 volume = 0; 528 529 elseif motorRpm > sound.minFadeOutRpm then 530 volume = volume * clamp01(inverseLerp(sound.maxFadeOutRpm, sound.minFadeOutRpm, motorRpm)); 531 end; 532 end; 533 534 if isInterieur and sound.interieurVolumeScale ~= nil then 535 volume = volume * sound.interieurVolumeScale; 536 end; 537 if sound.interieurLowpass ~= nil or sound.exterieurLowpass ~= nil then 538 if isInterieur then 539 AudioSource.setLowPass(sound.sampleId, sound.interieurLowpass or 0); 540 else 541 AudioSource.setLowPass(sound.sampleId, sound.exterieurLowpass or 0); 542 end; 543 end; 544 545 -- apply them both (but only clamped) 546 AudioSource.setPitch(sound.sampleId, clamp(pitch, sound.minPitch, sound.maxPitch)); 547 AudioSource.setVolume(sound.sampleId, volume); 548 end;
VehicleMotor:fixedUpdate(dt)
This is called once per physics update, i.e. possibly multiple times per frame. We therefore only perform necessary calculations.
552 function VehicleMotor:fixedUpdate(dt) 553 self.motorLastRpm = self.motorRpm; 554 self.wheelRpm = self:getDrivingWheelsRpm(); 555 self.motorRpm = 0.9 * self.motorRpm + 0.1 * self.wheelRpm * self:getGearTransmissionRatio(); 556 557 self:updateWheels(); 558 end;
VehicleMotor:updateWheels()
Updates the wheel torques, as well as steering values. The actual updating is done by VehicleMotor.singleWheel
.
561 function VehicleMotor:updateWheels() 562 local drivingWheelCount = self:getDrivingWheelsCount(); 563 local steerAngle = self:getCurrentSteerAngle(); 564 local brakeTorque = self:getCurrentBrakeTorque(); 565 566 -- get motor torque and inertia 567 local motorTorque, motorInertia = 0, 0; 568 if drivingWheelCount > 0 then 569 local a, b = self:getCurrentMotorTorque(); 570 motorTorque = a / drivingWheelCount; -- motor torque in [Nm], relative to wheel 571 motorInertia = b / drivingWheelCount; -- motor inertia in [kgm^2], relative to the wheel 572 end; 573 574 for n, axle in pairs(self.axles) do 575 576 -- apply wheel torques only when on ground 577 VehicleMotor.singleWheel(self, axle.leftWheel, axle, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0); 578 VehicleMotor.singleWheel(self, axle.rightWheel, axle, motorTorque * axle.motorGear, axle.brake and brakeTorque or 0); 579 580 -- and now steering 581 if axle.steeringScale ~= 0 then 582 Wheel.setSteerAngle(axle.leftWheel, steerAngle * axle.steeringScale); 583 Wheel.setSteerAngle(axle.rightWheel, steerAngle * axle.steeringScale); 584 end; 585 586 -- add powertrain inertia 587 if axle.motorGear ~= 0 then 588 local extraMass = motorInertia / axle.radius^2; 589 Wheel.setMass(axle.leftWheel, axle.wheelMass + extraMass); 590 Wheel.setMass(axle.rightWheel, axle.wheelMass + extraMass); 591 end; 592 end; 593 end;
VehicleMotor:singleWheel(wheelId, axle, motorTorque, brakeTorque)
Updates a single wheel using ABS (anti-lock braking system) to avoid wheel lock. This also includes a traction control to avoid spinning wheels. Both ABS and traction control operate depending on forward slip (which is physically not correct, but the best option for this game for performance reasons).
597 function VehicleMotor:singleWheel(wheelId, axle, motorTorque, brakeTorque) 598 local hit, ff, sf, force = Wheel.getGroundHit(wheelId); 599 600 if hit then 601 if axle.useABS and ff < -0.6 then 602 -- brake traction control (ABS) 603 local brakeEnabled = (getTime()%1.2) < 0.6; 604 Wheel.setTorques(wheelId, motorTorque, brakeEnabled and brakeTorque or 0); 605 606 elseif axle.tractionControl and ff > 0.5 then 607 -- traction control 608 Wheel.setTorques(wheelId, motorTorque, self.maxBrakeTorque); 609 610 else 611 Wheel.setTorques(wheelId, motorTorque, brakeTorque); 612 end; 613 614 else 615 Wheel.setTorques(wheelId, 0, brakeTorque); 616 end; 617 end;
VehicleMotor:setIsMotorOn(isOn)
Sets the motor to on or off, depending on isOn
(bool).
621 function VehicleMotor:setIsMotorOn(isOn) 622 if isOn == self.isMotorOn then return end; 623 624 self.isMotorOn = isOn; 625 626 if self.isMotorOn then 627 if self.motorSoundStop ~= nil then AudioSource.stop(self.motorSoundStop); end; 628 629 if self.motorSoundStart ~= nil then 630 AudioSource.play(self.motorSoundStart); 631 632 if self.motorSoundIdle ~= nil then 633 VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.playDelayed, self.motorSoundStartLength); 634 end; 635 if self.motorSoundLoad ~= nil then 636 VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.playDelayed, self.motorSoundStartLength); 637 end; 638 if self.motorSoundRun ~= nil then 639 VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.playDelayed, self.motorSoundStartLength); 640 end; 641 642 else 643 if self.motorSoundIdle ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.play); end; 644 if self.motorSoundLoad ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.play); end; 645 if self.motorSoundRun ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.play); end; 646 end; 647 648 else 649 if self.motorSoundIdle ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundIdle, AudioSource.stop); end; 650 if self.motorSoundLoad ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundLoad, AudioSource.stop); end; 651 if self.motorSoundRun ~= nil then VehicleMotor.foreachMotorSound(self, self.motorSoundRun, AudioSource.stop); end; 652 653 if self.motorSoundStart ~= nil then AudioSource.stop(self.motorSoundStart); end; 654 if self.motorSoundStop ~= nil then AudioSource.play(self.motorSoundStop); end; 655 end; 656 657 -- update exhaust particles 658 for k, v in pairs(self.exhaustParticleSystems) do 659 setActive(v.id, self.isMotorOn or false); 660 end; 661 end;
VehicleMotor:onEnter()
Start the motor unless disabled in GameplaySettings.
665 function VehicleMotor:onEnter() 666 if GameplaySettings.autoStartMotor and not self:getHasRunOutOfFuel() then 667 self:setIsMotorOn(true); 668 end; 669 end;
VehicleMotor:onLeave()
Stop the motor upon leaving. Also apply maximum braking torque to save performance on wheel physics.
673 function VehicleMotor:onLeave() 674 if GameplaySettings.autoStopMotor then 675 self:setIsMotorOn(false); 676 end; 677 678 -- automatically apply the brake upon leaving (because wheels will no longer be updated) 679 for n, axle in pairs(self.axles) do 680 Wheel.setTorques(axle.leftWheel, 0, self.maxBrakeTorque); 681 Wheel.setTorques(axle.rightWheel, 0, self.maxBrakeTorque); 682 end; 683 end;
VehicleMotor:getDrivingWheelsRpm()
Returns the current wheel rpm (average of all driving wheels).
687 function VehicleMotor:getDrivingWheelsRpm() 688 local numerator = 0; 689 local denominator = 0; 690 691 for n, axle in pairs(self.axles) do 692 if axle.motorGear > 0 then 693 numerator = numerator + 0.5 * (Wheel.getRpm(axle.leftWheel) + Wheel.getRpm(axle.rightWheel)); 694 denominator = denominator + axle.motorGear; 695 end; 696 end; 697 698 return (denominator > 0 and numerator / denominator or 0); 699 end;
VehicleMotor:getDrivingWheelsCount()
Returns the number of driving wheels.
703 function VehicleMotor:getDrivingWheelsCount() 704 local sum = 0; 705 for n, axle in pairs(self.axles) do 706 sum = sum + axle.motorGear; 707 end; 708 709 return 2*sum; 710 end;
VehicleMotor:getGearTransmissionRatio()
Returns the currently activated gear transmission ratio. This will be negative for reverse gear.
714 function VehicleMotor:getGearTransmissionRatio() 715 return self.gears[self.currentGear] * self.motorFinalDriveRatio; 716 end;
VehicleMotor:getCurrentMotorTorque()
Returns the current motor torque in Nm, depending on current transmission ratio and current motor torque (which itself depends on motorRpm and throttle value).
719 function VehicleMotor:getCurrentMotorTorque() 720 local transmissionRatio = self:getGearTransmissionRatio(); 721 local motorOutputTorque = not self.isMotorOn and 0 or self:getMotorTorqueAtRpm(self.motorRpm) * self.throttleValue; 722 723 -- motor inertia is directly applied to the wheel 724 inertia = 2 * self.motorInertiaMoment * transmissionRatio * transmissionRatio; 725 726 return motorOutputTorque * transmissionRatio, math.abs(inertia); 727 end;
VehicleMotor:setAutomaticDriveActive(isActive)
Enables or disables automatic drive, depending on isActive
(bool).
730 function VehicleMotor:setAutomaticDriveActive(isActive) 731 self.automaticDriveActive = isActive; 732 end;
Copyright
All contents of this page may be used for modding use for Winter Resort Simulator only. Any use exceeding this regulation is not permitted.
Copyright (C) HR Innoways, 2020. All Rights Reserved.