Загрузка данных
import math
import random
import sys
import pygame
# ==========================================
# КОНФИГУРАЦИЯ И КОНСТАНТЫ
# ==========================================
SCREEN_WIDTH = 1024
SCREEN_HEIGHT = 768
ROAD_WIDTH = 2000
SEGMENT_LENGTH = 200
CAMERA_HEIGHT = 1000
DRAW_DISTANCE = 300
FIELD_OF_VIEW = 60
CAMERA_DEPTH = 1.0 / math.tan((FIELD_OF_VIEW / 2.0) * math.pi / 180.0)
COLORS = {
'SKY': (110, 160, 230),
'GRASS_LIGHT': (16, 170, 16),
'GRASS_DARK': (0, 145, 0),
'ROAD_LIGHT': (105, 105, 105),
'ROAD_DARK': (98, 98, 98),
'RUMBLE_LIGHT': (255, 255, 255),
'RUMBLE_DARK': (220, 20, 20),
'LANE': (255, 255, 255),
'WHITE': (255, 255, 255),
'BLACK': (20, 20, 20),
'YELLOW': (255, 220, 0),
'RED': (220, 20, 20)
}
# ==========================================
# ЧАСТИЦЫ И ЭФФЕКТЫ
# ==========================================
class Particle:
def __init__(self, x, y, dx, dy, color, size, life):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.color = color
self.size = size
self.life = life
def update(self, dt):
self.x += self.dx * dt * 60
self.y += self.dy * dt * 60
self.size = max(0.5, self.size - 0.1 * dt * 60)
self.life -= 1 * dt * 60
def draw(self, surface):
if self.life > 0:
pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), int(self.size))
# ==========================================
# СЕГМЕНТ ТРАССЫ И ПРОЕКЦИЯ
# ==========================================
class Segment:
def __init__(self, index):
self.index = index
self.p1 = {'world': {'x': 0, 'y': 0, 'z': 0}, 'screen': {'x': 0, 'y': 0, 'w': 0}}
self.p2 = {'world': {'x': 0, 'y': 0, 'z': 0}, 'screen': {'x': 0, 'y': 0, 'w': 0}}
self.curve = 0
self.clip = 0
self.sprite = None # Дерево, кактус или знак
self.sprite_offset = 0
self.color = self._get_colors()
def _get_colors(self):
dark = (self.index // 3) % 2
return {
'road': COLORS['ROAD_DARK'] if dark else COLORS['ROAD_LIGHT'],
'grass': COLORS['GRASS_DARK'] if dark else COLORS['GRASS_LIGHT'],
'rumble': COLORS['RUMBLE_DARK'] if dark else COLORS['RUMBLE_LIGHT'],
'lane': COLORS['LANE'] if not dark else COLORS['ROAD_LIGHT']
}
def project(self, cam_x, cam_y, cam_z):
self._project_point(self.p1, cam_x, cam_y, cam_z)
self._project_point(self.p2, cam_x, cam_y, cam_z)
def _project_point(self, p, cx, cy, cz):
world = p['world']
screen = p['screen']
tx = world['x'] - cx
ty = world['y'] - cy
tz = world['z'] - cz
if tz <= 0:
tz = 0.001
scale = (SCREEN_HEIGHT / 2) / tz
screen['x'] = int((SCREEN_WIDTH / 2) + (scale * tx))
screen['y'] = int((SCREEN_HEIGHT / 2) - (scale * ty))
screen['w'] = int(scale * ROAD_WIDTH)
# ==========================================
# МАШИНЫ ТРАФИКА
# ==========================================
class NPCVehicle:
def __init__(self, z, offset, speed):
self.z = z
self.offset = offset
self.speed = speed
self.width = 160
self.height = 80
self.color = random.choice([
(65, 105, 225), (46, 139, 87), (218, 165, 32),
(148, 0, 211), (255, 140, 0), (200, 200, 200)
])
def update(self, dt, track_length):
self.z = (self.z + self.speed * dt) % track_length
# ==========================================
# МАШИНА ИГРОКА
# ==========================================
class PlayerCar:
def __init__(self):
self.x = 0.0
self.z = 0.0
self.speed = 0.0
self.max_speed = 13500.0
self.accel = self.max_speed / 4.5
self.breaking = -self.max_speed * 1.5
self.decel = -self.max_speed / 5.0
self.off_road_decel = -self.max_speed / 1.5
self.turn_speed = 3.5
self.gear = 1
self.gears = {
1: {'max': 3500, 'acc': 1.3},
2: {'max': 7000, 'acc': 1.0},
3: {'max': 10500, 'acc': 0.8},
4: {'max': 13500, 'acc': 0.55}
}
# ==========================================
# ОСНОВНОЙ КЛАСС ИГРЫ
# ==========================================
class Game:
def __init__(self):
pygame.init()
pygame.font.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Retro Highway Racer")
self.clock = pygame.time.Clock()
self.font_small = pygame.font.SysFont("monospace", 18, bold=True)
self.font_medium = pygame.font.SysFont("monospace", 28, bold=True)
self.font_big = pygame.font.SysFont("monospace", 46, bold=True)
self.segments = []
self.track_length = 0
self.build_track()
self.player = PlayerCar()
self.traffic = []
self.spawn_traffic(45)
self.particles = []
self.lap_time = 0.0
self.best_lap = 0.0
self.checkpoint_time = 40.0
self.game_over = False
def build_track(self):
self.segments.clear()
def add_road(enter, hold, leave, curve, elevation):
start_y = 0 if len(self.segments) == 0 else self.segments[-1].p2['world']['y']
total = enter + hold + leave
for i in range(total):
idx = len(self.segments)
seg = Segment(idx)
seg.p1['world']['z'] = idx * SEGMENT_LENGTH
seg.p2['world']['z'] = (idx + 1) * SEGMENT_LENGTH
# Расчет кривизны поворота
if i < enter:
c = curve * (i / enter)
seg_y = start_y + (elevation - start_y) * (i / enter)
elif i < enter + hold:
c = curve
seg_y = elevation
else:
c = curve * (1 - (i - enter - hold) / leave)
seg_y = elevation + (0 - elevation) * ((i - enter - hold) / leave)
seg.curve = c
seg.p1['world']['y'] = start_y if i == 0 else self.segments[-1].p2['world']['y']
seg.p2['world']['y'] = seg_y
# Генерация придорожных объектов
if idx % 15 == 0 and random.random() < 0.8:
seg.sprite = 'tree'
seg.sprite_offset = -1.6 if random.random() < 0.5 else 1.6
self.segments.append(seg)
# Конструктор трассы (вход, ровно, выход, кривизна, высота холма)
add_road(50, 100, 50, 0, 0)
add_road(80, 100, 80, 4, 1800)
add_road(60, 80, 60, -3.5, -1200)
add_road(100, 120, 100, 0, 2200)
add_road(70, 90, 70, 5, 0)
add_road(80, 80, 80, -4.5, -1500)
add_road(60, 60, 60, 0, 0)
self.track_length = len(self.segments) * SEGMENT_LENGTH
def spawn_traffic(self, count):
for _ in range(count):
z = random.uniform(2000, self.track_length - 1000)
offset = random.choice([-0.7, -0.35, 0.35, 0.7])
speed = random.uniform(3500, 7500)
self.traffic.append(NPCVehicle(z, offset, speed))
def find_segment(self, z):
return self.segments[int(z / SEGMENT_LENGTH) % len(self.segments)]
def handle_input(self, dt):
if self.game_over:
return
keys = pygame.key.get_pressed()
# Автоматическая КПП
for g, data in self.player.gears.items():
if self.player.speed <= data['max']:
self.player.gear = g
break
current_gear = self.player.gears[self.player.gear]
# Газ / Тормоз
if keys[pygame.K_UP] or keys[pygame.K_w]:
self.player.speed += self.player.accel * current_gear['acc'] * dt
elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
self.player.speed += self.player.breaking * dt
else:
self.player.speed += self.player.decel * dt
# Руление
speed_factor = self.player.speed / self.player.max_speed
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.player.x -= self.player.turn_speed * speed_factor * dt
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.player.x += self.player.turn_speed * speed_factor * dt
# Поведение на траве / обочине
if (self.player.x < -1.0 or self.player.x > 1.0) and self.player.speed > 500:
self.player.speed += self.player.off_road_decel * dt
if random.random() < 0.4:
self.particles.append(Particle(
SCREEN_WIDTH // 2 + random.randint(-40, 40),
SCREEN_HEIGHT - 30,
random.uniform(-1, 1), -1.5,
(170, 170, 170), random.randint(4, 8), 15
))
self.player.speed = max(0.0, min(self.player.speed, self.player.max_speed))
def update(self, dt):
if self.game_over:
return
self.handle_input(dt)
old_z = self.player.z
self.player.z = (self.player.z + self.player.speed * dt) % self.track_length
# Фиксация нового круга
if self.player.z < old_z:
if self.best_lap == 0 or self.lap_time < self.best_lap:
self.best_lap = self.lap_time
self.lap_time = 0.0
self.checkpoint_time += 30.0 # Бонусное время за круг
self.lap_time += dt
self.checkpoint_time -= dt
if self.checkpoint_time <= 0:
self.game_over = True
player_seg = self.find_segment(self.player.z)
# Центробежный снос
self.player.x -= (player_seg.curve * (self.player.speed / self.player.max_speed) * 1.8) * dt
# Обновление NPC
for car in self.traffic:
car.update(dt, self.track_length)
# Проверка коллизии с игроком
dist_z = abs(car.z - self.player.z)
if dist_z < 120 and abs(self.player.x - car.offset) < 0.3:
self.player.speed = min(self.player.speed * 0.3, 2000)
# Искры и дым от удара
for _ in range(20):
self.particles.append(Particle(
SCREEN_WIDTH // 2, SCREEN_HEIGHT - 60,
random.uniform(-4, 4), random.uniform(-4, 1),
(255, random.randint(50, 200), 0), random.randint(4, 9), 25
))
# Обновление частиц
for p in self.particles[:]:
p.update(dt)
if p.life <= 0:
self.particles.remove(p)
def draw_polygon(self, color, x1, y1, x2, y2, x3, y3, x4, y4):
pygame.draw.polygon(self.screen, color, [(x1, y1), (x2, y2), (x3, y3), (x4, y4)])
def draw_segment(self, seg):
p1 = seg.p1['screen']
p2 = seg.p2['screen']
# Трава
pygame.draw.rect(self.screen, seg.color['grass'], (0, p2['y'], SCREEN_WIDTH, p1['y'] - p2['y']))
# Обочина
r1 = p1['w'] / 6
r2 = p2['w'] / 6
self.draw_polygon(seg.color['rumble'],
p1['x'] - p1['w'] - r1, p1['y'],
p1['x'] - p1['w'], p1['y'],
p2['x'] - p2['w'], p2['y'],
p2['x'] - p2['w'] - r2, p2['y'])
self.draw_polygon(seg.color['rumble'],
p1['x'] + p1['w'] + r1, p1['y'],
p1['x'] + p1['w'], p1['y'],
p2['x'] + p2['w'], p2['y'],
p2['x'] + p2['w'] + r2, p2['y'])
# Полотно асфальта
self.draw_polygon(seg.color['road'],
p1['x'] - p1['w'], p1['y'],
p1['x'] + p1['w'], p1['y'],
p2['x'] + p2['w'], p2['y'],
p2['x'] - p2['w'], p2['y'])
# Разметка
l1 = p1['w'] / 32
l2 = p2['w'] / 32
self.draw_polygon(seg.color['lane'],
p1['x'] - l1, p1['y'],
p1['x'] + l1, p1['y'],
p2['x'] + l2, p2['y'],
p2['x'] - l2, p2['y'])
def draw_scenery(self, seg):
if not seg.sprite:
return
scale = (SCREEN_HEIGHT / 2) / (seg.p1['world']['z'] - self.player.z if seg.p1['world']['z'] > self.player.z else (self.track_length - self.player.z + seg.p1['world']['z']))
if scale <= 0:
return
sx = int((SCREEN_WIDTH / 2) + (scale * (seg.sprite_offset * ROAD_WIDTH - self.player.x * ROAD_WIDTH)))
sy = seg.p1['screen']['y']
w = int(120 * scale * 300)
h = int(240 * scale * 300)
if 0 <= sy < SCREEN_HEIGHT and -w < sx < SCREEN_WIDTH + w:
# Ствол
pygame.draw.rect(self.screen, (101, 67, 33), (sx - w // 8, sy - h, w // 4, h))
# Крона
pygame.draw.circle(self.screen, (34, 139, 34), (sx, sy - h), w // 2)
def draw_player(self):
x = SCREEN_WIDTH // 2
y = SCREEN_HEIGHT - 35
w = 150
h = 75
# Кузов спорткара
pygame.draw.polygon(self.screen, COLORS['RED'], [
(x - w // 2, y),
(x - w // 3, y - h),
(x + w // 3, y - h),
(x + w // 2, y)
])
# Заднее стекло / крыша
pygame.draw.polygon(self.screen, (40, 40, 40), [
(x - w // 3 + 8, y - h + 12),
(x - w // 4 + 8, y - h * 1.3),
(x + w // 4 - 8, y - h * 1.3),
(x + w // 3 - 8, y - h + 12)
])
# Задний диффузор и стоп-сигналы
pygame.draw.rect(self.screen, COLORS['BLACK'], (x - w // 2 + 10, y - 25, w - 20, 18))
pygame.draw.circle(self.screen, (255, 30, 30), (x - w // 2 + 25, y - 16), 8)
pygame.draw.circle(self.screen, (255, 30, 30), (x + w // 2 - 25, y - 16), 8)
def draw_traffic(self, base_seg):
for car in self.traffic:
car_seg = self.find_segment(car.z)
if base_seg.index <= car_seg.index < base_seg.index + DRAW_DISTANCE:
dz = car.z - self.player.z if car.z > self.player.z else (self.track_length - self.player.z + car.z)
scale = (SCREEN_HEIGHT / 2) / dz
if scale <= 0:
continue
sx = int((SCREEN_WIDTH / 2) + (scale * (car.offset * ROAD_WIDTH - self.player.x * ROAD_WIDTH)))
sy = int((SCREEN_HEIGHT / 2) - (scale * (car_seg.p1['world']['y'] - CAMERA_HEIGHT)))
w = int(car.width * scale * 300)
h = int(car.height * scale * 300)
if 0 <= sy < SCREEN_HEIGHT and 0 <= sx < SCREEN_WIDTH:
# Корпус NPC
pygame.draw.rect(self.screen, car.color, (sx - w // 2, sy - h, w, h))
# Огни
pygame.draw.rect(self.screen, (255, 50, 50), (sx - w // 2 + 4, sy - h // 3, w // 5, h // 4))
pygame.draw.rect(self.screen, (255, 50, 50), (sx + w // 2 - 4 - w // 5, sy - h // 3, w // 5, h // 4))
def render(self):
# Отрисовка неба и градиента горизонта
self.screen.fill(COLORS['SKY'])
pygame.draw.rect(self.screen, (90, 130, 200), (0, SCREEN_HEIGHT // 2, SCREEN_WIDTH, SCREEN_HEIGHT // 2))
base_seg = self.find_segment(self.player.z)
max_y = SCREEN_HEIGHT
x = 0
dx = -(base_seg.curve * 0.1)
# Отрисовка дороги от горизонта к игроку
for n in range(DRAW_DISTANCE):
seg = self.segments[(base_seg.index + n) % len(self.segments)]
cam_z = self.player.z - (self.track_length if (base_seg.index + n) >= len(self.segments) else 0)
seg.project(self.player.x * ROAD_WIDTH - x, CAMERA_HEIGHT, cam_z)
x += dx
dx += seg.curve
if seg.p1['screen']['y'] >= max_y or seg.p2['screen']['y'] >= seg.p1['screen']['y']:
continue
self.draw_segment(seg)
self.draw_scenery(seg)
max_y = seg.p1['screen']['y']
self.draw_traffic(base_seg)
self.draw_player()
for p in self.particles:
p.draw(self.screen)
# Рендеринг HUD
speed_kmh = int((self.player.speed / self.player.max_speed) * 290)
rpm = int((self.player.speed / self.player.gears[self.player.gear]['max']) * 7800)
hud_speed = self.font_big.render(f"{speed_kmh} KM/H", True, COLORS['YELLOW'])
hud_gear = self.font_small.render(f"GEAR: {self.player.gear} RPM: {rpm}", True, COLORS['WHITE'])
hud_timer = self.font_medium.render(f"TIME: {max(0.0, self.checkpoint_time):.1f}s", True, COLORS['WHITE'])
hud_lap = self.font_small.render(f"LAP: {self.lap_time:.2f}s", True, (200, 200, 200))
hud_best = self.font_small.render(f"BEST: {self.best_lap:.2f}s" if self.best_lap > 0 else "BEST: --:--", True, (200, 200, 200))
self.screen.blit(hud_speed, (30, SCREEN_HEIGHT - 70))
self.screen.blit(hud_gear, (35, SCREEN_HEIGHT - 100))
self.screen.blit(hud_timer, (30, 30))
self.screen.blit(hud_lap, (30, 70))
self.screen.blit(hud_best, (30, 95))
if self.game_over:
over_text = self.font_big.render("TIME OVER", True, COLORS['RED'])
sub_text = self.font_small.render("Press 'R' to Restart", True, COLORS['WHITE'])
self.screen.blit(over_text, (SCREEN_WIDTH // 2 - over_text.get_width() // 2, SCREEN_HEIGHT // 2 - 40))
self.screen.blit(sub_text, (SCREEN_WIDTH // 2 - sub_text.get_width() // 2, SCREEN_HEIGHT // 2 + 20))
pygame.display.flip()
def reset_game(self):
self.player = PlayerCar()
self.lap_time = 0.0
self.checkpoint_time = 40.0
self.game_over = False
self.particles.clear()
def run(self):
while True:
dt = self.clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_r and self.game_over:
self.reset_game()
self.update(dt)
self.render()
if __name__ == "__main__":
game = Game()
game.run()