Author Topic: TPS Prototype - Third Person Shooter Game Prototype  (Read 555 times)

Guva

  • Full Member
  • ***
  • Posts: 245
  • 🌈 ZX-Spectrum !!!
TPS Prototype - Third Person Shooter Game Prototype
« on: August 28, 2026, 09:06:52 pm »
A prototype third-person shooter game built with Pascal using the Raylib graphics library and custom game engine components.

https://github.com/GuvaCode/raylib-TPS-prototype

release
https://github.com/GuvaCode/raylib-TPS-prototype/releases

Fred vS

  • Hero Member
  • *****
  • Posts: 3963
    • StrumPract is the musicians best friend
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #1 on: August 28, 2026, 11:20:32 pm »
Hello Guva.

When trying the release of Linux 64 bit, there is that error:

Code: Bash  [Select][+][-]
  1. fred@fred-hpenvy:~$ cd /home/fred/Downloads/linux_64/
  2. fred@fred-hpenvy:~/Downloads/linux_64$ /home/fred/Downloads/linux_64/tps_prototype
  3. /home/fred/Downloads/linux_64/tps_prototype: error while loading shared libraries: libraylib.so.550: cannot open shared object file: No such file or directory

I see that libraylib.so.600 is in the folder but not libraylib.so.550.
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Guva

  • Full Member
  • ***
  • Posts: 245
  • 🌈 ZX-Spectrum !!!
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #2 on: August 28, 2026, 11:37:19 pm »
Hello Guva.

When trying the release of Linux 64 bit, there is that error:

Code: Bash  [Select][+][-]
  1. fred@fred-hpenvy:~$ cd /home/fred/Downloads/linux_64/
  2. fred@fred-hpenvy:~/Downloads/linux_64$ /home/fred/Downloads/linux_64/tps_prototype
  3. /home/fred/Downloads/linux_64/tps_prototype: error while loading shared libraries: libraylib.so.550: cannot open shared object file: No such file or directory

I see that libraylib.so.600 is in the folder but not libraylib.so.550.

Thank you for letting me know. I recompiled and updated it for Linux with static linking of raylib. But this won’t work with older glibc versions.

https://github.com/GuvaCode/raylib-TPS-prototype/releases/download/1.0/linux_64.zip

Fred vS

  • Hero Member
  • *****
  • Posts: 3963
    • StrumPract is the musicians best friend
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #3 on: August 29, 2026, 12:00:06 am »
I just try your https://github.com/GuvaCode/raylib-TPS-prototype/releases/download/1.0/linux_64.zip from previous post.

But same error. error while loading shared libraries: libraylib.so.550: cannot open shared object file: No such file or directory

My glibc version is 2.39.

Code: Bash  [Select][+][-]
  1. $ ldd --version
  2. ldd (Ubuntu GLIBC 2.39-0ubuntu8.8) 2.39
« Last Edit: August 29, 2026, 12:19:16 am by Fred vS »
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Guva

  • Full Member
  • ***
  • Posts: 245
  • 🌈 ZX-Spectrum !!!
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #4 on: August 29, 2026, 05:00:22 am »
I just try your https://github.com/GuvaCode/raylib-TPS-prototype/releases/download/1.0/linux_64.zip from previous post.

But same error. error while loading shared libraries: libraylib.so.550: cannot open shared object file: No such file or directory

My glibc version is 2.39.

Code: Bash  [Select][+][-]
  1. $ ldd --version
  2. ldd (Ubuntu GLIBC 2.39-0ubuntu8.8) 2.39

I rebuilt everything from scratch. The problem turned out to be with the symbolic links I had, which I had completely forgotten about.

 Maybe it’s even a good thing now — at least I know how to patch the paths in third‑party libraries that have dependencies on another library.
Code: C  [Select][+][-]
  1.  patchelf --set-rpath '$ORIGIN' ./libr3d.so
After that, libr3d will look for libassimp in the same folder.

This has happened before, I think, with UADE. https://forum.lazarus.freepascal.org/index.php/topic,70129.15.html

Fred vS

  • Hero Member
  • *****
  • Posts: 3963
    • StrumPract is the musicians best friend
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #5 on: August 29, 2026, 01:58:34 pm »
I just try your https://github.com/GuvaCode/raylib-TPS-prototype/releases/download/1.0/linux_64.zip from previous post.

But same error. error while loading shared libraries: libraylib.so.550: cannot open shared object file: No such file or directory

My glibc version is 2.39.

Code: Bash  [Select][+][-]
  1. $ ldd --version
  2. ldd (Ubuntu GLIBC 2.39-0ubuntu8.8) 2.39

I rebuilt everything from scratch. The problem turned out to be with the symbolic links I had, which I had completely forgotten about.

 Maybe it’s even a good thing now — at least I know how to patch the paths in third‑party libraries that have dependencies on another library.
Code: C  [Select][+][-]
  1.  patchelf --set-rpath '$ORIGIN' ./libr3d.so
After that, libr3d will look for libassimp in the same folder.

This has happened before, I think, with UADE. https://forum.lazarus.freepascal.org/index.php/topic,70129.15.html

Yep, this new one from https://github.com/GuvaCode/raylib-TPS-prototype/releases/download/1.0/linux_64.zip is working perfectly.

Many WoW and big congrats as usual for your code.
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #6 on: August 29, 2026, 03:53:20 pm »
I tested this game, and I'm still having serious issues running the games you publish.


The first issue (again) is related to performance. Sometimes when I launch the game, it runs at 60 fps, using 3.5% of CPU time and about 65% of GPU time, which seems reasonable. However, other times when I launch the game, it runs at ~20 fps, using all available CPU core time (17–18%) and ~100% of GPU time. See the first attachment.


The second problem is that while the game is running, the image always has massive glitches—almost the entire screen is filled with white rectangles covering 85% of the top half of the screen (only the very bottom of the screen renders correctly). These white areas are rendered in place of the game world’s content—there are white areas where building/road models should be, while text and object wireframes are rendered normally on top of them. Unfortunately, this can’t be captured in a screenshot—apparently, the framebuffer has correct, complete content, but glitched/incomplete frame content is displayed on the screen, so I see glitches on the screen, while the screenshot shows the correct content.

To show you what it looks like, I modified the screenshot so that it resembles what I actually see on my screen. However, imagine that in those white areas, I also see rendered text and wireframes. So see the second attachment.


In short, the operation and/or performance of this game is very poor. I don't know why I'm getting a glitchy image on the screen (maybe because of incompatibility with my graphics driver, even though it's up to date), but the issue of poor performance is pretty much the same as with your previous demo. Any news on this?

BTW: I have Dell Optiplex 3070, with Intel Core i5-9500T (six cores, up to 3.7GHz) with integrated Intel UHD Graphics 630, 16GB of RAM, SSD, Windows 11 pro. Typical CPU load on idle is ~5% at 1.5GHz or less (lowered because of efficient energy plan).
« Last Edit: August 29, 2026, 03:58:26 pm by flowCRANE »
Lazarus 4.8 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #7 on: August 31, 2026, 01:41:26 pm »
Okay, I used my smartphone to record what I actually see on the screen while the game is running. Attached to this post is a screenshot, and I’m also including a link to a video showing the game launching and a dozen or so seconds of gameplay. If you need this video to report issues with raylib, feel free to copy it and let me know once you’ve done so—I’ll then delete it from my Google Drive.

Here is the video:
https://drive.google.com/file/d/1-6TyKpYSTsF0WTZPNn_C6crDhkOfL4MD/view?usp=sharing

It shows that the game barely manages 21 fps, and the glitches on the screen make it impossible to play normally. All other standard games, including those made with Unity and similar tools, run without any issues for me; the only ones that don’t work properly are your demos built using raylib. Everything points to the fact that this library is faulty.
Lazarus 4.8 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

DrakkTheSeafarer

  • New Member
  • *
  • Posts: 16
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #8 on: September 01, 2026, 07:47:44 pm »
@Guva: well done!

@flowCRANE: you need some sort of discrete graphics card for this. Even the NVIDIA GTX 1060 3Gb (2016) is 10-11x faster in 3d performance than the Intel 630 iGPU.

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #9 on: September 02, 2026, 01:59:13 pm »
@flowCRANE: you need some sort of discrete graphics card for this.

No, I don't. The integrated GPU I have isn't top-of-the-line, but it handles modern (less demanding) games created in Unity or Godot with no problem. Besides, what I'm testing is just a stupid demo, not GTA 6, so it should work correctly.

And the fact that this demo runs painfully slowly isn't due to a weak GPU, but rather to the fact that raylib has a poor implementation of VSync and frame-to-frame delays. I mentioned earlier that sometimes this demo launches and runs normally (25% CPU core, 60% GPU, 60fps), while other times it barely runs (100% CPU core, 100% GPU, 20fps), even though it’s rendering exactly the same content—it all comes down to timing. As for the glitches on the screen, I don't know, but no other games and demos (not even made by Guva) causes glitches like that.


To sum up, if the raylib developers don’t know how to sensibly implement the main loop on Windows, here’s a tip: you can use high-resolution waitable timers for short waits plus the standard Sleep function for longer waits. You can create a hybrid solution by combining the above with short spinlocks to increase precision without boiling the CPU.

If anyone needs an example, they can check out the SDL3 source code and the implementations of its SDL_Delay, SDL_DelayNS, and SDL_DelayPrecise functions—I use them in the main loop of my engine to freeze the thread between frames, and they work wonderfully.
« Last Edit: September 02, 2026, 02:18:34 pm by flowCRANE »
Lazarus 4.8 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

DrakkTheSeafarer

  • New Member
  • *
  • Posts: 16
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #10 on: September 02, 2026, 10:13:29 pm »
@flowCRANE: you need some sort of discrete graphics card for this.
No, I don't. The integrated GPU I have isn't top-of-the-line, but it handles modern (less demanding) games created in Unity or Godot with no problem. Besides, what I'm testing is just a stupid demo, not GTA 6, so it should work correctly.
i never played a similar game, a 3d third-person shooter prototype, that runs at 60fps on very old hardware with iGPU. Take a look here: https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam
And the fact that this demo runs painfully slowly isn't due to a weak GPU, but rather to the fact that raylib has a poor implementation of VSync and frame-to-frame delays. I mentioned earlier that sometimes this demo launches and runs normally (25% CPU core, 60% GPU, 60fps), while other times it barely runs (100% CPU core, 100% GPU, 20fps), even though it’s rendering exactly the same content—it all comes down to timing. As for the glitches on the screen, I don't know, but no other games and demos (not even made by Guva) causes glitches like that.


To sum up, if the raylib developers don’t know how to sensibly implement the main loop on Windows, here’s a tip: you can use high-resolution waitable timers for short waits plus the standard Sleep function for longer waits. You can create a hybrid solution by combining the above with short spinlocks to increase precision without boiling the CPU.

If anyone needs an example, they can check out the SDL3 source code and the implementations of its SDL_Delay, SDL_DelayNS, and SDL_DelayPrecise functions—I use them in the main loop of my engine to freeze the thread between frames, and they work wonderfully.
Could you please present a prototype of a working main game Loop (using SDL3) that use these features?

flowCRANE

  • Hero Member
  • *****
  • Posts: 1009
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #11 on: September 03, 2026, 01:57:29 pm »
i never played a similar game, a 3d third-person shooter prototype, that runs at 60fps on very old hardware with iGPU.

Great, but that's an argumentum ad ignorantiam.

Besides, my integrated GPU isn't nearly as old and weak as it might seem. Anyway, it doesn’t matter, because as I already mentioned, this demo can run properly and use much less CPU/GPU time, but due to the messed-up timing, it’s a matter of luck—the game has to be launched at just the right moment (it works about once every few attempts). So, once again—the poor performance isn’t the fault of my GPU, but of the poor implementation of the main loop in raylib.

Quote
Could you please present a prototype of a working main game Loop (using SDL3) that use these features?

If you don't mind, below is the full code for the function that handles the main loop in my engine/game. There are a ton of features here, but what you're looking for is just a few lines of code at the very end of this function (I've highlighted more important lines). In any case, I'm pasting the full function code (including the documentation comments) so you have a broader context.

Code: Pascal  [Select][+][-]
  1. procedure Bas_LoopEnter ();
  2. var
  3.   StepsUpdate:     TBas_SInt32 = 0;
  4.   StepsRender:     TBas_SInt32 = 0;
  5.   StepsRefresh:    TBas_SInt32 = 0;
  6.   CurrPaceUpdate:  TBas_SInt32;
  7.   CurrPaceRender:  TBas_SInt32;
  8.   CurrPaceRefresh: TBas_SInt32;
  9.   {$IFDEF BAS_FEATURE_FAST_FORWARD}
  10.   CurrPaceForward: TBas_SInt32;
  11.   {$ENDIF}
  12.   TimeCurr:        TBas_UInt64;
  13.   TimePrev:        TBas_UInt64;
  14.   TimePrevEvent:   TBas_UInt64;
  15.   TimePrevClock:   TBas_UInt64;
  16.   ToDoSteps:       TBas_SInt32;
  17.   ToDoStepsMul:    TBas_SInt32;
  18.   ToDoInterval:    TBas_UInt64;
  19.   ToDoDrain:       TBas_Bool8;
  20.   AllowRender:     TBas_Bool8  = False;
  21. begin
  22.   // Initialize frame update logic and rendering performance meters.
  23.   Bas_MeterInitialize(@MeterUpdate, BAS_METER_SAMPLES_LOAD_DEFAULT, 256);
  24.   Bas_MeterInitialize(@MeterRender, BAS_METER_SAMPLES_LOAD_DEFAULT, 256);
  25.  
  26.   // Set all timestamps so that the first iteration will perform the step right away.
  27.   TimeCurr := SDL_GetTicksNS();
  28.  
  29.   // Watch out for possible integer underflows. If enough time has passed since SDL initialization, calculate the timestamps
  30.   // for the step and clock. Otherwise, set the minimum timestamp and wait to perform the step until some time has passed.
  31.   if TimeCurr >= BAS_LOOP_INTERVAL then TimePrev      := TimeCurr - BAS_LOOP_INTERVAL else TimePrev      := 0;
  32.   if TimeCurr >= BAS_LOOP_INTERVAL then TimePrevEvent := TimeCurr - BAS_LOOP_INTERVAL else TimePrevEvent := 0;
  33.   if TimeCurr >= 1000000000        then TimePrevClock := TimeCurr - 1000000000        else TimePrevClock := 0;
  34.  
  35.   // Start the main game loop.
  36.   repeat
  37.     // Copy the currently set activity paces to auxiliary variables and use them in a full iteration of the main loop. The
  38.     // game logic may change the pace of some activity in the meantime and then the loop would behave incorrectly or crash.
  39.     CurrPaceUpdate  := PaceUpdate;
  40.     CurrPaceRender  := PaceRender;
  41.     CurrPaceRefresh := PaceRefresh;
  42.     {$IFDEF BAS_FEATURE_FAST_FORWARD}
  43.     CurrPaceForward := PaceForward;
  44.     {$ENDIF}
  45.  
  46.     // Get the current time to calculate how many steps need to be taken.
  47.     TimeCurr  := SDL_GetTicksNS();
  48.  
  49.     // Calculate how much time has elapsed since the previous iteration and how many iterations the main loop is behind. Based
  50.     // on the number of outstanding iterations, determine whether the event queue should be drained. The only situation in
  51.     // which part of the events must remain in the queue is when there has been a lag significant enough that the number of
  52.     // logic updates in the current iteration had to be limited.
  53.     ToDoSteps := (TimeCurr - TimePrev) div BAS_LOOP_INTERVAL;
  54.     ToDoDrain := (ToDoSteps <= BAS_LOOP_STEPS_BATCH) or (ToDoSteps >= BAS_LOOP_STEPS_CATCH);
  55.  
  56.     {$IFDEF BAS_FEATURE_FAST_FORWARD}
  57.     // If the fast-forwarding is in progress, set the step multiplier to the current pace of this feature. It will be used to
  58.     // multiply the number of logic steps so that this iteration of the loop performs that many more logic updates.
  59.     if FastForward then
  60.       ToDoStepsMul := CurrPaceForward
  61.     else
  62.     {$ENDIF}
  63.       // Fast-forwarding is unavailable or disabled, so stick with the original number of logic steps.
  64.       ToDoStepsMul := 1;
  65.  
  66.     // If there was a significant lag, either due to a massive CPU load or because the simulation has been stopped for
  67.     // debugging (or for any other reason), instead of running a batch of updates, catch up to real time immediately.
  68.     if ToDoSteps >= BAS_LOOP_STEPS_CATCH then
  69.     begin
  70.       // Set the step counters so that all activities are performed in this iteration of the main loop. The number of steps
  71.       // for the logic update must account for the calculated multiplier in order to accommodate optional fast-forwarding.
  72.       StepsUpdate  := CurrPaceUpdate * ToDoStepsMul;
  73.       StepsRender  := CurrPaceRender;
  74.       StepsRefresh := CurrPaceRefresh;
  75.  
  76.       // Set the timestamp of the previous iteration to the real time.
  77.       TimePrev     := TimeCurr;
  78.     end
  79.     else
  80.     begin
  81.       // Check if the lag is significant enough that, in this iteration of the main loop, the limited batch of logic updates
  82.       // must be performed, while the rest must be left for the next iteration. Since we aren't trying to catch up to real
  83.       // time right away, the game will still render at least a few frames per second, even if it's lagging massively.
  84.       if ToDoSteps > BAS_LOOP_STEPS_BATCH then
  85.         ToDoSteps := BAS_LOOP_STEPS_BATCH;
  86.  
  87.       // Add the calculated number of outstanding iterations to the step counters for each activity. The number of steps for
  88.       // the logic update must account for the calculated multiplier in order to accommodate optional fast-forwarding.
  89.       StepsUpdate  += ToDoSteps * ToDoStepsMul;
  90.       StepsRender  += ToDoSteps;
  91.       StepsRefresh += ToDoSteps;
  92.  
  93.       // Add the sum of the intervals for the outstanding iterations to the timestamp of the previous iteration.
  94.       TimePrev     += ToDoSteps * BAS_LOOP_INTERVAL;
  95.     end;
  96.  
  97.     // Calculate the simulation tempo based on the current settings of the logic update pace. Normally, the tempo is "1.0",
  98.     // but if the simulation has been slowed down (for debugging purposes), the tempo will be less than that.
  99.     TempoUpdate := BAS_LOOP_TICKRATE / CurrPaceUpdate / BAS_LOOP_TICKRATE;
  100.  
  101.     {$IFDEF BAS_FEATURE_FAST_FORWARD}
  102.     // If the fast-forwarding is in progress, increase the tempo according to the current pace settings of this feature. By
  103.     // default the simulation tempo is "1.0" and the fast-forwarding pace is "3", so the result will be "3.0".
  104.     if FastForward then
  105.       TempoUpdate *= CurrPaceForward;
  106.     {$ENDIF}
  107.  
  108.     // Now let's check whether the game logic needs to be updated in this iteration of the loop. The only case in which the
  109.     // logic isn't updated in some iterations of the main loop is when the simulation was slowed down (due to debugging).
  110.     // Here, we're just checking if the logic must be updated, no matter how many times.
  111.     if StepsUpdate >= CurrPaceUpdate then
  112.     begin
  113.       // Due to lag or fast-forwarding, the logic in this iteration of the main loop may need to be updated multiple times.
  114.       // So calculate the time window so that each logic update processes the corresponding batch of SDL events.
  115.       ToDoInterval := (TimePrev - TimePrevEvent) div (StepsUpdate div CurrPaceUpdate);
  116.  
  117.       // Normally, one iteration of the main loop corresponds to one logic update, but in the case of short lags or active
  118.       // fast-forwarding, the logic may need to be updated multiple times.
  119.       repeat
  120.         // Start measuring the time of events processing and updating the game logic.
  121.         Bas_MeterAddLoadEnter(@MeterUpdate);
  122.         begin
  123.           // If this is the last logic update in this iteration of the main loop and the SDL event queue must be drained,
  124.           // process all events in the queue — this is a typical scenario where there are no lags or they are very short.
  125.           if (StepsUpdate div CurrPaceUpdate = 1) and ToDoDrain then
  126.           begin
  127.             // Updating the logic itself took some time, and the real time could drift so that we once again have at least
  128.             // one overdue logic update. We can drain the event queue only if there are no overdue updates.
  129.             if SDL_GetTicksNS() - (TimePrevEvent + ToDoInterval) < ToDoInterval then
  130.               Bas_EventsProcess(BAS_EVENT_TIMESTAMP_MAX)
  131.             else
  132.               // All the logic updates in this iteration of the main loop took so long that it caused an additional lag of at
  133.               // least one frame, so process only the batch of events from the time window corresponding to the last update.
  134.               Bas_EventsProcess(TimePrevEvent + ToDoInterval);
  135.           end
  136.           else
  137.             // This is not the last update in this iteration of the main loop, or it is the last one in a batch of updates
  138.             // during a massive lag, so process only the events from the time window corresponding to this update.
  139.             Bas_EventsProcess(TimePrevEvent + ToDoInterval);
  140.  
  141.           // Events are processed, so now perform a single step of updating game logic.
  142.           Bas_LoopUpdate();
  143.         end;
  144.         Bas_MeterAddLoadLeave(@MeterUpdate);
  145.         Bas_MeterAddRateEvent(@MeterUpdate);
  146.  
  147.         // Move the timestamp of the last event update by the size of the calculated time window.
  148.         TimePrevEvent += ToDoInterval;
  149.  
  150.         // Reduce the number of remaining logic updates to perform, based on the current pace settings.
  151.         StepsUpdate   -= CurrPaceUpdate;
  152.       until StepsUpdate < CurrPaceUpdate;
  153.  
  154.       // Time window calculations are based on integer division, which means that if several updates have been performed, the
  155.       // event timestamp may be smaller than the iteration timestamp. The more iterations that perform several updates, the
  156.       // more these timestamps will diverge. So, align both timestamps to avoid accumulating rounding errors from division.
  157.       TimePrevEvent := TimePrev;
  158.  
  159.       // As the game logic is being updated, set the flag allowing the frame to be rendered at the next opportunity. This flag
  160.       // must be set until a new frame is rendered, even for several iterations of the main loop, in case the rendering pace
  161.       // is lower than the pace of updating the game logic or the frequency of logic updates has been slowed down (debugging).
  162.       AllowRender   := True;
  163.     end;
  164.  
  165.     // Check whether in this iteration of the main loop, a new game frame must be rendered. If the render pace matches the
  166.     // loop tickrate, a new frame will be rendered in each loop iteration. If the logic update pace is lower than the render
  167.     // pace, frames will be rendered only in those iterations of the main loop in which the game logic has been updated.
  168.     if (StepsRender >= CurrPaceRender) and AllowRender then
  169.     begin
  170.       // Render a complete game frame onto the window swapchain texture and measure how long it took.
  171.       Bas_MeterAddLoadEnter(@MeterRender);
  172.       Bas_LoopRender();
  173.       Bas_MeterAddLoadLeave(@MeterRender);
  174.  
  175.       // Display the image on the screen. This function must be called after the frame has been rendered and after the
  176.       // rendering measurement has finished, so that in the case of enabled VSync, the measurement does not include waiting
  177.       // time of the GPU buffer flip.
  178.       SDL_RenderPresent(Renderer.Handle);
  179.  
  180.       // Add a new event to the meter and finish measuring rendering performance.
  181.       Bas_MeterAddRateEvent(@MeterRender);
  182.  
  183.       // The game frame is rendered only for the most recent game state (only once in a given iteration of the main loop), so
  184.       // catch up to real time, leaving only excess steps in the counter.
  185.       StepsRender := StepsRender mod CurrPaceRender;
  186.  
  187.       // As a new frame has been rendered, reset the flag and wait for the next logic update.
  188.       AllowRender := False;
  189.     end;
  190.  
  191.     // Check if in this iteration of the main loop, the performance counters must be refreshed and if so, do it.
  192.     if StepsRefresh >= CurrPaceRefresh then
  193.     begin
  194.       // Recalculate performance results based on collected samples.
  195.       Bas_MeterRefreshLoad(@MeterUpdate);
  196.       Bas_MeterRefreshRate(@MeterUpdate);
  197.  
  198.       Bas_MeterRefreshLoad(@MeterRender);
  199.       Bas_MeterRefreshRate(@MeterRender);
  200.  
  201.       // Update the status of system performance counters.
  202.       Bas_CountersUpdate();
  203.  
  204.       // Catch up to real time, leaving only excess steps in the counter.
  205.       StepsRefresh := StepsRefresh mod CurrPaceRefresh;
  206.     end;
  207.  
  208.     // Get the real time and update the clock — as last, because that's the least important thing to do.
  209.     TimeCurr := SDL_GetTicksNS();
  210.  
  211.     // If a full second has passed since the previous update, synchronize the clock now. The clock is designed solely for the
  212.     // purpose of rendering the local system time in the game window, so updating it once per second is perfectly sufficient.
  213.     if TimeCurr - TimePrevClock >= 1000000000 then
  214.     begin
  215.       Bas_ClockUpdate();
  216.  
  217.       // Catch up with real time, but don't overtake it.
  218.       while TimePrevClock + 1000000000 < TimeCurr do
  219.         TimePrevClock += 1000000000;
  220.     end;
  221.  
  222.     // Finally, check if there is still time left until the next iteration. If we have a lag and the next loop iteration is
  223.     // to be executed without any delay, skip thread sleep. In this case the freeze time will be negative, and since we are
  224.     // dealing with unsigned integers, the test must be protected against possible integer underflow.
  225.     TimeCurr := SDL_GetTicksNS();
  226.  
  227.     if TimeCurr < TimePrev + BAS_LOOP_INTERVAL then
  228.       SDL_DelayPrecise(TimePrev + BAS_LOOP_INTERVAL - TimeCurr);
  229.  
  230.   // Break the main loop only if the next game stage is the sentinel stage. This stage can be set due to a direct attempt to
  231.   // close the window or using a special option in the game's main menu UI, only inside the stage updater callback.
  232.   until Bas_StageGetNext() = BAS_STAGE_SENTINEL;
  233.  
  234.   // Finalize update and rendering performance meter objects.
  235.   Bas_MeterFinalize(@MeterUpdate);
  236.   Bas_MeterFinalize(@MeterRender);
  237. end;

The general rule is that a timestamp is retrieved before the loop begins, and then it is incremented by a fixed value in each iteration (in my case, 1/60 of a second; once or multiple times, depending on whether there are lags). After updating the logic and rendering the frame, the program checks whether there is still some time left until the next frame, and if so, it freezes the thread for that moment so as not to consume CPU time—this is done at the end of the provided function, so look there.

It doesn't matter whether VSync is enabled or not, because it doesn't affect anything—in my engine, a constant tick rate (60 ups) is maintained, while the frame rate can vary (60 fps by default, but it can be lower due to the lags). VSync, on the other hand, prevents screen tearing, which is why it’s enabled by default, and if it is enabled, the higher the screen refresh rate, the better.
« Last Edit: September 03, 2026, 02:38:39 pm by flowCRANE »
Lazarus 4.8 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

DrakkTheSeafarer

  • New Member
  • *
  • Posts: 16
Re: TPS Prototype - Third Person Shooter Game Prototype
« Reply #12 on: September 05, 2026, 12:01:49 pm »
@flowCRANE: I'm investigating it. Thanks a lot for sharing it!  8)

 

TinyPortal © 2005-2018