add cluster graphics and transmission simulation
This commit is contained in:
parent
2ac3bf166b
commit
37c7c89d1e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.pyc
|
||||
*.pyo
|
Binary file not shown.
Binary file not shown.
10
changelog.md
Normal file
10
changelog.md
Normal file
@ -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
|
12
changelog/changelog.md
Normal file
12
changelog/changelog.md
Normal file
@ -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
|
||||
|
||||

|
BIN
changelog/image.png
Normal file
BIN
changelog/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
15
engine.py
15
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)
|
||||
|
112
main.py
112
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
|
||||
|
28
transmission.py
Normal file
28
transmission.py
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user