Skip to content

Instantly share code, notes, and snippets.

@hsk
Created July 4, 2025 04:15
Show Gist options
  • Select an option

  • Save hsk/3e69853dc8bb42718b1f597544dc3908 to your computer and use it in GitHub Desktop.

Select an option

Save hsk/3e69853dc8bb42718b1f597544dc3908 to your computer and use it in GitHub Desktop.
import pygame
import math
import time
# Pygameの初期化
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36) # テキスト表示用フォント
track_width = 50
# オフスクリーンサーフェス
render_surface = pygame.Surface((800, 600))
# 車のクラス
class Car:
def __init__(self, x, y):
self.x = x
self.y = y
self.angle = 0 # 車の向き(度)
self.speed = 0 # 速度
self.max_speed = 2 # 最大速度
self.acceleration = 0.1 # 加速
self.deceleration = 0.05 # 減速
self.rotation_speed = 3 # 回転速度
self.prev_x = x # 前の位置
self.prev_y = y
def update(self, keys):
self.prev_x = self.x
self.prev_y = self.y
if keys[pygame.K_LEFT]:
self.angle += self.rotation_speed
if keys[pygame.K_RIGHT]:
self.angle -= self.rotation_speed
if keys[pygame.K_x]:
self.speed = min(self.speed + self.acceleration, self.max_speed)
elif keys[pygame.K_z]:
self.speed = max(self.speed - self.acceleration, -self.max_speed / 2)
else:
if self.speed > 0:
self.speed = max(self.speed - self.deceleration, 0)
elif self.speed < 0:
self.speed = min(self.speed + self.deceleration, 0)
rad = math.radians(self.angle)
self.x += self.speed * math.cos(rad)
self.y -= self.speed * math.sin(rad)
def get_points(self):
length = 30
width = 15
return [
(self.x + length / 2 * math.cos(math.radians(self.angle)),
self.y - length / 2 * math.sin(math.radians(self.angle))),
(self.x + width / 2 * math.cos(math.radians(self.angle + 90)),
self.y - width / 2 * math.sin(math.radians(self.angle + 90))),
(self.x - length / 2 * math.cos(math.radians(self.angle)),
self.y + length / 2 * math.sin(math.radians(self.angle))),
(self.x - width / 2 * math.cos(math.radians(self.angle + 90)),
self.y + width / 2 * math.sin(math.radians(self.angle + 90)))
]
def check_collision(self, outer_polygon, inner_polygon):
for point in self.get_points():
if not point_in_polygon(point, outer_polygon) or point_in_polygon(point, inner_polygon):
return True
return False
def draw(self, surface):
points = self.get_points()
pygame.draw.polygon(surface, (255, 0, 0), points)
width = 15
wing_points = [
points[0],
(self.x + width / 3 * math.cos(math.radians(self.angle + 90)),
self.y - width / 3 * math.sin(math.radians(self.angle + 90))),
(self.x - width / 3 * math.cos(math.radians(self.angle + 90)),
self.y + width / 3 * math.sin(math.radians(self.angle + 90)))
]
pygame.draw.polygon(surface, (0, 0, 0), wing_points)
# ポリゴンの内外判定
def point_in_polygon(point, polygon):
x, y = point
n = len(polygon)
inside = False
px, py = polygon[0]
for i in range(n + 1):
sx, sy = polygon[i % n]
if y > min(py, sy):
if y <= max(py, sy):
if x <= max(px, sx):
if py != sy:
xinters = (y - py) * (sx - px) / (sy - py) + px
if px == sx or x <= xinters:
inside = not inside
px, py = sx, sy
return inside
# 誘導弾風のパス生成
def generate_homing_path(checkpoints):
path_points = []
missile_x, missile_y = checkpoints[0]
missile_angle = 0
missile_speed = 5
max_turn_rate = 5
for i in range(len(checkpoints)):
target = checkpoints[(i + 1) % len(checkpoints)]
while math.hypot(missile_x - target[0], missile_y - target[1]) > 10:
target_angle = math.degrees(math.atan2(target[1] - missile_y, target[0] - missile_x))
angle_diff = (target_angle - missile_angle + 180) % 360 - 180
turn = max(min(angle_diff, max_turn_rate), -max_turn_rate)
missile_angle += turn
rad = math.radians(missile_angle)
missile_x += missile_speed * math.cos(rad)
missile_y += missile_speed * math.sin(rad)
path_points.append((missile_x, missile_y))
missile_x, missile_y = target
return path_points
# 台形変換(パース効果)
# ラインごとのパース変換
def transform_perspective(surface):
width, height = surface.get_size()
warped_surface = pygame.Surface((800, 600), pygame.SRCALPHA)
# 描画範囲:画面下(y=300)から上(y=600)
for dest_y in range(height//2, height):
# 横幅スケール:dest_y=300で0.5、dest_y=600で1.0(線形補間)
z = height / dest_y
t = 2 - z
src_y = t * height
if src_y < 0: src_y = 0
if src_y >= height:
src_y = height - 1
scale = 0.5 + (1.0 - 0.5) * ((dest_y * 2 - height) / height)
new_width = int(width * scale)
if new_width <= 0:
continue
# ラインを抽出
line_rect = pygame.Rect(0, int(src_y), width, 1)
line_surface = surface.subsurface(line_rect).copy()
# ラインを横にスケール
scaled_line = pygame.transform.scale(line_surface, (new_width, 1))
# 中央に配置
dest_x = 400 - new_width / 2
warped_surface.blit(scaled_line, (dest_x, dest_y))
warped_surface.set_at((int(t*100), dest_y), (255, 0, 0))
warped_surface.set_at((int(z*100), dest_y), (255, 0, 255))
warped_surface.set_at((100, dest_y), (255, 255, 0))
return warped_surface
# コースとチェックポイントの描画
def draw_course(surface, checkpoints, active_checkpoint, path_points):
outer_polygon = []
inner_polygon = []
for i, (x, y) in enumerate(path_points):
next_point = path_points[(i + 1) % len(path_points)]
angle = math.atan2(next_point[1] - y, next_point[0] - x)
ox = x + track_width * math.cos(angle + math.pi / 2)
oy = y + track_width * math.sin(angle + math.pi / 2)
ix = x - track_width * math.cos(angle + math.pi / 2)
iy = y - track_width * math.sin(angle + math.pi / 2)
outer_polygon.append((ox, oy))
inner_polygon.append((ix, iy))
surface.fill((0, 255, 0))
pygame.draw.polygon(surface, (100, 100, 100), outer_polygon)
pygame.draw.polygon(surface, (0, 255, 0), inner_polygon)
pygame.draw.lines(surface, (255, 255, 255), True, path_points, 2)
for i, (cx, cy) in enumerate(checkpoints):
color = (255, 0, 0) if i == active_checkpoint else (255, 255, 255)
pygame.draw.circle(surface, color, (int(cx), int(cy)), track_width+10, 1)
return outer_polygon, inner_polygon
# チェックポイント通過判定
def check_checkpoint(car, checkpoints, current_checkpoint):
cx, cy = checkpoints[current_checkpoint]
if math.hypot(car.x - cx, car.y - cy) < track_width + 10:
return (current_checkpoint + 1) % len(checkpoints)
return current_checkpoint
# チェックポイント操作
def handle_checkpoint_interaction(event, checkpoints, selected_checkpoint, path_points):
mouse_pos = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
for i, (cx, cy) in enumerate(checkpoints):
if math.hypot(mouse_pos[0] - cx, mouse_pos[1] - cy) < track_width / 2:
return i, checkpoints, path_points
min_dist = float('inf')
closest_segment = -1
closest_t = 0
for i in range(len(path_points)):
p1 = path_points[i]
p2 = path_points[(i + 1) % len(path_points)]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length_sq = dx * dx + dy * dy
if length_sq == 0:
dist = math.hypot(mouse_pos[0] - p1[0], mouse_pos[1] - p1[1])
t = 0
else:
t = max(0, min(1, ((mouse_pos[0] - p1[0]) * dx + (mouse_pos[1] - p1[1]) * dy) / length_sq))
proj_x = p1[0] + t * dx
proj_y = p1[1] + t * dy
dist = math.hypot(mouse_pos[0] - proj_x, mouse_pos[1] - proj_y)
if dist < min_dist and dist < 10:
min_dist = dist
closest_segment = i
closest_t = t
if closest_segment != -1:
new_x = path_points[closest_segment][0] + closest_t * (path_points[(closest_segment + 1) % len(path_points)][0] - path_points[closest_segment][0])
new_y = path_points[closest_segment][1] + closest_t * (path_points[(closest_segment + 1) % len(path_points)][1] - path_points[closest_segment][1])
insert_idx = closest_segment // (len(path_points) // len(checkpoints)) + 1
checkpoints.insert(insert_idx, (new_x, new_y))
path_points = generate_homing_path(checkpoints)
return None, checkpoints, path_points
elif event.button == 3:
if len(checkpoints) > 3:
for i, (cx, cy) in enumerate(checkpoints):
if math.hypot(mouse_pos[0] - cx, mouse_pos[1] - cy) < track_width / 2:
checkpoints.pop(i)
path_points = generate_homing_path(checkpoints)
return None, checkpoints, path_points
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
return None, checkpoints, path_points
elif event.type == pygame.MOUSEMOTION and selected_checkpoint is not None:
checkpoints[selected_checkpoint] = mouse_pos
path_points = generate_homing_path(checkpoints)
return selected_checkpoint, checkpoints, path_points
return selected_checkpoint, checkpoints, path_points
def draw_text(text, pos, color=(255, 255, 255)):
screen.blit(font.render(text, True, (0, 0, 0)), (pos[0]+2, pos[1]+2))
screen.blit(font.render(text, True, color), pos)
def draw_textl(text, pos, color=(255, 255, 255)):
surface = font.render(text, True, (0, 0, 0))
screen.blit(surface, (-surface.get_width() + pos[0]+2, pos[1]+2))
screen.blit(font.render(text, True, color), (-surface.get_width() + pos[0], pos[1]))
# メインループ
checkpoints = [(677, 506), (731, 125), (609, 326), (279, 374), (461, 189), (173, 76), (84, 448), (318, 510)]
car = Car(checkpoints[-1][0], checkpoints[-1][1])
path_points = generate_homing_path(checkpoints)
current_checkpoint = 0
lap_count = 0
lap_times = []
start_time = time.time()
last_lap_time = start_time
best_lap_time = float('inf')
selected_checkpoint = None
speed_adjust_cooldown = 0
is_perspective = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
selected_checkpoint, checkpoints, path_points = handle_checkpoint_interaction(event, checkpoints, selected_checkpoint, path_points)
# キー入力
keys = pygame.key.get_pressed()
car.update(keys)
# 最大速度の調整
if speed_adjust_cooldown > 0:
speed_adjust_cooldown -= 1
else:
if keys[pygame.K_UP]:
car.max_speed += 0.25
speed_adjust_cooldown = 10
if keys[pygame.K_DOWN]:
car.max_speed = max(0, car.max_speed - 0.25)
speed_adjust_cooldown = 10
if keys[pygame.K_c]:
is_perspective = not is_perspective
speed_adjust_cooldown = 10
# 衝突判定
outer_polygon, inner_polygon = draw_course(render_surface, checkpoints, current_checkpoint, path_points)
if car.check_collision(outer_polygon, inner_polygon):
car.speed *= 0.95
# チェックポイント通過判定
new_checkpoint = check_checkpoint(car, checkpoints, current_checkpoint)
if new_checkpoint != current_checkpoint:
current_checkpoint = new_checkpoint
if current_checkpoint == 0:
lap_count += 1
current_time = time.time()
lap_time = current_time - last_lap_time
lap_times.append(lap_time)
if len(lap_times) > 10:
lap_times.pop(0)
last_lap_time = current_time
best_lap_time = min(best_lap_time, lap_time)
# 車をサーフェスに描画
car.draw(render_surface)
# サーフェスを画面に描画
screen.fill((0, 0, 0)) # 背景クリア
if is_perspective:
transformed_surface = transform_perspective(render_surface)
screen.blit(transformed_surface, (0, 0))
else:
screen.blit(render_surface, (0, 0))
# 進捗とタイム表示
total_time = time.time() - start_time
ypos = 10
draw_text(f"Laps: {lap_count}", (10, ypos)); ypos += 30
draw_text(f"Time: {total_time:.2f}s", (10, ypos)); ypos += 30
draw_text(f"Max Speed: {car.max_speed:.2f}", (10, ypos)); ypos += 30
# 過去10個のラップタイムを右側に表示
ypos = 10
draw_textl(f"Best Lap: {best_lap_time if best_lap_time != float('inf') else 0:.2f}s", (800-20, ypos), (255, 255, 0)); ypos += 30
for i, lap_time in enumerate(reversed(lap_times[-10:])):
draw_textl(f"Lap {lap_count - i}: {lap_time:.2f}s", (800-20, ypos), (255, 255, 255) if lap_time != best_lap_time else (255, 255, 0))
ypos += 30
pygame.display.flip()
clock.tick(60)
pygame.quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment