>>Chris Pruett: Hi, folks. This is writing
real-time games for Android redux. I think
people are still trailing in but we have to
start.
There is totally a Wave open about this right
now, and here is the URL and you should totally
use it. There is somebody taking note, and
you can ask questions and I will try to answer
them at the end.
Who am I? I am a developer advocate. My name
is Chris Pruett. My focus is on video games
for Android. Before I joined Google I was
a video game engineer.
Today I want to talk about three things related
to Android, kind of three big buckets. Those
three things are basically hardware stuff
software design, and Android market for games.
Hardware stuff, I want to talk about how the
world has changed in the last year or so,
the type of devices we have, and also stuff
about performance. For game architecture,
that's the software design stuff. I would
like to talk to you about how games can be
developed for Android and what the right way
to go about that is. And also the tips and
tricks and pitfalls that game developers often
run into.
And then Android market for games.
I want to tell about what I learned shipping
a game on Android market. And to do that I
am going to use this game I wrote called Replica
Island as a case study. And I will refer back
to Replica Island throughout the talk.
So last year I was up on the stage and I gave
a talk about games. And at that point there
was basically one device available on the
market, and that was the G1. Android 1.5 had
just been released and there were less than
5,000 apps on the app store. So within a year
we came a long way. We have lots and lots
of devices on the market now, lots and lots
of apps in the store, and a lot of those are
games.
As game developers, one thing that a lot of
people probably worry about is if there are
a lot of devices, how do you target all of
them? Fortunately, when it comes to graphics
performance and CPU performance, all the devices
in the world have broken pretty cleanly out
into two discrete categories, which I am going
to call first generation and second generation.
This is fantastic because as a game developer,
you can choose sort of which category, or
both categories, to support, and that should
give you a baseline for performance that you
can use to target your game design. First
generation devices typified here by the HTC
Magic, also called the MyTouch in the U.S.,
basically the same hardware as the G1. Typically
500 megahertz ARM processor. Typically Android
-- I'm sorry, these devices support OpenGL
ES 1.0, 1.1. Almost all of them have a hardware
back end to back this up. They almost all
have HVGA screens. I can get about 5,000 textured
unlit colored verts per frame at 30 frames
per second on these devices, and about 1024
verts per frame at 60 frames a second. And
I will talk about how I arrived at these numbers
later. These devices are generally Android
1.5 or 1.6, although a bunch of them are due
to be upgraded to 2.0 in the near future.
And they represent about 60% of the market
at the moment. This is the first generation.
Sort of the low end.
Second generation devices basically started
with the Droid and include the Nexus One,
and I have a picture of the Xperia on the
other slide. But they are much higher end.
Much faster CPUs, much faster graphics performance.
These devices are supporting OpenGL ES 2.0.
They have big screens, and as you can see,
I can get about 27,000 verts per frame at
30 frames a second, so there's about a 5x
performance improvement in terms of pure graphics
performance, at least within the study of
the tests that I have done.
However, these devices have big screens, and
that means that they spend a lot of time copying
the pixels to fill those big screens every
frame. So these devices are basically fill
limited, and it's very hard to do faster than
30 frames a second on these high-end devices,
even though they can render quite quickly.
These devices are generally running Android
2.0, 2.1. Couple are on 1.6. And they are
about 40% of the market at the moment.
The first device, like I said, is the Droid.
It shipped in December, late November, I think,
of last year. That was the first device in
this class, and now these devices represent
40% of the market. So you can see in a short
period of time, these higher end devices have
sort of come from zero to a very significant
part of the market.
So as game developers, one of the things that
you probably are interested in is, well, you
know, CPU and graphics performance is one
kind of difference, but there's a lot of different
handsets. There's a lot of different other
things that game developers might care about,
like input system or screen size. How do I
deal with those? And I think, having built
a game for these devices and trying to get
it to run on every single thing under the
sun, I think that the three points of device
diversity that you need to be concerned about
are screen size, input hardware, and OpenGL
driver.
Screen size is really easy, actually, because
the Android framework gives you tons of tools
for dealing with different screen sizes. Basically
you can query the size of the screen, and
if you are using OpenGL you can set your view
bore to that size. It's not very hard. You
can also use the alternative resource system
to load different size assets and go all crazy
if you want to. But if you want to keep it
simple, managing multiple screen sizes is
not very difficult.
Input hardware is a little trickier because
some of these phones have a track ball, some
have a D-pad, some have a keyboard, some have
screens with multi-touch, some don't. Some
have an optical track pad. That's a new thing.
And for a game, a lot of the playability of
the game, regardless of the technology it
employs, is going to be based on how well
the user can actually control the game itself.
And the interface between the phone and the
user is going to determine a lot of the fun
factor for your game.
So having a lot of different hardware means
that you may have -- you may think you have
a significant challenge to support all these
devices, but actually the API does a pretty
good job of standardizing all these different
types of inputs into two types of input events:
motion events and key events. And I will talk
at some length later about how I manage to
sort of try to support all of these different
types of diverse input systems for my game.
And you can be pretty much guaranteed that
all devices are going to have a touch screen,
accelerometer, orientation sensors, things
like that.
So if you can make a game that only relies
on those things, then you are going to run
everywhere without any modification.
OpenGL driver. The thing here is that these
devices are built by different people and
they have different back ends, and the drivers
are written mostly by the OEMs, so those things
are also different. So for example the Nexus
One uses a Qualcomm back end which as an ATI
graphics back end in that, and that driver
supports ATITC texture compression.
The Droid has a power VR back end. That thing
supports PVRTC texture compression, and they
are not compatible. If you wanted to use texture
compression, you can ship versions of textures
that have both. You can choose not to use
texture compression. That's what I did. Or
you can use ETC1, which is the emerging standard
amongst OpenGL ES compatible devices.
That's the big driver delta that you probably
want to be aware of, is this texture format
issue.
Almost the entire world at this point is ATITC
backed, and the Droid is the outlier with
PVRTC. I'm sure that in the future, there
will be more. ETC1 is probably the way to
go here. It seems like it's the simplest,
most standard solution. It's also not the
best format. Doesn't support alpha. There
are some other problems with it. But that's
probably something to be cognizant of when
you are developing.
For my part, I actually didn't use any texture
compression. I just loaded all the textures
raw, and it was fine.
Some of these devices support OpenGL 1.1,
some support 2.0. That's something you ought
to be cognizant of.
If you are writing a shader based OpenGL 2.0
game, obviously it's not going to run on the
first generation of devices that don't support
OpenGL ES 2.0.
And also, GL extensions is a string that you
can query from OpenGL. It tells you what the
particular device you are running on supports
above and beyond the spec. So if you are going
to use extensions, and I actually use several
of them, to get higher performance, you need
to query the string before you start using
it. Things like what texture format are supported
will show up in the string.
So that's the sort of standard way for OpenGL
ES to allow device specific extensions. You
should check to see if they are available
if you want to use them and then fall back
on the regular spec if they are not.
The other thing to consider is that Android
versions in the wild are different. There's
basically three versions available in the
world right now. That's Android 2.1, Android
1.5 and Android 1.6. From a game developer's
perspective, there is not a whole lot of difference
between these three versions. Android 1.5
basically gives you everything you would need
the to build most every game in the world.
The big difference between Android 1.5 and
Android 1.6 as far as you're concerned is
Android 1.6 added support for different sized
screens.
So Android 1.5, every device is guaranteed
to be HVGA. From Android 1.6 on, they can
be different sizes.
The way that works in practice is, when you
query the size of the screen from the framework,
if you're running in Android 1.6, you'll get
a value that might not be HVGA.
And if you're in 1.5, you'll always get HVGA
values.
It's a pretty simple step up.
In 2.0, we produced support for our OpenGL
ES 2.0.
That's something that game developers usually
care about.
There's also multitouch APIs in 2.0.
Actually, Android, because you can write almost
your entire game on Android 1.5, it's pretty
easy to be backwards compatible and cover
the entire range of devices.
If you target Android 1.5 or 1.6, that's a
pretty good way to go.
You can see they're pretty big parts of this
pie.
You can also do things like target Android
2 and say that your MIN SDK is 1.5 and then
use Java Reflection to make sure that, say,
the multitouch APIs are there before you call
them.
You have quite a lot of flexibility in this
case.
But generally, the message here is, you know,
you don't want to forget about Android 1.5
devices or Android 1.6 devices, because they're
a big segment of the market.
So the big question that -- I work with a
lot of game developers.
The big question they always have is, so what's
the high end?
What's the upper bound for performance?
So what I did is, I wrote a 3D -- sort of
simple 3D profiler to see what these kind
of new devices, particularly I was interested
in the second-generation devices, to see what
they could do.
And this is a very simple test.
What it is is it takes a height map and extrudes
it in 3D.
And I can tune the complexity of the scene
up.
I can say you divide this ten times or I want
you to divide the height map 1,000 times.
So the number of verts in the stream can be
scaled.
I also have controls for scaling OpenGL state,
like, texture size and mip maps and things
like that.
And those aren't really included in this particular
test.
I was looking just at seeing complexity.
And what I found was that all of these devices
-- I looked at the Liquid, the Nexus One,
the Droid, and the Xperia, and just to have
one phone representing the first generation
of devices, I have the HTC Magic in here,
too.
What I found is, basically, regardless of
how many verts I draw, they all perform at
about the same speed.
The exception here is the Magic.
You can see that it starts out very fast,
at 500 verts.
It's able to render 60 frames a second.
Then it linearly grows, as you might expect
it to.
The other devices, like, especially the Xperia
-- that's the green line there -- the Xperia's
speed is probably linked to its refresh rate.
'Cause even if I draw a scene with nothing
in it, it takes 32 milliseconds.
That red line on this graph, by the way --
the bottom axis here is verts per frame,
and the "Y" axis is frame time.
And if you've done game development before,
you probably know that in order to render
at 30 frames a second, you need to complete
your frame in under 32 milliseconds.
The red line is the 32-second millisecond
barrier.
Any slower and the game may not be playable,
especially if it's a, you know, kind of action-packed
game or something that requires quick reflexes.
If it's a slower-paced game or something that
is not animation-heavy or the screen isn't
changing every frame, then you can certainly
get away with slower than 30 frames a second.
But most games make 30 frames a second their
target.
That's what I've put on this graph.
So why doesn't this time change?
Why doesn't the scene complexity have much
to do with the time?
I think this is because, as I mentioned earlier,
most of the time, for this frame is actually
being spent copying pixels, not rasterizing
the scene.
That's because these devices all have big
screens and are mostly fill-bound.
Good news is that even if we render a pretty
complicated scene, it doesn't cost us any
more.
If you target 30 frames a seconds, you should
be able to get quite a lot of performance
out of these devices at that speed.
If I increase the number of verts in the scene,
you know, it's pretty much the same story.
Things start to get slower, and all these
devices pass that 32-millisecond boundary
eventually.
I kind of binary-searched this space to find
out where the maximum bar was.
The average, to me, seems like about 27,000
textured unlit colored verts per frame.
There's a couple of devices that can do a
little bit better than that, but that seems
to be a good target.
This code is all open source.
It's on apps for Android.
I have the link at the end.
If you'd like to reproduce this on your device,
you can do that.
There's also a bunch of other tests and stuff
in this code that you can use.
So the message here is, on the low-end devices,
if your target is 30 frames a second, you
can push about 5,000 verts per frame.
If your target is the high-end devices, you
can push almost 30,000 verts per frame, looking
at five, five and a half X difference.
But the high-end devices are only 40% of the
market at the moment.
You'd like to be able to target everything.
You need to scale from the low end to the
high end.
And, actually, thinking about writing code
that can scale from the low end to the high
end in order to sort of make your user base
as large as possible has a lot of benefits
even for the high end.
Just real quickly, I kind of want to make
a point about level of detail.
Level of detail, the idea here is that if
things are farther away in the scene, like
the further away from the point of view of
the camera, then they're smaller on the screen.
They're taking up fewer total number of pixels.
In that case, they don't need to be as a high
resolution mesh as they would if they were
closer to the scene.
So in this case, what I did is I took the
test and I instanced a little programmer art
hill here and made it so that as the hills
get further out in the background, they become
simpler meshes.
The image on the left here has no level of
detail turned on.
The image all the way on the right has level
of detail turned on pretty aggressively.
You can see, you know, if you look at those
mountains in the background on the right that
kind of look kind of crappy, because this
is basically programmer 3D art.
But if you had a little dune buggy or something
that was right in the center of the frame
that the user's eye was targeted on, I don't
think they would notice.
And even though these scenes look pretty similar,
the actual performance is pretty different.
So I highly recommend thinking about scaling
between the low end and the high end.
You can do this with complexity of the scene,
like I'm doing here.
You can also do it with mip mapping and other
techniques to say, stuff that is further in
the background, make it simpler.
And if you're going to support these lower-end
devices, one way to do that is just to chop
off the high end and allow your device to
be lower resolution on these lower-res devices.
They have smaller screens, anyway.
Also, if you use artists to generate the stuff,
it looks a lot better.
I told you about what to expect in terms of
performance, at least based on this test.
I told you about the two device classes you
can think of as your baselines.
Now I want to give you a couple of best practices
about how to actually achieve maximum performance.
This is mostly OpenGL-centric stuff.
If you have done OpenGL, especially OpenGL
ES, on mobile devices before, I don't think
you'll see any surprises here.
If you take one thing away about performance
from this talk, it should be that you must
use VBOs.
VBOs are vertex buffer objects.
That's vertex arrays that are stored on the
video card in VRAM.
This is a huge performance improvement to
these guys.
And they are almost universally supported.
Now, that said, you want to have as view VBOs
as possible in memory at any given time, because
switching between them is a pretty expensive
operation.
Actually, all OpenGL state change is a pretty
expensive operation.
So when optimizing a 3D scene, your mantra
should be, "Limit state change."
There's no advantage to using fixed point
verts.
You can test this with the height map profiler
code.
Floating-point verts are actually faster in
most cases.
I mentioned (inaudible) texture compression
is the most compatible across all of these
OpenGL ES 2.0 devices.
It's not generally supported by the OpenGL
ES 1.0, 1.1 devices.
If you're going to do 2D texture blitting
to the screen, like you're drawing a 2D game
or you have (inaudible) elements, the draw
texture extension is pretty much universally
supported and it is the fast path for 2D.
And I mention that the WVGA devices are fill
bound, so your target should be 30 frames
a second.
To use GL extensions to check what you have
to scale between the low and the high end.
If you're using 2D games, if you're making
a simple 2D game, especially if you're targeting
high-end devices, you can probably get away
with just CPU drawing.
There's a whole system called Canvas that
will copy the bit maps on main memory in CPU.
It's not horribly slow, but it has a lower
high watermark on the low-end devices.
If your game is simple and doesn't need 30
frames a second animation or not the entire
frame is changing every frame, that's something
you can consider.
And if you decide to target those high-end
devices that support OpenGL ES 2.0, then always
use OpenGL ES 2.0 instead of 1.0.
Even though those devices are backwards compatible
GL ES 1.0, 1.1, they implement it as an emulation
layer between sort of the OpenGL ES 1.1 spec
and the actual 2.0 hardware.
So if have you a choice between 1 and 2 and
you are okay with cutting out the low end
that doesn't support 2, always choose 2.
So on Replica Island, I used a combination
of these methods to draw the scene.
This game, I worked really hard to make sure
that it runs on pretty much every device out
there, even the G1.
I developed most of the game on a G1.
I used the draw texture extension which I
just mentioned for my 2D sprites.
It also includes the background image, which
is basically a static texture that scrolls
a little bit.
The tile layers themselves are -- the foreground
is actually made out of tiles.
And I rendered that using VBOs.
And I have spoken before about the different
methods that I experimented with to render
this background efficiently.
And I've actually come up with another method,
which is what I shipped with, which was to
take the entire level and wrap it up into
a single large VBO and then draw scan lines
of tiles out of it.
You can actually draw a subset of vertices
within a single VBO.
It's much faster than having separate VBOs.
This worked out really well for me.
I have a single texture to bind, a single
buffer to bind, and then I can draw any part
of the level, depending on where the camera
is looking.
Okay.
We talked about performance.
Let's move on to game architecture.
I want to talk a little bit about how you
should go around architecting your software
for Android games.
I'm an advocate of this dual-thread approach.
The idea here is, you have a game thread and
a render thread, and they're separated.
The game thread is doing all your nongraphics
simulation update, AI, collision detection,
physics, whatever, and then it's telling the
render thread what to draw for the next frame.
So if you look at this chart, we have --
Android framework is kind of at the top, and
it's going to send input events to your activity
thread, which is basically your main UI thread.
All Android applications have this.
And the activity thread is going to be dormant
until it gets woken up by an input event.
And I think you should just take that input
event and pass it on to your game to let the
game thread figure out what to do with it.
The game thread and the rendering thread are
going to be running in parallel.
The reason to split these is that the rendering
thread is going to periodically have to block
on hardware.
If you're able to run fast enough that you're
ready to draw before the previous frame is
complete, then the rendering thread is going
to actually stop and wait for the hardware
to say, it's ready to start it, issuing commands
again.
So during that time, you know, this is a game,
there's no reason to leave the CPU idle.
We might as well have another thread doing
the next frame of simulation running at the
same time.
This is basically how Replica Island is implemented.
If you follow this sort of approach or if
you decide generally that you would like to
have a separated rendering thread, which I
highly recommend, you can use this GL surface
view class which we've written for you.
This a class that does all the setup and teardown
for OpenGL.
It will manage all of your OpenGL state through
pausing and stuff like that.
And it basically just manages this whole thread.
It gives you a very simple interface to load
textures and to set up the view port and to
actually draw the frame.
Just to give you an idea of how simple this
is, this is a Android application.
This is pretty much all the code you would
need to just get something up on the screen
that's rendering with OpenGL using GL surface
view.
The only part that I actually have to write
other than this sort of activity shell itself
is this part that says "awesome game renderer"
that's going to render my awesome game.
Plug that thing into the GL surface view and
then it's ready to go.
Just needs to know about pause events.
So what's the GL surface view renderer?
Well, it's basically these three call-backs
that I just mentioned.
You get a call-back when the surface is created.
That's when you load your textures and set
up your VBOs and any other work that you need
to do that requires writing to VRAM.
You get a call-back when the surface is changed.
That's when your view port is ready to go,
either because it's been ready to go the first
time or because the phone has been rotated
and you want to handle orientation changes,
things like that.
Set your viewpoint.
Then you get a call-back to undraw frame every
frame that you want to draw.
That's pretty simple; right?
Replica Island uses this.
Pretty much every 3D game on the market uses
this.
It saves you a lot of time.
You should use it.
Let's say you don't really want to write your
game in Java or you have your game written
in C++ and you want to bring it over to Android.
You can do this with NDK.
The way I would suggest is to use the same
framework.
Use Java threads to split this stuff up.
You still are going to have the same problem
with the hardware blocking sometimes.
You want to separate rendering out from game
simulation.
But you don't have to render -- implement
the rendering or the game (inaudible) in Java.
You can just implement it in native code.
I would set up these up with regular Java
threads and call in to native code to implement
the content of these threads.
The way that would change the example I just
showed you a minute ago might be like this.
In this case, I've taken this awesome game
renderer and added some sort of native prototypes.
I'm assuming that I wrote this code in C++
and compiled it with the NDK and then I'm
loading the library here in this little static
section.
And, basically, I'm just going to call similar
functions that I've defined in my own C code
from the Java call-backs.
You notice that the GL state is shared here.
I can set stuff up in GL with Java and then
refer to it in C land or vice versa.
It's all the same OpenGL context.
So I don't actually have to pass anything.
You can see Java gets a GL pointer.
But there's nothing to pass down to the actual
native code in this case.
This is how I recommend users go about setting
up your Android games if you have a lot of
code that's already written or that you're
going to write in C++.
Now, part of the reason that I'm recommending
this, some people are, like, "Why do I have
to have this whole Java thread nonsense?
I wrote my game already.
It's good to go."
Is because, well, Android's a multi-processing
operating system.
And because of that, there are some cases
that you won't see on other mobile devices
that aren't multiprocessing.
One of them is this pausing problem that I
want to talk about.
And what that is is, say I'm running this
game.
I have on the left here, you know, sort of
the game output.
It's a scene from the game.
And on the right, I have something which is
what I imagine the contents of VRAM to be,
just a bunch of textures that I've loaded.
I'm playing and everything is great and I've
set it all up and everything is fine.
And then I decide I'm going to go home.
And I check my e-mail.
And then I may run another OpenGL-intensive
application.
And at some point, what happens is, depending
on how much memory my device has, at some
point, you know, maybe the VRAM that I allocated
for all of those textures in my game, it needs
to be used, maybe by this profiler test.
In that case, you know, what Android is going
to do is leave my game running but go ahead
and give the VRAM to the other process.
So when my game comes back, all the VRAM is
basically toast.
If you use GL surface view, you will get a
call-back to on surface created in this case.
That means it's time to reload your texture
and buffers.
And some games are not built with the idea
that the texture handles that they've allocated
in VRAM are going to change in the future.
So this is something you definitely need to
be cognizant of.
But if you use GL surface view, it will work
out all the details.
When you come back from a pause, if your GL
context is dead, it will tell you and give
you a chance to load textures and buffers
again.
Now, on Replica Island, I actually made it
more complicated on myself, because GL surface
view actually proactively throws state away
when it pauses, on the theory other applications
need to use it.
But I had a case where I wanted the GL data
to stick around while I brought up another
activity.
That was this dialogue box case.
The game is paused in the background, and
this dialogue box activity has come to the
foreground.
But I really don't want to reload textures
just when a dialogue box pops up.
So I modified GL surface view in this case
to only reload textures when the context is
detected to be lost.
That works out fine, too.
While I'm talking about things I learned on
Replica Island, I want to sort of shift gears
to talk about something that's not graphics-related.
And that was input customization.
This is something that I did not anticipate.
Users really, really want to be able to customize
their controls.
And the game, just if you haven't tried it,
the game is mostly track ball, touch screen-based
game.
You can play it with a D pad or now you can
play it with the keyboard.
And I added some tilt controls in there after
a couple of updates to allow people like Xperia
owners, who had no track ball or D pad, to
play.
What I found out is, first of all, there are
a lot of devices with a lot of different controls
out there.
And all of those users want to be able to
play my game.
And all of those users are going to want to
be able to play your games as well.
So allowing people to customize the controls
to fit their phone is a good idea.
Above and beyond that, what surprised me was,
users actually want to be able to customize
their controls even if they have the default
setup that you had in mind when you built
it.
I have users telling me that they have a Droid
that has a D pad and has a keyboard, and they'd
rather use the tilt controls because that's
what they are comfortable with.
That's something I didn't anticipate.
I've made -- since I released the game in
March of this year, I've made four updates.
All of them have been to extend the amount
of input customization that I support.
Side effect of that is that my input system
got kind of complicated.
I started out with a pretty simple system
where the game wants to have a query-based
interface.
The game wants to be able to say, when was
the last time the jump button was pushed?
Or how long has the jump button been down?
Or what was the last vector of the track ball
roll? Something like that.
Android doesn't really give you events in
that way, doesn't really provide a query interface
for you.
It says, "Here's a delta. Update."
You know, Android doesn't really give you
events in that way. It doesn't really provide
a query interface for you. It says here is
a delta update. The track ball has moved a
little more or this keyboard button has come
up.
So I put a little system in between those
two just to record the current hardware state.
In the initial version, all it did was really
expose a track ball API to the game. So the
game could say what's the current track ball
state. And halfway through development I added
a couple of other APIs for what is the touch
screen doing and what's the orientation sensor
doing, and that's pretty much how the first
version shipped.
And then I started to add customization stuff
and I realized I wanted to add heuristics
in there because maybe the track ball is too
sensitive on some devices, so I wanted to
have a heuristic for tuning where the dead
zone is.
I didn't really want to increase the number
of APIs I supply to the game because then
the game is going to get ugly and complicated,
so instead I made the input system really
ugly and complicated.
And this is horrible; right? Like if I had
planned on this, I could have done it better
the first time out, but I didn't plan on it.
So in the most recent update, I ripped all
this crap out and rewrote it like this.
Now there's a generic input system that takes
input state from the Android framework and
records it but doesn't do anything with it.
And there's a separate interface called the
input interface that takes the stuff from
the input system and applies whatever heuristics
or filters or configuration control options
and provides a game-specific API. Jump button
is down, you know, the user wants to go left.
Things like that.
This is all open source. All the stuff is
up code.google.com. So if you are going to
make a game and you would like to have the
same sort of query interface, you can just
grab this input system and reuse it.
You can probably write your own input interface
depending on what inputs your game wants.
This is something I had not anticipated that
I got a lot of bang for the buck out of for
my users.
So last section here, is I'd like to talk
about Android Market for games. And I didn't
really know a lot about Android Market for
video games. I knew what games were up there,
and I knew that there were different ways
to try to promote games. And I didn't really
have any idea what makes a successful game
in terms of Android Market user base.
So I tried a bunch of stuff. The Replica Island
project, the entire point was to be an experiment.
First, can I write this sort of game on Android,
and then how do I write it. And then finally,
how do I publish it? What's the best way to
do that?
So the first thing I did is I took a look
at the traits of the most successful games
on Android Market.
This is a pretty small view of the market;
right? This is only the top ten titles across
all game categories, and I sorted them by
most popular, which is the market ranking,
and most downloaded.
And basically what I could see is that pretty
much all of these apps are backwards compatible
to 1.5. That's a big deal. There's a lot of
users who are still back on 1.5 and 1.6.
So the successful apps, at least in the top
ten, almost all support that.
Almost everybody has screen shots in their
market listings. That's a really big deal,
too because users want to see what it is,
even if it's a free app, they want to see
what they are getting before they download
it.
You will see the most downloaded free list
here actually has the fewest number of screen
shots. It's only 50%. My theory there is a
lot of those apps predate our market screen
shot system. They have been up there such
a long time that a lot of users download them
before they are even screen shots.
And for paid apps -- I was making a free app
so this wasn't particularly relevant to me,
but I was interested. For paid apps, most
of them supply a free version, some sort of
light version, free demo, something like that.
And it looks like the average price for the
most popular apps is less than 3.50.
So my idea was, well, I'm making this game.
I want it to be successful. It's going to
be free. I am going to open source it anyway.
So it doesn't really matter. I am not interested
in making money on it, per se, but I would
like to understand as an experiment what I
need to do to make this thing popular.
I gave a talk here at Google I/O last year,
and I mentioned this game and I showed a demo,
and I made the mistake of saying it was almost
done.
It's all up on YouTube now and it's totally
closed captioned of me saying it's almost
done. And I had this function in my head that
looked like this, I guess.
[ Laughter ]
>>Chris Pruett: Because I really released
it like two months ago. The lesson here is
don't ever say it's almost done.
The reason I didn't release it is because
I wasn't ready to. And part of the reason
for that was that I didn't know anything about
marketing and I had a lot to learn.
My initial marketing plan was to make a Web
site, to have a blog about it, and to send
out some press releases to a couple of Android
blogs, like five of them, and to have a QR
code that you could scan to download the game.
That was it.
And the base plan for this was that my marketing
spend was going to be zero dollars. That was
the most important part to me.
Actually, it turned out that I didn't have
a lot of confidence in writing the press release
myself, so I got this guy Nicholai, my PR
manager, to write it for me. And Nicholai
said he would do it pretty much for free as
long as I bought him the entire set of Ming's
Quest DVDs off Amazon.com.
So I looked and that cost 39.99, so my marketing
spend was up to 39.99.
So we did it, and actually he wrote the press
releases and sent them out and it was pretty
successful. And once we got past our agreed
upon cap, he said where is my Ming's Quest
DVDs? And I went back and looked on Amazon
again and I had made a mistake. They don't
have a Ming's Quest DVD set. I was looking
at simply Ming's, and so my marketing expense
went back to zero dollars.
Sorry, Nicholai.
I ended up releasing the app in March of this
year and it's done he pretty well. I passed
half a million downloads within two months
which was very exciting. User feedback has
been pretty good. I am at four and a half
out of five stars.
I will show you the graph, hooray, volume,
but you can tell there are some points at
which the angle of the graph changes, and
I wanted to talk about what those points were.
The first ten days -- Like I said, Replica
Island is an experiment. My goal is to be
able to use it in a case study at a talk like
this to tell you guys when you go out to make
a real game, here is what you should do. I
had no idea it was going to be this well accepted.
But as an experiment I decided to track the
difference between my zero-dollar marketing
plan and organic growth.
So for the first ten days I didn't say anything.
I launched the game. I just uploaded it to
market. I didn't write in my blog, I didn't
talk about it. I was at another conference
and I mentioned to the people there that I
had released it, so those 200 people there
knew I release. But there was no fanfare,
no marketing, no nothing. Because I wanted
to see what would happen if I just uploaded
it, said nothing about it.
The first ten days I got about 18,000 downloads.
That seemed to be about the rate of organic
growth, which wasn't bad. Ten days on, I said
okay, Nicholai, send out your press releases,
and he did. And you can see what happened
here. We had one YouTube video and this press
release he wrote, and as soon as he sent that
out, the angle went up a little bit. A bunch
of blogs were very nice and wrote about the
game, and people looked at the YouTube link,
and a bunch of people re-tweeted it, and that
was pretty cool. Gizmodo wrote an article
about it. That was cool. And the angle stayed
pretty nice after that until I got featured
on Android Market. Full disclosure, I am part
of the Android team. I didn't have anything
to do with the decision to feature Replica
Island. I was very excited about it when it
happened.
And that is what caused the graph to go to
its current angle, which is about 10,000 downloads
a day.
Didn't actually help my download speed at
all.
Some developers have come to me and said,
"I'm worried that developers are going to
ship superfluous updates just so they can
get into the 'just in' category." And these
were updates to add control options, so I
thought they would be very high value, and
the market comments suggested users really
liked them, but I did not see any sort of
spike from shipping an update in my downloads.
This evidence is only my application, but
from my point of view here, it doesn't look
like shipping updates helps a lot. So I don't
think you are going to artificially inflate
your downloads that way.
So going back to this couple-of-months calculation.
The real problem that I had with shipping
the game in a couple of months was not technical.
When I showed it last year, it was pretty
much running. The tech was pretty much there.
It ran on the G1. It scaled up to the newer
devices when those devices came out. I didn't
have a lot of technical problems to solve
at that point.
The problem was the game wasn't all that great.
I thought it was kind of boring. It was mediocre.
Part of the issue here was that my tools were
not that great. This is the level editor.
This is the level editor. I wrote it in three
days in JavaScript. It totally works, and
it was pretty limiting in the long run.
The problem was that I had developed these
levels and they worked, and they weren't as
fun as I want them to be, and in order to
make them more fun I was going to have to
spend more than three days on the tool, and
I really didn't want to do that.
So instead of making the tools better, I decided
to release the game to some users and try
to get user feedback.
When I worked in the game industry one of
the things we would do is bring kids in, have
them play the game, watch them play and see
where they would fail. And it was obvious.
Would you watch a kid run into a brick wall
for a while because he doesn't see wall there
because your texture hasn't high enough contrast
or something like that. It was very instructive
for making the game better.
But on a hand-held device like this, it's
hard to sit over someone's shoulder and watch
them play the game as they play, so instead
I made an automated system.
So I made a system where every time a player
dies, I was particularly interested in where
the players were going to die, everywhere
that they'd die, it would ping a server and
say, "Hey, player died here. Player died here."
And every time a player completed a level,
it would say, "Player completed a level it.
Took this amount of time."
And the output of that I graphed as a heat
map over my level geometry, and this was super
awesome because it totally told me where the
difficult parts of my game were and where
the not-so-difficult parts of my game were.
This little valley over here, that's like
the valley of death. Tons of people die there.
I didn't intend it to be that hard but it
turned out to be.
So I learned a lot from this. I looked at
this feedback and I said, okay, my camera
system needs work because too many people
are dying from falling in pits. And the control
system isn't tight enough, and this level
over here that I intended to be really easy
is really hard which indicates that people
aren't learning how to use this particular
game mechanic because I am not teaching it
well enough to them.
And in a very short amount of time I got a
huge volume of information that I could turn
into minor adjustments that improved the quality
of the game a lot.
I was so happy with the system that I shipped
the final game with it turned on. You can
turn it off in the options if you don't want
to share this information with me, but if
you don't turn it off, the game will ping
the server and tell me that an anonymous user
has died in this location. So now I have tons
and tons and tons of data from real users
about where I failed at level design.
And this is instructive because users are
also looking for developers who are interested
in continuing to improve their games. So if
I make an update based on this and push it
out and say, "Okay, everybody who got stuck
on level 22, I totally am there for you today,"
a lot of users are really happy about that.
I am very excited to be able to continue to
work directly with my users like that. That's
something that in other game spaces is basically
impossible.
You should listen to your users generally.
It's useful to listen to users because they
have really good feedback. Some of it is really
harsh and some of it is really hard to take,
but a lot of it is really, really positive
and really useful. I learned a lot from looking
at market comments.
Android Market users are also hilarious.
[ Laughter ]
>>Chris Pruett: My daily routine now is downloading
-- or opening up market and reading the comments
to see what's been posted lately.
Sometimes I learn something, and sometimes
it's just really funny.
Sorry about your G1 mouse thing, dude.
[ Laughter ]
>>Chris Pruett: Also, and this is another
thing I did not expect, users are fascinating.
Particularly this user group is fascinating
because you have a view into them with Android
Market that you don't get with other game
spaces.
Like this top one here says, "Completed game.
Now what? Need more levels or uninstall."
And the one below that, Jon, says, "Fix level
22 with the spikes and make it so you can
play through other levels, then I will give
it five stars."
This is basically like a threat; right? "I
am going to uninstall your game." Like, okay,
I beat the whole game. You have to give me
more content or I am going to uninstall.
And the bottom ones are like challenges and
bugs are the same thing? Like level 22 is
pretty hard. I made it pretty hard. It's pretty
late in the game. I made it hard intentionally,
but these users see it as a bug. If I can't
progress, then the game is broken. That's
fascinating to me.
And I don't think these users are necessarily
wrong. I mean, this is not the way that I
had thought about game design, but if I'm
really trying to reach the maximum number
of users, I should probably consider their
point of view.
So some lessons I learned from users are that
user bug reports are highly, highly unreliable.
That said, if you can find a user who is willing
to work with you and send you logs and try
to say here is how I am reproducing this bug,
those people are invaluable and you should
totally, totally have an e-mail correspondence
with them at the very least.
Also, I found that users tend to chalk up
bugs, even if they are not bugs, to which
device they are using. And almost all the
time, almost all the bugs I shipped affected
all the devices. And I have very, very few
device-specific bugs. So like here is a demo
here or example here where the first guy says,
"Works great on the Samsung Moment," and the
next poster says, "It doesn't work on Samsung
Moment." Which is it? Turns out it works pretty
well on the Moment.
So different strokes. This is what I was talking
about, different user perspectives. Some of
these users are extremely casual. Some just
want to be able to consume the content. They
don't want any sort of challenge. And other
users are extremely hard core, like they tell
me that the game is too easy. I included three
different endings and they unlock all three
in two hours and say now what?
Replica Island actually has a dynamic difficulty
adjustment in there which is a system by which
if the game sees that you are really sucking,
it will try to dial down the difficulty without
telling you about it. Players don't like to
hear they are hand held, but my goal is to
get them to continue to move through the game,
because if they get frustrated, they are going
to uninstall for sure.
That's a good idea, but that's not nearly
enough, and my future updates are going to
be even more proactive about either giving
more rewards to users that are hard core.
Maybe that's achievement systems or maybe
it's social sharing with other friends or
some other way for them to go out of their
way to want to beat the game more than once
or beat the game without taking a hit or something
like that, and also maybe an option to remove
all challenge completely from the game. Maybe
select any level or say I want to be able
to fly forever, whatever it is, f the users
who just want to consume the content who aren't
interested in a hard-core challenge.
Other thing to remember is your market placement
is not static. I have, like I said, I have
a 4.5 review score at the moment which is
fantastic, but for about three days there
I lost half a star and I was down to four
and that pissed me off. I was like I have
to get those guys back. And I had an update
that I had been working on and I fast tracked
the update and I shipped it and my half star
came back and I felt so good about it.
But the thing you have to remember is the
users are actively playing and rating your
content even after you sort of consider it
done. So if you want to stay at a high placement
in market, you have to get actively involved.
And it doesn't always mean shipping updates.
It might mean making your description text
more explicit, or telling users how to play
the game better or providing some other sorts
of support, but users are there and they are
active, and the market placement moves.
And then communicating as many ways as possible
is the last point here. This is actually really
hard. But when you make an update, unless
you are really verbose about it, users aren't
going to understand what you changed. The
number one complaints I have right now are
all related to control customization options
that I solved two updates ago. And the control
customization screen itself is not discoverable
enough, and people are not finding there's
a solution to their problem and they're writing
a report in Market about it. So my next step
is to try to make that more discoverable by
communicating it in more ways and different
ways. Something to think about.
To finish up here, I want to leave you here
with a quote by a guy named Jonathan Blow.
He is the developer of a game called Braid,
which is a pretty awesome game. He says, "It's
not so much about innovation as exploring
interestingness." This has been a really instructive
quote for me for the last year or two because
what he is talking about is your game needs
to be interesting. The way you define quality
of a game is by whether or not it's interesting
to users.
That might be a function of technology, or
it might be a function of innovative game
mechanics nobody has ever seen before, or
it might be art style or good writing or something
like that.
Whatever it takes, make sure the game is interesting
and then users will come.
Last but not least, here is the Wave link
again. There are some Google Moderator stuff
up here. I will put the questions up in a
second. I am also going to take questions
from the mics, wherever they are. We have
about 15 minutes for questions. And here is
the source code.
First, if you have never written anything
for Android before, then you might be kind
of wondering what the heck I am talking about
with this talk. But developer.Android.com
is where to start.
And SpriteMethodTest is another profile test
I wrote for 2D testing. That source and the
HeightMapProfiler I talked about today are
up on this apps-for-android project on Google
code.
And replicaisland.net has links to the game
and source and other things about the game
if you are interested. All the sources are
available under Appache, too. So if you want
to use it to make your own games, go for it.
Thank you very much.
[ Applause ]
>>Chris Pruett: Okay. So I am going to switch
over to Google Moderator here.
I apologize for the small screen but I totally
set it up wrong.
Okay. Let's do -- Is there a question on the
mic first? You, sir, over here.
>>> I was just wondering if you had any comments
about audio for games.
>>Chris Pruett: Sure.
>>> And then FPUs. Some devices, or about
half of the devices don't have them, and the
other half --
>>Chris Pruett: Right. So the question is
what to do about audio for games and what
about devices -- support for FPUs.
Most games, the way they want to play audio
is either by playing a streaming music track
or by playing sound effects. And both of those
APIs are available. They are only available
from Java, though.
For playing streaming music you probably want
to use the media player object. And for playing
sound effects there is an object called sound
pool, and will allow you to load a bunch of
samples and then fire them off and it has
a priority system. It's basically what most,
I think, game engines will implement themselves.
If you are writing your code in C++, you can't
talk to those systems directly, so the normal
mode that game developers I have worked with
are doing is they are either doing all the
mixing themselves in C++ and passing a PCM
stream up to a media player, which is fine
if you already have mixing code or if you
just like to let the Android framework do
it, you can either just call into Java from
C++ using JNI or you can just write the sort
of sound invocation code in Java.
On the point about FPUs, that's true. Some
devices have it, some devices do not.
The tests that I have seen suggest that there's
no benefit in Java to writing code in fixed
point because the VMs version -- or whatever
code you write to do fixed point emulation
is going to be as slow or the same speed at
the software emulator that's already running
in native code.
So if you're writing in Java, don't worry
about it.
Just use floating point.
That's what I did.
I didn't ever worry about it.
If you're writing code in native code, then,
yeah, there's some things you want to be
-- you want to be cognizant that there is
no floating-point device on a lot of devices.
That will probably continue to be true.
If you can detect that there is floating-point
hardware by the name of the architecture or
something like that, you can use it.
My suspicion is that you don't need to worry
about it too much.
And if you really have high-performance code,
then you're probably using fixed point already,
even in native code.
Okay. So question over here.
>>> Just wondering whether you have any suggestion
if you write to target the third generation
or fourth generation of device, for example,
the next Android release or even, like, a
device with a different processor other than
ARM?
>>Chris Pruett: So you're talking about the
emulator?
>>> On the real device.
>>Chris Pruett: I'm sorry, could you ask the
question again.
>>> Kind of in the future, or next -- future
generation, you probably have Android 2.2
and so forth, and also the device for (inaudible)
could be using, like, X86 or mixed kind of
processors.
So any suggestion if you would like to target
that kind of device.
>>Chris Pruett: The question is, if you want
to target devices that don't exist yet or
haven't come out yet that are not running
on our processors, how do you do it.
Basically, you can figure it out yourself.
The NDK is based on GDB.
And if you go and look at the make scripts
inside of the NDK, you can probably figure
out how to build a application for that architecture.
Now, the thing is, there's a whole bunch of
stuff in the framework for loading different
ABIs.
And we're going to have to support that before
you can really easily target those ABIs.
So what I would suggest is, by the time a
significant device that supports another architecture
comes out, the NDK will have support for that.
So I wouldn't worry about it too much.
If you're on the cutting edge, then you're
on the cutting edge.
But if you're a regular developer, we will
have software kits out for you by the time
those devices are going to ship.
I'm going to take a question from the moderator
thing here, if I can.
That's fantastic.
Yay!
Okay.
This is awesome.
Okay.
So I looked at this before, and it looks like
this question about Replica Island having
benefited from prerelease with a few (inaudible)
and no story to build up excitement for the
game and we have a lot of frustrated users,
people are totally ambivalent about that question.
But it has a lot of votes, so I'm going to
answer it.
I did prerelease Replica Island to a limited
number of users, all beta test users.
I didn't do it on market.
The story, actually, was written first.
I wrote the story before I did any of the
level design.
So cutting the story out would not have saved
me time.
Maybe this questioner is asking about if I
could have cut the story out as a reason for
users to come back after a public beta.
I don't know.
I think that users who download something
on Android Market and leave it installed are
interested in that app.
And you can continue to ship updates to them
very easily over Android Market.
It seems like a public beta thing could sort
of work.
In my particular case, I felt like the game
was pretty solid when I shipped it, and immediately
when I found out that, say, users who had
Xperia's couldn't play, I tried to make modifications
to help them.
I don't know if it would have benefited from
a prerelease.
But I don't think it would have benefited
me in terms of a development time.
The test I did with users to get feedback
from on the levels was useful.
But I didn't use Android Market for that.
I just sent them the APK.
Question over here.
>>> So when you were talking about your tips
and tricks, there were a bunch of things you
listed.
One of them was, it doesn't make sense to
go to the NDK just for OpenGL calls.
And I remember last year, you mentioned you
had a graph of how expensive each function
call is.
And JNI was way up there.
So it seems to me that it would make sense
to kind of bundle them into one since the
open GL calls are just JNIs, as far as I understand.
So why is that that it's not valuable?
>>Chris Pruett: I thought so, too.
I didn't touch on it, but I said in the slide
that one of the tests I did with the height
map profile senior I wrote the whole renderer
in Java and then the whole same code in C++
to see what the difference between the NDK
version of the renderer and the Java version
of the renderer is.
And it turned out there was almost no difference.
That's because I wasn't doing any intensive.
If you call it will from Java, it's going
to jump down to native code anyway.
The question is why isn't there a huge performance
benefit to skipping all those individual JNI
calls and having one function that does the
cause from the code.
There is a small delta.
It actually turns out to be less than a millisecond
even in the worst-case test, in this particular
test.
Now, I don't have a lot of state to set in
this thing.
I don't have a lot of GL calls to make.
So if you are the type of application that
has hundreds of thousands of GL calls to make
every frame, then JNI overhead is probably
something you want to be cognizant of.
For my particular tastes, I want to set up
all of the state ahead of time and issue as
few calls as possible, because that's the
fastest way to draw, especially for a test
like this.
None of that is CPU-intensive.
It's waiting for the driver to set the state.
Calling for an NDK is not a benefit.
Using the NDK closes off which platforms
-- if, you know, some theoretical X86 platform
shipped in the future, unless I recompile
my code, that NDK code won't work.
I've boxed myself in.
So if you're going to use native code, do
something intensive with it.
It does exist.
But, you know, do your physics calculation
or do your scene graph walking or whatever
it is that is expensive to do in an interpreted
language, do that in native code.
Don't just issue GL commands.
>>> Okay, thanks.
>>Chris Pruett: Okay.
Over here.
>>> If you need to add a video stream, be
it from a camera or a media file, into your
graphics, how would you do it?
Let's say it's a VGSI.
What kind of performance do you expect?
>>Chris Pruett: You're saying read from a
media file into --
>>> Or a camera stream, live camera stream
coming in.
>>Chris Pruett: You mean specifically coming
into GL memory?
The question is how to get bit maps into OpenGL
fast.
So there's only really one way to do it.
That's to set them as a texture.
You can set a render target and copy them
there.
There's a GL subtexture image and also a,
like, GL utiles will give you a copy for (inaudible).
That's generally not a fast operation.
And it's pretty much bus-bound, as far as
I can tell.
So the answer is, don't do that if you can
avoid it.
Preload all your textures.
And when you get the call to surface change,
load them again.
In the case of a camera or something like
that, I think it's going to be pretty difficult.
I think that you can do it.
And I think that you can do it at a frame
rate that is, you know, above 15 frames a
second.
But probably not on the current round of
-- the current range of hardware, anyway,
at 30 frames a second.
Because the bus is going to get saturated.
And also when you're in a case where the hardware
is waiting for this copy to complete, any
other sort of context switcher interrupt,
lining an event call back is going to be much
more expensive than it would be in a regular
game.
So my recommendation is, if you plan on sort
of loading textures at run time as the game
is running or as your simulation is running,
you should expect that to be very expensive.
>>> What about you mentioned (inaudible) texture.
I wasn't familiar with it.
>>Chris Pruett: Draw texture takes a level
-- a texture that's already been loaded into
GL memory and just draws it.
It's basically the same as making a quad and
making orthographic projection and saying
draw.
You don't have to do any stuff, you just say
draw texture and give it a scale and X, Y,
Z.
It's faster than to do it in code.
I'll take a question from moderator, if I
can make it scroll correctly.
So, let's see.
A question about Android Market in other countries
and a question about Flash 10.
So real quick about Flash.
I don't know anything about Flash.
[ Laughter ]
>>Chris Pruett: I'm involved in the game developer
side of things and not the framework, and
I don't work at Adobe.
So I think that there's probably lots to learn
about Flash at this conference.
But I don't know what those things are.
I do think that if you're going to make a
Flash game and you would like to bring it
over to Android, then, you know, the simplest
way to do it is probably to take whatever
they're building and ship it.
The problem with that is that, you know, you're
going to ship it to the devices that support
Flash, which is going to be a very small subset
of that graph that I showed you.
If you're interested in minimum time to ship,
it sounds like a really good idea.
If you're interested in taking content you
have already built and get it up on Android,
that sounds like a good first step.
If you're interested in using every single
Android user in the world, that may not be
a good way to go.
That's about all I have to say about Flash.
I have a very short amount of time remaining.
If you have a question, please come up to
the mike.
Because otherwise people won't be able to
hear you.
Any other mike questions?
Okay.
Speak loudly and I'll repeat it.
>>> Do you have any references on the best
way to mix in text with OpenGL?
>>Chris Pruett: The question is, how do you
mix in text with OpenGL.
OpenGL is really bad at text, generally.
You can make your texture with your characters
in it and then try to make quads to draw the
texture.
We actually have a demo in the API demos that
will take the -- will take the Android sort
of font system and rasterize them fonts into
a texture and you can build that to build
quads.
But it's not international, it won't do UTF-8
stuff, if you want to support languages like
Chinese, your textures are going to become
huge.
If you want to overlay text or overlay any
sort of regular view hierarchy stuff on top
of OpenGL output, you can do that very easily
using GL surface view.
That's going to just be an element in your
view hierarchy.
You can put other elements in front of it.
In my game, I have a pause text that comes
up which is actually just an image, but doesn't
draw with OpenGL, just draws with the regular
Android image view system.
I draw it right on top of the GL surface.
There's not a lot of overhead to doing it.
If you're talking about compositing text to
an only seam, that's the way to go.
Do I have time for one more question?
Is that it?
I think I'm out of time.
Thank you very much for coming.
And I'm looking forward to seeing all the
games you guys are going to make.
[ Applause ]