diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3072e6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.pyo \ No newline at end of file diff --git a/__pycache__/engine.cpython-311.pyc b/__pycache__/engine.cpython-311.pyc deleted file mode 100644 index c22f879..0000000 Binary files a/__pycache__/engine.cpython-311.pyc and /dev/null differ diff --git a/__pycache__/engine.cpython-313.pyc b/__pycache__/engine.cpython-313.pyc deleted file mode 100644 index 9b4582f..0000000 Binary files a/__pycache__/engine.cpython-313.pyc and /dev/null differ diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..0aa6ea1 --- /dev/null +++ b/changelog.md @@ -0,0 +1,10 @@ +6/23/25 +* Added visual accelerator pedal +* Scale accelerator pedal to new screen +* Parabolic curve for better feeling accelerator pedal +* Added numbers to RPM gauge +* Added Fuel gauge +* Added gauge cluster background +* Visual tweaks +* Added simple clutch simulation +* Added simple transmission simulation \ No newline at end of file diff --git a/changelog/changelog.md b/changelog/changelog.md new file mode 100644 index 0000000..1d79453 --- /dev/null +++ b/changelog/changelog.md @@ -0,0 +1,12 @@ +6/23/25 +* Added visual accelerator pedal +* Scale accelerator pedal to new screen +* Parabolic curve for better feeling accelerator pedal +* Added numbers to RPM gauge +* Added Fuel gauge +* Added gauge cluster background +* Visual tweaks +* Added simple clutch simulation +* Added simple transmission simulation + +![alt text](image.png) \ No newline at end of file diff --git a/changelog/image.png b/changelog/image.png new file mode 100644 index 0000000..8845291 Binary files /dev/null and b/changelog/image.png differ diff --git a/engine.py b/engine.py index 93b5607..7eb8fa7 100644 --- a/engine.py +++ b/engine.py @@ -24,7 +24,6 @@ class Engine: self.fuel_efficiency = fuel_efficiency self.torque_curve = torque_curve or self.default_torque_curve self.instant_torque = 0 - self.wheel_speed = 0 self.engineFriction = 5 self.peak_torque = 163 # Nm -> 120 ft/lb self.engine_brake_strength = 40 # Nm @@ -40,7 +39,8 @@ class Engine: self.coolant_pressure = 0 self.coolant_capacity = 0 - self.fuel_capacity = 0 + self.max_fuel_capacity = 68 # Liter + self.fuel_capacity = self.max_fuel_capacity # Fuel @@ -63,6 +63,7 @@ class Engine: self.vehicle_mass = 0 self.drivetrain_loss = 0 + # Just a parabolic curve for now def default_torque_curve(self, rpm): rpm_ratio = rpm / self.max_rpm @@ -70,8 +71,7 @@ class Engine: def start(self, dt): self.ignition = True - self.starting = True - + self.starting = True def update(self, throttle, load, dt): self.throttle = throttle @@ -107,11 +107,15 @@ class Engine: friction_force = 0 # Engine braking torque (If throttle closed) - if self.throttle < 0.1 and self.rpm > self.idle_rpm + 200 or not self.ignition: + if self.throttle == 0 and self.rpm > self.idle_rpm + 200 or not self.ignition: engine_brake_torque = self.engine_brake_strength * (self.rpm / self.max_rpm) else: engine_brake_torque = 0 + # Transmission Load + + + # Calculate torque if self.ignition: available_torque = self.torque_curve(self.rpm) @@ -123,5 +127,6 @@ class Engine: self.rpm += rpm_change * dt self.fuel_rate = self.rpm * throttle * self.fuel_efficiency + # self.fuel_capacity -= self.fuel_rate #self._update_temps(dt) diff --git a/main.py b/main.py index c4c906f..911642e 100644 --- a/main.py +++ b/main.py @@ -2,24 +2,25 @@ import pygame import math from engine import Engine +from transmission import Transmission # pygame setup pygame.init() +pygame.display.set_caption('Engine Sim') screen = pygame.display.set_mode((1280, 720)) clock = pygame.time.Clock() running = True dt = 0 -player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2) - # Text Setup pygame.font.init() font = pygame.font.SysFont(None, 64) +gauge_font = pygame.font.SysFont(None, 24) # Gauge Setup -rpm_pos = pygame.Vector2(screen.get_width() * 0.25, screen.get_height() / 2) -spd_pos = pygame.Vector2(screen.get_width() * 0.75, screen.get_height() / 2) +rpm_pos = pygame.Vector2(screen.get_width() * 0.30, screen.get_height() / 2) +spd_pos = pygame.Vector2(screen.get_width() * 0.70, screen.get_height() / 2) gauge_radius = screen.get_height() / 5 @@ -36,14 +37,33 @@ offset_vector = pygame.Vector2( ) e = Engine() +t = Transmission(e) throttle = 0 def map_value_to_angle(value, min_val, max_val): - clamped = max(min_val, min(value, max_val)) # Can change this later + clamped = max(min_val, value) return math.radians(150 + 270 * (clamped - min_val) / (max_val - min_val)) while running: + screen.fill("white") + sX, sY = screen.get_width(), screen.get_height() mX, mY = pygame.mouse.get_pos() + + # Gauge Cluster + cluster_padding = (sX / sY) * (sX / 64) + cluster_padding_x = (sX / sY) * (sX / 64) + cluster_y = min(rpm_pos.y, spd_pos.y) - gauge_radius - cluster_padding + cluster_bottom = max(rpm_pos.y, spd_pos.y) + gauge_radius + cluster_padding + cluster_x = rpm_pos.x - gauge_radius - cluster_padding - cluster_padding_x + cluster_right = spd_pos.x + gauge_radius + cluster_padding + cluster_padding_x + + cluster_width = cluster_right - cluster_x + cluster_height = cluster_bottom - cluster_y + + cluster_rect = pygame.Rect(cluster_x, cluster_y, cluster_width, cluster_height) + pygame.draw.rect(screen, (50, 50, 50), cluster_rect, border_radius=30) # dark gray with rounded corners + + # poll for events # pygame.QUIT event means the user clicked X to close your window for event in pygame.event.get(): @@ -53,57 +73,101 @@ while running: e.ignition = False else: e.start(dt) + if (pygame.key.name(event.key) == 'x'): + t.upshift() + if (pygame.key.name(event.key) == 'z'): + t.downshift() + if (pygame.key.name(event.key) == 'left shift'): + t.clutch_pressure = 1 + if event.type == pygame.KEYUP: + if (pygame.key.name(event.key) == 'left shift'): + t.clutch_pressure = 0 + if event.type == pygame.QUIT: running = False - # fill the screen with a color to wipe away anything from last frame - screen.fill("white") - rpm_text = font.render(f"RPM: {int(e.rpm)}", True, (0, 0, 0)) rpm_rect = rpm_text.get_rect(center=(screen.get_width() // 2, screen.get_height() // 4)) screen.blit(rpm_text, rpm_rect) - rpm_text = font.render(f"Torque: {int(e.instant_torque)}", True, (0, 0, 0)) + rpm_text = font.render(f"Torque: {int(e.instant_torque)}", True, (255, 0, 0)) rpm_rect = rpm_text.get_rect(center=(screen.get_width() // 2, screen.get_height() // 4 +100)) screen.blit(rpm_text, rpm_rect) rpm_angle = map_value_to_angle(e.rpm, 0, 8000) # RPM Gauge - speed_angle = map_value_to_angle(e.wheel_speed, 0, 160) rpm_vector = pygame.Vector2(math.cos(rpm_angle), math.sin(rpm_angle)) * gauge_radius pygame.draw.circle(screen, "black", rpm_pos, gauge_radius) pygame.draw.line(screen, needle_color, rpm_pos, rpm_pos + rpm_vector, needle_width) + for rpm_tick in range(0, 9000, 1000): # 1000 to 8000 + angle = map_value_to_angle(rpm_tick, 0, 8000) + direction = pygame.Vector2(math.cos(angle), math.sin(angle)) + label_pos = rpm_pos + direction * (gauge_radius - 20) # slight inward offset - # Trans Gauge + label_text = gauge_font.render(str(rpm_tick // 1000), True, (255, 255, 255)) + text_rect = label_text.get_rect(center=(label_pos.x, label_pos.y)) + screen.blit(label_text, text_rect) + + # Speed Gauge + + speed_angle = map_value_to_angle(t.get_velocity(), 0, 160) speed_vector = pygame.Vector2(math.cos(speed_angle), math.sin(speed_angle)) * gauge_radius - pygame.draw.circle(screen, "red", spd_pos, gauge_radius) - spd_tip = spd_pos + offset_vector - pygame.draw.line(screen, needle_color, spd_pos, spd_tip, needle_width) - pygame.draw.circle(screen, needle_color, spd_tip, tip_radius) + pygame.draw.circle(screen, "black", spd_pos, gauge_radius) + pygame.draw.line(screen, needle_color, spd_pos, spd_pos + speed_vector, needle_width) - pygame.draw.circle(screen, "red", player_pos, 40) + for speed_tick in range(0, 180, 20): # 1000 to 8000 + angle = map_value_to_angle(speed_tick, 0, 160) + direction = pygame.Vector2(math.cos(angle), math.sin(angle)) + label_pos = spd_pos + direction * (gauge_radius - 20) # slight inward offset + + label_text = gauge_font.render(str(speed_tick), True, (255, 255, 255)) + text_rect = label_text.get_rect(center=(label_pos.x, label_pos.y)) + screen.blit(label_text, text_rect) + + # Fuel Gauge - 10 Steps + fuel_steps = math.ceil((e.fuel_capacity / e.max_fuel_capacity) * 10) + + fuel_x = cluster_x + (cluster_padding / 1.5) + fuel_y = cluster_bottom - cluster_padding * 2 + fuel_width = cluster_padding / 1.5 + fuel_height = cluster_height / 20 + + fuel_step = cluster_height / 16 + + #fuel_rect = pygame.rect() Make this a rect as a backdrop for fuel + + for i in range(fuel_steps): + fuel_rect = pygame.Rect(fuel_x, fuel_y, fuel_width, fuel_height) + pygame.draw.rect(screen, "white", fuel_rect, border_radius=2) + fuel_y -= fuel_step + + + + # Temp Gauge - 10 Steps + + # Throttle + throttle_text = font.render(f"Throttle", True, (0, 0, 0)) + throttle_text_rect = throttle_text.get_rect(center=((sX - sX // 16), screen.get_height() // 4.5)) + screen.blit(throttle_text, throttle_text_rect) + throttle_rect = pygame.Rect((sX - sX / 16), sY / 4, sX / 16, sY / 2) + pygame.draw.rect(screen, "red", throttle_rect) keys = pygame.key.get_pressed() if keys[pygame.K_w]: e.rpm += 600 * dt if keys[pygame.K_s]: e.rpm -= 600 * dt - if keys[pygame.K_a]: - player_pos.x -= 300 * dt - if keys[pygame.K_d]: - player_pos.x += 300 * dt if keys[pygame.K_r]: e.rpm = e.idle_rpm + # Throttle (Using mouse pos) - throttle = 1 - (mY / screen.get_height()) - #print(throttle) + throttle = pow(max(0, min(1 - (((mY - (sY / 4)) / 2) / (sY / 4)), 1)),2) # Clamp value between 0 and 1 + # print(mX, mY) - - # flip() the display to put your work on screen pygame.display.flip() # limits FPS to 60 diff --git a/transmission.py b/transmission.py new file mode 100644 index 0000000..35c030a --- /dev/null +++ b/transmission.py @@ -0,0 +1,28 @@ +from engine import Engine + +class Transmission: + def __init__(self, engine): + self.clutch_pressure = 0 + self.gear_ratios = [0,216,108,72,54,43,36] + self.selected_gear = 0 + self.max_gear = 6 + self.min_gear = -1 + self.inertia = 0 + + self.engine = engine + + def upshift(self): + self.selected_gear = min(self.selected_gear + 1, self.max_gear) + print(self.selected_gear) + + def downshift(self): + self.selected_gear = max(self.selected_gear - 1, self.min_gear) + print(self.selected_gear) + + def get_velocity(self): + if (self.selected_gear == 0): + return 0 + return self.engine.rpm / self.gear_ratios[self.selected_gear] + + def calculate_load(self): + return \ No newline at end of file