imgui stuffz
This commit is contained in:
parent
9bab6a0265
commit
d54ecc33da
@ -0,0 +1,364 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
A single-player pygame racing simulation where engine RPM and speed (in mph)
|
||||||
|
are modeled as independent—but coupled—systems. The engine’s RPM (tachometer)
|
||||||
|
is updated by throttle/brake input plus a damping effect that tends to “lock”
|
||||||
|
the engine to the value expected from the current vehicle speed and gear.
|
||||||
|
Vehicle acceleration is computed from the RPM difference (engine load)
|
||||||
|
minus drag, yielding a more realistic feel.
|
||||||
|
|
||||||
|
Controls:
|
||||||
|
- LEFT/RIGHT arrows: steer
|
||||||
|
- UP arrow: throttle (increases engine RPM and, via the dynamics, accelerates the car)
|
||||||
|
- DOWN arrow: brake
|
||||||
|
- E: upshift (gear +1 with rev-match drop)
|
||||||
|
- Q: downshift (gear -1 with a slight rev bump)
|
||||||
|
- R: toggle reverse (gear 0) versus forward (gear 1)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
# ----- Screen & Road Configuration -----
|
||||||
|
SCREEN_WIDTH = 800
|
||||||
|
SCREEN_HEIGHT = 600
|
||||||
|
ROAD_WIDTH = 150 # Road drawn as a wide filled polygon
|
||||||
|
FPS = 60
|
||||||
|
|
||||||
|
# ----- Engine & Vehicle Dynamics -----
|
||||||
|
IDLE_RPM = 800
|
||||||
|
REDLINE_RPM = 6000
|
||||||
|
|
||||||
|
# Engine dynamics (in rpm/sec)
|
||||||
|
THROTTLE_RPM_RATE = 800 # When throttle is pressed, engine spins up
|
||||||
|
BRAKE_RPM_RATE = 2500 # When braking, engine RPM drops rapidly
|
||||||
|
DAMPING_FACTOR = 5 # How strongly engine RPM is forced toward the "ideal" RPM
|
||||||
|
|
||||||
|
# These dynamics model the connection between engine torque and vehicle acceleration:
|
||||||
|
# The vehicle's acceleration (in mph/s) is proportional to the difference between the
|
||||||
|
# current engine RPM and the ideal RPM that would be produced by the current speed in the given gear.
|
||||||
|
ACCELERATION_CONVERSION = 150 # (rpm difference) to mph/s conversion factor
|
||||||
|
DRAG_COEFF = 0.02 # Drag (per second) that always slows the vehicle
|
||||||
|
|
||||||
|
# Conversion constant (empirical) for what engine RPM should be for a given vehicle speed
|
||||||
|
# in a particular gear. In this model, ideal_rpm = speed × gear_ratio × K.
|
||||||
|
K = 21
|
||||||
|
|
||||||
|
# Maximum vehicle speed (in mph)
|
||||||
|
MAX_SPEED = 90
|
||||||
|
|
||||||
|
# ----- Transmission & Gear Ratios -----
|
||||||
|
# These gear ratios (including reverse = gear 0) are assumed to incorporate the effect
|
||||||
|
# of the transmission (and final drive) so that:
|
||||||
|
# ideal_rpm = speed (mph) × gear_ratio × K.
|
||||||
|
GEAR_RATIOS = {
|
||||||
|
0: 4.866, # Reverse
|
||||||
|
1: 4.696,
|
||||||
|
2: 2.985,
|
||||||
|
3: 2.146,
|
||||||
|
4: 1.769,
|
||||||
|
5: 1.520,
|
||||||
|
6: 1.275,
|
||||||
|
7: 1.000,
|
||||||
|
8: 0.854,
|
||||||
|
9: 0.689,
|
||||||
|
10: 0.636
|
||||||
|
}
|
||||||
|
|
||||||
|
# Turning rate (radians per second)
|
||||||
|
TURN_RATE = 2.0
|
||||||
|
|
||||||
|
# ----- Gauge Configuration -----
|
||||||
|
GAUGE_RADIUS = 50
|
||||||
|
SPEED_GAUGE_CENTER = (150, 100)
|
||||||
|
TACH_GAUGE_CENTER = (650, 100)
|
||||||
|
|
||||||
|
# ----- Helper Functions -----
|
||||||
|
def rotate_point(point, angle):
|
||||||
|
"""Rotate a 2D point by angle in radians."""
|
||||||
|
x, y = point
|
||||||
|
cos_a = math.cos(angle)
|
||||||
|
sin_a = math.sin(angle)
|
||||||
|
return (x * cos_a - y * sin_a, x * sin_a + y * cos_a)
|
||||||
|
|
||||||
|
def draw_gauge(surface, center, radius, min_val, max_val, current_val, label):
|
||||||
|
"""
|
||||||
|
Draw an analog gauge (circle with a needle) mapping a value from min_val to max_val
|
||||||
|
to an angle between -135° and +135°.
|
||||||
|
"""
|
||||||
|
pygame.draw.circle(surface, (255, 255, 255), center, radius, 2)
|
||||||
|
min_angle = -135
|
||||||
|
max_angle = 135
|
||||||
|
ratio = (current_val - min_val) / (max_val - min_val) if max_val != min_val else 0
|
||||||
|
angle_deg = min_angle + ratio * (max_angle - min_angle)
|
||||||
|
angle_rad = math.radians(angle_deg)
|
||||||
|
needle_length = radius - 10
|
||||||
|
needle_end = (center[0] + needle_length * math.cos(angle_rad),
|
||||||
|
center[1] + needle_length * math.sin(angle_rad))
|
||||||
|
pygame.draw.line(surface, (255, 0, 0), center, needle_end, 3)
|
||||||
|
font = pygame.font.SysFont(None, 24)
|
||||||
|
text_surf = font.render(f"{label}: {int(current_val)}", True, (255, 255, 255))
|
||||||
|
text_rect = text_surf.get_rect(center=(center[0], center[1] + radius + 20))
|
||||||
|
surface.blit(text_surf, text_rect)
|
||||||
|
|
||||||
|
def point_line_distance(P, A, B):
|
||||||
|
"""Return the shortest distance from point P to line segment AB."""
|
||||||
|
AB = (B[0] - A[0], B[1] - A[1])
|
||||||
|
AP = (P[0] - A[0], P[1] - A[1])
|
||||||
|
ab2 = AB[0]**2 + AB[1]**2
|
||||||
|
if ab2 == 0:
|
||||||
|
return math.hypot(P[0] - A[0], P[1] - A[1])
|
||||||
|
t = max(0, min(1, (AP[0]*AB[0] + AP[1]*AB[1]) / ab2))
|
||||||
|
proj = (A[0] + t*AB[0], A[1] + t*AB[1])
|
||||||
|
return math.hypot(P[0] - proj[0], P[1] - proj[1])
|
||||||
|
|
||||||
|
# ----- Game Objects -----
|
||||||
|
class Car:
|
||||||
|
def __init__(self):
|
||||||
|
self.x = 0.0 # Position (in feet or arbitrary units that scale to mph)
|
||||||
|
self.y = 0.0
|
||||||
|
self.angle = 0.0 # Orientation (radians; 0 means facing right)
|
||||||
|
self.engine_rpm = IDLE_RPM
|
||||||
|
self.gear = 1 # 0 = reverse, 1..10 forward
|
||||||
|
self.speed = 0.0 # Vehicle speed in mph
|
||||||
|
|
||||||
|
def update(self, throttle, brake, dt):
|
||||||
|
"""
|
||||||
|
Update engine RPM and vehicle speed over time.
|
||||||
|
- The engine RPM is updated based on throttle input, brake input, and a damping term that
|
||||||
|
forces it toward the "ideal" RPM determined by the current speed.
|
||||||
|
- The vehicle acceleration is then computed from the difference between actual and ideal RPM.
|
||||||
|
"""
|
||||||
|
# Compute the ideal engine RPM given the current speed and gear.
|
||||||
|
# For realism, if the car is stopped, ideal RPM should be at least IDLE_RPM.
|
||||||
|
ideal_rpm = max(IDLE_RPM, self.speed * GEAR_RATIOS[self.gear] * K)
|
||||||
|
|
||||||
|
# --- Engine RPM Dynamics ---
|
||||||
|
# Throttle increases engine RPM; braking drops it.
|
||||||
|
throttle_effect = THROTTLE_RPM_RATE if throttle else 0
|
||||||
|
brake_effect = BRAKE_RPM_RATE if brake else 0
|
||||||
|
# Engine tends to return toward the ideal RPM.
|
||||||
|
d_rpm = throttle_effect - brake_effect - DAMPING_FACTOR * (self.engine_rpm - ideal_rpm)
|
||||||
|
self.engine_rpm += d_rpm * dt
|
||||||
|
# Clamp engine RPM between IDLE and REDLINE.
|
||||||
|
if self.engine_rpm < IDLE_RPM:
|
||||||
|
self.engine_rpm = IDLE_RPM
|
||||||
|
if self.engine_rpm > REDLINE_RPM:
|
||||||
|
self.engine_rpm = REDLINE_RPM
|
||||||
|
|
||||||
|
# --- Vehicle Acceleration Dynamics ---
|
||||||
|
# The acceleration is proportional to how much the engine is "over-revving" relative to ideal.
|
||||||
|
# A positive difference means extra torque; a negative difference means the engine is not producing enough torque.
|
||||||
|
accel = (self.engine_rpm - ideal_rpm) / ACCELERATION_CONVERSION
|
||||||
|
# Subtract drag proportional to speed.
|
||||||
|
accel -= DRAG_COEFF * self.speed
|
||||||
|
# Update vehicle speed.
|
||||||
|
self.speed += accel * dt
|
||||||
|
if self.speed < 0:
|
||||||
|
self.speed = 0
|
||||||
|
if self.speed > MAX_SPEED:
|
||||||
|
self.speed = MAX_SPEED
|
||||||
|
|
||||||
|
# --- Update Position ---
|
||||||
|
# When in reverse (gear 0), the vehicle moves backward.
|
||||||
|
direction = 1 if self.gear != 0 else -1
|
||||||
|
# Convert speed (mph) to distance per second. (1 mph ≈ 1.46667 ft/s)
|
||||||
|
ft_per_sec = self.speed * 1.46667
|
||||||
|
self.x += math.cos(self.angle) * ft_per_sec * dt * direction
|
||||||
|
self.y += math.sin(self.angle) * ft_per_sec * dt * direction
|
||||||
|
|
||||||
|
def rev_match_on_shift_up(self):
|
||||||
|
"""Simulate a realistic drop in RPM when upshifting (rev-match)."""
|
||||||
|
# On an upshift, the engine RPM typically drops by 10–20%.
|
||||||
|
self.engine_rpm *= 0.85
|
||||||
|
if self.engine_rpm < IDLE_RPM:
|
||||||
|
self.engine_rpm = IDLE_RPM
|
||||||
|
|
||||||
|
def rev_match_on_shift_down(self):
|
||||||
|
"""Simulate a realistic bump in RPM when downshifting."""
|
||||||
|
self.engine_rpm *= 1.15
|
||||||
|
if self.engine_rpm > REDLINE_RPM:
|
||||||
|
self.engine_rpm = REDLINE_RPM
|
||||||
|
|
||||||
|
class Track:
|
||||||
|
"""
|
||||||
|
A procedural track that extends as the car moves, giving the illusion of an infinite road.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.segments = []
|
||||||
|
self.last_point = (0.0, 0.0)
|
||||||
|
self.last_angle = 0.0
|
||||||
|
self.generate_initial_track()
|
||||||
|
|
||||||
|
def generate_initial_track(self):
|
||||||
|
num_segments = 20
|
||||||
|
for _ in range(num_segments):
|
||||||
|
self.extend_segment()
|
||||||
|
|
||||||
|
def extend_segment(self):
|
||||||
|
length = 150
|
||||||
|
angle_change = random.uniform(-0.3, 0.3)
|
||||||
|
self.last_angle += angle_change
|
||||||
|
dx = length * math.cos(self.last_angle)
|
||||||
|
dy = length * math.sin(self.last_angle)
|
||||||
|
next_point = (self.last_point[0] + dx, self.last_point[1] + dy)
|
||||||
|
self.segments.append((self.last_point, next_point))
|
||||||
|
self.last_point = next_point
|
||||||
|
|
||||||
|
def ensure_track_length(self, car_pos, min_distance=500):
|
||||||
|
dist = math.hypot(self.last_point[0] - car_pos[0], self.last_point[1] - car_pos[1])
|
||||||
|
while dist < min_distance:
|
||||||
|
self.extend_segment()
|
||||||
|
dist = math.hypot(self.last_point[0] - car_pos[0], self.last_point[1] - car_pos[1])
|
||||||
|
|
||||||
|
def is_on_road(car, track, road_width):
|
||||||
|
"""
|
||||||
|
Checks whether the car's center is within half the road width of any track segment.
|
||||||
|
"""
|
||||||
|
P = (car.x, car.y)
|
||||||
|
hw = road_width / 2
|
||||||
|
for seg in track.segments:
|
||||||
|
A, B = seg
|
||||||
|
if point_line_distance(P, A, B) <= hw:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ----- Drawing Functions -----
|
||||||
|
def draw_road(surface, track, offset_x, offset_y, road_width):
|
||||||
|
"""
|
||||||
|
Draw each track segment as a filled polygon representing a wide road.
|
||||||
|
"""
|
||||||
|
for seg in track.segments:
|
||||||
|
start, end = seg
|
||||||
|
dx = end[0] - start[0]
|
||||||
|
dy = end[1] - start[1]
|
||||||
|
seg_len = math.hypot(dx, dy)
|
||||||
|
if seg_len == 0:
|
||||||
|
continue
|
||||||
|
# Compute a perpendicular vector.
|
||||||
|
nx = -dy / seg_len
|
||||||
|
ny = dx / seg_len
|
||||||
|
half_w = road_width / 2
|
||||||
|
start_left = (start[0] + nx * half_w, start[1] + ny * half_w)
|
||||||
|
start_right = (start[0] - nx * half_w, start[1] - ny * half_w)
|
||||||
|
end_left = (end[0] + nx * half_w, end[1] + ny * half_w)
|
||||||
|
end_right = (end[0] - nx * half_w, end[1] - ny * half_w)
|
||||||
|
poly = [
|
||||||
|
(int(start_left[0] + offset_x), int(start_left[1] + offset_y)),
|
||||||
|
(int(end_left[0] + offset_x), int(end_left[1] + offset_y)),
|
||||||
|
(int(end_right[0] + offset_x), int(end_right[1] + offset_y)),
|
||||||
|
(int(start_right[0] + offset_x), int(start_right[1] + offset_y))
|
||||||
|
]
|
||||||
|
pygame.draw.polygon(surface, (100, 100, 100), poly)
|
||||||
|
# Draw a center line for visual effect.
|
||||||
|
center_start = (start[0] + offset_x, start[1] + offset_y)
|
||||||
|
center_end = (end[0] + offset_x, end[1] + offset_y)
|
||||||
|
pygame.draw.line(surface, (255, 255, 255), center_start, center_end, 2)
|
||||||
|
|
||||||
|
def draw_car(surface, car, offset_x, offset_y):
|
||||||
|
"""
|
||||||
|
Draw the car as a rotated, car-like polygon.
|
||||||
|
"""
|
||||||
|
center = (car.x + offset_x, car.y + offset_y)
|
||||||
|
half_length = 40 / 2
|
||||||
|
half_width = 20 / 2
|
||||||
|
# Define a 5-point polygon for the car shape.
|
||||||
|
points = [
|
||||||
|
( half_length, 0), # Front tip
|
||||||
|
( half_length * 0.2, -half_width), # Rear top
|
||||||
|
(-half_length, -half_width), # Rear left
|
||||||
|
(-half_length, half_width), # Rear right
|
||||||
|
( half_length * 0.2, half_width) # Rear bottom
|
||||||
|
]
|
||||||
|
rotated = []
|
||||||
|
for p in points:
|
||||||
|
rp = rotate_point(p, car.angle)
|
||||||
|
rotated.append((rp[0] + center[0], rp[1] + center[1]))
|
||||||
|
pygame.draw.polygon(surface, (255, 0, 0), rotated)
|
||||||
|
pygame.draw.polygon(surface, (255, 255, 255), rotated, 2)
|
||||||
|
|
||||||
|
# ----- Main Game Loop -----
|
||||||
|
def main():
|
||||||
|
pygame.init()
|
||||||
|
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||||
|
pygame.display.set_caption("Super Realistic Car Simulation")
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
font = pygame.font.SysFont(None, 24)
|
||||||
|
|
||||||
|
car = Car()
|
||||||
|
track = Track()
|
||||||
|
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
dt = clock.tick(FPS) / 1000.0
|
||||||
|
|
||||||
|
# ----- Event Handling -----
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
running = False
|
||||||
|
elif event.type == pygame.KEYDOWN:
|
||||||
|
# Upshift (E)
|
||||||
|
if event.key == pygame.K_e and car.gear < 10:
|
||||||
|
old_ratio = GEAR_RATIOS[car.gear]
|
||||||
|
car.gear += 1
|
||||||
|
# Rev-match on upshift: drop RPM to roughly maintain consistency.
|
||||||
|
car.rev_match_on_shift_up()
|
||||||
|
# Downshift (Q), but do not downshift below gear 1.
|
||||||
|
elif event.key == pygame.K_q and car.gear > 1:
|
||||||
|
old_ratio = GEAR_RATIOS[car.gear]
|
||||||
|
car.gear -= 1
|
||||||
|
car.rev_match_on_shift_down()
|
||||||
|
# Toggle reverse (R)
|
||||||
|
elif event.key == pygame.K_r:
|
||||||
|
if car.gear == 0:
|
||||||
|
car.gear = 1
|
||||||
|
else:
|
||||||
|
car.gear = 0
|
||||||
|
|
||||||
|
# ----- Continuous Input -----
|
||||||
|
keys = pygame.key.get_pressed()
|
||||||
|
if keys[pygame.K_LEFT]:
|
||||||
|
car.angle -= TURN_RATE * dt
|
||||||
|
if keys[pygame.K_RIGHT]:
|
||||||
|
car.angle += TURN_RATE * dt
|
||||||
|
throttle = keys[pygame.K_UP]
|
||||||
|
brake = keys[pygame.K_DOWN]
|
||||||
|
|
||||||
|
# ----- Update Car & Extend Track -----
|
||||||
|
car.update(throttle, brake, dt)
|
||||||
|
track.ensure_track_length((car.x, car.y), min_distance=500)
|
||||||
|
|
||||||
|
# Determine the view offset so the car is centered.
|
||||||
|
offset_x = SCREEN_WIDTH / 2 - car.x
|
||||||
|
offset_y = SCREEN_HEIGHT / 2 - car.y
|
||||||
|
|
||||||
|
# ----- Drawing -----
|
||||||
|
screen.fill((0, 150, 0)) # Grass background.
|
||||||
|
draw_road(screen, track, offset_x, offset_y, ROAD_WIDTH)
|
||||||
|
draw_car(screen, car, offset_x, offset_y)
|
||||||
|
|
||||||
|
# Display status (on-road/off-road)
|
||||||
|
status_text = "On Road" if is_on_road(car, track, ROAD_WIDTH) else "Off Road"
|
||||||
|
status_surf = font.render(status_text, True, (255, 255, 255))
|
||||||
|
screen.blit(status_surf, (10, 10))
|
||||||
|
|
||||||
|
# Draw gauges.
|
||||||
|
# Speed gauge shows vehicle speed in mph.
|
||||||
|
draw_gauge(screen, SPEED_GAUGE_CENTER, GAUGE_RADIUS, 0, MAX_SPEED, car.speed, "Speed (mph)")
|
||||||
|
# Tachometer shows engine RPM.
|
||||||
|
draw_gauge(screen, TACH_GAUGE_CENTER, GAUGE_RADIUS, 0, REDLINE_RPM, car.engine_rpm, "RPM")
|
||||||
|
|
||||||
|
# Display current gear.
|
||||||
|
gear_text = "Reverse" if (car.gear == 0) else f"Gear: {car.gear}"
|
||||||
|
gear_surf = font.render(gear_text, True, (255, 255, 255))
|
||||||
|
screen.blit(gear_surf, (SPEED_GAUGE_CENTER[0] - GAUGE_RADIUS, SPEED_GAUGE_CENTER[1] + GAUGE_RADIUS + 40))
|
||||||
|
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
pygame.quit()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc
Normal file
BIN
pygame-imgui-v2/__pycache__/imgui.cpython-311.pyc
Normal file
Binary file not shown.
72
pygame-imgui-v2/example.py
Normal file
72
pygame-imgui-v2/example.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import pygame
|
||||||
|
import sys
|
||||||
|
from imgui import PyGUI
|
||||||
|
|
||||||
|
WIDTH, HEIGHT = 800, 600
|
||||||
|
|
||||||
|
class Ball:
|
||||||
|
def __init__(self):
|
||||||
|
self.pos = [WIDTH // 2, HEIGHT // 2]
|
||||||
|
self.speed = 5
|
||||||
|
self.direction = 45
|
||||||
|
self.radius = 20
|
||||||
|
self.color = (255, 0, 0)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# (For demonstration, implement ball movement if desired.)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def draw(self, surface):
|
||||||
|
pygame.draw.circle(surface, self.color, (int(self.pos[0]), int(self.pos[1])), self.radius)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
pygame.init()
|
||||||
|
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||||
|
pygame.display.set_caption("PyGUI Example")
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
PyGUI.init(screen)
|
||||||
|
|
||||||
|
ball = Ball()
|
||||||
|
ball_speed = ball.speed
|
||||||
|
ball_direction = ball.direction
|
||||||
|
ball_radius = ball.radius
|
||||||
|
ball_color_r, ball_color_g, ball_color_b = ball.color
|
||||||
|
bounce_enabled = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
events = pygame.event.get()
|
||||||
|
for event in events:
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
pygame.quit()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
PyGUI.Update(events)
|
||||||
|
|
||||||
|
screen.fill((50, 50, 50))
|
||||||
|
|
||||||
|
# Draw the GUI window.
|
||||||
|
PyGUI.Begin("Ball Controls", 10, 10)
|
||||||
|
ball_speed = PyGUI.Slider("Speed", ball_speed, 1, 10)
|
||||||
|
ball_direction = PyGUI.Slider("Direction", ball_direction, 0, 360)
|
||||||
|
ball_radius = PyGUI.Slider("Radius", ball_radius, 10, 100)
|
||||||
|
ball_color_r = PyGUI.Slider("Red", ball_color_r, 0, 255)
|
||||||
|
ball_color_g = PyGUI.Slider("Green", ball_color_g, 0, 255)
|
||||||
|
ball_color_b = PyGUI.Slider("Blue", ball_color_b, 0, 255)
|
||||||
|
bounce_enabled = PyGUI.Checkbox("Bounce", bounce_enabled)
|
||||||
|
if PyGUI.Button("Reset Position"):
|
||||||
|
ball.pos = [WIDTH // 2, HEIGHT // 2]
|
||||||
|
PyGUI.End()
|
||||||
|
|
||||||
|
# Update the ball parameters.
|
||||||
|
ball.speed = ball_speed
|
||||||
|
ball.direction = ball_direction
|
||||||
|
ball.radius = int(ball_radius)
|
||||||
|
ball.color = (int(ball_color_r), int(ball_color_g), int(ball_color_b))
|
||||||
|
|
||||||
|
ball.draw(screen)
|
||||||
|
|
||||||
|
pygame.display.flip()
|
||||||
|
clock.tick(60)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
256
pygame-imgui-v2/imgui.py
Normal file
256
pygame-imgui-v2/imgui.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import pygame
|
||||||
|
|
||||||
|
class PyGUI:
|
||||||
|
_surface = None
|
||||||
|
_font = None
|
||||||
|
_events = []
|
||||||
|
_window_stack = []
|
||||||
|
|
||||||
|
# UI layout constants
|
||||||
|
TITLE_BAR_HEIGHT = 30
|
||||||
|
PADDING = 10
|
||||||
|
SPACING = 5
|
||||||
|
BOTTOM_PADDING = 10
|
||||||
|
MIN_WINDOW_WIDTH = 220
|
||||||
|
|
||||||
|
# Colors and style
|
||||||
|
WINDOW_BG_COLOR = (60, 60, 60)
|
||||||
|
TITLE_BAR_COLOR = (30, 30, 30)
|
||||||
|
BORDER_COLOR = (20, 20, 20)
|
||||||
|
BUTTON_COLOR = (100, 100, 100)
|
||||||
|
BUTTON_HOVER_COLOR = (130, 130, 130)
|
||||||
|
SLIDER_TRACK_COLOR = (100, 100, 100)
|
||||||
|
SLIDER_KNOB_COLOR = (150, 150, 150)
|
||||||
|
WIDGET_TEXT_COLOR = (255, 255, 255)
|
||||||
|
CHECKBOX_BG_COLOR = (80, 80, 80)
|
||||||
|
CHECKBOX_CHECK_COLOR = (200, 200, 200)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init(surface, font_name='Arial', font_size=18):
|
||||||
|
"""Initialize PyGUI with the main Pygame surface and font."""
|
||||||
|
PyGUI._surface = surface
|
||||||
|
PyGUI._font = pygame.font.SysFont(font_name, font_size)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Begin(title, x, y):
|
||||||
|
"""
|
||||||
|
Begin a new draggable, auto–sizing window.
|
||||||
|
A new window context is created; widget calls will record
|
||||||
|
their draw commands and update the context layout.
|
||||||
|
"""
|
||||||
|
context = {
|
||||||
|
'title': title,
|
||||||
|
'x': x,
|
||||||
|
'y': y,
|
||||||
|
'width': PyGUI.MIN_WINDOW_WIDTH,
|
||||||
|
'content_height': 0, # the vertical extent of all widgets
|
||||||
|
'commands': [], # list of widget draw command lambdas
|
||||||
|
'drag': False,
|
||||||
|
'drag_offset': (0, 0),
|
||||||
|
'local_y': 0 # current Y offset (relative to content area)
|
||||||
|
}
|
||||||
|
PyGUI._window_stack.append(context)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def End():
|
||||||
|
"""
|
||||||
|
Ends the current window, calculates final dimensions,
|
||||||
|
draws the window background, title bar and border, then blits the window.
|
||||||
|
"""
|
||||||
|
if not PyGUI._window_stack:
|
||||||
|
return
|
||||||
|
context = PyGUI._window_stack.pop()
|
||||||
|
final_height = PyGUI.TITLE_BAR_HEIGHT + context['content_height'] + PyGUI.BOTTOM_PADDING
|
||||||
|
|
||||||
|
# Create an offscreen surface for the entire window with alpha support.
|
||||||
|
window_surf = pygame.Surface((context['width'], final_height), pygame.SRCALPHA)
|
||||||
|
# Draw the window background with rounded corners.
|
||||||
|
pygame.draw.rect(window_surf, PyGUI.WINDOW_BG_COLOR,
|
||||||
|
(0, 0, context['width'], final_height), border_radius=8)
|
||||||
|
# Execute widget drawing commands onto window_surf.
|
||||||
|
for cmd in context['commands']:
|
||||||
|
cmd(window_surf)
|
||||||
|
# Draw the title bar on top.
|
||||||
|
title_bar_rect = pygame.Rect(0, 0, context['width'], PyGUI.TITLE_BAR_HEIGHT)
|
||||||
|
pygame.draw.rect(window_surf, PyGUI.TITLE_BAR_COLOR, title_bar_rect, border_radius=8)
|
||||||
|
# Render the window title.
|
||||||
|
title_surf = PyGUI._font.render(context['title'], True, PyGUI.WIDGET_TEXT_COLOR)
|
||||||
|
window_surf.blit(title_surf, (PyGUI.PADDING, (PyGUI.TITLE_BAR_HEIGHT - title_surf.get_height()) // 2))
|
||||||
|
# Draw a border around the entire window.
|
||||||
|
pygame.draw.rect(window_surf, PyGUI.BORDER_COLOR,
|
||||||
|
(0, 0, context['width'], final_height), 2, border_radius=8)
|
||||||
|
# Blit the finished window onto the main surface.
|
||||||
|
PyGUI._surface.blit(window_surf, (context['x'], context['y']))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _current_window():
|
||||||
|
if not PyGUI._window_stack:
|
||||||
|
return None
|
||||||
|
return PyGUI._window_stack[-1]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Update(events):
|
||||||
|
"""Update the internal event cache and process dragging for all windows."""
|
||||||
|
PyGUI._events = events
|
||||||
|
for context in PyGUI._window_stack:
|
||||||
|
PyGUI._process_dragging(context)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_dragging(context):
|
||||||
|
"""If the mouse clicks the title bar, allow dragging to update window position."""
|
||||||
|
mouse_pos = pygame.mouse.get_pos()
|
||||||
|
mouse_buttons = pygame.mouse.get_pressed()
|
||||||
|
title_rect = pygame.Rect(context['x'], context['y'], context['width'], PyGUI.TITLE_BAR_HEIGHT)
|
||||||
|
for event in PyGUI._events:
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
||||||
|
if title_rect.collidepoint(mouse_pos):
|
||||||
|
context['drag'] = True
|
||||||
|
context['drag_offset'] = (mouse_pos[0] - context['x'],
|
||||||
|
mouse_pos[1] - context['y'])
|
||||||
|
if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
|
||||||
|
context['drag'] = False
|
||||||
|
if context['drag'] and mouse_buttons[0]:
|
||||||
|
context['x'] = mouse_pos[0] - context['drag_offset'][0]
|
||||||
|
context['y'] = mouse_pos[1] - context['drag_offset'][1]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Button(label, width=100, height=30):
|
||||||
|
"""
|
||||||
|
Create a button widget; returns True if the button is clicked.
|
||||||
|
The interactive test is done immediately and the draw command is recorded.
|
||||||
|
"""
|
||||||
|
context = PyGUI._current_window()
|
||||||
|
if context is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compute local widget position inside the window's content area.
|
||||||
|
local_x = PyGUI.PADDING
|
||||||
|
local_y = context['local_y'] + PyGUI.PADDING
|
||||||
|
# Calculate absolute position for interaction.
|
||||||
|
abs_x = context['x'] + local_x
|
||||||
|
abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y
|
||||||
|
widget_rect = pygame.Rect(abs_x, abs_y, width, height)
|
||||||
|
|
||||||
|
clicked = False
|
||||||
|
hovered = widget_rect.collidepoint(pygame.mouse.get_pos())
|
||||||
|
for event in PyGUI._events:
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered:
|
||||||
|
clicked = True
|
||||||
|
|
||||||
|
color = PyGUI.BUTTON_HOVER_COLOR if hovered else PyGUI.BUTTON_COLOR
|
||||||
|
|
||||||
|
# Record the draw command (drawing relative to the window surface).
|
||||||
|
def draw_command(surf):
|
||||||
|
btn_rect = pygame.Rect(local_x, local_y, width, height)
|
||||||
|
pygame.draw.rect(surf, color, btn_rect, border_radius=4)
|
||||||
|
text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
|
||||||
|
text_rect = text_surf.get_rect(center=btn_rect.center)
|
||||||
|
surf.blit(text_surf, text_rect)
|
||||||
|
|
||||||
|
context['commands'].append(draw_command)
|
||||||
|
context['local_y'] += height + PyGUI.SPACING
|
||||||
|
context['content_height'] = max(context['content_height'],
|
||||||
|
context['local_y'] + PyGUI.PADDING)
|
||||||
|
needed_width = local_x + width + PyGUI.PADDING
|
||||||
|
if needed_width > context['width']:
|
||||||
|
context['width'] = needed_width
|
||||||
|
|
||||||
|
return clicked
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Slider(label, value, min_val, max_val, width=150, height=20):
|
||||||
|
"""
|
||||||
|
Create a slider widget; returns the updated value.
|
||||||
|
A label is drawn above the slider track.
|
||||||
|
"""
|
||||||
|
context = PyGUI._current_window()
|
||||||
|
if context is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
local_x = PyGUI.PADDING
|
||||||
|
local_y = context['local_y'] + PyGUI.PADDING
|
||||||
|
abs_x = context['x'] + local_x
|
||||||
|
abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y
|
||||||
|
|
||||||
|
# Render label (with current value) and get its height.
|
||||||
|
label_surf = PyGUI._font.render(f"{label}: {value:.2f}", True, PyGUI.WIDGET_TEXT_COLOR)
|
||||||
|
label_height = label_surf.get_height()
|
||||||
|
|
||||||
|
# Define slider track's absolute rectangle.
|
||||||
|
track_rect = pygame.Rect(abs_x, abs_y + label_height + 2, width, height)
|
||||||
|
new_value = value
|
||||||
|
if track_rect.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
|
||||||
|
mouse_x = pygame.mouse.get_pos()[0]
|
||||||
|
relative = (mouse_x - track_rect.x) / track_rect.width
|
||||||
|
relative = max(0.0, min(1.0, relative))
|
||||||
|
new_value = min_val + relative * (max_val - min_val)
|
||||||
|
|
||||||
|
relative = (new_value - min_val) / (max_val - min_val)
|
||||||
|
|
||||||
|
def draw_command(surf):
|
||||||
|
# Draw the label.
|
||||||
|
surf.blit(label_surf, (local_x, local_y))
|
||||||
|
# Draw the slider track.
|
||||||
|
local_track_rect = pygame.Rect(local_x, local_y + label_height + 2, width, height)
|
||||||
|
pygame.draw.rect(surf, PyGUI.SLIDER_TRACK_COLOR, local_track_rect, border_radius=4)
|
||||||
|
# Draw the knob.
|
||||||
|
knob_rect = pygame.Rect(local_track_rect.x + int(relative * local_track_rect.width) - 5,
|
||||||
|
local_track_rect.y - 2, 10, local_track_rect.height + 4)
|
||||||
|
pygame.draw.rect(surf, PyGUI.SLIDER_KNOB_COLOR, knob_rect, border_radius=4)
|
||||||
|
|
||||||
|
context['commands'].append(draw_command)
|
||||||
|
total_height = label_height + 2 + height + PyGUI.SPACING
|
||||||
|
context['local_y'] += total_height
|
||||||
|
context['content_height'] = max(context['content_height'],
|
||||||
|
context['local_y'] + PyGUI.PADDING)
|
||||||
|
if local_x + width + PyGUI.PADDING > context['width']:
|
||||||
|
context['width'] = local_x + width + PyGUI.PADDING
|
||||||
|
|
||||||
|
return new_value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Checkbox(label, value):
|
||||||
|
"""
|
||||||
|
Create a checkbox widget; returns the toggled boolean value.
|
||||||
|
"""
|
||||||
|
context = PyGUI._current_window()
|
||||||
|
if context is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
local_x = PyGUI.PADDING
|
||||||
|
local_y = context['local_y'] + PyGUI.PADDING
|
||||||
|
abs_x = context['x'] + local_x
|
||||||
|
abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y
|
||||||
|
|
||||||
|
box_size = 20
|
||||||
|
box_rect = pygame.Rect(abs_x, abs_y, box_size, box_size)
|
||||||
|
new_value = value
|
||||||
|
hovered = box_rect.collidepoint(pygame.mouse.get_pos())
|
||||||
|
for event in PyGUI._events:
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered:
|
||||||
|
new_value = not value
|
||||||
|
|
||||||
|
def draw_command(surf):
|
||||||
|
local_box_rect = pygame.Rect(local_x, local_y, box_size, box_size)
|
||||||
|
pygame.draw.rect(surf, PyGUI.CHECKBOX_BG_COLOR, local_box_rect, border_radius=3)
|
||||||
|
if new_value:
|
||||||
|
pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR,
|
||||||
|
(local_box_rect.left, local_box_rect.top),
|
||||||
|
(local_box_rect.right, local_box_rect.bottom), 2)
|
||||||
|
pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR,
|
||||||
|
(local_box_rect.left, local_box_rect.bottom),
|
||||||
|
(local_box_rect.right, local_box_rect.top), 2)
|
||||||
|
text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
|
||||||
|
surf.blit(text_surf, (local_box_rect.right + 5, local_box_rect.top))
|
||||||
|
|
||||||
|
context['commands'].append(draw_command)
|
||||||
|
text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
|
||||||
|
line_height = max(box_size, text_surf.get_height())
|
||||||
|
context['local_y'] += line_height + PyGUI.SPACING
|
||||||
|
context['content_height'] = max(context['content_height'],
|
||||||
|
context['local_y'] + PyGUI.PADDING)
|
||||||
|
needed_width = local_x + box_size + 5 + text_surf.get_width() + PyGUI.PADDING
|
||||||
|
if needed_width > context['width']:
|
||||||
|
context['width'] = needed_width
|
||||||
|
|
||||||
|
return new_value
|
@ -1,7 +1,7 @@
|
|||||||
import pygame
|
import pygame
|
||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
import pygui
|
from pygui import PyGUI
|
||||||
from pygui_pygame_backend import Render, Rect, get_mouse_pos, get_mouse_pressed, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION
|
from pygui_pygame_backend import Render, Rect, get_mouse_pos, get_mouse_pressed, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION
|
||||||
import pygui_pygame_backend as backend
|
import pygui_pygame_backend as backend
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ clock = pygame.time.Clock()
|
|||||||
|
|
||||||
# Create the backend renderer and assign the backend to PyGUI.
|
# Create the backend renderer and assign the backend to PyGUI.
|
||||||
renderer = Render(screen)
|
renderer = Render(screen)
|
||||||
pygui.PyGUI.set_backend(backend, renderer)
|
PyGUI.set_backend(backend, renderer)
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# Ball Simulation
|
# Ball Simulation
|
||||||
@ -51,22 +51,22 @@ while running:
|
|||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
running = False
|
running = False
|
||||||
pygui.PyGUI.handle_event(event)
|
PyGUI.handle_event(event)
|
||||||
|
|
||||||
screen.fill((30, 30, 30))
|
screen.fill((30, 30, 30))
|
||||||
|
|
||||||
# Draw a draggable, auto–sizing GUI window.
|
# Draw a draggable, auto–sizing GUI window.
|
||||||
pygui.PyGUI.Begin("Ball Controls", 10, 10)
|
PyGUI.Begin("Ball Controls", 10, 10)
|
||||||
ball_speed = pygui.PyGUI.Slider("Speed", ball_speed, 1, 10)
|
ball_speed = PyGUI.Slider("Speed", ball_speed, 1, 10)
|
||||||
ball_direction = pygui.PyGUI.Slider("Direction", ball_direction, 0, 360)
|
ball_direction = PyGUI.Slider("Direction", ball_direction, 0, 360)
|
||||||
ball_radius = pygui.PyGUI.Slider("Radius", ball_radius, 10, 100)
|
ball_radius = PyGUI.Slider("Radius", ball_radius, 10, 100)
|
||||||
ball_color_r = pygui.PyGUI.Slider("Red", ball_color_r, 0, 255)
|
ball_color_r = PyGUI.Slider("Red", ball_color_r, 0, 255)
|
||||||
ball_color_g = pygui.PyGUI.Slider("Green", ball_color_g, 0, 255)
|
ball_color_g = PyGUI.Slider("Green", ball_color_g, 0, 255)
|
||||||
ball_color_b = pygui.PyGUI.Slider("Blue", ball_color_b, 0, 255)
|
ball_color_b = PyGUI.Slider("Blue", ball_color_b, 0, 255)
|
||||||
bounce_enabled = pygui.PyGUI.Checkbox("Bounce", bounce_enabled)
|
bounce_enabled = PyGUI.Checkbox("Bounce", bounce_enabled)
|
||||||
if pygui.PyGUI.Button("Reset Position"):
|
if PyGUI.Button("Reset Position"):
|
||||||
ball.pos = [WIDTH // 2, HEIGHT // 2]
|
ball.pos = [WIDTH // 2, HEIGHT // 2]
|
||||||
pygui.PyGUI.End()
|
PyGUI.End()
|
||||||
|
|
||||||
rad = math.radians(ball_direction)
|
rad = math.radians(ball_direction)
|
||||||
ball.velocity[0] = ball_speed * math.cos(rad)
|
ball.velocity[0] = ball_speed * math.cos(rad)
|
||||||
@ -81,7 +81,7 @@ while running:
|
|||||||
ball.draw(screen)
|
ball.draw(screen)
|
||||||
|
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
pygui.PyGUI.update()
|
PyGUI.update()
|
||||||
|
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
Loading…
Reference in New Issue
Block a user