fix: clean gitignore for Godot C#
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
||||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Godot 4+ imported files
|
||||||
|
.import/
|
||||||
|
.godot/
|
||||||
|
|
||||||
|
# Mono / C# build folders
|
||||||
|
.mono/
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Visual Studio / Rider / VS Code
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userprefs
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# OS junk
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Build/export folders (optional depending on project)
|
||||||
|
export/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
7
Physics.csproj
Normal file
7
Physics.csproj
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Godot.NET.Sdk/4.6.1">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net9.0</TargetFramework>
|
||||||
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
19
Physics.sln
Normal file
19
Physics.sln
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 2012
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Physics", "Physics.csproj", "{6DC8B879-E534-4466-8C36-228A171D7521}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||||
|
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||||
|
{6DC8B879-E534-4466-8C36-228A171D7521}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
131
Scripts/Components/Engine.cs
Normal file
131
Scripts/Components/Engine.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public class Engine : RotatingComponent
|
||||||
|
{
|
||||||
|
public double ThrottlePosition { get; set; }
|
||||||
|
public double CurrentPower { get; private set; }
|
||||||
|
public double CombustionPower { get; private set; }
|
||||||
|
public int CylinderCount { get; private set; }
|
||||||
|
public EngineControlUnit Ecu = new EngineControlUnit();
|
||||||
|
|
||||||
|
// Friction
|
||||||
|
private const double _baseFriction = 12.0; // Seals, oil pump, valvetrain (Nm)
|
||||||
|
private const double _linearFriction = 0.025; // Hydrodynamic bearing drag (Nm/(rad/s))
|
||||||
|
private const double _quadraticFriction = 0.00002; // Windage & churning (Nm/(rad/s)²)
|
||||||
|
|
||||||
|
// Engine geometry
|
||||||
|
private double _displacementCC;
|
||||||
|
private double _compressionRatio;
|
||||||
|
|
||||||
|
// Volumetric efficiency tuning – now in rad/s
|
||||||
|
public double VolumetricEfficiencyPeak { get; set; } = 1.15; // was 1.10
|
||||||
|
public double AngularVelocityForVEPeak { get; set; } = 550.0; // 5250 RPM (rad/s = 550)
|
||||||
|
public double AngularVelocityForVEMin { get; set; } = 800.0; // 7639 RPM
|
||||||
|
public double VEminAtRedline { get; set; } = 0.85; // much higher at redline
|
||||||
|
|
||||||
|
// Physics constants
|
||||||
|
private const double ATM_PRESSURE_KPA = 101.3;
|
||||||
|
private const double AIR_DENSITY_KG_PER_M3 = 1.225;
|
||||||
|
private const double FUEL_HEAT_VALUE_J_PER_KG = 43e6;
|
||||||
|
private const double STOICHIOMETRIC_AIR_FUEL_RATIO = 14.7;
|
||||||
|
|
||||||
|
public Engine(double inertia, int cylinders, double displacementCC, double compressionRatio)
|
||||||
|
{
|
||||||
|
CylinderCount = cylinders;
|
||||||
|
_displacementCC = displacementCC;
|
||||||
|
_compressionRatio = compressionRatio;
|
||||||
|
|
||||||
|
MomentOfInertia = inertia;
|
||||||
|
AngularVelocity = 100; // rad/s (~955 RPM)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(double dt)
|
||||||
|
{
|
||||||
|
Ecu.Update(dt, this);
|
||||||
|
|
||||||
|
ApplyThrottleTorque();
|
||||||
|
ApplyFrictionTorque();
|
||||||
|
|
||||||
|
CurrentPower = AccumulatedTorque * AngularVelocity;
|
||||||
|
|
||||||
|
base.Update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double ComputeIndicatedTorque()
|
||||||
|
{
|
||||||
|
double w = AngularVelocity; // rad/s
|
||||||
|
double throttle = Math.Clamp(ThrottlePosition, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Volumetric Efficiency (WOT) as function of w (rad/s)
|
||||||
|
double veWOT;
|
||||||
|
if (w <= AngularVelocityForVEPeak)
|
||||||
|
{
|
||||||
|
// Start at a realistic 0.75 at zero RPM, peaking at your target
|
||||||
|
double t = w / AngularVelocityForVEPeak;
|
||||||
|
veWOT = 0.75 + (VolumetricEfficiencyPeak - 0.75) * t * (2 - t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Linear drop from VE_peak to VEminAtRedline
|
||||||
|
double t = (w - AngularVelocityForVEPeak) / (AngularVelocityForVEMin - AngularVelocityForVEPeak);
|
||||||
|
t = Math.Clamp(t, 0.0, 1.0);
|
||||||
|
veWOT = VolumetricEfficiencyPeak - t * (VolumetricEfficiencyPeak - VEminAtRedline);
|
||||||
|
}
|
||||||
|
veWOT = Math.Clamp(veWOT, 0.25, VolumetricEfficiencyPeak);
|
||||||
|
|
||||||
|
// Intake loss
|
||||||
|
double maxEngineDemandSpeed = 700.0; // rad/s, roughly 6700 RPM
|
||||||
|
double throttleArea = Math.Pow(throttle, 1.5);
|
||||||
|
double engineDemand = (w / maxEngineDemandSpeed) * veWOT;
|
||||||
|
|
||||||
|
const double intakeResistance = 0.03; // was 0.5 – now physically realistic
|
||||||
|
double mapFraction = throttleArea / (throttleArea + intakeResistance * engineDemand);
|
||||||
|
|
||||||
|
if (throttle == 0) mapFraction = 0;
|
||||||
|
|
||||||
|
double manifoldPressureKpa = ATM_PRESSURE_KPA * mapFraction;
|
||||||
|
manifoldPressureKpa = Math.Clamp(manifoldPressureKpa, 0, ATM_PRESSURE_KPA);
|
||||||
|
|
||||||
|
double veActual = veWOT * (manifoldPressureKpa / ATM_PRESSURE_KPA);
|
||||||
|
|
||||||
|
// Exhaust loss (backpressure)
|
||||||
|
double exhaustBackpressureKpa = 2.0e-5 * w * w;
|
||||||
|
double exhaustLossFactor = 1.0 - Math.Min(0.25, exhaustBackpressureKpa / ATM_PRESSURE_KPA);
|
||||||
|
|
||||||
|
// Air & fuel mass per cycle
|
||||||
|
double displacementM3 = _displacementCC * 1e-6;
|
||||||
|
double airMassPerCycleKg = veActual * AIR_DENSITY_KG_PER_M3 * displacementM3;
|
||||||
|
double fuelMassPerCycleKg = airMassPerCycleKg / STOICHIOMETRIC_AIR_FUEL_RATIO;
|
||||||
|
double energyPerCycleJ = fuelMassPerCycleKg * FUEL_HEAT_VALUE_J_PER_KG;
|
||||||
|
|
||||||
|
// Thermal efficiency
|
||||||
|
double gamma = 1.4;
|
||||||
|
double ottoEfficiency = 1.0 - 1.0 / Math.Pow(_compressionRatio, gamma - 1.0);
|
||||||
|
double thermalEfficiency = 0.65 * ottoEfficiency;
|
||||||
|
|
||||||
|
double workPerCycleJ = energyPerCycleJ * thermalEfficiency * exhaustLossFactor;
|
||||||
|
|
||||||
|
double indicatedTorque = workPerCycleJ / (4.0 * Math.PI);
|
||||||
|
indicatedTorque = Math.Min(600.0, Math.Max(0.0, indicatedTorque));
|
||||||
|
|
||||||
|
return indicatedTorque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyThrottleTorque()
|
||||||
|
{
|
||||||
|
double torque = ComputeIndicatedTorque();
|
||||||
|
CombustionPower = torque * AngularVelocity;
|
||||||
|
ApplyTorque(torque);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyFrictionTorque()
|
||||||
|
{
|
||||||
|
// Friction uses angular velocity (rad/s) directly
|
||||||
|
double w = AngularVelocity;
|
||||||
|
double frictionMag = _baseFriction
|
||||||
|
+ _linearFriction * Math.Abs(w)
|
||||||
|
+ _quadraticFriction * w * w;
|
||||||
|
double frictionTorque = -Math.Sign(w) * frictionMag;
|
||||||
|
ApplyTorque(frictionTorque);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/Components/Engine.cs.uid
Normal file
1
Scripts/Components/Engine.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c3gg5mg8rjw50
|
||||||
48
Scripts/Components/EngineControlUnit.cs
Normal file
48
Scripts/Components/EngineControlUnit.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public class EngineControlUnit
|
||||||
|
{
|
||||||
|
public double Throttle;
|
||||||
|
public bool IsInNeutral = true;
|
||||||
|
|
||||||
|
private const double _revLimit = 7000;
|
||||||
|
private const double _idleRpm = 800;
|
||||||
|
private const double _idleSensitivity = 1e-2;
|
||||||
|
private const double _throttleSensitivity = 20;
|
||||||
|
private const double _neutralThrottleLimit = 0.2;
|
||||||
|
|
||||||
|
private double _idlePosition = 0;
|
||||||
|
private double _targetThrottle = 0;
|
||||||
|
private double _currentThrottle = 0;
|
||||||
|
|
||||||
|
public EngineControlUnit()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(double dt, Engine engine)
|
||||||
|
{
|
||||||
|
if (engine.RPM > _revLimit)
|
||||||
|
{
|
||||||
|
engine.ThrottlePosition = 0; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_targetThrottle = Math.Clamp(GetIdleThrottle(dt, engine.RPM) + Throttle, 0, 1);
|
||||||
|
_currentThrottle = Util.Lerp(_currentThrottle, _targetThrottle, dt * _throttleSensitivity);
|
||||||
|
|
||||||
|
if (IsInNeutral) _currentThrottle = Math.Min(_currentThrottle, _neutralThrottleLimit);
|
||||||
|
|
||||||
|
engine.ThrottlePosition = _currentThrottle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetIdleThrottle(double dt, double rpm)
|
||||||
|
{
|
||||||
|
if (rpm > _idleRpm) return 0;
|
||||||
|
|
||||||
|
double diff = rpm - _idleRpm;
|
||||||
|
|
||||||
|
_idlePosition = Math.Clamp(_idlePosition - (diff * _idleSensitivity * dt), 0, 1.0);
|
||||||
|
|
||||||
|
return _idlePosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/Components/EngineControlUnit.cs.uid
Normal file
1
Scripts/Components/EngineControlUnit.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://0k3uqu06s0im
|
||||||
58
Scripts/Core/RotatingComponent.cs
Normal file
58
Scripts/Core/RotatingComponent.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public abstract class RotatingComponent
|
||||||
|
{
|
||||||
|
public double MomentOfInertia { get; set; } // kg·m²
|
||||||
|
public double AngularVelocity { get; set; } // rad/s
|
||||||
|
public double AngularPosition { get; set; } // rad (0..2π, wrapped)
|
||||||
|
public double TotalAngularPosition { get; set; } // rad (never wraps)
|
||||||
|
public double AccumulatedTorque {get; private set; }
|
||||||
|
|
||||||
|
public RotatingComponent(double momentOfInertia = 0.1, double angularVelocity = 0, double angularPosition = 0)
|
||||||
|
{
|
||||||
|
MomentOfInertia = momentOfInertia;
|
||||||
|
AngularVelocity = angularVelocity;
|
||||||
|
AngularPosition = angularPosition;
|
||||||
|
TotalAngularPosition = angularPosition;
|
||||||
|
AccumulatedTorque = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ApplyTorque(double torqueNm)
|
||||||
|
{
|
||||||
|
AccumulatedTorque += torqueNm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(double dt)
|
||||||
|
{
|
||||||
|
if (dt <= 0) return;
|
||||||
|
|
||||||
|
if (MomentOfInertia > 0)
|
||||||
|
{
|
||||||
|
double angularAcceleration = AccumulatedTorque / MomentOfInertia;
|
||||||
|
AngularVelocity += angularAcceleration * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
double deltaAngle = AngularVelocity * dt;
|
||||||
|
TotalAngularPosition += deltaAngle;
|
||||||
|
AngularPosition += deltaAngle;
|
||||||
|
|
||||||
|
// Wrap AngularPosition to 0..2π for sin/cos
|
||||||
|
AngularPosition = AngularPosition % (2 * Math.PI);
|
||||||
|
if (AngularPosition < 0) AngularPosition += 2 * Math.PI;
|
||||||
|
|
||||||
|
AccumulatedTorque = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double RPM => AngularVelocity * 60 / (2 * Math.PI);
|
||||||
|
public double TotalAngleDeg => TotalAngularPosition * 180 / Math.PI;
|
||||||
|
|
||||||
|
public void SetRPM(double rpm)
|
||||||
|
{
|
||||||
|
AngularVelocity = rpm * 2 * Math.PI / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ResetTorque()
|
||||||
|
{
|
||||||
|
AccumulatedTorque = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/Core/RotatingComponent.cs.uid
Normal file
1
Scripts/Core/RotatingComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cym3n25hyryxy
|
||||||
55
Scripts/Core/TorqueMap.cs
Normal file
55
Scripts/Core/TorqueMap.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
public class TorqueMap
|
||||||
|
{
|
||||||
|
public Dictionary<int, double> points;
|
||||||
|
|
||||||
|
public TorqueMap()
|
||||||
|
{
|
||||||
|
points = new Dictionary<int, double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPoint(int rpm, double torque)
|
||||||
|
{
|
||||||
|
points.Add(rpm, torque);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetTorque(int rpm)
|
||||||
|
{
|
||||||
|
if (points == null || points.Count == 0)
|
||||||
|
throw new System.InvalidOperationException("Torque map has no points.");
|
||||||
|
|
||||||
|
var sortedRpms = points.Keys.OrderBy(r => r).ToList();
|
||||||
|
int firstRpm = sortedRpms[0];
|
||||||
|
int lastRpm = sortedRpms[sortedRpms.Count - 1];
|
||||||
|
|
||||||
|
if (rpm <= firstRpm)
|
||||||
|
return points[firstRpm];
|
||||||
|
if (rpm >= lastRpm)
|
||||||
|
return points[lastRpm];
|
||||||
|
|
||||||
|
for (int i = 0; i < sortedRpms.Count - 1; i++)
|
||||||
|
{
|
||||||
|
int r1 = sortedRpms[i];
|
||||||
|
int r2 = sortedRpms[i + 1];
|
||||||
|
if (rpm >= r1 && rpm <= r2)
|
||||||
|
{
|
||||||
|
double t1 = points[r1];
|
||||||
|
double t2 = points[r2];
|
||||||
|
return t1 + (rpm - r1) * (t2 - t1) / (r2 - r1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new System.InvalidOperationException("Interpolation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetTorque(double angularVelocityRadPerSec)
|
||||||
|
{
|
||||||
|
// Convert rad/s to RPM: 1 rad/s = 60/(2π) RPM
|
||||||
|
double rpmDouble = angularVelocityRadPerSec * 60.0 / (2.0 * Math.PI);
|
||||||
|
int rpm = (int)rpmDouble; // or use rounding if preferred: (int)Math.Round(rpmDouble)
|
||||||
|
return GetTorque(rpm);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/Core/TorqueMap.cs.uid
Normal file
1
Scripts/Core/TorqueMap.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bn0khacav57pi
|
||||||
95
Scripts/PhysicsManager.cs
Normal file
95
Scripts/PhysicsManager.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public partial class PhysicsManager : Node2D
|
||||||
|
{
|
||||||
|
private const double TARGET_DT = 1.0 / 2000.0;
|
||||||
|
private const double VALUE_SENSITIVITY = 5;
|
||||||
|
|
||||||
|
private double _accumulator = 0.0;
|
||||||
|
private double _simulationTime = 0.0;
|
||||||
|
|
||||||
|
private double _rpmSmooth = 0, _powerSmooth = 0, _throttleSmooth = 0;
|
||||||
|
|
||||||
|
private Label _rpmLabel, _powerLabel, _throttleLabel;
|
||||||
|
|
||||||
|
private Engine _engine;
|
||||||
|
|
||||||
|
[Export] public ProceduralEngine EngineAudio;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_rpmLabel = GetNode<Label>("UI/EngineRPM");
|
||||||
|
_powerLabel = GetNode<Label>("UI/EnginePower");
|
||||||
|
_throttleLabel = GetNode<Label>("UI/EngineThrottle");
|
||||||
|
|
||||||
|
_engine = new Engine(inertia: 0.2, cylinders: 1, displacementCC: 1998, compressionRatio: 10.5);
|
||||||
|
_engine.MomentOfInertia = 0.4;
|
||||||
|
|
||||||
|
// --- Audio initialization ---
|
||||||
|
if (EngineAudio != null)
|
||||||
|
{
|
||||||
|
EngineAudio.CylinderCount = _engine.CylinderCount;
|
||||||
|
EngineAudio.EngineLayout = "v";
|
||||||
|
EngineAudio.IsCrossplane = true;
|
||||||
|
EngineAudio.BaseEngineVolume = 0.35f;
|
||||||
|
EngineAudio.IntakeRoarAmount = 0.28f;
|
||||||
|
EngineAudio.IntakeHissAmount = 0.12f;
|
||||||
|
EngineAudio.ExhaustRumbleAmount = 0.32f;
|
||||||
|
EngineAudio.ExhaustCrackleAmount = 0.08f;
|
||||||
|
|
||||||
|
EngineAudio.ResetAudioState();
|
||||||
|
|
||||||
|
EngineAudio.CurrentRPM = 750f;
|
||||||
|
EngineAudio.CurrentThrottle = 0f;
|
||||||
|
EngineAudio.CurrentCombustionPower = 0f;
|
||||||
|
|
||||||
|
if (!EngineAudio.Playing)
|
||||||
|
EngineAudio.Play();
|
||||||
|
|
||||||
|
EngineAudio.PrewarmBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double dt)
|
||||||
|
{
|
||||||
|
bool wPressed = Input.IsKeyPressed(Key.W);
|
||||||
|
|
||||||
|
double throttle = wPressed ? 1.0f : 0.0f;
|
||||||
|
_engine.Ecu.Throttle = throttle;
|
||||||
|
|
||||||
|
double safeDelta = Math.Min(dt, 0.025);
|
||||||
|
_accumulator += safeDelta;
|
||||||
|
while (_accumulator >= TARGET_DT)
|
||||||
|
{
|
||||||
|
SimulateStep(TARGET_DT);
|
||||||
|
_simulationTime += TARGET_DT;
|
||||||
|
_accumulator -= TARGET_DT;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rpmSmooth = Util.Lerp(_rpmSmooth, _engine.RPM, VALUE_SENSITIVITY * dt);
|
||||||
|
_powerSmooth = Util.Lerp(_powerSmooth, _engine.CurrentPower/1000, VALUE_SENSITIVITY * dt);
|
||||||
|
_throttleSmooth = Util.Lerp(_throttleSmooth, _engine.ThrottlePosition * 100, VALUE_SENSITIVITY * dt);
|
||||||
|
|
||||||
|
_rpmLabel.Text = $"RPM: {_rpmSmooth:F0}";
|
||||||
|
_powerLabel.Text = $"Power: {_powerSmooth:F2} Kw";
|
||||||
|
_throttleLabel.Text = $"Throttle: {_throttleSmooth:F1} %";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Draw()
|
||||||
|
{
|
||||||
|
base._Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SimulateStep(double dt)
|
||||||
|
{
|
||||||
|
_engine.Update(dt); // Engine uses _engine.Throttle inside its Update
|
||||||
|
|
||||||
|
if (EngineAudio != null)
|
||||||
|
{
|
||||||
|
EngineAudio.CurrentRPM = (float)_engine.RPM;
|
||||||
|
EngineAudio.CurrentThrottle = (float)_engine.ThrottlePosition;
|
||||||
|
EngineAudio.CurrentCombustionPower = (float)_engine.CombustionPower;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/PhysicsManager.cs.uid
Normal file
1
Scripts/PhysicsManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b7yhefukbbd1h
|
||||||
269
Scripts/ProceduralEngine.cs
Normal file
269
Scripts/ProceduralEngine.cs
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public partial class ProceduralEngine : AudioStreamPlayer
|
||||||
|
{
|
||||||
|
private AudioStreamGeneratorPlayback _playback;
|
||||||
|
private float _crankAngle = 0f;
|
||||||
|
|
||||||
|
private int[] _firingOrder = Array.Empty<int>();
|
||||||
|
private float[] _combustionEnvelope;
|
||||||
|
|
||||||
|
// Persistent phases for engine order harmonics
|
||||||
|
private float _phase05 = 0f, _phase1 = 0f, _phase2 = 0f, _phase4 = 0f;
|
||||||
|
|
||||||
|
// Intake resonance filter state
|
||||||
|
private float _intakeFilterState = 0f, _intakeFilterPrev = 0f;
|
||||||
|
private float _currentIntakeResFreq = 100f; // smoothed resonance frequency
|
||||||
|
private float _targetIntakeResFreq = 100f;
|
||||||
|
|
||||||
|
// Noise generator state (simple LCG)
|
||||||
|
private uint _noiseState = 123456789;
|
||||||
|
|
||||||
|
public float CurrentRPM { get; set; }
|
||||||
|
public float CurrentThrottle { get; set; }
|
||||||
|
public float CurrentCombustionPower { get; set; }
|
||||||
|
public float RumbleIntensity { get; set; } = 1.0f; // 0=off, 1=normal, >1=extra rumble
|
||||||
|
|
||||||
|
private int _cylinderCount = 1;
|
||||||
|
public int CylinderCount
|
||||||
|
{
|
||||||
|
get => _cylinderCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != _cylinderCount && value >= 1 && value <= 12)
|
||||||
|
{
|
||||||
|
_cylinderCount = value;
|
||||||
|
UpdateFiringOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EngineLayout { get; set; } = "inline";
|
||||||
|
public bool IsCrossplane { get; set; } = true;
|
||||||
|
|
||||||
|
// Tunable parameters
|
||||||
|
public float BaseEngineVolume { get; set; } = 0.4f;
|
||||||
|
public float IntakeRoarAmount { get; set; } = 0.25f;
|
||||||
|
public float IntakeHissAmount { get; set; } = 0.08f; // reduced, now noise
|
||||||
|
public float ExhaustRumbleAmount { get; set; } = 0.3f;
|
||||||
|
public float ExhaustCrackleAmount { get; set; } = 0.1f;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
Play();
|
||||||
|
_playback = (AudioStreamGeneratorPlayback)GetStreamPlayback();
|
||||||
|
PrecomputeCombustionEnvelope();
|
||||||
|
UpdateFiringOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFiringOrder()
|
||||||
|
{
|
||||||
|
// (unchanged – same as your code)
|
||||||
|
if (_cylinderCount == 1) _firingOrder = new int[] { 0 };
|
||||||
|
else if (_cylinderCount == 2) _firingOrder = new int[] { 0, 1 };
|
||||||
|
else if (_cylinderCount == 3) _firingOrder = new int[] { 0, 2, 1 };
|
||||||
|
else if (_cylinderCount == 4) _firingOrder = new int[] { 0, 2, 3, 1 };
|
||||||
|
else if (_cylinderCount == 5) _firingOrder = new int[] { 0, 1, 3, 4, 2 };
|
||||||
|
else if (_cylinderCount == 6) _firingOrder = new int[] { 0, 4, 2, 5, 1, 3 };
|
||||||
|
else if (_cylinderCount == 8)
|
||||||
|
{
|
||||||
|
if (IsCrossplane && EngineLayout == "v")
|
||||||
|
_firingOrder = new int[] { 0, 7, 3, 2, 5, 4, 6, 1 };
|
||||||
|
else if (!IsCrossplane && EngineLayout == "v")
|
||||||
|
_firingOrder = new int[] { 0, 7, 1, 6, 3, 4, 2, 5 };
|
||||||
|
else if (EngineLayout == "flat")
|
||||||
|
_firingOrder = new int[] { 0, 7, 1, 6, 3, 4, 2, 5 };
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_firingOrder = new int[8];
|
||||||
|
for (int i = 0; i < 8; i++) _firingOrder[i] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_cylinderCount == 10) _firingOrder = new int[] { 0, 9, 4, 6, 1, 7, 2, 8, 3, 5 };
|
||||||
|
else if (_cylinderCount == 12) _firingOrder = new int[] { 0, 11, 3, 8, 1, 10, 5, 6, 2, 9, 4, 7 };
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_firingOrder = new int[_cylinderCount];
|
||||||
|
for (int i = 0; i < _cylinderCount; i++) _firingOrder[i] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrecomputeCombustionEnvelope()
|
||||||
|
{
|
||||||
|
const int steps = 128;
|
||||||
|
_combustionEnvelope = new float[steps];
|
||||||
|
float peakPhase = 0.26f;
|
||||||
|
float decay = 12f;
|
||||||
|
for (int i = 0; i < steps; i++)
|
||||||
|
{
|
||||||
|
float phase = (float)i / (steps - 1) * Mathf.Pi;
|
||||||
|
if (phase < peakPhase)
|
||||||
|
_combustionEnvelope[i] = phase / peakPhase;
|
||||||
|
else
|
||||||
|
_combustionEnvelope[i] = Mathf.Exp(-decay * (phase - peakPhase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta) => FillBuffer();
|
||||||
|
|
||||||
|
public void ResetAudioState()
|
||||||
|
{
|
||||||
|
_crankAngle = 0f;
|
||||||
|
_phase05 = _phase1 = _phase2 = _phase4 = 0f;
|
||||||
|
_intakeFilterState = _intakeFilterPrev = 0f;
|
||||||
|
_currentIntakeResFreq = _targetIntakeResFreq = 100f;
|
||||||
|
_noiseState = 123456789;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrewarmBuffer() => FillBuffer();
|
||||||
|
|
||||||
|
// Simple white noise generator (xorshift style)
|
||||||
|
private float GetNoise()
|
||||||
|
{
|
||||||
|
_noiseState ^= _noiseState << 13;
|
||||||
|
_noiseState ^= _noiseState >> 17;
|
||||||
|
_noiseState ^= _noiseState << 5;
|
||||||
|
return (_noiseState % 65536) / 32768.0f - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillBuffer()
|
||||||
|
{
|
||||||
|
if (_playback == null || _firingOrder.Length == 0) return;
|
||||||
|
|
||||||
|
float sampleRate = ((AudioStreamGenerator)Stream).MixRate;
|
||||||
|
float dt = (float)GetProcessDeltaTime();
|
||||||
|
int framesToFill = (int)(sampleRate * dt);
|
||||||
|
int framesAvailable = _playback.GetFramesAvailable();
|
||||||
|
int pushCount = Mathf.Min(framesToFill, framesAvailable);
|
||||||
|
|
||||||
|
float cycleRad = Mathf.Tau * 2f;
|
||||||
|
float radPerSec = (CurrentRPM / 60f) * Mathf.Tau;
|
||||||
|
float rpmNorm = Mathf.Clamp(CurrentRPM / 7000f, 0f, 1f);
|
||||||
|
float loadNorm = Mathf.Clamp(CurrentCombustionPower / 150000f, 0f, 1f);
|
||||||
|
float crankFreq = CurrentRPM / 60f;
|
||||||
|
|
||||||
|
// Engine order frequencies and delta phases
|
||||||
|
float deltaPhase05 = (crankFreq * 0.5f) / sampleRate * Mathf.Tau;
|
||||||
|
float deltaPhase1 = crankFreq / sampleRate * Mathf.Tau;
|
||||||
|
float deltaPhase2 = (crankFreq * 2f) / sampleRate * Mathf.Tau;
|
||||||
|
float deltaPhase4 = (crankFreq * 4f) / sampleRate * Mathf.Tau;
|
||||||
|
|
||||||
|
// Smooth intake resonance frequency to avoid filter pops
|
||||||
|
_targetIntakeResFreq = Mathf.Clamp(crankFreq * (_cylinderCount / 2f) * 0.8f, 60f, 400f);
|
||||||
|
_currentIntakeResFreq = Mathf.Lerp(_currentIntakeResFreq, _targetIntakeResFreq, 0.01f);
|
||||||
|
float intakeOmega = 2f * Mathf.Pi * _currentIntakeResFreq / sampleRate;
|
||||||
|
float intakeQ = 2.5f;
|
||||||
|
float intakeAlpha = Mathf.Sin(intakeOmega) / (2f * intakeQ);
|
||||||
|
float intakeA0 = 1f + intakeAlpha;
|
||||||
|
float intakeA1 = -2f * Mathf.Cos(intakeOmega);
|
||||||
|
float intakeA2 = 1f - intakeAlpha;
|
||||||
|
float intakeB0 = intakeAlpha;
|
||||||
|
float intakeB1 = 0;
|
||||||
|
float intakeB2 = -intakeAlpha;
|
||||||
|
|
||||||
|
// Low‑pass filter for noise (one‑pole, cutoff 2000 Hz)
|
||||||
|
float noiseCutoff = 2000f / sampleRate;
|
||||||
|
float noiseA = Mathf.Exp(-Mathf.Tau * noiseCutoff);
|
||||||
|
float noiseB = 1f - noiseA;
|
||||||
|
float filteredNoise = 0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < pushCount; i++)
|
||||||
|
{
|
||||||
|
float sample = 0f;
|
||||||
|
|
||||||
|
// ---- 1. Combustion pulses (unchanged) ----
|
||||||
|
float clatterPulse = Mathf.Max(0, Mathf.Sin(_phase05));
|
||||||
|
float clatter = GetNoise() * Mathf.Pow(clatterPulse, 10.0f) * 0.02f;
|
||||||
|
clatter *= (1.0f - rpmNorm * 0.5f); // Quieter as oil pressure/RPM rises
|
||||||
|
sample += clatter;
|
||||||
|
|
||||||
|
for (int idx = 0; idx < CylinderCount; idx++)
|
||||||
|
{
|
||||||
|
int cyl = _firingOrder[idx];
|
||||||
|
float cylinderOffset = (cycleRad / CylinderCount) * cyl;
|
||||||
|
float jitter = GetNoise() * 0.02f;
|
||||||
|
float cylPhase = (_crankAngle + cylinderOffset + jitter) % cycleRad;
|
||||||
|
|
||||||
|
if (cylPhase < Mathf.Pi)
|
||||||
|
{
|
||||||
|
float t = cylPhase / Mathf.Pi;
|
||||||
|
int envIdx = (int)(t * (_combustionEnvelope.Length - 1));
|
||||||
|
float pulse = _combustionEnvelope[envIdx];
|
||||||
|
float powerStroke = pulse * loadNorm * (0.8f + 0.2f * rpmNorm);
|
||||||
|
|
||||||
|
sample += powerStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 2. Intake pulses (per cylinder) ----
|
||||||
|
if (cylPhase > Mathf.Pi * 3f)
|
||||||
|
{
|
||||||
|
float t = (cylPhase - Mathf.Pi * 3f) / Mathf.Pi;
|
||||||
|
float pulse = Mathf.Sin(t * Mathf.Pi);
|
||||||
|
float throttleFactor = Mathf.Pow(CurrentThrottle, 0.5f);
|
||||||
|
float airRush = pulse * throttleFactor * IntakeRoarAmount;
|
||||||
|
|
||||||
|
// === FIXED HISS: filtered noise instead of pure sine ===
|
||||||
|
// Generate white noise, low‑pass filter it
|
||||||
|
float rawNoise = GetNoise();
|
||||||
|
filteredNoise = noiseA * filteredNoise + noiseB * rawNoise;
|
||||||
|
float hiss = filteredNoise * IntakeHissAmount * throttleFactor * (0.3f + 0.7f * rpmNorm);
|
||||||
|
airRush += hiss;
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
sample += airRush;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 3. Exhaust pulses (unchanged) ----
|
||||||
|
if (cylPhase >= Mathf.Pi && cylPhase < Mathf.Tau)
|
||||||
|
{
|
||||||
|
float t = (cylPhase - Mathf.Pi) / Mathf.Pi;
|
||||||
|
float rumble = Mathf.Sin(t * Mathf.Pi * 4f) * (1f - t);
|
||||||
|
float exhaustAmp = ExhaustRumbleAmount * (0.3f + 0.7f * rpmNorm);
|
||||||
|
sample += rumble * exhaustAmp;
|
||||||
|
|
||||||
|
bool overrun = CurrentThrottle < 0.1f && CurrentRPM > 2000f;
|
||||||
|
if (overrun && t > 0.7f)
|
||||||
|
{
|
||||||
|
float crackle = Mathf.Sin(t * Mathf.Pi * 20f) * (1f - t) * 0.5f;
|
||||||
|
sample += crackle * ExhaustCrackleAmount * rpmNorm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cylPhase < 0.1f) {
|
||||||
|
sample += GetNoise() * 0.03f * loadNorm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sample = Mathf.Tanh(sample * (1.0f + loadNorm * 0.5f));
|
||||||
|
|
||||||
|
// ---- 4. Global intake resonance (unchanged, but with smoothed frequency) ----
|
||||||
|
float intakeResonance = Mathf.Sin(_crankAngle * (_currentIntakeResFreq / crankFreq) * 2f);
|
||||||
|
float filteredRes = intakeB0 * intakeResonance + intakeB1 * _intakeFilterPrev + intakeB2 * _intakeFilterState
|
||||||
|
- intakeA1 * _intakeFilterPrev - intakeA2 * _intakeFilterState;
|
||||||
|
filteredRes /= intakeA0;
|
||||||
|
_intakeFilterState = _intakeFilterPrev;
|
||||||
|
_intakeFilterPrev = filteredRes;
|
||||||
|
sample += filteredRes * IntakeRoarAmount * 0.5f * CurrentThrottle * rpmNorm;
|
||||||
|
|
||||||
|
// ---- 5. Engine order harmonics (unchanged) ----
|
||||||
|
float roughnessFactor = (EngineLayout == "v" && IsCrossplane) ? 1.2f : 1.0f;
|
||||||
|
float harmonic = 0f;
|
||||||
|
harmonic += Mathf.Sin(_phase05) * 0.08f * rpmNorm * (1f - loadNorm) * roughnessFactor;
|
||||||
|
harmonic += Mathf.Sin(_phase1) * 0.25f * rpmNorm;
|
||||||
|
harmonic += Mathf.Sin(_phase2) * 0.12f * rpmNorm;
|
||||||
|
harmonic += Mathf.Sin(_phase4) * 0.06f * rpmNorm;
|
||||||
|
sample += harmonic;
|
||||||
|
|
||||||
|
sample = Mathf.Clamp(sample * BaseEngineVolume, -0.95f, 0.95f);
|
||||||
|
_playback.PushFrame(Vector2.One * sample);
|
||||||
|
|
||||||
|
// Update phases
|
||||||
|
_phase05 = (_phase05 + deltaPhase05) % Mathf.Tau;
|
||||||
|
_phase1 = (_phase1 + deltaPhase1) % Mathf.Tau;
|
||||||
|
_phase2 = (_phase2 + deltaPhase2) % Mathf.Tau;
|
||||||
|
_phase4 = (_phase4 + deltaPhase4) % Mathf.Tau;
|
||||||
|
_crankAngle = (_crankAngle + radPerSec / sampleRate) % cycleRad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/ProceduralEngine.cs.uid
Normal file
1
Scripts/ProceduralEngine.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://q8j0rw61k0sw
|
||||||
7
Scripts/Util.cs
Normal file
7
Scripts/Util.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
public static class Util
|
||||||
|
{
|
||||||
|
public static double Lerp(double start, double end, double t)
|
||||||
|
{
|
||||||
|
return start * (1 - t) + end * t;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Scripts/Util.cs.uid
Normal file
1
Scripts/Util.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dagmu6lght0f7
|
||||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 995 B |
43
icon.svg.import
Normal file
43
icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://xxpkhh3iv1r5"
|
||||||
|
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
28
project.godot
Normal file
28
project.godot
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="Physics"
|
||||||
|
run/main_scene="uid://b7aah147mnd1r"
|
||||||
|
config/features=PackedStringArray("4.6", "C#", "Forward Plus")
|
||||||
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="Physics"
|
||||||
|
|
||||||
|
[physics]
|
||||||
|
|
||||||
|
3d/physics_engine="Jolt Physics"
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
rendering_device/driver.windows="d3d12"
|
||||||
37
scene.tscn
Normal file
37
scene.tscn
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[gd_scene format=3 uid="uid://b7aah147mnd1r"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://b7yhefukbbd1h" path="res://Scripts/PhysicsManager.cs" id="1_ulcgi"]
|
||||||
|
[ext_resource type="Script" uid="uid://q8j0rw61k0sw" path="res://Scripts/ProceduralEngine.cs" id="2_nxogm"]
|
||||||
|
|
||||||
|
[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_nxogm"]
|
||||||
|
|
||||||
|
[node name="Scene" type="Node2D" unique_id=1646116984 node_paths=PackedStringArray("EngineAudio")]
|
||||||
|
script = ExtResource("1_ulcgi")
|
||||||
|
EngineAudio = NodePath("AudioStreamPlayer")
|
||||||
|
|
||||||
|
[node name="UI" type="Node" parent="." unique_id=1659532716]
|
||||||
|
|
||||||
|
[node name="EngineRPM" type="Label" parent="UI" unique_id=792596633]
|
||||||
|
offset_right = 152.0
|
||||||
|
offset_bottom = 26.0
|
||||||
|
text = "RPM:"
|
||||||
|
|
||||||
|
[node name="EnginePower" type="Label" parent="UI" unique_id=283884278]
|
||||||
|
offset_left = 1.0
|
||||||
|
offset_top = 28.0
|
||||||
|
offset_right = 153.0
|
||||||
|
offset_bottom = 77.0
|
||||||
|
text = "Power:
|
||||||
|
"
|
||||||
|
|
||||||
|
[node name="EngineThrottle" type="Label" parent="UI" unique_id=298487516]
|
||||||
|
offset_left = 2.0
|
||||||
|
offset_top = 58.0
|
||||||
|
offset_right = 154.0
|
||||||
|
offset_bottom = 107.0
|
||||||
|
text = "Throttle:
|
||||||
|
"
|
||||||
|
|
||||||
|
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1105764304]
|
||||||
|
stream = SubResource("AudioStreamGenerator_nxogm")
|
||||||
|
script = ExtResource("2_nxogm")
|
||||||
Reference in New Issue
Block a user