Post date: Mar 30, 2012 2:8:46 AM
I originally thought it would not be hard to get started with Android development. Installation and Setup is a meticulous but well documented process. Do not skip any steps or skim any section. There will be multiple setup pages each with their own sub-setup procedures.
I started from the main page here http://developer.android.com/sdk/installing.html and was required to download the following things and install them in order.
1. Java JDK
a. http://www.oracle.com/technetwork/java/javase/downloads/index.html
b. You will also need to set your path for java jdk/bin folder (I’ll get back to this in the next section)
i. http://docs.oracle.com/javase/7/docs/webnotes/install/windows/jdk-installation-windows.html
2. Eclipse IDE
a. http://www.eclipse.org/downloads/
b. Not even an install, just a folder with the exe
3. Android SDK Starter Package
a. http://developer.android.com/sdk/index.html
b. Be sure to set a path to the tools folder where you install it (I’ll get back to this in the next section)
4. Android ADT Plugin inside Eclipse
a. http://developer.android.com/sdk/eclipse-adt.html#installing
b. Make a new virtual device
5. Android SDK Manager
a. Open Eclipse then go to Window -> Android SDK Manager
i. Download the target API version you wish to use ( download your lowest and a highest, mine was 2.3 and 4.0 respectively)
This whole process will take a few hours. You will need to restart a few times, and one final time after everything is done.
Following the tutorial from http://developer.android.com/resources/tutorials/hello-world.html, one would end up with the following code.
public class HelloWorldActivity extends Activity
{
/** Called when the activity is first created.*/
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("Hello, Android");
setContentView(tv);
}
}
However, this code does not compile. The code below is auto generated when you make a new Android application, just stick with this. You can edit res/layout/main.xml instead of having to code everything.
public class HelloWorldActivity extends Activity
{
/** Called when the activity is first created.*/
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
This is a great way to make basic applications, however we are here to make real-time games. We need more rendering and input control which I’ll get into in the next section. Now is also a good time to determine the minimum Android SDK version and the orientation of the game.
I chose SDK version 10 because that is what my phone is, you must choose something equal to or less than the version on the device. This needs to be set in two different areas.
In AndroidManifest.xml add the following line under <manifest>
<uses-sdk android:minSdkVersion="10" />
While we are in here, if you want to lock your orientation, add the following under your activity. This would have saved me a lot of time and headaches if I knew about this earlier. For a while, everything in the game was rotated 90 degrees and the X coordinate was actually negative Y. If you set this early, you can avoid all that frustration.
<activity
android:label="@string/app_name"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
Additionally you will need to open project.properties and make sure it matches the AndroidManifest.xml. This part is auto-generated, but I have had to come back here a few times when I was changing information about the SDK, just be aware of it.
# Project target.
target=android-10
Now it is time to run. This will be the first test to see if you set everything up correctly. Start a virtual device by going to the AVD Manager, or just clicking run and creating a new one there. Wait for the device to start and get into the home screen by unlocking it. I fortunately did not set it up correctly, so I have an example of what it looks like when it fails. You will get the following error if you did not set it up correctly.
E/dalvikvm(230): Unable to open stack trace file '/data/anr/traces.txt': Permission denied
This is not actually an error generated by any code. It means that it tries to output a stack trace file and is unable to. In order to view the error, you need to open the DDMS Debug View and run the project in debug mode, not the play button, the bug button. After doing so I found out that my Activity was null and I got stuck here for about 3 or 4 hours. After running through some other tutorials, I found that I did not make environment paths for android sdk/tools and jdk/bin folders. Oops! I restarted after making the paths and made a new project and everything ran like a charm.
Now it is time to get it running on your device. My device did not show up in the Android Device Chooser until I installed the USB driver. The driver did not auto-install with Windows 7 or from the SDK manager Google USB driver package. I specifically had to get it from the Samsung website. This was the case for both devices I have used. Additionally, on your device you need to go to Settings -> Applications -> Development and enable USB debugging. I also recommend setting Stay Awake on, because if you do any debugging, when you go back to your game the screen will be black and when you go back to your game you will lose all of your buffers and textures (I’ll get more into this in the next section).
Whenever you are testing textures, it is best to avoid using a virtual device(I will cover more about this later). Textures on the virtual device will work when they do not on an actual device. Textures on an actual device will not load unless they are a power of two width and height.
Whenever focus leaves an application, the Android OS releases all buffers and textures from the graphics card. The OS still maintains all classes and states of the game. Allocate time early in development to reloading buffers whenever the device comes back from another application.
In my basic activity, I want as much screen space as possible, so I call the following in the onCreate function.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
Next we need input, because without input you can’t have a game. I use made a class extending GLSurfaceView called SurfaceInput.
screenContext = new SurfaceInput(this);
setContentView(screenContext);
Inside SurfaceInput’s constructor you is where you make the renderer. I made a class called OpenGLRenderer which extends Renderer.
public SurfaceInput(Context context)
{
super(context);
//setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
mRenderer = new OpenGLRenderer();
setRenderer(mRenderer);
}
The SurfaceInput class will give you input events that you must override.
public boolean onTouchEvent(final MotionEvent event)
The new Renderer that you made will give you three main functions.
public void onSurfaceCreated(GL10 gl, EGLConfig config)
public void onSurfaceChanged(GL10 gl, int width, int height)
public void onDrawFrame(GL10 gl)
onDrawFrame will give get called per frame, so you will do your buffer clear and draw your scene. onSurfaceChanged will get called when the viewport changes size. onSurfaceCreated gets called when the Surface gets created or recreated, this functions is important because it will be the time to reload all of your lost textures, reset the state of OpenGL for your program, and reload all of your buffers.
Next I recommend getting Text Rendering working next, there are multiple suggested methods to doing this, such as using something similar to the first application to render the text and then save out the scene into textures. However, this process is very slow and if you wanted to cache results, it would be memory intensive. I suggest using the BFF Loader/Renderer by Karl Walsh found here http://www.codehead.co.uk/cbfg/TexFont.java. With his code, I had text-rendering working in about 15 minutes. There is only one major drawback to using this approach and it is that is uses ((GL11Ext)gl).glDrawTexfOES which cannot rotate text because it copies pixels straight onto the frame buffer into a given depth (usually zero). I would also recommend getting this system up and working very early. You can use it to print errors to the screen or track variables that you are debugging without having to switch back into debug mode and view inside the IDE.
One of the biggest things to note about OpenGL ES is that it is fixed function OpenGL. If you need your own shader programs you should get into a native OpenGL ES 2.0 C++ project before you even start coding. Your minimum SDK requirement will automatically become Android 2.0. I recommend you make an UpdateAndDraw function to be called from Java and just do everything inside native. Since context switching to native is an expensive process, you will want to do this as little as possible, and do as much done in native before switching back. Since Java handles all input, I recommend also caching all the input that happened that frame and pass it to C++ in a structure to be handled there instead.
I recommend using Visual Studio with Android NDK by using vs-android found here http://code.google.com/p/vs-android/wiki/Installation. With this setup, you can edit Java code inside Eclipse while being able to see your C++ project and code while also being able to code in C++ in visual studio and see your Java code, which is very important when setting everything up correctly. When running applications in the native client it will automatically run on the first device in the AVD Manager list, with preference over actual devices to virtual devices. Only major downfall with the Visual Studio setup is that there is no breakpoint debugging yet, but there is a solution under development called WinGDB, you can check this out to see if it has been released or use their base NDK setup through GNU Make, Cygwin, and Awk (instructions can be found here http://developer.android.com/sdk/ndk/overview.html ).
Using assertions is a good practice in C++ while debugging especially for unit tests. Java, by default, disables runtime assertions even in debug mode. To enable runtime assertions, you need either to make a new JUnit inside Eclipse. In Eclipse you can enable assertions by adding a runtime parameter by going to Windows -> Preferences -> Java -> Installed JREs. Then select your JDK and click the Edit… button. In the “Default VM Arguments” box, add -ea.
The verbosity of Java after coming from a large C++ background is killing production time. There were bugs caused specifically by this.
Example:
C++:
static Vector3f triangleNormal(const Vector3f& tl, const Vector3f& tr, const Vector3f& bl )
{
Vector3f normal = cross((tr-tl), (bl-tl));
normal.normalize();
return normal;
}
Nice, clean, clear, and clear control of my memory in stack space.
Java:
static Vector3f tlbl = new Vector3f();
public Vector3f equalsTriangleNormal(Vector3f tl, Vector3f tr, Vector3f bl )
{
AssignToOther(tr);
SubInPlace(tl);
tlbl.AssignToOther(bl);
tlbl.SubInPlace(tl);
CrossInPlace( tlbl);
normalize();
return this;
}
This design all essentially to make sure there is no per-frame new. On a downside, this code would need locks to become thread safe.
Assign to other is essentially a deep copy. And I'm also using a static so I don't create anything new on a per-frame, however since this is in my vector class and is pretty much used for anything, the more that gets loaded into my game, the higher chance of a cache miss due to the cache coherency of statics. Writing this piece of code makes me sad. It's ugly and there's not much I can do about it.
Speaking of assignment, especially with a vector class, I kept trying to use assignment as a deep copy, however everything besides primitives are pointers. Out of habit I would keep writing = expecting a deep copy, and would overlook the lines when it came time to debug things that were wrong.
Example:
collisionPlaneDir = currTerrainPos;
collisionPlaneDir.SubInPlace(prevTerrainPos);
This would actually cause currTerrainPos to be changed, what I really wanted was the following:
collisionPlaneDir.AssignToOther(currTerrainPos);
collisionPlaneDir.SubInPlace(prevTerrainPos);
Every class in Java is a shared pointer managed by the garbage collector. Every variable is null until assigned or new. However, some things need additional reference counting by the programmer, like textures. Reference counting textures is a good idea so that you can free up graphics memory when you no longer use the textures by using glDeleteTextures.
There are quite a few useful techniques in the IDE that a programmer should know about.
· Automatically include missing imports: Ctrl + Shift + o
o Be careful when there are ambiguous classes.
· Automatically correct text alignment: Ctril + i
o If there are missing braces, this is a great way to find them
· Block comment: Ctrl + /
· Automatic variable completion or suggest: Ctrl + Space ( works for compile errors too)
· Auto fix compile errors: Ctrl + 1
o Not always the right solution
· Parameter hint tooltip: Ctrl + Shift + Space
· Surround with try catch: Alt + Shift + z
· Refactor – Rename: Alt + Shift + R
o Only done per file, if you do this on any public or protected data or classes, be aware
You should try and complete as much of the game as possible with as little assets as possible. Every time you run the code it compiles an .apk file which has every asset inside of it. Then it will transfer the .apk to the device or virtual device and finally install it. This process takes longer with the more assets.
Use a real device if you can. Virtual devices have very long start up times because they are even simulating that as well (which developers do not care about). On top of that, actual devices have a higher frame-rate, graphics processing power, and actual processing power. If you have a frame-rate dependent game, it’s important to feel how the game plays and you can’t really do that with a virtual device especially if you have multi-touch input or accelerometer input.