Загрузка данных
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import random
import sys
# --- ВЕКТОРНАЯ МАТЕМАТИКА ---
def v_len(v):
return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
def v_norm(v):
l = v_len(v)
return [0.0, 0.0, 0.0] if l < 1e-6 else [v[0]/l, v[1]/l, v[2]/l]
def v_dist(a, b):
return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2)
# --- 3D ГЕОМЕТРИЯ ---
def draw_box(size, color, offset=(0, 0, 0)):
sx, sy, sz = size[0] / 2.0, size[1] / 2.0, size[2] / 2.0
ox, oy, oz = offset[0], offset[1], offset[2]
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, [*color, 1.0])
glBegin(GL_QUADS)
# Front
glNormal3f(0, 0, 1)
glVertex3f(ox-sx, oy-sy, oz+sz); glVertex3f(ox+sx, oy-sy, oz+sz)
glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox-sx, oy+sy, oz+sz)
# Back
glNormal3f(0, 0, -1)
glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox-sx, oy+sy, oz-sz)
glVertex3f(ox+sx, oy+sy, oz-sz); glVertex3f(ox+sx, oy-sy, oz-sz)
# Top
glNormal3f(0, 1, 0)
glVertex3f(ox-sx, oy+sy, oz-sz); glVertex3f(ox-sx, oy+sy, oz+sz)
glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox+sx, oy+sy, oz-sz)
# Bottom
glNormal3f(0, -1, 0)
glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox+sx, oy-sy, oz-sz)
glVertex3f(ox+sx, oy-sy, oz+sz); glVertex3f(ox-sx, oy-sy, oz+sz)
# Right
glNormal3f(1, 0, 0)
glVertex3f(ox+sx, oy-sy, oz-sz); glVertex3f(ox+sx, oy+sy, oz-sz)
glVertex3f(ox+sx, oy+sy, oz+sz); glVertex3f(ox+sx, oy-sy, oz+sz)
# Left
glNormal3f(-1, 0, 0)
glVertex3f(ox-sx, oy-sy, oz-sz); glVertex3f(ox-sx, oy-sy, oz+sz)
glVertex3f(ox-sx, oy+sy, oz+sz); glVertex3f(ox-sx, oy+sy, oz-sz)
glEnd()
def draw_cylinder(radius, length, segments=12, color=(0.2, 0.2, 0.2)):
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, [*color, 1.0])
glBegin(GL_QUAD_STRIP)
for i in range(segments + 1):
angle = 2.0 * math.pi * i / segments
x = radius * math.cos(angle)
y = radius * math.sin(angle)
glNormal3f(math.cos(angle), math.sin(angle), 0.0)
glVertex3f(x, y, 0.0)
glVertex3f(x, y, length)
glEnd()
# --- СИСТЕМА ЧАСТИЦ ---
class ParticleSystem:
def __init__(self):
self.particles = []
def emit(self, pos, count=16, color=(1.0, 0.5, 0.1), speed=9.0, size=0.28):
for _ in range(count):
rx, ry, rz = random.uniform(-1, 1), random.uniform(0.5, 2.2), random.uniform(-1, 1)
norm = v_norm([rx, ry, rz])
sp = random.uniform(speed * 0.3, speed)
self.particles.append({
'x': pos[0], 'y': pos[1], 'z': pos[2],
'vx': norm[0] * sp, 'vy': norm[1] * sp, 'vz': norm[2] * sp,
'life': 1.0, 'decay': random.uniform(1.2, 2.5),
'color': color, 'size': size
})
def update(self, dt):
survivors = []
for p in self.particles:
p['x'] += p['vx'] * dt
p['y'] += p['vy'] * dt
p['z'] += p['vz'] * dt
p['vy'] -= 9.8 * dt * 0.6
p['life'] -= p['decay'] * dt
if p['life'] > 0:
survivors.append(p)
self.particles = survivors
def draw(self):
glDisable(GL_LIGHTING)
for p in self.particles:
s = p['size'] * p['life']
glPushMatrix()
glTranslatef(p['x'], p['y'], p['z'])
draw_box((s, s, s), p['color'])
glPopMatrix()
glEnable(GL_LIGHTING)
# --- БОНУСЫ (АПТЕЧКИ И БУСТЕРЫ) ---
class DropItem:
def __init__(self, x, z, item_type="heal"):
self.pos = [float(x), 0.8, float(z)]
self.item_type = item_type
self.rot = 0.0
self.alive = True
def update(self, dt):
self.rot += 90.0 * dt
def draw(self):
if not self.alive:
return
glPushMatrix()
glTranslatef(self.pos[0], self.pos[1], self.pos[2])
glRotatef(self.rot, 0, 1, 0)
if self.item_type == "heal":
draw_box((1.2, 1.2, 1.2), (0.1, 0.8, 0.2))
draw_box((0.4, 1.25, 1.25), (1.0, 1.0, 1.0))
draw_box((1.25, 1.25, 0.4), (1.0, 1.0, 1.0))
glPopMatrix()
# --- СНАРЯД С РАСЧЕТОМ КОЛЛИЗИЙ ---
class Shell:
def __init__(self, pos, direction, is_player=True, dmg=1):
self.pos = list(pos)
self.dir = v_norm(direction)
self.speed = 85.0
self.is_player = is_player
self.dmg = dmg
self.active = True
self.life = 3.0
def update(self, dt):
self.pos[0] += self.dir[0] * self.speed * dt
self.pos[1] += self.dir[1] * self.speed * dt
self.pos[2] += self.dir[2] * self.speed * dt
self.life -= dt
if self.life <= 0:
self.active = False
def draw(self):
glPushMatrix()
glTranslatef(self.pos[0], self.pos[1], self.pos[2])
col = (1.0, 0.9, 0.1) if self.is_player else (1.0, 0.2, 0.1)
draw_box((0.3, 0.3, 0.8), col)
glPopMatrix()
# --- СТРОЕНИЯ ---
class Building:
def __init__(self, x, z, sx, sy, sz):
self.pos = [float(x), sy / 2.0, float(z)]
self.size = (float(sx), float(sy), float(sz))
self.hp = 4
self.alive = True
self.color = (0.42, 0.38, 0.35)
def draw(self):
if not self.alive:
return
draw_box(self.size, self.color, offset=self.pos)
def check_point_collision(self, px, pz, radius=1.8):
if not self.alive:
return False
min_x = self.pos[0] - self.size[0] / 2.0 - radius
max_x = self.pos[0] + self.size[0] / 2.0 + radius
min_z = self.pos[2] - self.size[2] / 2.0 - radius
max_z = self.pos[2] + self.size[2] / 2.0 + radius
return (min_x <= px <= max_x) and (min_z <= pz <= max_z)
# --- ТАНК ---
class Tank:
def __init__(self, x, z, base_color, max_hp=5, speed=10.0, is_player=False, tank_type="assault"):
self.pos = [float(x), 0.0, float(z)]
self.rotation_y = 0.0
self.turret_angle = 0.0
self.recoil = 0.0
self.color = base_color
self.is_player = is_player
self.tank_type = tank_type
self.max_hp = max_hp
self.hp = max_hp
self.speed = speed
self.turn_speed = 95.0
self.shoot_cd = 0.0
self.max_shoot_cd = 0.45 if is_player else 2.5
self.alive = True
def get_forward_vector(self):
rad = math.radians(self.rotation_y)
return [math.sin(rad), 0.0, math.cos(rad)]
def get_turret_forward(self):
rad = math.radians(self.rotation_y + self.turret_angle)
return [math.sin(rad), 0.0, math.cos(rad)]
def update(self, dt):
if self.shoot_cd > 0:
self.shoot_cd -= dt
if self.recoil > 0:
self.recoil = max(0.0, self.recoil - dt * 4.0)
def draw(self):
if not self.alive:
return
glPushMatrix()
glTranslatef(self.pos[0], self.pos[1], self.pos[2])
glRotatef(self.rotation_y, 0, 1, 0)
# Гусеницы
draw_box((0.8, 0.7, 4.6), (0.12, 0.12, 0.12), offset=(-1.4, 0.35, 0.0))
draw_box((0.8, 0.7, 4.6), (0.12, 0.12, 0.12), offset=(1.4, 0.35, 0.0))
# Корпус
draw_box((2.4, 0.7, 4.2), self.color, offset=(0.0, 0.6, 0.0))
draw_box((2.1, 0.35, 3.8), [c * 0.85 for c in self.color], offset=(0.0, 0.95, -0.1))
# Башня
glPushMatrix()
glTranslatef(0.0, 1.25, 0.0)
glRotatef(self.turret_angle, 0, 1, 0)
draw_box((1.9, 0.7, 2.4), self.color, offset=(0.0, 0.0, -0.2))
draw_box((0.8, 0.4, 0.6), (0.1, 0.1, 0.1), offset=(0.0, 0.0, 1.0))
draw_box((0.5, 0.3, 0.5), (0.2, 0.2, 0.2), offset=(0.5, 0.45, -0.4))
# Ствол
glPushMatrix()
glTranslatef(0.0, 0.0, 1.25 - self.recoil)
draw_cylinder(0.14, 3.0, segments=14, color=(0.15, 0.15, 0.15))
draw_box((0.38, 0.38, 0.55), (0.1, 0.1, 0.1), offset=(0.0, 0.0, 3.0))
glPopMatrix()
glPopMatrix()
glPopMatrix()
# Аккуратный HP-бар только для врагов
if not self.is_player and self.alive:
self.draw_mini_hp()
def draw_mini_hp(self):
glDisable(GL_LIGHTING)
glPushMatrix()
glTranslatef(self.pos[0], 3.2, self.pos[2])
# Billboard
mat = glGetFloatv(GL_MODELVIEW_MATRIX)
for i in range(3):
for j in range(3):
mat[i][j] = 1.0 if i == j else 0.0
glLoadMatrixf(mat)
pct = max(0.0, self.hp / self.max_hp)
draw_box((2.0, 0.2, 0.02), (0.1, 0.1, 0.1))
col = (0.2, 0.9, 0.2) if pct > 0.5 else ((0.9, 0.6, 0.1) if pct > 0.25 else (0.9, 0.1, 0.1))
draw_box((1.96 * pct, 0.16, 0.04), col, offset=(-0.98 * (1.0 - pct), 0.0, 0.01))
glPopMatrix()
glEnable(GL_LIGHTING)
# --- ГЛАВНЫЙ ИГРОВОЙ ДВИЖОК ---
class TankWarGame:
def __init__(self):
pygame.init()
self.res = (1280, 720)
pygame.display.set_mode(self.res, DOUBLEBUF | OPENGL)
pygame.display.set_caption("Iron Steel: 3D Panzer Warfare")
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
self.clock = pygame.time.Clock()
self.particles = ParticleSystem()
self.map_size = 280.0
self.score = 0
self.wave = 1
self.game_over = False
self.init_gl()
self.init_world()
def init_gl(self):
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE)
glLightfv(GL_LIGHT0, GL_POSITION, [80.0, 120.0, 80.0, 1.0])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.95, 0.92, 0.88, 1.0])
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.42, 0.42, 0.45, 1.0])
glClearColor(0.48, 0.68, 0.88, 1.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60, (self.res[0] / self.res[1]), 0.5, 450.0)
glMatrixMode(GL_MODELVIEW)
def init_world(self):
# Игрок: 15 HP, хорошая скорость
self.player = Tank(0, -50, (0.2, 0.6, 0.25), max_hp=15, speed=15.0, is_player=True)
self.enemies = []
self.bullets = []
self.buildings = []
self.drops = []
# Генерация плотного города укрытий
random.seed(999)
for _ in range(50):
bx = random.uniform(-self.map_size / 2 + 25, self.map_size / 2 - 25)
bz = random.uniform(-self.map_size / 2 + 25, self.map_size / 2 - 25)
if abs(bx) > 16 or abs(bz) > 16:
self.buildings.append(Building(bx, bz, random.choice([6, 9, 12]), random.choice([5, 8, 10]), random.choice([6, 9, 12])))
self.spawn_wave()
self.cam_yaw = 0.0
self.cam_pitch = 22.0
self.cam_dist = 17.0
def spawn_wave(self):
# Спавн разных типов танков
count = 4 + self.wave * 2
for _ in range(count):
ex = random.uniform(-self.map_size / 2 + 30, self.map_size / 2 - 30)
ez = random.uniform(10, self.map_size / 2 - 30)
t_roll = random.random()
if t_roll < 0.35:
# Разведчик
e = Tank(ex, ez, (0.85, 0.6, 0.1), max_hp=2, speed=12.0, tank_type="scout")
e.max_shoot_cd = 1.8
elif t_roll < 0.8:
# Штурмовик
e = Tank(ex, ez, (0.75, 0.25, 0.2), max_hp=4, speed=8.0, tank_type="assault")
e.max_shoot_cd = 2.4
else:
# Джаггернаут
e = Tank(ex, ez, (0.45, 0.15, 0.45), max_hp=8, speed=5.5, tank_type="heavy")
e.max_shoot_cd = 3.2
self.enemies.append(e)
def handle_input(self, dt):
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
if event.key == K_r and self.game_over:
self.score = 0
self.wave = 1
self.game_over = False
self.init_world()
return
if event.type == MOUSEMOTION:
dx, dy = event.rel
self.cam_yaw -= dx * 0.22
self.cam_pitch = max(8.0, min(55.0, self.cam_pitch + dy * 0.16))
if event.type == MOUSEBUTTONDOWN and event.button == 1:
if self.player.alive and self.player.shoot_cd <= 0:
self.fire_tank(self.player)
if not self.player.alive:
return
keys = pygame.key.get_pressed()
if keys[K_a]:
self.player.rotation_y += self.player.turn_speed * dt
if keys[K_d]:
self.player.rotation_y -= self.player.turn_speed * dt
move_dir = 0.0
if keys[K_w]:
move_dir += 1.0
if keys[K_s]:
move_dir -= 0.65
if move_dir != 0:
fwd = self.player.get_forward_vector()
step_x = fwd[0] * (move_dir * self.player.speed * dt)
step_z = fwd[2] * (move_dir * self.player.speed * dt)
new_x = self.player.pos[0] + step_x
new_z = self.player.pos[2] + step_z
limit = self.map_size / 2.0 - 5.0
if abs(new_x) < limit and abs(new_z) < limit:
# Коллизия игрока со зданиями
blocked = False
for b in self.buildings:
if b.check_point_collision(new_x, new_z, radius=2.2):
blocked = True
break
# Коллизия игрока с танками
if not blocked:
for e in self.enemies:
if e.alive and v_dist([new_x, 0, new_z], e.pos) < 3.8:
blocked = True
break
if not blocked:
self.player.pos[0] = new_x
self.player.pos[2] = new_z
# Поворот башни на точку взгляда
target_turret = (self.cam_yaw - self.player.rotation_y) % 360
if target_turret > 180:
target_turret -= 360
self.player.turret_angle = target_turret
def fire_tank(self, tank):
tank.shoot_cd = tank.max_shoot_cd
tank.recoil = 0.45
fwd = tank.get_turret_forward()
spawn_pos = [
tank.pos[0] + fwd[0] * 4.0,
tank.pos[1] + 1.25,
tank.pos[2] + fwd[2] * 4.0
]
self.bullets.append(Shell(spawn_pos, fwd, is_player=tank.is_player, dmg=2 if tank.tank_type == "heavy" else 1))
self.particles.emit(spawn_pos, count=16, color=(1.0, 0.75, 0.2), speed=7.0, size=0.25)
def update_ai(self, dt):
for e in self.enemies:
if not e.alive:
continue
dist_to_p = v_dist(e.pos, self.player.pos)
to_p = [self.player.pos[0] - e.pos[0], 0, self.player.pos[2] - e.pos[2]]
target_yaw = math.degrees(math.atan2(to_p[0], to_p[2]))
rel_yaw = (target_yaw - e.rotation_y) % 360
if rel_yaw > 180:
rel_yaw -= 360
e.turret_angle = rel_yaw
sign = 1.0 if rel_yaw > 0 else (-1.0 if rel_yaw < 0 else 0.0)
e.rotation_y += sign * e.turn_speed * 0.45 * dt
# Обход и движение
if dist_to_p > 22.0:
fwd = e.get_forward_vector()
nx = e.pos[0] + fwd[0] * e.speed * dt
nz = e.pos[2] + fwd[2] * e.speed * dt
# Проверка стен ботами
hit_wall = any(b.check_point_collision(nx, nz, radius=2.2) for b in self.buildings)
if not hit_wall:
e.pos[0] = nx
e.pos[2] = nz
else:
e.rotation_y += 60.0 * dt
# Огонь с упреждением
if dist_to_p < 75.0 and e.shoot_cd <= 0 and self.player.alive:
self.fire_tank(e)
e.update(dt)
def update_physics(self, dt):
self.player.update(dt)
self.update_ai(dt)
self.particles.update(dt)
# Бонусы
for d in self.drops:
d.update(dt)
if d.alive and v_dist(self.player.pos, d.pos) < 3.0:
d.alive = False
self.player.hp = min(self.player.max_hp, self.player.hp + 5)
self.particles.emit(d.pos, count=25, color=(0.2, 1.0, 0.4), speed=8.0)
# Снаряды
alive_shells = []
for b in self.bullets:
b.update(dt)
if not b.active:
continue
hit = False
# Коллизия снарядов со зданиями
for bld in self.buildings:
if bld.check_point_collision(b.pos[0], b.pos[2], radius=0.6):
bld.hp -= b.dmg
self.particles.emit(b.pos, count=12, color=(0.6, 0.5, 0.4), speed=6.0)
if bld.hp <= 0:
bld.alive = False
self.particles.emit(bld.pos, count=45, color=(0.4, 0.4, 0.4), speed=12.0, size=0.6)
if random.random() < 0.3:
self.drops.append(DropItem(bld.pos[0], bld.pos[2]))
hit = True
break
if hit:
continue
# Коллизия снарядов с танками
if b.is_player:
for e in self.enemies:
if e.alive and v_dist(b.pos, [e.pos[0], e.pos[1] + 1.0, e.pos[2]]) < 2.5:
e.hp -= b.dmg
self.particles.emit(b.pos, count=25, color=(1.0, 0.3, 0.1), speed=12.0)
if e.hp <= 0:
e.alive = False
self.score += 150
self.particles.emit([e.pos[0], 1.2, e.pos[2]], count=70, color=(0.95, 0.45, 0.1), speed=16.0, size=0.8)
if random.random() < 0.5:
self.drops.append(DropItem(e.pos[0], e.pos[2]))
hit = True
break
else:
if self.player.alive and v_dist(b.pos, [self.player.pos[0], 1.0, self.player.pos[2]]) < 2.5:
self.player.hp -= b.dmg
self.particles.emit(b.pos, count=30, color=(1.0, 0.2, 0.1), speed=14.0)
if self.player.hp <= 0:
self.player.alive = False
self.game_over = True
self.particles.emit(self.player.pos, count=90, color=(1.0, 0.3, 0.1), speed=18.0, size=0.9)
hit = True
if not hit:
alive_shells.append(b)
self.bullets = alive_shells
# Переход к следующей волне
if self.player.alive and len(self.enemies) > 0 and all(not e.alive for e in self.enemies):
self.wave += 1
self.score += 500
self.spawn_wave()
def render_ground(self):
glDisable(GL_LIGHTING)
step = 14
half = int(self.map_size / 2)
glBegin(GL_QUADS)
for x in range(-half, half, step):
for z in range(-half, half, step):
if (x // step + z // step) % 2 == 0:
glColor3f(0.30, 0.44, 0.26)
else:
glColor3f(0.34, 0.48, 0.30)
glVertex3f(x, 0.0, z)
glVertex3f(x + step, 0.0, z)
glVertex3f(x + step, 0.0, z + step)
glVertex3f(x, 0.0, z + step)
glEnd()
glEnable(GL_LIGHTING)
def draw_hud(self):
# Переключение на чистый 2D ортографический рендер
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(0, self.res[0], self.res[1], 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
# 1. ПРИЦЕЛ ПО ЦЕНТРУ ЭКРАНА
cx, cy = self.res[0] // 2, self.res[1] // 2
r = 18
cd_ratio = max(0.0, self.player.shoot_cd / self.player.max_shoot_cd)
cross_col = (1.0, 0.2, 0.2) if cd_ratio > 0 else (0.2, 1.0, 0.4)
glColor3f(*cross_col)
glLineWidth(2.0)
glBegin(GL_LINES)
glVertex2f(cx - r, cy); glVertex2f(cx - 6, cy)
glVertex2f(cx + 6, cy); glVertex2f(cx + r, cy)
glVertex2f(cx, cy - r); glVertex2f(cx, cy - 6)
glVertex2f(cx, cy + 6); glVertex2f(cx, cy + r)
glEnd()
# 2. НИЖНЯЯ ПАНЕЛЬ СОСТОЯНИЯ ТАНКА
panel_w, panel_h = 420, 60
px = self.res[0] // 2 - panel_w // 2
py = self.res[1] - 80
# Подложка
glColor4f(0.08, 0.08, 0.1, 0.8)
glBegin(GL_QUADS)
glVertex2f(px, py); glVertex2f(px + panel_w, py)
glVertex2f(px + panel_w, py + panel_h); glVertex2f(px, py + panel_h)
glEnd()
# Рамка
glColor3f(0.3, 0.35, 0.4)
glBegin(GL_LINE_LOOP)
glVertex2f(px, py); glVertex2f(px + panel_w, py)
glVertex2f(px + panel_w, py + panel_h); glVertex2f(px, py + panel_h)
glEnd()
# Полоса HP (15 сегментов)
bar_x, bar_y = px + 20, py + 20
max_bw = panel_w - 40
hp_pct = max(0.0, self.player.hp / self.player.max_hp)
cur_bw = max_bw * hp_pct
hp_color = (0.2, 0.9, 0.3) if hp_pct > 0.5 else ((0.95, 0.6, 0.1) if hp_pct > 0.25 else (0.9, 0.15, 0.15))
glColor3f(*hp_color)
glBegin(GL_QUADS)
glVertex2f(bar_x, bar_y); glVertex2f(bar_x + cur_bw, bar_y)
glVertex2f(bar_x + cur_bw, bar_y + 20); glVertex2f(bar_x, bar_y + 20)
glEnd()
# 3. МИНИ-КАРТА (РАДАР) В ПРАВОМ ВЕРХНЕМ УГЛУ
radar_size = 130
rx = self.res[0] - radar_size - 25
ry = 25
glColor4f(0.05, 0.08, 0.05, 0.85)
glBegin(GL_QUADS)
glVertex2f(rx, ry); glVertex2f(rx + radar_size, ry)
glVertex2f(rx + radar_size, ry + radar_size); glVertex2f(rx, ry + radar_size)
glEnd()
glColor3f(0.2, 0.6, 0.3)
glBegin(GL_LINE_LOOP)
glVertex2f(rx, ry); glVertex2f(rx + radar_size, ry)
glVertex2f(rx + radar_size, ry + radar_size); glVertex2f(rx, ry + radar_size)
glEnd()
# Точки на радаре
def world_to_radar(wx, wz):
nx = (wx / (self.map_size / 2.0)) * (radar_size / 2.0)
nz = (wz / (self.map_size / 2.0)) * (radar_size / 2.0)
return rx + radar_size / 2.0 + nx, ry + radar_size / 2.0 + nz
glPointSize(4.0)
glBegin(GL_POINTS)
# Здания
glColor3f(0.4, 0.4, 0.4)
for b in self.buildings:
if b.alive:
mx, my = world_to_radar(b.pos[0], b.pos[2])
glVertex2f(mx, my)
# Враги
glColor3f(1.0, 0.2, 0.2)
for e in self.enemies:
if e.alive:
mx, my = world_to_radar(e.pos[0], e.pos[2])
glVertex2f(mx, my)
# Игрок
glColor3f(0.2, 1.0, 0.3)
px_r, py_r = world_to_radar(self.player.pos[0], self.player.pos[2])
glVertex2f(px_r, py_r)
glEnd()
# ТЕКСТОВЫЙ БЛОК (Счет и волна)
font = pygame.font.SysFont("impact", 24)
hud_text = f"WAVE: {self.wave} SCORE: {self.score} ENEMIES: {sum(1 for e in self.enemies if e.alive)}"
surf = font.render(hud_text, True, (240, 240, 240))
data = pygame.image.tostring(surf, "RGBA", True)
glRasterPos2i(30, 45)
glDrawPixels(surf.get_width(), surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, data)
hp_text = f"ARMOR HP: {self.player.hp} / {self.player.max_hp}"
hp_surf = pygame.font.SysFont("consolas", 14, bold=True).render(hp_text, True, (255, 255, 255))
hp_data = pygame.image.tostring(hp_surf, "RGBA", True)
glRasterPos2i(bar_x + 5, py + 15)
glDrawPixels(hp_surf.get_width(), hp_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, hp_data)
# Экран поражения
if self.game_over:
m_font = pygame.font.SysFont("impact", 54)
m_surf = m_font.render("TANK DESTROYED!", True, (255, 40, 40))
m_data = pygame.image.tostring(m_surf, "RGBA", True)
glRasterPos2i(self.res[0] // 2 - m_surf.get_width() // 2, self.res[1] // 2 - 40)
glDrawPixels(m_surf.get_width(), m_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, m_data)
sub_font = pygame.font.SysFont("consolas", 20, bold=True)
s_surf = sub_font.render("Press R to Deploy New Tank | ESC to Exit", True, (255, 255, 255))
s_data = pygame.image.tostring(s_surf, "RGBA", True)
glRasterPos2i(self.res[0] // 2 - s_surf.get_width() // 2, self.res[1] // 2 + 20)
glDrawPixels(s_surf.get_width(), s_surf.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, s_data)
glDisable(GL_BLEND)
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
def run(self):
while True:
dt = self.clock.tick(60) / 1000.0
self.handle_input(dt)
if not self.game_over:
self.update_physics(dt)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
rad_yaw = math.radians(self.cam_yaw)
rad_pitch = math.radians(self.cam_pitch)
cam_x = self.player.pos[0] - self.cam_dist * math.sin(rad_yaw) * math.cos(rad_pitch)
cam_y = self.player.pos[1] + self.cam_dist * math.sin(rad_pitch) + 2.2
cam_z = self.player.pos[2] - self.cam_dist * math.cos(rad_yaw) * math.cos(rad_pitch)
target_y = self.player.pos[1] + 1.2
gluLookAt(cam_x, cam_y, cam_z, self.player.pos[0], target_y, self.player.pos[2], 0, 1, 0)
# Рендеринг сцены
self.render_ground()
for bld in self.buildings:
bld.draw()
for d in self.drops:
d.draw()
self.player.draw()
for e in self.enemies:
e.draw()
for b in self.bullets:
b.draw()
self.particles.draw()
self.draw_hud()
pygame.display.flip()
if __name__ == "__main__":
game = TankWarGame()
game.run()