Пример кода порта графической сцены “Гром” с языка DarkBasic Pro на Lazarus 4.6 + SDL 2 + dglOpenGL + Debian 13. Медиа ресурсы которые использованы в графической сцене и сам исходный код на языке DarkBasic Pro можно скачать с сайта https://ant2on.narod.ru/source.htm или по прямой ссылке http://ant2on.narod.ru/download/storm.zip.
Рисунок 1. Пример работы программы "Гром"
program ogl_p5;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Classes,
SysUtils,
dglOpenGL,
sdl2,
sdl2_mixer,
sdl2_ttf,
Math;
const
WINDOW_WIDTH = 800;
WINDOW_HEIGHT = 600;
type
TRainDrop = record
X, Y, Z: Single;
end;
var
Window: PSDL_Window = nil;
GLContext: TSDL_GLContext = nil;
Event: TSDL_Event;
Running: Boolean = True;
// Настройки симуляции
NoRain: Integer = 100;
CloudSize: Single = 500.0;
CloudHeight: Single = 100.0;
RainDrops: array of TRainDrop;
SoundRain: PMIX_Music = nil;
SoundThunder: PMix_Chunk = nil;
FloorTextureID: GLuint;
MatrixHeights: array[0..25, 0..25] of Single;
// ---------- НОВАЯ СИСТЕМА КАМЕРЫ ----------
CamX: Single = 5000.0; // позиция камеры
CamY: Single = 500.0; // подняли начальную высоту для лучшего обзора
CamZ: Single = 500.0;
CamPitch: Single = 0.0; // угол наклона (вверх/вниз)
CamYaw: Single = -90.0; // угол поворота (влево/вправо) – смотрим вдоль +Z? начальный угол -90 чтобы смотреть в сторону увеличения Z
// Скорость и чувствительность
MoveSpeed: Single = 300.0; // единиц в секунду
MouseSensitivity: Single = 0.2;
// Флаги движения
moveForward, moveBack, moveLeft, moveRight{, moveUp, moveDown}: Boolean;
// НОВОЕ: Флаг захвата мыши
MouseCaptured: Boolean = True;
// Для дельты времени
LastTime: UInt32 = 0;
DeltaTime: Single = 0.0;
ThunderActive: Boolean = False;
Font: PTTF_Font = nil;
procedure InitFont;
begin
if TTF_Init() = -1 then
begin
WriteLn('Ошибка TTF_Init: ', TTF_GetError());
Exit;
end;
// Укажите путь к любому TTF-шрифту в вашей системе
Font := TTF_OpenFont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 16);
if Font = nil then
WriteLn('Ошибка загрузки шрифта: ', TTF_GetError());
end;
procedure InitSystem;
var
audio_rate: Integer;
audio_format: Word;
audio_channels: Integer;
audio_buffers: Integer;
begin
SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
if SDL_Init(SDL_INIT_VIDEO or SDL_INIT_AUDIO) < 0 then
begin
WriteLn('Ошибка инициализации SDL2: ', SDL_GetError());
Halt(1);
end;
audio_rate := 44100;
audio_format := AUDIO_S16SYS;
audio_channels := 2;
audio_buffers := 2048;
if Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) < 0 then
begin
WriteLn('Ошибка Mix_OpenAudio: ', Mix_GetError());
SDL_Quit();
Exit;
end;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
Window := SDL_CreateWindow(
'SDL2 + dglOpenGL + Lazarus 4.6 + Debian 13',
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT,
SDL_WINDOW_OPENGL or SDL_WINDOW_SHOWN or SDL_WINDOW_RESIZABLE
);
if Window = nil then
raise Exception.Create('Не удалось создать окно SDL2');
GLContext := SDL_GL_CreateContext(Window);
if GLContext = nil then
raise Exception.Create('Не удалось создать контекст OpenGL');
if not InitOpenGL then
raise Exception.Create('Не удалось инициализировать dglOpenGL');
ReadExtensions;
ReadImplementationProperties;
InitFont;
// FIX: дальняя плоскость увеличена до 50000 (было 100)
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, WINDOW_WIDTH / WINDOW_HEIGHT, 0.1, 50000.0);
glMatrixMode(GL_MODELVIEW);
glClearColor(0.1, 0.1, 0.15, 1.0);
// Включаем глубину
glEnable(GL_DEPTH_TEST);
end;
procedure HandleResize(Width, Height: Integer);
begin
if Height = 0 then Height := 1;
glViewport(0, 0, Width, Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, Width / Height, 0.1, 50000.0); // FIX
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
end;
// NEW: обработка ввода с клавиатуры (непрерывное состояние)
procedure ProcessKeyboardInput;
var
KeyState: PUint8;
begin
KeyState := SDL_GetKeyboardState(nil);
moveForward := KeyState[SDL_SCANCODE_W] = 1;
moveBack := KeyState[SDL_SCANCODE_S] = 1;
moveLeft := KeyState[SDL_SCANCODE_A] = 1;
moveRight := KeyState[SDL_SCANCODE_D] = 1;
//moveUp := KeyState[SDL_SCANCODE_Q] = 1; // подъём
//moveDown := KeyState[SDL_SCANCODE_E] = 1; // спуск
end;
// NEW: обработка событий окна и клавиатуры
procedure HandleEvents;
begin
while SDL_PollEvent(@Event) <> 0 do
begin
case Event.type_ of
SDL_QUITEV: Running := False;
SDL_WINDOWEVENT:
if Event.window.event in [SDL_WINDOWEVENT_RESIZED, SDL_WINDOWEVENT_SIZE_CHANGED] then
HandleResize(Event.window.data1, Event.window.data2);
SDL_KEYDOWN:
case Event.key.keysym.sym of
SDLK_ESCAPE:
begin
if MouseCaptured then
begin
// Если мышь захвачена - освобождаем её, чтобы можно было нажать на кнопки окна
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_ShowCursor(SDL_ENABLE);
MouseCaptured := False;
end
else
begin
// Если мышь уже свободна, повторное нажатие Esc закрывает игру
Running := False;
end;
end;
SDLK_F11:
begin
// Переключение полноэкранного режима (Borderless Fullscreen)
if (SDL_GetWindowFlags(Window) and SDL_WINDOW_FULLSCREEN_DESKTOP) <> 0 then
SDL_SetWindowFullscreen(Window, 0) // Выход из полноэкранного режима
else
SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN_DESKTOP); // Разворот на весь экран
end;
end;
SDL_MOUSEBUTTONDOWN:
begin
// Если мышь свободна и пользователь кликнул по окну, захватываем её обратно
if not MouseCaptured then
begin
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_ShowCursor(SDL_DISABLE);
MouseCaptured := True;
end;
end;
SDL_MOUSEMOTION:
begin
// Вращение камеры мышью (только если мышь захвачена)
if MouseCaptured then
begin
CamYaw := CamYaw + Event.motion.xrel * MouseSensitivity;
CamPitch := CamPitch - Event.motion.yrel * MouseSensitivity;
if CamPitch > 89.0 then CamPitch := 89.0;
if CamPitch < -89.0 then CamPitch := -89.0;
end;
end;
end;
end;
end;
procedure DrawHints;
var
W, H: Integer;
Lines: array of string;
I: Integer;
Surface: PSDL_Surface;
TexID: GLuint;
Color: TSDL_Color;
XPos, YPos: Integer;
BgHeight: Integer;
MaxWidth: Integer;
LineHeights: array of Integer;
TotalHeight: Integer;
ConvSurface: PSDL_Surface;
begin
if Font = nil then Exit;
SDL_GetWindowSize(Window, @W, @H);
Lines := [
'ESC - Освободить мышку (Нажмите повторно для выхода)',
'LMB - Захватить мышку',
'F11 - Во весь экран (Нажмите повторно для режима окна)'
];
// Сначала вычисляем размеры всех строк
SetLength(LineHeights, Length(Lines));
MaxWidth := 0;
TotalHeight := 0;
Color.r := 255; Color.g := 255; Color.b := 255; Color.a := 255;
for I := 0 to High(Lines) do
begin
Surface := TTF_RenderUTF8_Blended(Font, PChar(Lines[I]), Color);
if Surface <> nil then
begin
LineHeights[I] := Surface^.h;
if Surface^.w > MaxWidth then MaxWidth := Surface^.w;
TotalHeight := TotalHeight + Surface^.h + 5;
SDL_FreeSurface(Surface);
end
else
LineHeights[I] := 0;
end;
// --- Сохраняем состояние OpenGL ---
glPushAttrib(GL_ENABLE_BIT or GL_TEXTURE_BIT or GL_CURRENT_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix;
glLoadIdentity;
glOrtho(0, W, H, 0, -1, 1); // 2D-проекция: (0,0) — верхний левый угол
glMatrixMode(GL_MODELVIEW);
glPushMatrix;
glLoadIdentity;
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
XPos := 10;
YPos := 10;
// --- Полупрозрачная чёрная подложка ---
BgHeight := TotalHeight + 10;
glDisable(GL_TEXTURE_2D);
glColor4f(0.0, 0.0, 0.0, 0.6);
glBegin(GL_QUADS);
glVertex2f(XPos, YPos);
glVertex2f(XPos + MaxWidth + 20, YPos);
glVertex2f(XPos + MaxWidth + 20, YPos + BgHeight);
glVertex2f(XPos, YPos + BgHeight);
glEnd;
// --- Рендерим каждую строку текста ---
YPos := YPos + 8;
for I := 0 to High(Lines) do
begin
Surface := TTF_RenderUTF8_Blended(Font, PChar(Lines[I]), Color);
if Surface <> nil then
begin
// Конвертируем поверхность в правильный формат
ConvSurface := SDL_ConvertSurfaceFormat(Surface, SDL_PIXELFORMAT_ABGR8888, 0);
if ConvSurface <> nil then
begin
glGenTextures(1, @TexID);
glBindTexture(GL_TEXTURE_2D, TexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Учитываем pitch поверхности
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, ConvSurface^.pitch div 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ConvSurface^.w, ConvSurface^.h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, ConvSurface^.pixels);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
SDL_FreeSurface(ConvSurface);
glEnable(GL_TEXTURE_2D);
glColor4f(1.0, 1.0, 1.0, 1.0);
glBegin(GL_QUADS);
// Правильные текстурные координаты (без инверсии Y, т.к. мы уже конвертировали)
glTexCoord2f(0.0, 0.0); glVertex2f(XPos + 10, YPos);
glTexCoord2f(1.0, 0.0); glVertex2f(XPos + 10 + Surface^.w, YPos);
glTexCoord2f(1.0, 1.0); glVertex2f(XPos + 10 + Surface^.w, YPos + Surface^.h);
glTexCoord2f(0.0, 1.0); glVertex2f(XPos + 10, YPos + Surface^.h);
glEnd;
glDeleteTextures(1, @TexID);
end;
YPos := YPos + Surface^.h + 5;
SDL_FreeSurface(Surface);
end;
end;
// --- Восстанавливаем состояние OpenGL ---
glPopMatrix;
glMatrixMode(GL_PROJECTION);
glPopMatrix;
glMatrixMode(GL_MODELVIEW);
glPopAttrib;
end;
function GetGroundHeight(X, Z: Single): Single;
var
GridSize: Single;
CellX, CellZ: Integer;
FracX, FracZ: Single;
H00, H10, H01, H11: Single;
HeightT, HeightB: Single;
begin
GridSize := 400.0;
if (X < 0) or (X >= 10000.0) or (Z < 0) or (Z >= 10000.0) then
Exit(0.0);
CellX := Trunc(X / GridSize);
CellZ := Trunc(Z / GridSize);
if CellX > 24 then CellX := 24;
if CellZ > 24 then CellZ := 24;
FracX := (X / GridSize) - CellX;
FracZ := (Z / GridSize) - CellZ;
H00 := MatrixHeights[CellX, CellZ];
H10 := MatrixHeights[CellX + 1, CellZ];
H01 := MatrixHeights[CellX, CellZ + 1];
H11 := MatrixHeights[CellX + 1, CellZ + 1];
HeightT := H00 + FracX * (H10 - H00);
HeightB := H01 + FracX * (H11 - H01);
Result := HeightT + FracZ * (HeightB - HeightT);
end;
// NEW: обновление позиции камеры с использованием дельты времени
procedure UpdateCamera;
var
RadYaw: Single;
Vel: Single;
ForwardX, ForwardZ: Single;
RightX, RightZ: Single;
NewX, NewZ: Single;
GroundY: Single;
const
EyeHeight = 100.0; // высота глаз над поверхностью
EdgeMargin = 50.0;
begin
Vel := MoveSpeed * DeltaTime;
// Направление "вперёд" в горизонтальной плоскости (без учёта наклона)
RadYaw := DegToRad(CamYaw);
ForwardX := Cos(RadYaw);
ForwardZ := Sin(RadYaw);
RightX := -Sin(RadYaw);
RightZ := Cos(RadYaw);
NewX := CamX;
NewZ := CamZ;
if moveForward then
begin
NewX := NewX + ForwardX * Vel;
NewZ := NewZ + ForwardZ * Vel;
end;
if moveBack then
begin
NewX := NewX - ForwardX * Vel;
NewZ := NewZ - ForwardZ * Vel;
end;
if moveLeft then
begin
NewX := NewX - RightX * Vel;
NewZ := NewZ - RightZ * Vel;
end;
if moveRight then
begin
NewX := NewX + RightX * Vel;
NewZ := NewZ + RightZ * Vel;
end;
// Ограничиваем перемещение в пределах ландшафта (0..10000)
if NewX < EdgeMargin then NewX := EdgeMargin;
if NewX > 10000 - EdgeMargin then NewX := 10000 - EdgeMargin;
// аналогично для Z
if NewZ < EdgeMargin then NewZ := EdgeMargin;
if NewZ > 10000 - EdgeMargin then NewZ := 10000 - EdgeMargin;
CamX := NewX;
CamZ := NewZ;
// Привязываем высоту камеры к рельефу с добавлением EyeHeight
GroundY := GetGroundHeight(CamX, CamZ);
//CamY := GroundY + EyeHeight;
// Плавное изменение высоты (сглаживание)
CamY := CamY * 0.9 + (GroundY + EyeHeight) * 0.1;
end;
procedure ApplyCamera;
var
LookDirX, LookDirY, LookDirZ: Single;
RadYaw, RadPitch: Single;
begin
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
// Вычисляем вектор направления взгляда из углов Эйлера
RadYaw := DegToRad(CamYaw);
RadPitch := DegToRad(CamPitch);
LookDirX := Cos(RadPitch) * Cos(RadYaw);
LookDirY := Sin(RadPitch);
LookDirZ := Cos(RadPitch) * Sin(RadYaw);
gluLookAt(CamX, CamY, CamZ,
CamX + LookDirX, CamY + LookDirY, CamZ + LookDirZ,
0.0, 1.0, 0.0);
end;
// ОСТАЛЬНЫЕ ФУНКЦИИ (MoveRain, DrawRain, GetGroundHeight, RegenRain, DrawMatrix, Thunder и т.д.)
// ------- без изменений, за исключением того, что RegenRain больше не привязан к камере, но это не мешает -------
const
RainFallSpeed = 600.0; // единиц в секунду
procedure MoveRain;
var
I: Integer;
begin
for I := 0 to NoRain - 1 do
RainDrops[I].Y := RainDrops[I].Y - RainFallSpeed * DeltaTime;
end;
procedure DrawRain;
var
I: Integer;
begin
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glDepthMask(GL_FALSE);
glColor4f(0.6, 0.7, 0.8, 0.3);
glBegin(GL_LINES);
for I := 0 to NoRain - 1 do
begin
glVertex3f(RainDrops[I].X, RainDrops[I].Y, RainDrops[I].Z);
glVertex3f(RainDrops[I].X, RainDrops[I].Y + 50.0, RainDrops[I].Z);
end;
glEnd;
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
end;
procedure RegenRain;
var
I: Integer;
GroundH: Single;
begin
for I := 0 to NoRain - 1 do
begin
GroundH := GetGroundHeight(RainDrops[I].X, RainDrops[I].Z);
if RainDrops[I].Y < GroundH then
begin
// Капли пересоздаются где-то над камерой, но камера теперь может летать высоко – пусть так
RainDrops[I].X := CamX + Random(Trunc(CloudSize)) - Random(Trunc(CloudSize));
RainDrops[I].Y := CamY + CloudHeight;
RainDrops[I].Z := CamZ + Random(Trunc(CloudSize)) - Random(Trunc(CloudSize));
end;
end;
end;
procedure DrawMatrix;
var
X, Z: Integer;
GridSize: Single;
begin
GridSize := 400.0;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, FloorTextureID);
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_QUADS);
for X := 0 to 24 do
begin
for Z := 0 to 24 do
begin
glTexCoord2f(0.0, 0.0); glVertex3f(X * GridSize, MatrixHeights[X, Z], Z * GridSize);
glTexCoord2f(1.0, 0.0); glVertex3f((X+1)*GridSize, MatrixHeights[X+1, Z], Z * GridSize);
glTexCoord2f(1.0, 1.0); glVertex3f((X+1)*GridSize, MatrixHeights[X+1, Z+1], (Z+1)*GridSize);
glTexCoord2f(0.0, 1.0); glVertex3f(X * GridSize, MatrixHeights[X, Z+1], (Z+1)*GridSize);
end;
end;
glEnd;
glDisable(GL_TEXTURE_2D);
end;
procedure DrawWhiteFlashMatrix;
var
X, Z: Integer;
GridSize: Single;
begin
GridSize := 400.0;
glDisable(GL_TEXTURE_2D);
glColor3f(1.0, 1.0, 1.0);
for X := 0 to 24 do
begin
glBegin(GL_QUADS);
for Z := 0 to 24 do
begin
glVertex3f(X * GridSize, MatrixHeights[X, Z], Z * GridSize);
glVertex3f((X+1)*GridSize, MatrixHeights[X+1, Z], Z * GridSize);
glVertex3f((X+1)*GridSize, MatrixHeights[X+1, Z+1], (Z+1)*GridSize);
glVertex3f(X * GridSize, MatrixHeights[X, Z+1], (Z+1)*GridSize);
end;
glEnd;
end;
end;
procedure Thunder;
var
Rand: Integer;
begin
Rand := Random(201);
if Rand = 120 then
begin
glClearColor(0.1, 0.1, 0.15, 1.0); // Возвращаем исходный цвет
ThunderActive := True;
if SoundThunder <> nil then
Mix_PlayChannel(-1, SoundThunder, 0);
end
else
begin
glClearColor(0.0, 0.0, 0.0, 1.0);
ThunderActive := False;
end;
end;
procedure GenerateMatrix;
var
x, z: Integer;
begin
//Randomize;
for x := 0 to 25 do
for z := 0 to 25 do
MatrixHeights[x, z] := Random * 200.0;
end;
procedure LoadFloorTexture;
var
Surface: PSDL_Surface;
MyFormat: GLint;
begin
Surface := SDL_LoadBMP('floor1.bmp');
if Surface = nil then Exit;
glGenTextures(1, @FloorTextureID);
glBindTexture(GL_TEXTURE_2D, FloorTextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
MyFormat := GL_BGR;
if Surface^.format^.BytesPerPixel = 4 then MyFormat := GL_BGRA;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Surface^.w, Surface^.h, 0, MyFormat, GL_UNSIGNED_BYTE, Surface^.pixels);
SDL_FreeSurface(Surface);
end;
procedure UpdateFrame;
var
CurrentTime: UInt32;
begin
// Вычисляем дельту времени
CurrentTime := SDL_GetTicks();
DeltaTime := (CurrentTime - LastTime) / 1000.0;
if DeltaTime > 0.1 then DeltaTime := 0.1; // защита от больших скачков
LastTime := CurrentTime;
ProcessKeyboardInput;
UpdateCamera;
MoveRain;
RegenRain;
Thunder;
end;
procedure RenderFrame;
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity;
ApplyCamera;
if not ThunderActive then
DrawMatrix
else
DrawWhiteFlashMatrix;
DrawRain;
DrawHints;
SDL_GL_SwapWindow(Window);
end;
procedure CleanUp;
begin
if Font <> nil then
begin
TTF_CloseFont(Font);
TTF_Quit();
end;
if SoundRain <> nil then Mix_FreeMusic(SoundRain);
if SoundThunder <> nil then Mix_FreeChunk(SoundThunder);
Mix_CloseAudio();
glDeleteTextures(1, @FloorTextureID);
if GLContext <> nil then SDL_GL_DeleteContext(GLContext);
if Window <> nil then SDL_DestroyWindow(Window);
SDL_Quit();
end;
var
I: Integer;
begin
try
InitSystem;
SoundRain := Mix_LoadMUS('rain.wav');
if SoundRain <> nil then Mix_PlayMusic(SoundRain, -1);
SoundThunder := Mix_LoadWAV('thunder.wav');
SetLength(RainDrops, NoRain);
Randomize;
for I := 0 to NoRain - 1 do
begin
RainDrops[I].X := CamX + Random(Trunc(CloudSize)) - Random(Trunc(CloudSize));
RainDrops[I].Y := CamY + CloudHeight - Random(Trunc(CloudHeight));
RainDrops[I].Z := CamZ + Random(Trunc(CloudSize)) - Random(Trunc(CloudSize));
end;
GenerateMatrix;
LoadFloorTexture;
// FIX: Захватываем мышь (относительный режим) для нормального управления
// Инициализация захвата мыши
MouseCaptured := True;
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_ShowCursor(SDL_DISABLE); // Скрываем системный курсор
LastTime := SDL_GetTicks();
Running := True;
while Running do
begin
HandleEvents;
UpdateFrame;
RenderFrame;
SDL_Delay(16);
end;
except
on E: Exception do
Writeln('Ошибка: ', E.Message);
end;
CleanUp;
end.






