You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: chapter2/chapter2.md
+34-34Lines changed: 34 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
2
2
# The Game Loop
3
3
4
-
In this chapter we will start developing our game engine by creating our game loop. The game loop is the core component of every game, it is basically an endless loop which is responsible of periodically handling user input, updating game state and render to the screen.
4
+
In this chapter we will start developing our game engine by creating our game loop. The game loop is the core component of every game. It is basically an endless loop which is responsible for periodically handling user input, updating game state and rendering to the screen.
5
5
6
6
The following snippet shows the structure of a game loop:
7
7
@@ -13,9 +13,9 @@ while (keepOnRunning) {
13
13
}
14
14
```
15
15
16
-
So, is that all? Have we finished with game loops? Well, not yet. The above snippet has many pitfalls. First of all the speed that the game loop runs will execute at different speeds depending on the machine it runs on. If the machine is fast enough the user will not even be able to see what is happening in the game. Moreover, that game loop will consume all the machine resources.
16
+
So, is that all? Are we finished with game loops? Well, not yet. The above snippet has many pitfalls. First of all the speed that the game loop runs at will be different depending on the machine it runs on. If the machine is fast enough the user will not even be able to see what is happening in the game. Moreover, that game loop will consume all the machine resources.
17
17
18
-
Thus, we need the game loop to try run at a constant rate independently of the machine it runs on. Let us suppose that we want our game to run at a constant rate of 50 Frames Per Second (FPS). Our game loop could be something like this:
18
+
Thus, we need the game loop to try running at a constant rate independently of the machine it runs on. Let us suppose that we want our game to run at a constant rate of 50 Frames Per Second (FPS). Our game loop could be something like this:
19
19
20
20
```java
21
21
double secsPerFrame =1/50;
@@ -29,15 +29,15 @@ while (keepOnRunning) {
29
29
}
30
30
```
31
31
32
-
This game loop is simple and could be used for some games but it also presents some problems. First of all, it assumes that our update and render methods fit in the available time we have in order to render at a constant rate of 50 FPS (that is, ```secsPerFrame``` which is equals to 20 ms.).
32
+
This game loop is simple and could be used for some games but it also presents some problems. First of all, it assumes that our update and render methods fit in the available time we have in order to render at a constant rate of 50 FPS (that is, ```secsPerFrame``` which is equal to 20 ms.).
33
33
34
-
Besides that, our computer may be prioritizing another tasks that prevent our game loop to execute for certain period of time. So, we may end up updating our game state at very variable time steps which are not suitable for game physics.
34
+
Besides that, our computer may be prioritizing other tasks that prevent our game loop from executing for a certain period of time. So, we may end up updating our game state at very variable time steps which are not suitable for game physics.
35
35
36
-
Finally, sleep accuracy may range to tenth of a second, so we are not even updating at a constant frame rate even if our update and render methods are no time. So, as you see the problem is not so simple.
36
+
Finally, sleep accuracy may range to tenth of a second, so we are not even updating at a constant frame rate even if our update and render methods take no time. So, as you see the problem is not so simple.
37
37
38
-
In the Internet you can find tons of variants for game loops, in this book we will use a not too complex approach that can work well in many situations. So let us move on and explain the basis for our game loop. The pattern used her is usually called as Fixed Step Game Loop.
38
+
On the Internet you can find tons of variants for game loops. In this book we will use a not too complex approach that can work well in many situations. So let us move on and explain the basis for our game loop. The pattern used here is usually called Fixed Step Game Loop.
39
39
40
-
First of all we may want to control separately the period at which the game state is update and the period at which the game is rendered to the screen. Why we do this? Well, updating our game state at a constant rate is more important, especially if we use some physics engine. On the contraire, if our rendering is not done on time it makes no sense to render old frames while processing our game loop, we have the flexibility to skip some ones.
40
+
First of all we may want to control separately the period at which the game state is updated and the period at which the game is rendered to the screen. Why do we do this? Well, updating our game state at a constant rate is more important, especially if we use some physics engine. On the contrary, if our rendering is not done in time it makes no sense to render old frames while processing our game loop. We have the flexibility to skip some frames.
41
41
42
42
Let us have a look at how our game loop looks like:
43
43
@@ -63,7 +63,7 @@ while (true) {
63
63
}
64
64
```
65
65
66
-
With this game loop we update our game state at fixed steps, but, How do we control that we do not exhaust computer resources by rendering continuously? This is done in the sync method:
66
+
With this game loop we update our game state at fixed steps. But how do we control that we do not exhaust the computer's resources by rendering continuously? This is done in the sync method:
So What are we doing in the above method? In summary we calculate how many seconds our game loop iteration should last (which is stored in the ```loopSlot``` variable) and we wait for that time taking into consideration the time we have spent in our loop. But instead of doing a single wait for the whole available time period we do small waits. This will allow other tasks to run and will avoid the sleep accuracy problems we mentioned before. Then, what we do is:
80
+
So what are we doing in the above method? In summary we calculate how many seconds our game loop iteration should last (which is stored in the ```loopSlot``` variable) and we wait for that amount of time taking into consideration the time we spent in our loop. But instead of doing a single wait for the whole available time period we do small waits. This will allow other tasks to run and will avoid the sleep accuracy problems we mentioned before. Then, what we do is:
81
81
1. Calculate the time at which we should exit this wait method and start another iteration of our game loop (which is the variable ```endTime```).
82
-
2. Compare current time with that end time and wait just one second if we have not reached that time yet.
82
+
2. Compare the current time with that end time and wait just one second if we have not reached that time yet.
83
83
84
-
Now it is time to structure our code base in order to start writing our first version of our Game Engine. But before doing that we will talk about another way of controlling the rendering rate. In the code presented above, we are doing micro-sleeps in order to control how much time we need to wait. But we can chose another approach in order to limit the frame rate, we can use vsync (vertical synchronization). The main purpose of v-sync is to avoid screen tearing. What is screen tearing? It’s a visual effect that is produced when we update the video memory while it’s being rendered. The result will be that part of the image will represent the previous image and the other part will represent the updated one. If we enable v-sync we won’t send an image to the GPU while is being rendered into the screen.
84
+
Now it is time to structure our code base in order to start writing our first version of our Game Engine. But before doing that we will talk about another way of controlling the rendering rate. In the code presented above, we are doing micro-sleeps in order to control how much time we need to wait. But we can choose another approach in order to limit the frame rate. We can use v-sync (vertical synchronization). The main purpose of v-sync is to avoid screen tearing. What is screen tearing? It’s a visual effect that is produced when we update the video memory while it’s being rendered. The result will be that part of the image will represent the previous image and the other part will represent the updated one. If we enable v-sync we won’t send an image to the GPU while it is being rendered onto the screen.
85
85
86
-
When we enable v-sync we are synchronizing to the refresh card of the video card, which at the end will result in a constant frame rate. This is done with the following line:
86
+
When we enable v-sync we are synchronizing to the refresh rate of the video card, which at the end will result in a constant frame rate. This is done with the following line:
87
87
88
88
```java
89
89
glfwSwapInterval(1);
90
90
```
91
91
92
-
With that line we are specifying that we must wait, at least, one screen update before drawing to the screen. In fact, we are not directly drawing, we store the information and a buffer and we swap it with this method:
92
+
With that line we are specifying that we must wait, at least, one screen update before drawing to the screen. In fact, we are not directly drawing to the screen. We instead store the information to a buffer and we swap it with this method:
93
93
94
94
```java
95
95
glfwSwapBuffers(windowHandle);
96
96
```
97
97
98
-
So, if we enable v-sync we achieve a constant frame rate without performing the micro-sleeps to check the available time. Besides that, the frame rate will match the refresh rate of our graphics card, that is, if it’s set to 60Hz (60 times per second), we will have 60 Frames Per Second. We can scale down that rate by setting a number higher than one in the ```glfwSwapInterval``` method (if we set it to 2, we would get 30 FPS).
98
+
So, if we enable v-sync we achieve a constant frame rate without performing the micro-sleeps to check the available time. Besides that, the frame rate will match the refresh rate of our graphics card. That is, if it’s set to 60Hz (60 times per second), we will have 60 Frames Per Second. We can scale down that rate by setting a number higher than 1 in the ```glfwSwapInterval``` method (if we set it to 2, we would get 30 FPS).
99
99
100
100
Let’s get back to reorganize the source code. First of all we will encapsulate all the GLFW Window initialization code in a class named ```Window``` allowing some basic parameterization of its characteristics (such as title and size). That ```Window``` class will also provide a method to detect key presses which will be used in our game loop:
101
101
@@ -114,22 +114,22 @@ glfwSetWindowSizeCallback(windowHandle, windowSizeCallback = new GLFWWindowSizeC
114
114
Window.this.width = width;
115
115
Window.this.height = height;
116
116
Window.this.setResized(true);
117
-
}
117
+
}
118
118
});
119
119
```
120
120
121
-
We will also create a ```Renderer``` class which will do our game render logic. By now, it will just have an empty ```init``` method and another method to clear the screen with the configured clear color:
121
+
We will also create a ```Renderer``` class which will handle our game render logic. By now, it will just have an empty ```init``` method and another method to clear the screen with the configured clear color:
122
122
123
123
```java
124
-
publicvoid init() throws Exception {
124
+
publicvoid init() throws Exception {
125
125
}
126
126
127
127
publicvoid clear() {
128
128
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
129
129
}
130
130
```
131
131
132
-
Then we will create an interface named ```IGameLogic``` which will encapsulate our game logic. By doing this we will make our game engine reusable across different titles. This interface will have methods to get the input, to update the game state and to render gamespecific data.
132
+
Then we will create an interface named ```IGameLogic``` which will encapsulate our game logic. By doing this we will make our game engine reusable across different titles. This interface will have methods to get the input, to update the game state and to render game-specific data.
133
133
134
134
```java
135
135
publicinterfaceIGameLogic {
@@ -167,7 +167,7 @@ The ```vSync``` parameter allows us to select if we want to use v-sync or not. Y
167
167
```java
168
168
public void start() {
169
169
gameLoopThread.start();
170
-
}
170
+
}
171
171
172
172
@Override
173
173
publicvoidrun() {
@@ -179,11 +179,11 @@ public void run() {
179
179
}
180
180
}
181
181
```
182
-
Our ```GameEngine``` classprovides a start method which just starts our Thread so run method will be executed asynchronously. That method will perform the initialization tasks and will run the game loop until our window is closed. It is very important to initialize GLFW code inside the thread that is going to update it later. Thus, in that ```init``` method our Window and ```Renderer``` instances are initialized.
182
+
Our ```GameEngine``` classprovides a start method which just starts our Thread so the run method will be executed asynchronously. That method will perform the initialization tasks and will run the game loop until our window is closed. It is very important to initialize GLFW inside the thread that is going to update it later. Thus, in that ```init``` method our Window and ```Renderer``` instances are initialized.
183
183
184
-
In the source code you will see that we have created other auxiliary classes such as Timer (which will provide utility methods for calculating elapsed time) and will be used by our game loop logic.
184
+
In the source code you will see that we created other auxiliary classes such as Timer (which will provide utility methods for calculating elapsed time) and will be used by our game loop logic.
185
185
186
-
Our ```GameEngine``` classjust delegates the input and update methods to the ```IGameLogic``` instance. In the render method it delegates also to the ```IGameLogic``` instance an updates the window.
186
+
Our ```GameEngine``` classjust delegates the input and update methods to the ```IGameLogic``` instance. In the render method it delegates also to the ```IGameLogic``` instance and updates the window.
187
187
188
188
```java
189
189
protected void input() {
@@ -220,21 +220,21 @@ public class Main {
220
220
221
221
}
222
222
```
223
-
At the end we only need to create or game logic class, which forthis chapter will be a simpler one. It will just update the increase / decrease the clear color of the window whenever the user presses the up / down key. The render method will just clear the window with that color.
223
+
At the end we only need to create or game logic class, which forthis chapter will be a simpler one. It will just increase / decrease the clear color of the window whenever the user presses the up / down key. The render method will just clear the window with that color.
224
224
225
225
```java
226
226
publicclassDummyGameimplementsIGameLogic {
227
227
228
228
privateint direction =0;
229
-
229
+
230
230
privatefloat color =0.0f;
231
231
232
232
privatefinalRenderer renderer;
233
233
234
234
publicDummyGame() {
235
235
renderer =newRenderer();
236
236
}
237
-
237
+
238
238
@Override
239
239
publicvoidinit() throwsException {
240
240
renderer.init();
@@ -273,9 +273,9 @@ public class DummyGame implements IGameLogic {
273
273
}
274
274
```
275
275
276
-
In the ```render``` method we need to be ware ifthe window has been resized and update the view port to locate the center of the coordinates in the center of the window.
276
+
In the ```render``` method we get notified when the window has been resized in order to update the viewport to locate the center of the coordinates to the center of the window.
277
277
278
-
The classhierarchy that we have created will help us to separate our game engine code from the code of a specific game. Although it may seem necessary at this moment we need to isolate generic tasks that every game will use from the state logic, artwork and resources of an specific game in order to reuse our game engine. In later chapters we will need to restructure this classhierarchy as our game engine gets more complex.
278
+
The classhierarchy that we have created will help us to separate our game engine code from the code of a specific game. Although it may seem necessary at this moment we need to isolate generic tasks that every game will use from the state logic, artwork and resources of a specific game in order to reuse our game engine. In later chapters we will need to restructure this classhierarchy as our game engine gets more complex.
279
279
280
280
281
281
## Threading issues
@@ -288,9 +288,9 @@ Exception in thread "GAME_LOOP_THREAD" java.lang.ExceptionInInitializerError
288
288
289
289
What does this mean? The answer is that some functions of the GLFW library cannot be called in a ```Thread``` which is not the main ```Thread```. We are doing the initializing stuff, including window creation in the ```init``` method of the ```GameEngine class```. That method gets called in the ```run``` method of the same class, which is invoked by a new ```Thread``` instead the one that's used to launch the program.
290
290
291
-
This is a constraint of the GLFW library and basically it implies that we should avoid the creation of new Threads for the game loop. We could try to create all the Windows related stuff in the main thread but we will not be able to render anything. The problem is that, OpenGL calls need to be performed in the same ```Thread``` that its context was created.
291
+
This is a constraint of the GLFW library and basically it implies that we should avoid the creation of new Threads for the game loop. We could try to create all the Windows related stuff in the main thread but we will not be able to render anything. The problem is that, OpenGL calls need to be performed in the same ```Thread``` that its context was created.
292
292
293
-
In Windows and Linux platforms, although we are not using the main thread to initialize the GLFW stuff the samples will work. The problem is with OSX, so we need to change the source code of the ```run``` method of the ```GameEngine``` classto support that platform like this:
293
+
On Windows and Linux platforms, although we are not using the main thread to initialize the GLFW stuff the samples will work. The problem is with OSX, so we need to change the source code of the ```run``` method of the ```GameEngine``` classto support that platform like this:
294
294
295
295
```java
296
296
public void start() {
@@ -303,13 +303,13 @@ public void start() {
303
303
}
304
304
```
305
305
306
-
What we are doing is just ignoring the game loop thread when we are in OSX and execute the game loop code directly in the main Thread. This is not a perfect solution but it will allow you to run the samples inMac. Other solutions found in the forums (suchasexecutingtheJVMwiththe ```-XstartOnFirstThread``` flagseemtonotwork).
306
+
What we are doing is just ignoring the game loop thread when we are in OSX and execute the game loop code directly in the main Thread. This is not a perfect solution but it will allow you to run the samples onMac. Other solutions found in the forums (suchasexecutingtheJVMwiththe ```-XstartOnFirstThread``` flagseemtonotwork).
307
307
308
-
In the future it may be interesting to explore if LWJGL provides other GUI libraries to check if this restriction applies to them. (Many thanks to Timo Bühlmann for pointing this issue).
308
+
In the future it may be interesting to explore if LWJGL provides other GUI libraries to check if this restriction applies to them. (Many thanks to Timo Bühlmann for pointing out this issue).
309
309
310
310
## PlatformDifferences (OSX)
311
311
312
-
You will be able to run the code described above in Windows or Linux, but we still need to do some modifications for OSX. As it's stated in th GLFW documentation:
312
+
You will be able to run the code described above on Windows or Linux, but we still need to do some modifications for OSX. As it's stated in th GLFW documentation:
313
313
314
314
> The only OpenGL 3.x and 4.x contexts currently supported by OS X are forward-compatible, core profile contexts. The supported versions are 3.2 on 10.7 Lion and 3.3 and 4.1 on 10.9 Mavericks. In all cases, your GPU needs to support the specified OpenGL version for context creation to succeed.
315
315
>
@@ -323,4 +323,4 @@ So, in order to support features explained in later chapters we need to add thes
This will make the program to use highest OpenGL version possible between 3.2 and 4.1. If those lines are not included, a Legacy version of OpenGL is used.
326
+
This will make the program use the highest OpenGL version possible between 3.2 and 4.1. If those lines are not included, a Legacy version of OpenGL is used.
0 commit comments