Android Game Development - A Basic Game Loop
来源:互联网 发布:北京盛世光明软件 编辑:程序博客网 时间:2024/05/17 00:16
Following the series so far you we have an understanding of the game architecture. Even if just briefly but we know that we need to take input in some form, update the internal state of the game and finally render it to the screen and also produce some sounds and/or vibrations. Furthermore we have created an example Android project for our first game. In this article we are going to discuss and implement the basic game loop.
Let’s keep it simple. Check the following diagram.
A Basic Game Loop
We handle input, update the state of our internal objects and render the current state. The Update and Render are grouped logically. They are tied together and tend to be executed one after the other.
Anything in Android happens inside an Activity. The Activity will create a View. The View is where everything happens. It is where the touch takes place and the resulting image gets displayed. Think of the Activity as a table that holds a sheet of paper (the View) enabling us to draw something. We will use our pencil to draw something onto the paper. That will be our touch and the actual chemistry happens on the paper so the result of our interaction with the View produces an image. The same is with Activity and View. Something like the following diagram:
Android Game Loop
Let’s open up DroidzActivity.java from our project. We see the line
This does nothing more than assigns the default (R) view to the activity when it is created. In our case it happens at startup.
Let’s create a new View which we will use. A View is a simple class that provides us with event handling (like onTouch) and a visible rectangle shaped space to draw on. The simplest way is to extend Android’s own SurfaceView. We will also implement SurfaceHolder.Callback to gain access to surface changes, for example when it is destroyed or the orientation of the device has changed.
MainGamePanel.java
The above code is a plain class that overrides the methods we are interested in.
Nothing special apart from lines 15 and 17.
This line sets the current class (MainGamePanel) as the handler for the events happening on the actual surface.
The above line makes our Game Panel focusable, which means it can receive focus so it can handle events. We added the callback and made it focusable in the constructor so we won’t miss.
The over-riden methods (line 20 onwards) will all be used but currently keep them empty.
Let’s create the thread that will be our actual game loop.
MainThread.java
As you can see this does not do much. It overrides the run() method and while the running flag is set to true it does an infinite loop.
Currently the thread is not instantiated so let’s start it up when the screen loads.
Let’s take a look at the modified MainGamePanel class.
We added the following lines:
Line 12 declares the thread as a private attribute.
In line 19 we instantiate the thread.
In the surfaceCreated method we set the running flag to true and we start up the thread (lines 30 and 31). By the time the this method is called the surface is already created and the game loop can be safely started.
Take a look at the surfaceDestroyed method.
This method is called directly before the surface is destroyed. It is not the place to set the running flag but the code we put in ensures that the thread shuts down cleanly. We simply block the thread and wait for it to die.
If we now run our project in the emulator won’t be able to see much but we’ll use some logging to test it. Don’t worry about it as I will cover logging in a later chapter.
You can find more on the Android site.
Add interaction with the screen
We will exit the application when we touch the lower part of the screen. If we touch it anywhere else we’ll just log the coordinates.
In the MainThread class we add the following lines:
We declared the gamePanel and surfaceHolder variables and a constructor taking the instances as parameters.
It is important to have them both and not just the gamePanel as we need to lock the surface when we draw and that can be done through the surfaceHolder only.
Change the line int the constructor of the MainGamePanel that instantiates the thread to
We are passing the current holder and the panel to its new constructor so the thread can access them. We will create the game update method in the game panel and we’ll trigger it from the thread but currently just leave it as it is.
Add the TAG constant to the MainThread class. Every class will have its own String constant called TAG. The value of the constant will be the name of the class containing it. We are using Android’s own logging framework and that takes two parameters. The firs is the tag which is just a string to identify the source of the log message and the second is the message we want to log. It’s a good practice to use the name of the class for the tag as it makes it simple to look up the logs.
A note on logging
To open the log viewer go to Windows -> Show View -> Other… and in the dialog select Android -> LogCat
Show View -> LogCat
Now you should see the LogCat view. This is nothing more than a console where you can follow Android’s log. It’s a great tool as you can filter for logs containing a specific text or logs with a certain tag which is quite useful.
Let’s get back to our code. The MainThread.java class looks like this:
In line 08 we define the tag for logging.
In the run() method we define tickCount which is incremented every time the while loop (the game loop) is executed.
We log the results.
Let’s go back to MainGamePanel.java class where we modified the onTouchEvent method so we handle touches on the screen.
Line 02 we check if the event on the screen is a start of a pressed gesture (MotionEvent.ACTION_DOWN). If so we check if the touch happened in the lower part of the screen. That is, the Y coordinate of the gesture is in the lower 50 pixels of the screen. If so we set the thread’s running status to false and call finish() on the main activity which basically exits the application.
Note: The screen is a rectangle with the upper left coordinates at (0,0) and the lower right coordinates at (getWidth(),getHeight()).
I have also modified the DroidzActivity.java class so we log its lifecycle.
Line 20 makes the display fullscreen.
The onDestroy() and onStop() methods were overridden just to log the activity’s lifecycle.
Let’s run the application by right-clicking on the project and select Run As -> Android application
You should see a black screen. If you click around a few time on the upper half and then you click on the bottom of your emulator’s screen the application should exit.
At this stage it is worth checking the logs.
LogCatThe highlighted lines are the most interesting as if you match look the logs up in the code you will see exactly the order of the method calls. You should also see how many times the thread’s while loop executed. It is a very high number but next time we will be more considerate about the cycles as we will introduce FPS and UPS. That is Frames Per Secondand Updates Per Second. We will create a game loop that will actually draw something onto the screen and it will do it as many times per second as we specify it.
Things we did so far:
Download the source code here.
Import it into eclipse and it should work right away.
Reference: A Basic Game Loop from our JCG partner Tamas Jano from "Against The Grain" blog.
Let’s keep it simple. Check the following diagram.
A Basic Game Loop
We handle input, update the state of our internal objects and render the current state. The Update and Render are grouped logically. They are tied together and tend to be executed one after the other.
Anything in Android happens inside an Activity. The Activity will create a View. The View is where everything happens. It is where the touch takes place and the resulting image gets displayed. Think of the Activity as a table that holds a sheet of paper (the View) enabling us to draw something. We will use our pencil to draw something onto the paper. That will be our touch and the actual chemistry happens on the paper so the result of our interaction with the View produces an image. The same is with Activity and View. Something like the following diagram:
Android Game Loop
Let’s open up DroidzActivity.java from our project. We see the line
1
setContentView(R.layout.main);
This does nothing more than assigns the default (R) view to the activity when it is created. In our case it happens at startup.
Let’s create a new View which we will use. A View is a simple class that provides us with event handling (like onTouch) and a visible rectangle shaped space to draw on. The simplest way is to extend Android’s own SurfaceView. We will also implement SurfaceHolder.Callback to gain access to surface changes, for example when it is destroyed or the orientation of the device has changed.
MainGamePanel.java
01
package
net.obviam.droidz;
02
03
import
android.content.Context;
04
import
android.graphics.Canvas;
05
import
android.view.MotionEvent;
06
import
android.view.SurfaceHolder;
07
import
android.view.SurfaceView;
08
09
public
class
MainGamePanel
extends
SurfaceView
implements
10
SurfaceHolder.Callback {
11
12
public
MainGamePanel(Context context) {
13
super
(context);
14
// adding the callback (this) to the surface holder to intercept events
15
getHolder().addCallback(
this
);
16
// make the GamePanel focusable so it can handle events
17
setFocusable(
true
);
18
}
19
20
@Override
21
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
22
}
23
24
@Override
25
public
void
surfaceCreated(SurfaceHolder holder) {
26
}
27
28
@Override
29
public
void
surfaceDestroyed(SurfaceHolder holder) {
30
}
31
32
@Override
33
public
boolean
onTouchEvent(MotionEvent event) {
34
return
super
.onTouchEvent(event);
35
}
36
37
@Override
38
protected
void
onDraw(Canvas canvas) {
39
}
40
}
The above code is a plain class that overrides the methods we are interested in.
Nothing special apart from lines 15 and 17.
1
getHolder().addCallback(
this
);
This line sets the current class (MainGamePanel) as the handler for the events happening on the actual surface.
1
setFocusable(
true
);
The above line makes our Game Panel focusable, which means it can receive focus so it can handle events. We added the callback and made it focusable in the constructor so we won’t miss.
The over-riden methods (line 20 onwards) will all be used but currently keep them empty.
Let’s create the thread that will be our actual game loop.
MainThread.java
01
package
net.obviam.droidz;
02
03
public
class
MainThread
extends
Thread {
04
05
// flag to hold game state
06
private
boolean
running;
07
public
void
setRunning(
boolean
running) {
08
this
.running = running;
09
}
10
11
@Override
12
public
void
run() {
13
while
(running) {
14
// update game state
15
// render state to the screen
16
}
17
}
18
}
As you can see this does not do much. It overrides the run() method and while the running flag is set to true it does an infinite loop.
Currently the thread is not instantiated so let’s start it up when the screen loads.
Let’s take a look at the modified MainGamePanel class.
01
package
net.obviam.droidz;
02
03
import
android.content.Context;
04
import
android.graphics.Canvas;
05
import
android.view.MotionEvent;
06
import
android.view.SurfaceHolder;
07
import
android.view.SurfaceView;
08
09
public
class
MainGamePanel
extends
SurfaceView
implements
10
SurfaceHolder.Callback {
11
12
private
MainThread thread;
13
14
public
MainGamePanel(Context context) {
15
super
(context);
16
getHolder().addCallback(
this
);
17
18
// create the game loop thread
19
thread =
new
MainThread();
20
21
setFocusable(
true
);
22
}
23
24
@Override
25
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
26
}
27
28
@Override
29
public
void
surfaceCreated(SurfaceHolder holder) {
30
thread.setRunning(
true
);
31
thread.start();
32
}
33
34
@Override
35
public
void
surfaceDestroyed(SurfaceHolder holder) {
36
boolean
retry =
true
;
37
while
(retry) {
38
try
{
39
thread.join();
40
retry =
false
;
41
}
catch
(InterruptedException e) {
42
// try again shutting down the thread
43
}
44
}
45
}
46
47
@Override
48
public
boolean
onTouchEvent(MotionEvent event) {
49
return
super
.onTouchEvent(event);
50
}
51
52
@Override
53
protected
void
onDraw(Canvas canvas) {
54
}
55
}
We added the following lines:
Line 12 declares the thread as a private attribute.
1
private
MainThread thread;
In line 19 we instantiate the thread.
1
thread =
new
MainThread();
In the surfaceCreated method we set the running flag to true and we start up the thread (lines 30 and 31). By the time the this method is called the surface is already created and the game loop can be safely started.
Take a look at the surfaceDestroyed method.
01
public
void
surfaceDestroyed(SurfaceHolder holder) {
02
// tell the thread to shut down and wait for it to finish
03
// this is a clean shutdown
04
boolean
retry =
true
;
05
while
(retry) {
06
try
{
07
thread.join();
08
retry =
false
;
09
}
catch
(InterruptedException e) {
10
// try again shutting down the thread
11
}
12
}
13
}
This method is called directly before the surface is destroyed. It is not the place to set the running flag but the code we put in ensures that the thread shuts down cleanly. We simply block the thread and wait for it to die.
If we now run our project in the emulator won’t be able to see much but we’ll use some logging to test it. Don’t worry about it as I will cover logging in a later chapter.
You can find more on the Android site.
Add interaction with the screen
We will exit the application when we touch the lower part of the screen. If we touch it anywhere else we’ll just log the coordinates.
In the MainThread class we add the following lines:
1
private
SurfaceHolder surfaceHolder;
2
private
MainGamePanel gamePanel;
3
4
public
MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
5
super
();
6
this
.surfaceHolder = surfaceHolder;
7
this
.gamePanel = gamePanel;
8
}
We declared the gamePanel and surfaceHolder variables and a constructor taking the instances as parameters.
It is important to have them both and not just the gamePanel as we need to lock the surface when we draw and that can be done through the surfaceHolder only.
Change the line int the constructor of the MainGamePanel that instantiates the thread to
1
thread =
new
MainThread(getHolder(),
this
);
We are passing the current holder and the panel to its new constructor so the thread can access them. We will create the game update method in the game panel and we’ll trigger it from the thread but currently just leave it as it is.
Add the TAG constant to the MainThread class. Every class will have its own String constant called TAG. The value of the constant will be the name of the class containing it. We are using Android’s own logging framework and that takes two parameters. The firs is the tag which is just a string to identify the source of the log message and the second is the message we want to log. It’s a good practice to use the name of the class for the tag as it makes it simple to look up the logs.
A note on logging
To open the log viewer go to Windows -> Show View -> Other… and in the dialog select Android -> LogCat
Show View -> LogCat
Now you should see the LogCat view. This is nothing more than a console where you can follow Android’s log. It’s a great tool as you can filter for logs containing a specific text or logs with a certain tag which is quite useful.
Let’s get back to our code. The MainThread.java class looks like this:
01
package
net.obviam.droidz;
02
03
import
android.util.Log;
04
import
android.view.SurfaceHolder;
05
06
public
class
MainThread
extends
Thread {
07
08
private
static
final
String TAG = MainThread.
class
.getSimpleName();
09
10
private
SurfaceHolder surfaceHolder;
11
private
MainGamePanel gamePanel;
12
private
boolean
running;
13
public
void
setRunning(
boolean
running) {
14
this
.running = running;
15
}
16
17
public
MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
18
super
();
19
this
.surfaceHolder = surfaceHolder;
20
this
.gamePanel = gamePanel;
21
}
22
23
@Override
24
public
void
run() {
25
long
tickCount = 0L;
26
Log.d(TAG,
"Starting game loop"
);
27
while
(running) {
28
tickCount++;
29
// update game state
30
// render state to the screen
31
}
32
Log.d(TAG,
"Game loop executed "
+ tickCount +
" times"
);
33
}
34
}
In line 08 we define the tag for logging.
In the run() method we define tickCount which is incremented every time the while loop (the game loop) is executed.
We log the results.
Let’s go back to MainGamePanel.java class where we modified the onTouchEvent method so we handle touches on the screen.
01
public
boolean
onTouchEvent(MotionEvent event) {
02
if
(event.getAction() == MotionEvent.ACTION_DOWN) {
03
if
(event.getY() > getHeight() -
50
) {
04
thread.setRunning(
false
);
05
((Activity)getContext()).finish();
06
}
else
{
07
Log.d(TAG,
"Coords: x="
+ event.getX() +
",y="
+ event.getY());
08
}
09
}
10
return
super
.onTouchEvent(event);
11
}
Line 02 we check if the event on the screen is a start of a pressed gesture (MotionEvent.ACTION_DOWN). If so we check if the touch happened in the lower part of the screen. That is, the Y coordinate of the gesture is in the lower 50 pixels of the screen. If so we set the thread’s running status to false and call finish() on the main activity which basically exits the application.
Note: The screen is a rectangle with the upper left coordinates at (0,0) and the lower right coordinates at (getWidth(),getHeight()).
I have also modified the DroidzActivity.java class so we log its lifecycle.
01
package
net.obviam.droidz;
02
03
import
android.app.Activity;
04
import
android.os.Bundle;
05
import
android.util.Log;
06
import
android.view.Window;
07
import
android.view.WindowManager;
08
09
public
class
DroidzActivity
extends
Activity {
10
/** Called when the activity is first created. */
11
12
private
static
final
String TAG = DroidzActivity.
class
.getSimpleName();
13
14
@Override
15
public
void
onCreate(Bundle savedInstanceState) {
16
super
.onCreate(savedInstanceState);
17
// requesting to turn the title OFF
18
requestWindowFeature(Window.FEATURE_NO_TITLE);
19
// making it full screen
20
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
21
// set our MainGamePanel as the View
22
setContentView(
new
MainGamePanel(
this
));
23
Log.d(TAG,
"View added"
);
24
}
25
26
@Override
27
protected
void
onDestroy() {
28
Log.d(TAG,
"Destroying..."
);
29
super
.onDestroy();
30
}
31
32
@Override
33
protected
void
onStop() {
34
Log.d(TAG,
"Stopping..."
);
35
super
.onStop();
36
}
37
}
Line 20 makes the display fullscreen.
The onDestroy() and onStop() methods were overridden just to log the activity’s lifecycle.
Let’s run the application by right-clicking on the project and select Run As -> Android application
You should see a black screen. If you click around a few time on the upper half and then you click on the bottom of your emulator’s screen the application should exit.
At this stage it is worth checking the logs.
LogCatThe highlighted lines are the most interesting as if you match look the logs up in the code you will see exactly the order of the method calls. You should also see how many times the thread’s while loop executed. It is a very high number but next time we will be more considerate about the cycles as we will introduce FPS and UPS. That is Frames Per Secondand Updates Per Second. We will create a game loop that will actually draw something onto the screen and it will do it as many times per second as we specify it.
Things we did so far:
- Create a full screen application
- Have a separate thread controlling the application
- Intercepting basic gestures like pressed gesture
- Shutting down the application graciously
Download the source code here.
Import it into eclipse and it should work right away.
Reference: A Basic Game Loop from our JCG partner Tamas Jano from "Against The Grain" blog.
- Android Game Development - A Basic Game Loop
- 3-A Basic Game Loop
- Game Loop
- Android Game Development - Sprite Animation
- Android Game Development - Measuring FPS
- A list of game development resources
- Game Loop - Game Programming Patterns
- Android Game Development - Displaying Images with Android
- MUD Game Programming (Game Development)
- Android Game Development - Moving Images on Screen
- 5-The Game Loop
- The Game Loop
- J2ME Game Development
- Mobile Game Development Fundamental
- Open Source Game Development
- Free (game development) libraries
- AI Game Development
- Game Engine Toolset Development
- VDisAsm Alpha
- IDR Interactive Delphi Reconstructor 2.5.3 beta
- Hash Codes Version 1.62.780.7200 (11/30/2011)
- Process Hacker 2.29 SVN r5089 bin
- BreakPoint Hex Workshop 6.7.2.5284 portable
- Android Game Development - A Basic Game Loop
- 嵌入式Linux Qt4 中文显示乱码和中文不显示
- java 小题目
- LINUX KERNEL 配置编译中文指南
- Busybox支持中文的解决办法
- EFSL文件系统移植记录
- Android Game Development - Displaying Images with Android
- Red Hat linux进入单用户环境
- Regist