tag:blogger.com,1999:blog-21270239738457767372024-03-28T20:29:45.950-07:00Replica IslandInformation about Replica Island, a 2D platformer for Android.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.comBlogger30125tag:blogger.com,1999:blog-2127023973845776737.post-22669641616575831822011-07-09T00:58:00.000-07:002011-07-09T00:58:56.369-07:00Want to Change the Game Industry? Support the Xperia PLAY.The problems with the console game industry are well documented. The industry is fairly <a href="http://www.next-gen.biz/opinion/viking-development">homogeneous</a> (at least in the US), the cost of development is high (tens of millions of dollars has been the norm for a few years now), the hardware cycle is slow. The game industry does a poor job at reaching people outside of the "core" gamer group (which is mostly men between 15 and 25), and generally tends to make the same sorts of games over and over again (see if you can count how many first person war games have been released in the last decade; I can't). The big companies making games are too risk-averse to actually make anything interesting, and the developers themselves are often subjected to grueling death march development cycles. And after all that, 10% of developers make big bucks and everybody else loses their shirts. It's not a pretty picture.<br />
<br />
Depending on which school of thought you come from, this is the point in the lecture at which you stand and declare that consoles are doomed and the future of gaming is ______ (pick your poison: iOS / mobile, tablets, Facebook, etc). The argument here is that these devices are more widely accessible, development and distribution is very cheap, and even at a lower price point the potential for revenue is a better bet than a long-winded space opera designed for guys in their last year of high school. I mean, Angry Birds made about 10 zillion dollars, who needs Call of Duty anyway?<br />
<br />
Then, from the other side of the auditorium, the console game developer stands up and points out that Mario would be unplayable on an iPhone. You'll never beat consoles, he argues, because a touch screen interface just isn't good enough for a lot of console genres. Lack of physical buttons dooms mobile devices to "casual" (and he says it like it's a dirty word) games: Tetris and color matching and launching fowl. No depth, valuable only as a time waster. The market for more complex and meaningful games ensures a future for consoles, he argues.<br />
<br />
Here's where I stand on the future of gaming: I think traditional game consoles are going away, but traditional console <i>games</i> are here to stay. My rational is thus:<br />
<br />
Mobile platforms are ubiquitous--everybody has a phone. Software delivery to these platforms is extremely easy, and people download lots of apps (actually, <a href="http://www.engadget.com/2011/07/07/nielsen-majority-of-us-app-purchases-are-games-ios-users-play/">mostly games</a>). Therefore, the installed base of potential customers is much, much larger than all the consoles combined. Better yet, the hardware cycle for phones is much faster than consoles. Phones will surpass current gen consoles very soon. Heck, <a href="http://www.industrygamers.com/news/john-carmack-unquestionable-that-mobile-will-surpass-current-consoles/">even Carmack thinks so</a>. And the cost to the consumer is lowered because carriers subsidize hardware in exchange for contracts.<br />
<br />
I think simple mechanics and touch screen controls act as a gateway drug for new gamers. The number one thing that the Nintendo Wii proved is that all kinds of people are willing to play video games if the interface is designed in a way that doesn't turn them off. The success of mobile games is proof of this as well; the user base of iPhones is much wider than that of an Xbox360, and yet we know that iPhone users download games en masse. Users who get hooked on playing games on their phone are more likely to try games on other devices; the phone has made it ok for them to experiment with gaming.<br />
<br />
I also think that we've only just begun to experiment with touch screen interfaces. Many other interface transitions have occurred in the past; people thought that Adventure games and FPSs could not work on consoles until Resident Evil and Halo came along and showed them how it was done. Touch interfaces will certainly continue to improve and thereby widen the range of game styles that can be played on a mobile device.<br />
<br />
That said, there is truth to the points my fictional console developer argued above: there are still a great many game genres that are simply not playable with out sticks or buttons. And more importantly, there are a great many core gamers who are simply not interested in playing games without physical controls. Or even on a small screen.<br />
<br />
But consider this: if you bought an Android phone in the last year, you might have gotten one that supports HDMI out. Better yet, you might have one that comes with a dock that has HDMI out on it. The phone screen itself is probably not far off from the native resolution of your HD TV. Plus your phone has Bluetooth support, and as Google demo'd at Google IO this year, <a href="http://www.droidgamers.com/index.php/game-news/events/google-io-2011/1603-honeycomb-31-to-include-usb-host-support-use-your-xbox-360-controller-or-other-ones-to-play-games">support for USB devices is in the most recent versions of Android.</a><br />
<br />
What if you could come home, drop your phone in its dock, pick up the wireless controller sitting on your desk, turn on your TV, and suddenly be playing a high-end game at full resolution from your couch, powered by your phone? Part way through the game you get up to leave, grab the phone on the way out, and continue playing on the small screen in the elevator. Sounds pretty slick, huh?<br />
<br />
All the necessary technology for this type of device is already in place. You've got enough power in the phones to drive a TV, support for traditional game interfaces via HDMI and Bluetooth, and a target audience of people who want a cool smartphone that can double as their game console. If this was the norm, what would be the point of spending another couple hundred dollars on a dedicated game device? This would be fantastic for game developers; there's space for big-budget, high-end titles as well as low-cost casual games, all running on the same device and delivered through the same point of sale systems. We could have our cake and eat it too, accomodate both the core gamer and everybody else with the same platform.<br />
<br />
There are two major problems with this vision of the future. <br />
<br />
The first is the problem of content delivery; console games are pretty gigantic (we're counting data in gigabytes here), and getting lots of heavy data to a mobile device is still arduous. Nobody is about to download a 24 GB PS3 game to their phone over 3G. The solution to this one is probably just time. Better network infrastructure will come along and solve it. Until then, streaming and compression technologies must pick up the slack.<br />
<br />
The second problem is more immediate: in order for controller-based gaming to come to phones, phone games must support controllers. The barrier to a hybrid game console / smartphone is not technology or even development cost, it's lack of applicable content. Who cares if you can use a controller if all the games are expecting a touch interface?<br />
<br />
This latter problem is one us mobile game developers can solve. And the best way do start, I think, is by supporting Sony Ericsson's Xperia PLAY.<br />
<br />
The gaming press has pretty much thumbed its nose at the PLAY. It looks like the PSP Go (not a good association) but it can't play PSP games. It has a few Sony logos on it but it's not really a "Playstation Phone." Some have <a href="http://www.destructoid.com/sony-xperia-play-not-another-n-gage-reality-yes-it-is--205220.phtml">likened it to the N-Gage</a>, which is a convenient conceit but not a very useful comparison; perhaps those folks have forgotten that the N-Gage had a terrible portrait-only display and required that the battery be removed before game cartridges could be inserted. The best way to describe the PLAY is that it's a regular Android smartphone with a slide out game pad and game buttons. That's it. Oh, and it's also really fast (it's the fastest device I own, and I have a bunch).<br />
<br />
Whatever you think of the PLAY, supporting its game controls in your game is a step towards a hybrid game console / mobile device future. Consoles may die off but physical buttons will not. By adding physical button support to your mobile game, you are increasing the business viability of a mobile console. If every game supported physical buttons we could have such a device today.<br />
<br />
Secondly, if the PLAY does well in the market then it is safe to assume that other manufactures will produce knock-off "gaming phones." If these catch on, it could create a new subset of smartphone, much the way phones with physical keyboards have come to define smartphones for business people. It would be pretty cool to have a whole array of phones with gaming controls to choose from; that would make button-based genres viable on mobile devices very quickly. Of course, there'd need to be some games that support that interface before the manufactures are likely to really get on board. <br />
<br />
So, if you want to promote a future where we can enjoy both console-style games and the latest tower defense color match physics playground social gold farming title on the same mobile device, support physical buttons. Support customization of controls. Support the Xperia PLAY pads, and hope that the device is successful enough to spawn more like it. The game industry is in the midst of a major transition, and it's one that we, the console and mobile developers, can control. It's an opportunity to create an environment where development doesn't suck and costs are not insane. It's too good to pass up.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com37tag:blogger.com,1999:blog-2127023973845776737.post-23536021002096997772011-01-16T23:52:00.000-08:002011-01-16T23:52:40.349-08:00Replica Island UpdatedYesterday I uploaded the first update to Replica Island is something like six months. This update corrects some bugs (most importantly, a crash that could occur in some situations related to a divide-by-zero case in the collision system), adds some features (apps to sd support, on-screen control pad, linear story mode, level select mode, game-over statistics), and a bunch of minor edits and clean-up (I added, for example, the name of the current level when the game is paused).<br />
<br />
I worked on this update off and on for the last six months. It originally was designed with other goals in mind; I had planned to add an achievement system, along with a couple of other superfluous modes. In the end I cut it back to the key requests from users: a way to select levels after beating the game, more statistical information, and on-screen controls for multitouch capable touch screens). The achievement system sounded good on paper but ended up being useless, which is why I cut it: the game has been on Market nowadays that just playing it normally for achievements isn't exciting enough, and I don't have time to properly back achievement challenges up with new content or modes. So I dropped that part and shipped the damn thing, finally.<br />
<br />
I made a couple of changes to the game design which I thought were interesting. <br />
<br />
First, I changed the robot spawners so that they can no longer be possessed by the possession orb. This was a difficult change for me to make, because I really liked having that easter egg in there, but it was the right move: the number of users who complained about the frustration of accidentally possessing the spawner instead of the robot would astound you (and then there are the folks who thought <a href="http://www.youtube.com/watch?v=X1A8qUyPL5E">it was a bug</a>). Once I changed it, the advantage to game play was clear. I should have come up with a harder-to-find easter egg.<br />
<br />
Second, I modified the Shadow Slime character. This is a black puddle of goo that rises up into an energy-ball-spitting monster when the player gets close. The problem with this guy is that if you are moving really fast, you don't have any time to maneuver out of the way before the Shadow Slime pops up and kills you. Half the time you can't even see the guy. Also, because the Slime was coded to pop up and then drop back down based on distance, it doesn't often get a chance to shoot its energy bolt out. Changing the Slime to never hide improved this enemy a lot: he's more visible, less of a cheap surprise hit, and he actually gets to fire. On later levels, this guy can be pretty rough to deal with, which is exactly how he should be.<br />
<br />
Speaking of difficulty, I also added three difficulty modes: Baby, Kids, and Adults. "Regular" Replica Island game play up until now is the "Kids" level. "Adults" is harder than usual and "Baby" is (much) easier than usual. Playing the game on Adults is really fun, even for me (and generally speaking, I have trouble enjoying games I've worked on for a long time). With the level of challenge increased, careful movement and precision attacks are required, which adds a lot to the experience. It was also a great way to test the new on-screen control pad I added; beating the game with those controls on Adults mode required a lot of tuning.<br />
<br />
I spent a lot of time thinking about how to name the difficulty modes. My goal is to accurately represent the level of difficulty of the game, but also to challenge certain types of players into selecting a mode that will provide an accurate level of challenge. Some players are extremely casual; they want to see the content and read the story but are not interested in precision game play. Other players are hardcore, and <i>like</i> being hardcore; besting difficult gameplay is a point of pride for them. The difficult users are the semi-casual users; these are players who might not be that interested in the game yet, but once they get into it are likely to play through to the end, even if it's a little hard. The semi-casual user will quit, however, if the game gets hard or frustrating early on; unlike the hardcore player, completing a challenge isn't a point of pride for these players.<br />
<br />
To make this game as accessible as possible, I need difficulty levels that speak to each of these types of players. "Baby" is a good name for the very easy mode because it's something that a player who prides himself in a challenge will avoid. It also communicates that the player need not be intimidated by the game mechanics, which should help those who just want to see the content. "Kids" is also a good middle ground; for players who don't care too much about accomplishment, but are still interested in a bit of challenge, this describes that level in a non-condescending way. "Adults" suggests that only real gamers apply, which is the message you want to give to a hardcore user. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/blog/difficulty.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="http://replicaisland.net/img/blog/difficulty.png" width="400" /></a></div><br />
To back these difficulty level names up, I added short descriptions to each. You can see what they look like in this screenshot. Again, the goal was to separate players who want a little challenge along with their game and players who'd prefer to have a little game along with their challenge. I'm pretty happy with the wording.<br />
<br />
But the biggest change I made with this update was the addition of on-screen controls. I'll write about the design of that system when I get the chance.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com19tag:blogger.com,1999:blog-2127023973845776737.post-28639995740643522482010-12-24T06:23:00.000-08:002010-12-24T06:23:30.850-08:00Hot FailureAn article I originally wrote for Game Developer Magazine called Hot Failure has been reposted over at Gamasutra.com. It's all about the metrics reporting system in Replica Island. Check it out!<br />
<br />
<a href="http://www.gamasutra.com/view/feature/6155/hot_failure_tuning_gameplay_with_.php">http://www.gamasutra.com/view/feature/6155/hot_failure_tuning_gameplay_with_.php</a>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com7tag:blogger.com,1999:blog-2127023973845776737.post-52605714855893242882010-11-21T07:21:00.000-08:002010-11-21T07:21:01.255-08:00Building a Reflective Object System in C++Every game engine I've worked with for the last several years has had some sort of reflection system. <i>Reflection</i>, and its cousin <i>introspection</i>, are features supported by some languages that allow runtime inspection of object structure and types. My most recent game, Replica Island, as a Java application makes use of Java's Class object, as well as useful type-related tests like <i>instanceof</i>. Many other languages support some sort of reflection, especially those that are modern and VM-based (like C#). Reflective objects can provide the answers to all sorts of interesting questions at runtime, like "is this anonymous pointer derived from class X?" or "does this object have a field named Y?" It's also common to be able to allocate and construct objects by string in reflective systems. <br />
<br />
These traits make it very easy to serialize and deserialize reflective objects: entire object trees can be written to disk and then read back again without the serialization code having to know anything about the objects it is writing. Reflection also makes it very easy to write tools; if the tools and the runtime have access to the same type data, the tools can output data in the same format that the runtime will access it in memory (see my previous post about loading object hierarchies). Reflection systems can also make for some pretty powerful tools: for example, it's easy to build a Property Editor dialog that can edit any object in the code base, and automatically adds support for new classes as they are written over the course of development.<br />
<br />
But C++ doesn't support reflection. There's RTTI, which gives a tiny little sample of the power that a real reflection system brings, but it's hardly worth the overhead on its own. C++'s powerful template system is often used in places that reflection might be used in other languages (e.g. type independent operators), but that method is also restricted to a small subset of a fully reflective system. If we want real reflection in C++, we'll need to build it ourselves.<br />
<br />
Before we dive into the code, let me take a moment to describe the goals of my reflective object system. Other reflection systems might choose a different approach based on different needs (compare, for example, Protocol Buffers to the method I'm about to describe). For me, the goals are:<br />
<ol><li>Runtime type information of pointers.</li>
<li>Ability to iterate over the fields in an object.</li>
<li>Ability to query and set a field in an object.</li>
<li>Ability to allocate and construct an object by string, or based on other meta data.</li>
<li>No extra overhead for normal class use.</li>
</ol>The Java Class object is a pretty good model to follow here: each class has a static object that describes the class in detail. Using classes normally involves no extra overhead, but if we choose we can also load the Class descriptor and work with objects using reflection. So my goal is to make something like the Java Class object in C++; my approach is to generate a class for each reflected class that holds metadata about the class it describes. A "meta object."<br />
<br />
Once I start generating meta objects for individual classes, servicing the first goal of anonymous pointer typing isn't very hard. As long as we know that the pointer comes from some base type that implements a virtual accessor for returning the meta object, we can pull a static class definition out and compare it to other classes, or to a registry of meta objects (and, if we're tricky, we can even do this when we can't guarantee that the pointer is derived from some known base). So let's assume we have a way to generate meta information for each class, wrap it up in a class called a MetaObject, and stuff it as a static field into the class it describes.<br />
<br />
The next question is, what does this MetaObject class contain? Well, per requirement #2, it must at least contain some information about fields. In order to access fields within a class we'll need to know the offset of the field, its size (and, if it's an array, the size of each element), and probably the name of the field and the name of its type as strings. <br />
<br />
Now might be a good time to think about what a C++ object actually looks like in memory. Say we have the following object:<br />
<pre class="prettyprint">class Foo : public MetaBase
{
public:
static const MetaObject sMeta; // The object describing Foo
virtual const MetaObject* getMetaObject() { return &sMeta; } // required by the abstract base
void setup() { mBar = 10; mBaz = 267; }
private:
int mBar;
int mBaz;
};
</pre><br />
If we allocate an instance of Foo and then pop open our debugger to inspect the raw memory, it probably looks something like this (assuming MetaBase has no fields):<br />
<br />
<pre class="prettyprint">C0 A1 D0 F7 // Pointer to the virtual table; some random address
00 00 00 0A // Value of mBar
00 00 01 0B // Value of mBaz
</pre><br />
I say "probably" because the actual format of a class in memory is basically undefined in C++; as long as it works the way the programmer expects it to, the compiler can do whatever it wants. For example, the actual size of this object might very well be 16 bytes (rather than the twelve bytes shown above), with zero padding or junk in the last word; some architectures require such padding for alignment to byte boundaries in memory. Or the vtable might be at the end of the object rather than the beginning (though, to be fair, all the compilers I've worked with have put it at the top). <br />
<br />
Anyway, assuming this is what we see in the debugger, it's not hard to pull out the information we want for our meta object. The value of mBar is at offset 4 (the first four bytes are the address of the virtual table), and the value of mBaz is at offset 8. We know that sizeof(int) == 4. And sMeta, because it is static, doesn't actually appear in the class instance at all--it's stored off somewhere else in the data segment. If we had this information about every field in every class, we'd easily be able to access and modify fields in objects without knowing the type of the object, which satisfies most of the goals above. And since this data is stored outside the object itself, there shouldn't be any overhead to standard class use.<br />
<br />
Here's a abbreviated version of the object I use to describe individual fields in classes. You can see the entire object <a href="http://code.google.com/p/androidndkgame/source/browse/project/jni/game/core/MetaField.h">here</a>.<br />
<br />
<pre class="prettyprint">class MetaField : public Object
{
public:
enum MetaType
{
TYPE_value,
TYPE_pointer,
};
MetaField(const MetaType type, const char* pName,
const char* pTypeName, int offset, size_t fieldSize)
: mType(type),
mpName(pName),
mpTypeName(pTypeName),
mOffset(offset),
mFieldSize(fieldSize),
{};
const char* getName() const;
const char* getTypeName() const;
const int getOffset() const;
const size_t getFieldSize() const;
const MetaType getStorageType() const;
virtual void* get(const MetaBase* pObject) const;
virtual void set(MetaBase* pObject, const void* pData) const;
private:
const MetaType mType;
const char* mpName;
const char* mpTypeName;
const int mOffset;
const size_t mFieldSize;
};
</pre><br />
A static array of MetaFields is defined for each class and wrapped up in a container MetaObject, which also provides factory methods and some other utility functions. You can see that object <a href="http://code.google.com/p/androidndkgame/source/browse/project/jni/game/core/MetaObject.h">here</a>. These two objects, MetaField and MetaObject, make up the core of my C++ reflection implementation.<br />
<br />
So we know what information that we need, and we have a class structure to describe it. The hard part is finding a way to generate this information automatically in a compiler-independent way. We could fill out MetaObjects by hand for every class, but that's error prone. It might be possible to pull this information out of the debug symbols generated for a debug build, but symbol formats change across compilers and we don't want to compile a debug build for every release build. We could probably contort C++ macro expansion to generate meta data, but in the interests of sanity let's not do that. We could write a preprocessor that walks our header files and generates the necessary meta data, but that's actually a sort of annoying problem because of the idiosyncrasies of C++ syntax. <br />
<br />
The solution I chose is to use a separate format, an interface definition language, to generate metadata-laden C++ header files using a preprocessor tool. The idea is that you write your class headers in the IDL, which converts them to C++ and generates the necessary metadata objects in the output as it goes. I leverage compiler intrinsics like sizeof() and offsetof() to let the compiler provide the appropriate field information (meaning I don't care where the vtable is stored, or what padding might be inserted). My IDL looks like this:<br />
<br />
<pre class="prettyprint">metaclass PhysicsComponent
{
base GameComponent
function void update(const float timeDelta, GameObject* pParentObject) { public }
function virtual bool runsInPhase(const GameObjectSystem::GameObjectUpdatePhase phase) { public }
field mMass { type float, value 1.0f, private }
field mStaticFrictionCoeffecient { type float, value 0.5f, private }
field mDynamicFrictionCoeffecient { type float, value 0.1f, private }
// mBounciness = coeffecient of restitution. 1.0 = super bouncy, 0.0 = no bounce.
field mBounciness { type float, value 0.0f, private }
field mInertia { type float, value 0.1f, private }
}
</pre><br />
.. and the output of the preprocessor tool looks like this:<br />
<br />
<pre class="prettyprint">class PhysicsComponent : public GameComponent
{
public:
void update(const float timeDelta, GameObject* pParentObject);
virtual bool runsInPhase(const GameObjectSystem::GameObjectUpdatePhase phase);
private:
float mMass;
float mStaticFrictionCoeffecient;
float mDynamicFrictionCoeffecient;
// mBounciness = coeffecient of restitution. 1.0 = super bouncy, 0.0 = no bounce.
float mBounciness;
float mInertia;
public:
// AUTO-GENERATED CODE
static void initialize(PhysicsComponent* pObject);
static PhysicsComponent* factory(void* pAddress = 0);
static void* factoryRaw(void* pAddress, bool initializeObject);
static PhysicsComponent* arrayFactory(int elementCount);
static const MetaObject* getClassMetaObject();
virtual const MetaObject* getMetaObject() const;
static bool registerMetaData();
static PhysicsComponent* dynamicCast(MetaBase* pObject);
};
</pre><br />
You can see that the IDL pretty much just spits C++ out exactly as it was written, but in the process it also records enough information to generate the functions at the bottom of the class. The most interesting of those is getClassMetaObject(), which is a static method that defines the meta data itself:<br />
<br />
<pre class="prettyprint">inline const MetaObject* PhysicsComponent::getClassMetaObject()
{
static MetaField field_mMass(MetaField::TYPE_value, "mMass", "float",
offsetof(PhysicsComponent, mMass), sizeof(float));
static MetaField field_mStaticFrictionCoeffecient(MetaField::TYPE_value,
"mStaticFrictionCoeffecient", "float",
offsetof(PhysicsComponent, mStaticFrictionCoeffecient), sizeof(float));
static MetaField field_mDynamicFrictionCoeffecient(MetaField::TYPE_value,
"mDynamicFrictionCoeffecient", "float",
offsetof(PhysicsComponent, mDynamicFrictionCoeffecient), sizeof(float));
static MetaField field_mBounciness(MetaField::TYPE_value, "mBounciness", "float",
offsetof(PhysicsComponent, mBounciness), sizeof(float));
static MetaField field_mInertia(MetaField::TYPE_value, "mInertia", "float",
offsetof(PhysicsComponent, mInertia), sizeof(float));
static const MetaField* fields[] =
{
&field_mMass,
&field_mStaticFrictionCoeffecient,
&field_mDynamicFrictionCoeffecient,
&field_mBounciness,
&field_mInertia,
};
static MetaObject meta(
"PhysicsComponent",
MetaObject::generateTypeIDFromString("PhysicsComponent"),
MetaObject::generateTypeIDFromString("GameComponent"),
sizeof(PhysicsComponent),
static_cast(sizeof(fields) / sizeof(MetaField*)),
fields,
GameComponent::getClassMetaObject(),
&PhysicsComponent::factoryRaw);
return &meta;
}
</pre><br />
*note that, in more recent versions, I've replaced offsetof() with a macro that does the right thing for compilers that do not support that intrinsic. Offsetof() isn't really kosher in C++, but for my purposes it works fine. If you want to learn all about it, and why it's rough for "non-POD types," try <a href="http://stackoverflow.com/questions/1129894/why-cant-you-use-offsetof-on-non-pod-strucutures-in-c">Stack Overflow</a>.<br />
<br />
With this data, I now have a pretty complete reflection system in C++. I can iterate over fields in a class, look them up by string, get and set their values given an anonymous pointer. I can compare object types without knowing the type itself (I can implement my own dynamic_cast by walking up the MetaObject parent hierarchy and comparing MetaObject pointers until I find a match or reach the root). It's very easy to construct objects from a file, or serialize a whole object tree. I can, for example, make a memory manager that can output an entire dump of the heap, annotated with type and field information for every block. And I can compile all of my objects into a DLL and load them into a tool and have full type information outside of the game engine environment. Sweet!<br />
<br />
There are, however, many caveats. This implementation doesn't even attempt to support templates, multiple inheritance, or enums. If we serialize and deserialize using only this data, some standard programming practices start to get screwed: what happens when we can construct objects without invoking the constructor? How do we deal with invasive smart pointers or other data that weakly links to objects outside of the immediate pointer tree? How do we mix objects that have this meta data and objects that do not? How do we deal with complex types like std::vector? If object structure is compiler dependent, how can we serialize class contents in a way that is safe across architectures? These are all solvable problems, but the solutions are all pretty complicated. They often involve dusty corners of the C++ language that I rarely visit, like placement new. If you get into this stuff, <a href="http://www.amazon.com/Inside-Object-Model-Stanley-Lippman/dp/0201834545">Stanley Lippman</a> is your new best friend.<br />
<br />
But even with those caveats in mind, the power of reflection is absolutely worth the price of admission. It's the first chunk of code I write or port whenever I start a new project in C++. It was the first bits of my old game engine that I got running on Android, and is now the core of the engine I am building on that platform. Reflection is not a simple bit of infrastructure to get up and running, but once you have it it's really hard to go back.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com7tag:blogger.com,1999:blog-2127023973845776737.post-39453088270952395292010-11-07T00:27:00.000-07:002010-11-07T04:30:09.527-08:00Leveraging Java and C++ for Hybrid GamesI've been thinking a lot lately about how best to use the resources that Android provides for game development. A lot of the game developers I know (and I know a lot!) are quick to treat any new platform as a dumb host to their game engines. Usually developers have a bunch of code, or even entire games, that are written to be aggressively cross-platform, so all they need is a way to compile the source, attach it to input events, and draw to screen. Any platform that can provide those basics can host their tech, so when evaluating a new platform to support, these developers only look at the most basic level of functionality.<br />
<br />
This is certainly true on Android as well. Lots of developers look at the NDK and see a C++ environment that they can run their code in and decide that supporting the platform only requires gluing their existing code to the hooks that Android exposes. And that's true--if your only goal is to port an existing game from one platform to another, only the minimal set of common functionality is necessary to get something up and running.<br />
<br />
But since I am in a position to write games exclusively for Android, I've been thinking about how to leverage parts of the platform that most game developers ignore: the OS and Java runtime itself. There's a lot of functionality there, and maybe there are ways that I could leverage it to make better games.<br />
<br />
One project I've been working on recently is a little game framework using the NDK. My friend Gregg and I ported Google's open source browser-based 3D framework, O3D, to Android a while back, and I've been using that to get some dudes running around on the screen. O3D has a big Javascript component which we've ignored; the rest of it is a C++-based, shader-centric rendering backend. Gregg did the heavy lifting of getting the thing to run on OpenGL ES 2.0 and I've been hacking in bits and pieces of old game engines on top. The result is that we have a pretty complete rendering engine running on Android without a whole lot of effort.<br />
<br />
It's a lot of code considering that it doesn't really do anything yet--almost 500k lines of C/C++. But it wasn't hard to port because in the end, Android is really just another Linux OS with hooks into things like OpenGL ES 2.0. So for this work, we basically did what lots of other game developers do: we ported the code using as little Android-specific stuff as possible and got something up pretty fast.<br />
<br />
I've been slowly adding game code to this project, and as of this writing I have an early prototype of a shooting game up and running: you can run a little test character around and shoot placeholder art zombies with dual thumbsticks. It's not a game, yet, but it's enough to prove out the code.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://replicaisland.net/img/blog/zombies.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="240" src="http://replicaisland.net/img/blog/zombies.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Not a game, yet. Place holder art courtesy of <a href="http://3drt.com/">3drt.com</a>.</td></tr>
</tbody></table><div>If this thing ever gets off the ground, it'll be a game written almost entirely in C++, with just a few hooks back to Java for input, sound, and application life cycle events. Just like most games that are built to be cross platform, or brought over from other platforms.</div><div><br />
</div><div>But I think there's an opportunity to use Android's unique hybrid application structure to do things that might be difficult or impossible on other platforms. There are areas where I can get a lot of value out of Java while leaving the game engine and performance-critical code all in C++.</div><div><br />
</div><div>For example, I've hooked up a web server to this game. It's a very, very simple web server; I found some code on the web that implemented a basic HTTP server in Java, copied and pasted it, and then hacked it up until it did what I needed. It runs in a separate thread within the main game process, and allows us to connect to the device from a desktop browser while the game is running. Here's a graphic to illustrate the structure of the code.</div><div><br />
</div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://replicaisland.net/img/blog/o3dlayout.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="285" src="http://replicaisland.net/img/blog/o3dlayout.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The high-level structure of this engine. Red bits are Android Framework, blue are separate threads, and green is native code.</td></tr>
</tbody></table><div><br />
</div><div>I'm sure you're reading this and are thinking, <i>why the heck would you want to run a web server inside a game?!</i> Well, sir, I'll tell you. With the web server in place, I've opened the door to real-time game editing. This web server doesn't serve static pages, it reads and writes data directly to and from the native engine. I can, for example, pipe O3D's scene graph up to the web server and let the user browse its structure from their browser. I can do that with my game objects too (thanks to the meta system I referenced in the last post, which lets me query the structure of a given object by string). And perhaps most useful, I implemented a simple interface for editing shader code on the fly; I can write vertex and fragment shaders right in the browser, click a button, and immediately see the rendering change in the running game.<br />
<br />
This obviously isn't a full runtime editor, but with just a little bit of effort it's already proved to be pretty powerful. The whole thing is exceedingly simple: my copy-pasted web browser calls down into the native code via JNI and just passes a string payload, which a few hundred lines of runtime code process and then return to the server and thus to the browser. I'll extend this interface as necessary to other aspects of the game; building a way to do very fast iteration for things like game play physics and shaders is the way to turn a mediocre game into a good one.<br />
<br />
Despite the simplicity of the web server system, I'm not sure it would have been as successful on other platforms. C++ is great for rendering a 3D shader-based game, but it's actually a bit arduous to use for building a web server. Java, on the other hand, is a great language to write a web server in--it's actually designed with that kind of application in mind. Android hybrid apps let you leverage both native code and Java simultaneously, which can lead to some pretty neat combinations. I think that, if this particular game engine ever becomes a full-fledged game, this kind of language diversity will make it a lot of fun to build.<br />
<br />
<b>Update</b>: Oh, internet, you fickle beast. Every potentially disputable line of text must be disputed!<br />
<br />
OK, to be clear: of course it's not very difficult to write a web server in C or C++. I did not mean to offend your sensitive language fanboyism by suggesting that maybe, perhaps, possibly, some languages are more predisposed to certain types of work than others. Though I could write a GLES 2.0 game entirely in Java, I would not choose to do so: that language is not the best fit for that problem. So yes, you may of course write a web server in C++, or in C, or in assembler or any other language. And it's not that hard. But in Java, it's so, so easy. Heck, I even implemented a memory file cache just for the heck of it. The code generates JSON on the fly based on results coming back from the engine runtime. Sure, you could do this in C. Be my guest. Me, I'm looking for the simplest possible solution to each of my problems, so I can spend most of my time on the part that counts: making the game fun.<br />
<br />
I also did not mean to suggest that I am the first to think of piping game data through a web server. I just thought it was a neat and easy method for this project specifically on Android. So there.</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com15tag:blogger.com,1999:blog-2127023973845776737.post-32637300819826985252010-11-03T06:56:00.000-07:002010-11-03T06:56:12.228-07:00Game Object Construction Rabbit HoleToday I want to write a little bit about a boring, yet utterly fundamental part of game engine design: spawning game objects.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX-MWa8-RtPNAqsH1nGRFdfxIdSKmts8-S_06apWTe-QmiUCDLJH4SU8jFYN2t758XGtW3MBw7_bIePJqXuxDYAsOvM9-k-vFeonL3UKeaBlMmVY5fa7nXrIVvciMl_8nberLdJAuQz9U/s1600/Garrys_Mod_Updates_September.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX-MWa8-RtPNAqsH1nGRFdfxIdSKmts8-S_06apWTe-QmiUCDLJH4SU8jFYN2t758XGtW3MBw7_bIePJqXuxDYAsOvM9-k-vFeonL3UKeaBlMmVY5fa7nXrIVvciMl_8nberLdJAuQz9U/s320/Garrys_Mod_Updates_September.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Garry's Mod makes it look easy.</td></tr>
</tbody></table>Say you have a level that contains a player object and an enemy object. However these objects are represented in memory, you have to allocate them somehow. And probably register them with some sort of update loop, and maybe load some other data (graphics, sound?) associated with those objects. There's a little bit of bootstrap to just get the game into a state where the simulation can start.<br />
<br />
So, how do you do it? How do you get that player and enemy up and running? This is one of those problems that can be as complex as you choose to make it.<br />
<br />
Well, I mean, you could write a function that looks like this:<br />
<br />
<pre class="prettyprint">void startUpGame()
{
spawnPlayer();
spawnEnemy();
// done!
}
</pre><br />
Sounds good until you get to level 2, which has two enemies. You could make a spawning function for every single level, I guess. It would work, but it wouldn't scale, especially if you have lots of objects to spawn. I think the average Replica Island level has several hundred objects to spawn, so writing one of these for each level would suck hard.<br />
<br />
Long, long ago I wrote a game called <i>Fysko's Playhouse</i> for Mac OS 6 computers. If that fact alone doesn't date me, this will: it starred the mascot of a local BBS of the same name. Anyway, back then I had no idea what I was doing, and so when faced with this problem of how to spawn game objects for different levels in a way that doesn't suck, I hit the code with a hammer and moved on. My solution back then was to write a level editor (in <a href="http://en.wikipedia.org/wiki/HyperCard">HyperCard</a>!!) that would output start up functions like the one above. I could draw a level in the editor, hit a button, and out would pop a bunch of Pascal code which I could then copy and paste into the game. Great!<br />
<br />
Well, not really. That kind of solution works only for very simple games and only when the programmer is the level designer. And even then it's kind of crappy. Move a guy 2 pixels to the left and you have to recompile your code.<br />
<br />
Some years later I wrote a Bomberman clone called <a href="http://www.dreamdawn.com/bakudanjin/sshots.php">Bakudanjin</a>. This time I was a little smarter. Instead of hard coding my level information, I made a map file describing every level. The map file was basically just a bunch of indexes with XY locations. To start the level, I load the file and then walk through each index. The index maps to a table of function pointers (or, since this was also Pascal, a big-ass switch statement) that call the appropriate spawn functions. Actually, Replica Island <a href="http://code.google.com/p/replicaisland/source/browse/trunk/src/com/replica/replicaisland/GameObjectFactory.java">works this way too</a>.<br />
<br />
And if you just clicked on that link, you can see why this method isn't so hot either: Replica Island only has about 50 different object types and that code is still 6500 lines long. And a lot of it is copy and paste, because many objects turn out to be structurally similar. And god forbid you accidentally get your map format indexes out of sync with your runtime indexes; arbitrary enums needing to correctly index into separate arrays is a recipe for bugs.<br />
<br />
Still, this is a quick and easy method, and it worked fine for Bakudanjin and Replica Island. All that code gets thrown out and rewritten when I start a new game, though.<br />
<br />
The problem here is that all this bootstrap code is basically code describing data. I draw a line between "code" and "data" as follows: code is literally programming that instructs the game how to operate. Data is non-code that is input to the code system. You feed data into your game and out comes a playable simulation. Things like enemy placement on a map are subject to lots of iteration and change; the code to actually move an enemy to that location is probably pretty stable and static. Therefore, the placement information is data and shouldn't live in code, while the runtime for consuming that data is code but can be generic and reused across multiple levels and games.<br />
<br />
So in order to write better code and to enable faster iteration and reusability, it's probably a good idea to move more of the information for spawning guys into data. Moving spawn locations into a file wasn't a bad first step, but we can go further.<br />
<br />
What does spawnPlayer() do, anyway? It probably allocates a bunch of objects, ties them together with pointers, and sets a bunch of parameters on those objects. Hmm, sounds like something we could represent with just some smarter data.<br />
<br />
How about this: we'll make all objects in the world basically the same, make a single spawnObject() function, and then expose a bunch of parameters which we can use to customize the objects and differentiate them. If we can do that, all we need to do is serialize all the parameters and pass them to spawnObject(). Health = 5, Speed = 10, Sprite = "AngryGorilla.png", etc.<br />
<br />
<pre class="prettyprint">GameObject* spawnObject(ObjectParams* params)
{
GameObject* object = new GameObject;
object->setLife(params->life);
object->setSprite(params->sprite);
if (params->isThePlayer)
{
object->setRespondToControllerInput(true);
}
// ...
return object;
}</pre><br />
OK, that works, but now we have a new problem: it's actually pretty hard to make all our objects exactly the same. Take the code that reacts to controller input, for example. That belongs on a player object but on nothing else; with this type of system every single angry gorilla is going to carry that code around, probably turned off with a flag that we serialized. Or consider what happens when an object is hit. The player probably wants to get damaged, go into a short invincibility mode, and then go back to normal. The enemies probably want to get damaged and not be invincible. If the player dies there's probably some code to cause the Game Over screen to pop up. Not so for an enemy.<br />
<br />
We could make this all the same code and control it with flags, but it's going to become ugly fast. Maybe if we can refactor our game such that all objects are functionally similar we can ease the pain, but that will make it hard to say, add in little bits of player-specific hacks to improve the feel of game play later. A better system would be able to generate objects that only contain the code that they need. If we use the <a href="http://replicaisland.blogspot.com/2009/09/aggregate-objects-via-components.html">aggregate object model</a> that I often recommend, we could think of this as only inserting relevant components. In a more traditional object model, we could think about instantiating the object at a particular level of derivation from the base to provide only the necessary set of functionality. Either way, we're talking about sticking code together with pointers and setting parameters. Hmm, sounds like data again.<br />
<br />
One method I've seen but never tried myself is to define a hard-coded list of object types ("player", "angry gorilla", "banana bomb", etc), and then provide a unique struct of static spawn data related to each type. For example, the player object would be allocated to contain code for movement based on the controller, and it would read at spawn time a PlayerObjectData struct.<br />
<br />
<pre class="prettyprint">GameObject* spawnObject(ObjectType type, void* params)
{
GameObject* object = NULL;
switch (type)
{
case TYPE_PLAYER:
object = new PlayerObject();
object->parseParams(static_cast<PlayerObjectData*>(params));
break;
// ...
}
return object;
}
</pre><br />
The attractive thing about this method is that you get a chance to control object creation on a per-type basis, but you can also serialize different data for each type, thus avoiding the one-size-fits-all problem. That should let you move almost all information about this object into data, and just leave this one spawn function in code.<br />
<br />
But let's go a step further. Say we don't want to have to enumerate every object type in code. If objects are really just collections of data and code, why can't we move the entire structure of a game object into data?<br />
<br />
In a language that supports reflection, this shouldn't be too hard. We can encode an entire object hierarchy, say as XML, and then use it to allocate classes, patch pointers, and set fields. In a language like Java, we can look up class names by string and instantiate them, and poke into fields to finish constructing objects. We can imagine some XML that looks like this:<br />
<br />
<pre class="prettyprint"><object name="gorilla" type="com.gorillaboom.gameobject">
<field name = "mLife">10</field>
<field name = "mSprite">gorillagraphic</field>
</object>
<object name="gorillagraphic" type="com.gorillaboom.imagefile">AngryGorilla.png</object>
</pre><br />
If we write an XML parser to build object trees for us based on this, our entire spawn code can become a single call to that parser. The parsing code is complicated but generic, it can be reused across objects and levels and games. And we still have the ability to customize each object because the hierarchy can contain custom classes or be structured differently per type.<br />
<br />
<pre class="prettyprint">GameObject player = (GameObject)constructFromXML("player.xml");</pre><br />
Even cooler, this method isn't even specific to game objects. We could use it to serialize all sorts of data, even the contents of our main loop. The code is still code, but once we put the structure in data we have a crazy amount of control.<br />
<br />
But, getting back to just the game object application of this idea, there are two problems. First, this approach requires us to walk the XML object tree every time we want to spawn an object. We could try to walk the XML once and then use the resulting object tree as an entity "archetype," but that way leads to a particular brand of hell known as "shallow copy vs deep copy." When trying to copy the tree to create a new game object instance, how do you know which pointers should be recursively copied and which should be copied by value? People have lost years of their life to that problem.<br />
<br />
A less practical but ultimately simpler solution is just to re-parse the XML for every instantiation. Which brings us to the second problem: reading XML and stuff is slow. I mean, really slow. Compared to the simple code functions we started with, it's glacial. And allocation-heavy. Not something we really want to do during the runtime of a game.<br />
<br />
I know what you're going to say. <i>Hey, dumbass, just use a binary format instead of XML. Problem solved.</i> And that's true, to an extent. Excuse me for a moment while I go out on a limb to the extreme of this line of thought.<br />
<br />
If you're going to go to a binary format, why not just store the entire object tree in its native memory format on disk? Build the object hierarchy offline, write it as binary data to a file, then load it at runtime and just patch pointers. Boom, instant object tree. In C++ you can actually do this by building your own reflection system, making liberal use of placement new, and then walking the objects stored in the file to patch pointers. <br />
<br />
I've actually written this system before. It becomes horrifically complicated, but it does work. A couple of years ago I wrote an engine that could load a binary blob of C++ objects as a contiguous block, walk the objects therein to patch vtables and other pointers, and then simply start using the objects as regular fully constructed C++ objects. You lose constructers (objects were constructed before they were written to disk) and destructors (lifetime of the object is the lifetime of the blob; individual objects within the blob can't be freed), and god help you if you try to manage those objects with reference counts or smart pointers, but the method does work and it's pretty fast. To make a new instance of the object tree, you can just memcpy the whole block and repatch pointers. Cool.<br />
<br />
It starts to break down when you need to target multiple platforms, or build your data files on an architecture that does not match the runtime architecture. These problems are also solvable, but probably not in-place; you'll need to read the objects out of the file and then allocate them at runtime to ensure padding and vtable placement is correct. And if you do that you're back to a lot of runtime allocation and object parsing. The system is still complicated but some value is lost.<br />
<br />
So for a new code base that I'm working on, I'm experimenting with a slightly different approach. I still want to use the "load code structure from data" approach, but I don't want it to be slow or architecture dependent (or complicated, if I can avoid it). And I need to be able to spawn objects with this system dynamically at runtime. So instead of constructing objects directly, I'm constructing factory objects that can generate new instances of my object hierarchy on the fly.<br />
<br />
The method is to read in an object hierarchy as XML. Instead of building the tree right there, I build a bunch of "instruction" objects--basically the minimal list of commands required to recreate the object tree described in XML. "Create an object of type GameObject," "Set field mLife of object index 5 to 10," "Point field mSprite of object index 22 to object index 25." Each of these small "patch" objects gets initialized with a single command (representing a delta from the default state of the object upon construction), and the whole list of patches is stored in a factory object I call a "builder." Reading the XML is still slow, but I only need to do it once before the game starts; at runtime, when I want to spawn a new object tree, I simply execute the appropriate builder. The runtime speed is similar to what we had way back at the top of this lengthy post: just a bunch of object allocations and field initializations. Should be pretty fast.<br />
<br />
<pre class="prettyprint">Builder* enemyBuilder = makeBuilderFromXML("angrygorilla.xml");
GameObject* enemy1 = enemyBuilder->build();
GameObject* enemy2 = enemyBuilder->build();
GameObject* enemy3 = enemyBuilder->build();
</pre><br />
One nifty aspect of this approach is that I can easily extend the XML format to do more complicated things by building more complex patch object types. The basic set of patches (int, float, vector, pointer, string, etc) are entirely generic, as is the builder system itself. But I can add to that engine- or game-specific patches if necessary. I've already added a patch that knows about this particular engine's asset system, and can use it to schedule other files for loading, thus allowing for references to external assets which may be loaded in the future (and patched at that time appropriately). A different game might have an entirely different asset system, in which case I can chuck the one patch written for this game and write a new one against that engine; the system should scale without losing its general purpose core. <br />
<br />
The actual XML parser and builder system is very simple--only a couple hundred lines of code. But I should mention that it only works in C++ because my game engine is backed by a (fairly involved) reflection system. Using an Interface Definition Language, I can create classes that are laden with meta data, much like Java's Class object. Using that data I can poke into anonymous classes at runtime and set fields, which is how the patches in the builder actually work. I think this approach could be done without reflection, but it would basically resemble the hard-coded types with unique static data structs method that I mentioned above. I'll talk more about the reflection system in a future post.<br />
<br />
To bring this giant document to a close, I just want to note that the methods I've described here are hardly an exhaustive list. These are the various approaches that I've tried to spawn objects in games; there are many others and probably a lot of really good ideas that I've never considered. But when making a game engine, the question of how objects get spawned--and what a game object actually <i>is</i>, is a pretty huge piece. Though sort of mundane, it's probably worthy of a lot of thought.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com14tag:blogger.com,1999:blog-2127023973845776737.post-23968706804105551452010-09-17T08:20:00.000-07:002010-09-17T18:29:40.613-07:00Long Time, No SeeI haven't written anything here lately because, well, I've been <b>swamped</b>, and I mean like seriously submerged in the murky depths of work, travel, and more work. We're talking <i>Creature from the Black Lagoon </i>levels of swamped here. I'm probably growing gills. <span class="Apple-style-span" style="font-size: x-small;">That would actually be pretty cool.</span><br />
<span class="Apple-style-span" style="font-size: x-small;"><br />
</span><br />
Apparently I need to, at the very least, log in to the blog a little more often because I was pretty surprised to find 45 comments awaiting moderation today. Sorry about that. I turned off moderation for now, and unless the spammers ruin it, we can leave it off.<br />
<br />
That said, let's lay down some ground rules.<br />
<br />
1) If you're stuck, or have a question about a particular level, posting a comment here is probably not the fastest route to an answer. I don't have time to answer (I'm <b>swamped</b>, remember?), so another forum someplace might be a better bet.<br />
<br />
2) I love bug reports. I especially love bug reports that are entered into bug reporting systems. If you'd like to report a bug (or, as the case seems to be most commonly, a feature request), there's a whole<a href="http://code.google.com/p/replicaisland/issues/list"> official-looking system all set up for you already</a>.<br />
<br />
3) There's some pretty interesting code discussion going on at the <a href="http://groups.google.com/group/replica-island-coding-community?pli=1">forum setup for that purpose</a>. If you want to talk code, that's the right spot.<br />
<br />
So, being swamped, I don't have a whole lot of new information to report, so I'll leave you with two cool things.<br />
<br />
First: there are at least five games on the Market that were built with Replica Island code: Android Jump (Papijump clone), Prototype (an Arkanoid game with some interesting twists), Greedy Pirates (a Nanaca Crash sort of game with cannon balls instead of safe-for-workified girls), Super Treasure Rocket (a platformer), and Project G.E.R.T. (I'm not sure what to call this one). Though not yet on the Market, there's also a pretty neat Fruit Ninja clone, complete with 3D fruit to slice, that was built on the RI code. I think that's pretty freaking awesome. <b>Update:</b> We can add Ski Classic and Auto Traffic to the list, as well as Pocket Racing to list! These games were built with <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/SpriteMethodTest">SpriteMethodTest</a> source, which is basically the primordial version of the renderer in Replica Island.<br />
<br />
Second: Here's a picture of the Replica Island booth (part of a larger Android booth) at Tokyo Game Show this week. This was all put together by a third-party event organizer, and while I gave them permission to show the game, I had no idea it'd be so, I dunno, official looking. Cool!<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHGmG0FsKI3DF4nt0gT4F1rahfauPZQZhUjb2clcTEYP-IF8oUam7V8HQTcnVoRQKGkgPg05pgw6DmCPvYwauTHJmPYliPFXpq1XjFGjPsgXNTnJPYNPYBRLVt1XLdpnkxyf-zRcSIPbY/s1600/IMG_20100916_133712.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHGmG0FsKI3DF4nt0gT4F1rahfauPZQZhUjb2clcTEYP-IF8oUam7V8HQTcnVoRQKGkgPg05pgw6DmCPvYwauTHJmPYliPFXpq1XjFGjPsgXNTnJPYNPYBRLVt1XLdpnkxyf-zRcSIPbY/s640/IMG_20100916_133712.jpg" width="480" /></a></div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com11tag:blogger.com,1999:blog-2127023973845776737.post-48052424274539055802010-06-20T07:36:00.000-07:002010-06-20T07:36:34.196-07:00Replica Island Passes a Million InstallsHoly crap!<br />
<br />
Replica Island passed 1,000,000 installs today, 103 days since its launch on March 9, 2010.<br />
<br />
Thanks for your support, everybody!Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com17tag:blogger.com,1999:blog-2127023973845776737.post-67989710626154805212010-05-03T13:55:00.000-07:002010-05-03T13:55:27.399-07:00Control Configuration and Abstraction<br class="Apple-interchange-newline" /><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The #1 thing that I've learned since shipping Replica Island is that users want configurable controls. I mean, I might have guessed that some devices would have one sort of controller and not another, but I didn't anticipate the number of people who prefer a specific control configuration even when others are available. Users with trackballs and directional pads asked for configurable keyboard settings, and when I added orientation sensor-based movement for devices without other controls (I'm looking at you, Xperia), many users who could already play the game chose to switch to tilt controls too. I've made four updates so far and all of them have had to do with the input system; in the first three I added more and more configuration options, and in the most recent (v1.3) I rewrote the core input framework to improve non-standard control configurations.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">When I started writing Replica Island, the only device available was the G1. About half way through development I switched to an HTC Magic, and at the very end of development I switched to a Nexus One. The game was entirely designed around HTC's trackball-on-the-right design. Fairly late in development, devices sporting directional pads (like the Motorola Cliq, and more importantly, the Droid) started to hit the market, so I added some support for d-pad controls. I didn't really think anybody was going to use the keyboard to play, so I only added a few key-based controls to support the ODROID.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The input system started out like this:</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/blog/input_original.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://replicaisland.net/img/blog/input_original.png" /></a></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">MotionEvents from touch and trackball motion, as well as KeyEvents, were passed to the InputSystem (via the GameThread, for reasons I'd rather not discuss), which recorded them in some internal structures. The goal here was to abstract the Android events from the game interface. The game wants to be able to say things like "is the jump button pressed," or "was the jump button just pressed since the last frame," or "how long has it been since the last time the button was pressed." It's a query-based interface, rather than the message-based interface that the Android framework provides. So the initial role of the InputSystem was to record Android events so that they could be queried in the future.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The trackball was tricky to get right. I want to allow the player to flick the trackball in a direction and have the character get an impulse in that direction scaled by the magnitude of the flick. But the Android motion events come in at a fixed frequency and fixed magnitude, so in order to build a vector describing recent motion, I needed to maintain some history between motion events. My first implementation, which survived for the entire course of development, was to cache a history of 10 motion events and calculate the average direction of motion across all of them to find the flick direction and magnitude. After a specific timeout had passed with no new events, the cache was cleared and averaging would begin again with the next event.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">This worked ok as a way to calculate a motion vector, but it had problems. The biggest issue was that there was no way for a user to move slowly; even if the user rolled the ball slowly (thus causing motion events to come less frequently), as long as he rolled fast enough to make the internal event timeout, the events would get averaged together and would come out looking the same as a fast flick. So users who tried to move with precision or in small steps often found themselves rocketing across the level.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">When I went to add d-pad support, I just treated the pad as a different source of motion events. I treated each keydown as a roll of a specific magnitude in a specific direction, and fed that into the same cache system I used for motion events. This worked, sort of: it allowed me to pipe the directional pad through the trackball interface (which connected directly to the game) pretty easily, but it didn't feel good. The problem with this approach was that directional pad events don't need any averaging; in fact, you want exactly the most recent state to be represented, as the player can release a key at any time (the trackball, unlike other kinds of input, never goes "up", and thus required a history). So directional pad support in Replica Island, in the first few versions, sucked.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Add in configurable control options and very quickly my simple G1-centric input system grew into a mess that didn't work very well. So, for the most recent version, I rewrote the whole thing. Now the structure looks like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/blog/input_new.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://replicaisland.net/img/blog/input_new.png" /></a></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The main change here is to separate input recording (necessary for querying) from game-specific filtering and control configuration switching. The <a href="http://code.google.com/p/replicaisland/source/browse/trunk/src/com/replica/replicaisland/InputSystem.java">InputSystem</a> is now generic; it just records input events from the keyboard, touch panel, orientation sensor, and trackball, and provides an interface for the current state (as defined by the most recently received events) to be queried. A new system, <a href="http://code.google.com/p/replicaisland/source/browse/trunk/src/com/replica/replicaisland/InputGameInterface.java">InputGameInterface</a>, reads the hardware state from InputSystem, applies heuristics and filters, and presents fake buttons for the game to use. This way the game can ask for the "directional pad" and get input from a trackball, orientation sensor, keyboard, directional pad, or whatever, already filtered and normalized. I put all of the filtering code for the trackball into this class, and I can now pass directional pad input directly to the game without tying it to the trackball.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">Speaking of the trackball, I changed my approach to filtering. Now I accumulate trackball events that occur within a very short cutoff, and average them after a slightly longer cutoff. Instead of turning the trackball input "off" after a fixed duration, I make it decay until it reaches zero. This lets the user make small, precise movements, and still get a big motion from a large flick (as in the latter case, events occur in rapid succession and accumulate). This method also gave me an obvious spot to stick a sensitivity factor for the trackball, which several users of devices with optical trackpads (HTC Desire, Samsung Moment, etc) had requested.</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">The new system probably needs a bit more tuning, but I played the game through with it and it feels pretty good. The code is about 100x cleaner now, and InputSystem is something that others can easily reuse without any other dependencies.</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com16tag:blogger.com,1999:blog-2127023973845776737.post-59960522529347626942010-04-11T10:41:00.000-07:002010-04-11T10:41:40.277-07:00Design Post-Mortem: Three MistakesWhile I'm pretty happy with Replica Island, releasing it has definitely been a learning experience. Some of the design choices Genki and I made were good, others were ok, and a few were bad. Today I'm going to talk about three mistakes I made in the design of this game.<br />
<br />
<b>Mistake #1</b>: This particular jump.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://www.replicaisland.net/img/blog/problem3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http:///www.replicaisland.net/img/blog/problem3.jpg" /></a></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Turns out that a lot of players die right here. Heck, I have the <a href="http://replicaisland.net/img/playermetrics/8/Memory%20%23021.png">metrics to prove it</a>. This is sort of a difficult jump because you have to make it across a wide pit and then land in a very small space near the top of the screen. This jump started out a little easier, but it got harder when I compressed the vertical size of this level to deal with some <a href="http://replicaisland.blogspot.com/2010/01/elusive-perfect-platformer-camera.html">camera problems</a>. The real problem here, though, is the distance. It turns out that this is the first spot in the game where you are required to use second-order flying skills to reach the other side.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Just what is that skill? Well, it turns out that some players never realize that they can stay aloft for longer if they let momentum rather than fuel carry them forward. If you press the jump button and get your speed up in the air, you can release the jump button and stay aloft until gravity overtakes you and you begin to fall. At that point you can hit the jump button again to combat gravity and maintain your altitude. You can't do this forever because you'll eventually run out of fuel, but using this method you can jump much greater distances than if you just hold the jump button down constantly.<br />
</div><div class="separator" style="clear: both; text-align: left;">The real problem is that I never teach players how to fly that way explicitly. There is lots of tutorial levels designed to make sure you can fly, but it turns out that only the most basic flying skills are required to get passed those sections. This area is the first in which you cannot progress unless you've figured out how to use the more complex flight mechanics, and people get stuck here.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">What can I do about it? Probably this area itself is fine; a better tutorial on this particular skill is probably in order.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;"><b>Mistake #2: </b>The Case of the Robot Spewing Spawner</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div style="text-align: center;"><a href="http://www.replicaisland.net/img/blog/problem2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.replicaisland.net/img/blog/problem2.jpg" /></a></div><div style="text-align: left;"><br />
</div><div style="text-align: left;">When I made the robot spawners, I though it would be a funny easter egg if you could possess them. Part of the plan was to allow the possession orb to possess any machine, and I thought possessing the spawners would be a little hidden reward for users that experiment. The problem is, I added this functionality before we understood how the robot puzzles were going to work; in the final game, it's trivially easy to possess a spawner, and most users are confused rather than elated. Even worse, being able to possess a spawner sometimes gets in the way of possessing a robot, which is pretty annoying. I realized that this might be a problem before we released the game, but I left it alone because I thought it was funny. Now there are <a href="http://www.youtube.com/watch?v=G3xI_vBRdJU">multiple</a> <a href="http://www.youtube.com/watch?v=8JKyY2PxD_0">videos</a> <a href="http://www.youtube.com/watch?v=X1A8qUyPL5E">about it</a>. Pretty undeniable proof that I messed this up.</div><div style="text-align: left;"><br />
</div><div style="text-align: left;">What should I do about it? I should probably turn the easter egg off or at least find a way to make it much harder to find.</div><div style="text-align: left;"><br />
</div><div style="text-align: left;"><b>Mistake #3:</b> The impossible puzzle.</div><div style="text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="http://www.replicaisland.net/img/blog/problem1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.replicaisland.net/img/blog/problem1.jpg" /></a></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">You have no idea how many people have e-mailed me about this puzzle. It's clearly the most difficult part of the game for a huge number of users.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">In the first version of the game, this section was missing the red spikes due to a bug in the level data, which made this puzzle even more confusing. But even after fixing that, I continue to get several e-mails a day asking about this part.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">If you don't want this puzzle spoiled for you, stop reading now.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">OK, I warned you. The solution here is to use the Possession Orb to grab the robot and run him into the button. Pretty simple, I thought; the real challenge in this puzzle's original form was actually just maneuvering the Orb through the spikes before it dissipates (in an update I added a gem to this level, so the Orb survives for longer). But it turns out that users weren't even figuring out that they could use the Possession Orb here, even though they've seen this pattern several times in slightly different contexts earlier in the game.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">I finally realized what the real failure is here: players don't realize that the Orb can fly. The tutorial says something like, "tilt the phone to control the orb," but if you never try tilting it up, you'll never learn that the Orb can fly. I think that players also forget that the Orb is in their arsenal, but they'd probably figure that out eventually. Not expecting it to be able to fly means that many don't even try it. This is a fundamental teaching failure on my part and makes this puzzle a whole lot harder than it was intended to be.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">What can I do about it? Well, as a first step, the tutorial level should force you to send the ball up at some point. After that though, the game needs to reenforce the fact that the Orb can fly, so I'll probably need to modify some levels to encourage that behavior.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">The great thing about this is, I can fix the problems and send out an update and see if my fixes were successful. And if not I'll try again. Traditional video games have never had this sort of ability to iterate interactively with the audience, and I'm really learning a lot from it.</div><div style="text-align: left;"><br />
</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com26tag:blogger.com,1999:blog-2127023973845776737.post-44532607497867880032010-04-09T10:31:00.000-07:002010-04-09T10:32:51.666-07:00Replica Island: One Month On<div class="separator" style="clear: both; text-align: left;">As of today, Replica Island has been available on Android Market for exactly one month. I thought this would be a good time to see how it's been doing.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Turns out it's doing pretty well! It's holding steady at 4.5 stars and it just passed 250,000 installs. There have been a few bugs; I've made a few updates, and there are a few more fixes in the works. But most users have been extremely positive about the game.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">I've been diligently recording my install numbers from the Android Market Publisher site all month so that I can produce this graph.</div><div class="separator" style="clear: both; text-align: center;"><img border="0" height="306" src="http://replicaisland.net/img/blog/first_month_installs.png" width="400" /></div><br />
<div class="separator" style="clear: both; text-align: left;">Before I talk about the graph, let me tell you a little bit about my launch plan.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Replica Island is a series of experiments. I'm trying to learn how games should be made for Android, and part of that process is learning how they should be marketed. So as part of the experiment, I made the following launch plan:</div><div class="separator" style="clear: both; text-align: left;"></div><ul><li>First ten days: no PR, no announcements, nothing. Just upload it and see what happens.</li>
<li>Day 10: send out press releases, upload YouTube video, post on blog.</li>
<li>When installs start to slow, prepare a second round of press releases, this time for a different audience.</li>
</ul><div>My marketing budget for this project is $0.00. I know basically nothing about marketing, and I sometimes think that Nicholai, my PR guy, was dropped on his head as a child. This plan reflected my need to spend no money and have basically no method of marketing other than trying to get the word out to blogs and news sites.</div><div><br />
</div><div>The first week I made no announcements in order to track the quality of organic growth. Turns out organic growth was steady and apparently fairly constant; Replica Island was downloaded about 18k times in that first week. </div><div><br />
</div><div>On 3/18/10 I uploaded the first update. This update fixed a few bugs and added tilt-based controls.</div><div><br />
</div><div>On 3/19/10 Nicholai sent out <a href="http://replicaisland.net/replica_island_press_release_3_18_10.doc">press releases</a> to a bunch of Android blogs and a few non-Android gaming blogs. The total number of blogs targeted was small, maybe five or six. Of those, AndroidGuys.com was the <a href="http://www.androidguys.com/2010/03/20/download-replica-island/">first to respond</a> (and their report remains one of the most detailed). A bunch of other blogs copied and pasted the AndroidGuys report (I swear 99% of blogs nowadays are basically retweets of other people). There was an immediate increase in daily downloads.</div><div><br />
</div><div>On 3/22/10 Gizmodo posted the YouTube video and wrote a <a href="http://gizmodo.com/5498976/the-android-game-that-every-android-fan-must-get">nearly content free report</a> (though they still managed to work in an anti-iPhone reference, as if part of their editorial goal is to promote fanboyism). Apparently a lot of people read Gizmodo, as downloads spiked on that day and then continued at increased volume. Also, the retweeting of the Gizmodo article guaranteed that the first several pages of search results for "Replica Island" were all different sites referring to the same two articles.</div><div><br />
</div><div>On 3/30/10 Replica Island was added to the list of Featured Apps in Android Market (full disclosure: I'm a Google employee but was not involved in the decision to feature this game). You can see the effect of being featured in the graph; downloads per day shot way up, and have remained high. Featuring makes apps really visible!</div><div><br />
</div><div>Since app downloads haven't slowed down yet, I haven't implemented the next part of the plan, which is a second round of press releases aimed at different blogs. I hear that Nicholai has some crazy scheme up his sleeve, but as long as it costs $0.00, I'm fine with it.</div><div><br />
</div><div>So, here's my takeaway from this first month on Market:</div><div><ul><li>Users want games in traditional genres. Replica Island gets a lot of love from users just for being a recognizable game genre.</li>
<li>Updates don't seem to really affect downloads. Some developers have complained that mixing uploads and new releases in "Just In" allows unscrupulous developers to ship superfluous updates just to increase their visibility, but my data doesn't show any particular advantage to being in Just In. I think that list moves too quickly.</li>
<li>Organic growth can work, but even the tiniest bit of marketing goes a long way. Getting the word out to blogs, even with a simple web site or press release, is huge. Those people who are expecting direct-to-consumer digital distribution to be the end of marketing are sorely mistaken.</li>
<li>The YouTube video we made was almost an afterthought, but it's been reproduced in more blogs than any of our screenshots or promotional text. YouTube will definitely be a big part of our next push.</li>
<li>Nicholai went to all the trouble to make a press kit, and write press releases, as described in the excellent <a href="http://www.netpress.org/careandfeeding.html">Care and Feeding of the Press</a>, but 99% of blogs just copied and pasted the summary text we put on the front page. I think less than 10% of the blogs that posted about the game actually tried playing it.</li>
<li>Development and marketing on a budget of $0.00 totally works!</li>
</ul><div>So that's all for now. I'll report back when the data reveals some new pattern. Thanks much to everybody who is playing, and to all of the blogs who covered the game, even those that just copied their text from somebody else.</div></div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com13tag:blogger.com,1999:blog-2127023973845776737.post-56592315854010118592010-04-08T07:17:00.000-07:002010-04-08T07:18:13.779-07:00My New Favorite User Feedback<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/i_hate_you_robert.png" /></div><br />
<div class="separator" style="clear: both; text-align: left;">You can't write this stuff!</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com10tag:blogger.com,1999:blog-2127023973845776737.post-77691927062120093472010-03-29T10:22:00.000-07:002010-03-29T18:53:48.401-07:00Replica Island User Comments Are Hilarious<b>Update</b>: Blogger apparently isn't advanced enough to deal with images reliably. I moved them to a regular web site.<br />
<br />
<br />
So Replica Island has been out for about three weeks now, and today it reached 85,000 installs, which is pretty cool.<br />
<br />
Lots of users have been nice enough to leave feedback, too. Most of the feedback is overwhelmingly positive, which is fantastic--there's nothing better than knowing that something you made is bringing other people pleasure. And even the negative comments can be useful; I've identified several bugs and high-value features thanks to the suggestions of users posting on the Market page.<br />
<br />
Here's a few of the comments that really made my day.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/compelling.png" /></div><div class="separator" style="clear: both; text-align: left;">Flattery will get you everywhere. I'm preparing a fix as we speak!</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/dig_it.png" /></div><br />
<div class="separator" style="clear: both; text-align: left;">That's some high praise right there.</div><div style="text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/wow.png" /></div><div style="text-align: left;">Thanks!</div><div style="text-align: center;"><br />
</div>Then there are the useless comments. These are comments that, typically, contain so little information or are so undecipherable that they don't tell me (or a perspective user) anything useful about the game. These are mostly comments like, "doesn't work" or "I'm stuck." <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/cant_work.png" /></div><br />
First the shift key and now this!<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/really_boring_5_stars.png" /></div><div class="separator" style="clear: both; text-align: center;"><br />
</div>Wait, which is it? Man, how would you rate a game that you liked?<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/worked_then_stopped.png" /></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="" style="clear: both; text-align: left;">Thanks for the bug report! I'll get right on that, uh, wait, what did you say happened again?<br />
<br />
</div><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/cant_maneuver.png" /></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="" style="clear: both; text-align: left;">Perhaps you missed the text at the very beginning of the game that explained how to move? It's pretty complicated, I know: "roll the trackball" or "press the d-pad." I mean, come on, give me a break, right?</div><div class="" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/2_stars_for_effort.png" /></div><div class="" style="clear: both; text-align: left;"><br />
</div><div class="" style="clear: both; text-align: left;">Harsh! Wait, are you saying that the effort is only worth 2 stars, or that the game is so bad that it only warrants 2 stars because we put a lot of effort into it. Wait, what are you saying again?</div><div class="" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/do_not_get.png" /></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="" style="clear: both; text-align: left;">Your feedback has ensured a higher standard of quality for our next app!</div><div class="" style="clear: both; text-align: left;"><br />
</div><div class="" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/best_rpg.png" /></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Thanks, but maybe you meant to post on the <a href="http://www.eurodroid.com/2010/03/famed-iphone-rpg-zenonia-hits-android-for-5-99-and-1-6-phones-and-above/">Zenonia</a> page?</div><br />
</div>Then there are the hilarious comments. Sometimes they are sort of innocent, like this one.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/mildly_decent_average.png" /></div><br />
Sorry to let you down Etan, but I'm not sure exactly where that expectation came from, as this is our first game and all. Also, maybe you could have added "mediocre" or "run-of-the-mill" in there somewhere--I'm sure there's some way to fit in another few words meaning "average."<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/too_fast_too_slow.png" /></div><br />
See, this is almost a useful comment! There really aren't any cutscenes in the game (well, there's one, but I won't spoil it), but there is a lot of dialog--maybe that's what you meant? I'm not sure how to make the game faster and slower at the same time, though. <br />
<br />
There were a bunch of comments with a common theme that I couldn't figure out.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/cant_go_left.png" /></div><br />
Uh-oh, that sounds like a bad bug!<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/cant_move_forward.png" /></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;">How could this happen? I tested the Droid extensively!</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/wont_let_me_move.png" /></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Oh no! This sounds like a real problem!</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">I was so confused about what these posters were trying to tell me. Is there some horrible bug in my game that prevents moving forward? How can that be? And if that's the case, how come only 4 people out of 1000 have reported it? I've played the game on the Hero, I <i>know</i> that it works fine.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Finally, one final post from a user with this problem suddenly made it clear what was happening. This post has since been removed by its author, but it lives on forever in the databases of services like AppBrain.</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div style="text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/yuck.png" /></div><div style="text-align: left;"><br />
</div><div style="text-align: left;">If you've played Replica Island, you might remember the intro sequence. In it, a character (Wanda) runs through a few short credits, finds the broken Android robot, and says a few words. Then the game changes to the first level, in which Dr. Kabochanomizu teaches you how to play. That intro sequence takes, on average, 48 seconds (I know because I have user data from 85k users telling me so). During that period, you can't control the game. These users are people who booted the game up and closed it again in less than 48 seconds, and then went back to Android Market and wrote a review.</div><div style="text-align: left;"><br />
</div><div style="text-align: left;">I actually figured this out before Patricia came along and made it clear, so in the 1.1 update I released about two weeks ago I added a little flashing "Please Wait" message to this part of the game. Still a few folks (like our friend Patricia here) didn't figure it out and gave up in less than 48 seconds.</div><div style="text-align: left;"><br />
</div><div style="text-align: left;">I guess I don't have anything to say to people who give an app less than one minute to be awesome. It might be nice if Market displayed the amount of time each user had the app installed before writing a review.</div><div style="text-align: left;"><br />
</div><br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/50_fps.png" /></div><br />
Ah, the good old backhanded complement! However, this poster is mistaken: I never claimed that the game would get 50 fps on the Droid. I said it would get 50 fps on the G1, which it does during normal play (it can drop to about 30 in complex scenes). No game draws faster than 30 fps on the Droid (or Nexus One) because those devices are fill limited. Sorry about your luck, Mario, but your device is going to be 30 fps for all games forever. It's still a pretty cool phone, though.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="http://replicaisland.net/img/blog/awesome.png" /></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Do you see this!? THIS is how you write useful feedback! Both points of feedback are great, particularly the suggestion box. I am totally going to add that in the next update. Glad she liked the game, too.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">The funniest comment so far, however, wasn't posted on Android Market. The user was so incensed at the game that he visited the <a href="http://replicaisland.net/">Replica Island web site</a> and sent e-mail to Nicholai, my PR guy.</div><blockquote>By the way, your game sucks big donkey balls. I have spent a Good amount if time playing only to find I am at a part I can't pass. I would tell you what part of this stupid game it is from but I can't go to a reference point. I have also ran into several game glitches and I just can't believe you would publish such a piece of shit. Fuck you.</blockquote>This is so awesome. Mr. Russell Hall, whoever you are, you totally made my day. Hopefully one day you'll figure out wherever it is that you are stuck and continue putting a good amount of time into my donkey-ball-sucking-game. Seriously, this was the first comment to really make me laugh out loud. Thanks.<br />
<div class="separator" style="clear: both; text-align: left;"><br />
</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com75tag:blogger.com,1999:blog-2127023973845776737.post-58868478954141586482010-03-23T06:37:00.000-07:002010-03-23T06:38:26.416-07:00Design Post-Mortem: The Possession Orb<div style="text-align: center;"><div style="text-align: left;">Rather than write a big long post-mortem of how developing Replica Island went, I'm thinking about writing smaller articles that focus on a single element of development. Just because the game has shipped does not mean that development is over, and I'm sure that I have much more to learn now than I did before the game shipped. And speaking of the game's release, it's tearing up the charts, thanks to Nicholai and his crack team of PR ninjas (ok, he doesn't really have any PR ninjas). <a href="http://gizmodo.com/5498976/the-android-game-that-every-android-fan-must-get">This article</a>, despite being ultra thin on details, seems to have reached a lot of users. I'll post more details soon.</div></div><div style="text-align: left;"><br />
</div>Ok, so for this first Design Post-Mortem, I thought I would talk about the evolution of the Possession Orb. The Possession Orb is a special projectile that the Android robot can fire out of his body. It has the power to possess machines, and running it into, say, an enemy robot will let you control that robot. The Orb is controlled by tilting the phone, and you can extend its life time by collecting red gems.<br />
<br />
The Possession Orb did not originally start out as an orb. And actually, it didn't originally have anything to do with possession either.<br />
<br />
My original design for the Possession Orb was called the Player Ghost. The idea was that the player could drop the ghost somewhere in the game world, and after a few seconds it would begin to replay the player's recent movement. At any time the player could click a button and instantly teleport back to the ghost's current location--it was like a way for the player to undo a few seconds of movement. The ghost, I figured, could also press buttons, so there could be timing challenges involving hitting buttons with the player's body and then again a few seconds later with the ghost.<br />
<br />
I got this idea from a crazy Nintendo Entertainment System game called Lot Lot. Lot Lot is a puzzle game in which two cursors move across a board filled with balls. One cursor is under the player's direct control, and the second cursor is following the path of the first, but about two seconds delayed. When the player's cursor is over a grid square that has balls in it, the player can press a button to swap those balls for the ones in the square pointed to by the second cursor. So game play consists of thinking about where you want the two cursors to be, drawing a path that you know will be correct two seconds into the future, and then hitting the button at the right time to shift balls to the edge of the board before they fill up. It's very difficult to understand at first but once it clicks the game play is crazy. Here's a video of Lot Lot in action:<br />
<br />
<object height="344" width="425"><param name="movie" value="http://www.youtube.com/v/_xXdk3xNxDA&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/_xXdk3xNxDA&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br />
<br />
So, my idea was that the Player Ghost would be like Lot Lot's second cursor: a replay of the player's recent movement that could still affect the game world.<br />
<br />
Here's some early design sketches I made about this idea:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKzQqnLNLyxdqjIs3SOwx5ppr2lB8yWN3sXztk_mI1Xb6MJePccABMpab6jMMpNf0jGfHWEg4eY7rtcTtB36M5KjGL3nJl2F_XBnfkFlIPS2yfaRRYLlEnUwiUXlEuxSQCNH87PCua0GI/s1600-h/Ghost-design-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKzQqnLNLyxdqjIs3SOwx5ppr2lB8yWN3sXztk_mI1Xb6MJePccABMpab6jMMpNf0jGfHWEg4eY7rtcTtB36M5KjGL3nJl2F_XBnfkFlIPS2yfaRRYLlEnUwiUXlEuxSQCNH87PCua0GI/s400/Ghost-design-2.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Sounds cool, right? I thought it would be cool if you could snap back to the ghost's location too.</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZt3v2vdV9Zr3ctY8lXDWUK_1ymcPrQnagFNAOjfuOjnCI2S2eG8yPYq-DVgltop4Td5QYPhx7TdU1G5ZWGo5U-U4LwZuBnlnASkFV1z9TheDZPaX_xj3paQQvC7yVK3pbubUaNvlIfgs/s1600-h/Ghost-design-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZt3v2vdV9Zr3ctY8lXDWUK_1ymcPrQnagFNAOjfuOjnCI2S2eG8yPYq-DVgltop4Td5QYPhx7TdU1G5ZWGo5U-U4LwZuBnlnASkFV1z9TheDZPaX_xj3paQQvC7yVK3pbubUaNvlIfgs/s400/Ghost-design-1.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">I liked this idea, but when I ran it by Genki, my partner in crime, he was skeptical. He thought that puzzles would be hard to design and that users would have a hard time understanding the mechanic. But I wanted to try it out so I went ahead and implemented it anyway.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Functionally, the Player Ghost worked exactly as I had imagined. It wasn't hard to implement and it actually looked pretty cool. But it quickly became clear that Genki was right: the concept made no sense and designing puzzles was really hard. It was clear that puzzles were going to require a bunch of different objects just so that the ghost could work (e.g. doors that close immediately after you pass through them, rather than after a fixed duration), which lowered the quality of the idea significantly. And it was confusing as heck; I wasn't confident that even with supporting game objects the idea would ever really make sense to users.</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Genki suggested that instead of the player leaving the ghost behind, the ghost should leave the player behind. This idea made a lot more sense, but I wasn't sure how to make puzzles out of it. Eventually we realized that possession is a cool mechanic that the ghost could be useful for (rather than just depressing buttons), and that tilt controls might be a fun way to add diversity to the input system. Tilt controls implied flying, so the ghost was freed from gravity. And at that point, it didn't make any sense for it to look like the Android robot any more, so we changed it into an orb (collisions with the background were more believable this way too).</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Once we hit on the mechanic a lot of potential avenues for game design immediately presented themselves, which was a good indicator that the idea had more legs than its original version. Actually, I think that this mechanic is quite under-used in Replica Island. There's a lot more that we could have done with it, and actually tested with it, that didn't make it into the final game (try running the Possession Orb into a turret, for example). Maybe this is something to experiment with in a future update.</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com13tag:blogger.com,1999:blog-2127023973845776737.post-71004958994974809992010-03-20T06:58:00.000-07:002010-03-20T06:58:55.662-07:00Replica Island Released!I released Replica Island almost two weeks ago, just before I gave a talk about Android game development at the Game Developers Conference. Here's the quick details:<br />
<br />
<ul><li>Home page: <a href="http://replicaisland.net/">replicaisland.net</a></li>
<li>Source: <a href="http://code.google.com/p/replicaisland/">http://code.google.com/p/replicaisland/</a></li>
</ul><div>Here's a QR Code you can scan to bring the game up in Market:</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/market_link_qr.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="http://replicaisland.net/img/market_link_qr.png" width="320" /></a></div><div class="separator" style="clear: both; text-align: left;">And here's a quick trailer video:</div><div class="separator" style="clear: both; text-align: left;"><object height="340" width="560"><param name="movie" value="http://www.youtube.com/v/Gzvndy9MXUw&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/Gzvndy9MXUw&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object></div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div><br />
Whew, glad to get that stuff out of the way.<br />
<br />
You might be wondering why I am posting this now instead of two weeks ago. Well, I wanted to run an experiment. I wanted to see what the game would do on Android Market with zero promotion. So other than mentioning the game in my talk, I've not advertised Replica Island anywhere or done anything (other than a quick update to address control concerns) to make it particularly visible. I wanted to see what would happen if I just left it alone for a while.<br />
<br />
So after 10 days on Market, I'm sitting at about 18k installs and 4 out of 5 stars. Not bad! By comparing the data I get from the Market Publisher site to the data coming back via my own reporting system, I can tell that about 15% of users choose to opt-out of the anonymous metrics reporting system. Which is great--I have tons of data from users who are cool with me having it. <br />
<br />
As of today I've had Nicholai, my PR guy, turn on the PR faucet. He's going crazy with press releases and other promotional tools. The next experiment is to see what happens to my download growth rate once Nicholai has worked his magic. Once that experiment has produced enough data, I'll post about it here.<br />
<br />
I've also rewritten a lot of my data visualization tools to deal with the huge amount of data I am getting from users. I'll write a post about the new system I am using to visualize the player death data next.<br />
<br />
In the mean time, check the game out and let me know what you think!</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com10tag:blogger.com,1999:blog-2127023973845776737.post-28530262458359095372010-01-22T06:20:00.000-08:002010-01-22T06:20:12.536-08:00Fragmentation? More like Fragmentawesome.I'm lucky enough to have occasional access to lots of different Android devices via my work. The whole point of the Android approach to apps is that you can write an app on one device (or even an emulator) and deploy it across everything. In my case, that's been pretty true. I've tried Replica Island on the following devices:<br />
<ul><li> Google Nexus One<br />
</li>
<li> Verizon Droid by Motorola<br />
</li>
<li> HTC Magic (The myTouch in the US, HT-03A here in Japan)<br />
</li>
<li> HTC Dream (The G1)<br />
</li>
<li> Samsung Behold II<br />
</li>
<li> Samsung Galaxy<br />
</li>
<li> HTC Hero<br />
</li>
<li> HTC Tattoo<br />
</li>
<li> LG Eve<br />
</li>
<li> ODROID<br />
</li>
<li> Covia SmartQ5<br />
</li>
<li> Android for x86<br />
</li>
</ul>The cool thing is, Replica Island ran on all of them without complaint. That's not to say it was playable on all of them--the Covia SmartQ5, for example, has no keyboard, trackball, or even orientation sensors, and you have to use a stylus on the screen (it also has no GPU so the game runs extremely slowly). And some devices (like the LG Eve) have directional pads that are really poor for games. I ran the game under Android for x86 via virtualization, and while it worked it was slow enough that I have new respect for the Android emulator. But the game runs, pretty much error free, on every single Android device I've tested it on.<br />
<br />
I don't have regular access to all this hardware to test on, so when a phone comes my way I jump on it, install the game, and try it out. Excepting basic support for multiple screen sizes and input systems, I don't have any device-specific code. I developed Replica Island almost entirely on Dream and Magic devices; it's built for a trackball and touch screen. I've added support for directional pads as well, which covers almost all of the phones on the market. The game runs at a nice frame rate on the Dream/Magic hardware, and it's very smooth on faster devices like the Nexus One and Droid. But that's it--no special case code for any particular device anywhere. Cool! <br />
<br />
You might be interested in which of these devices plays Replica Island the best. The answer might surprise you. Wait for it... ok, it's the ODROID. <br />
<br />
Yeah, seriously. <br />
<br />
What the heck is the ODROID? Well, it's an Android device (not a phone) sold by a South Korean company called <a href="http://www.hardkernel.com/">Hardkernel</a>. Actually, the consumer version isn't even for sale yet, but you can buy test hardware for nebulous "development purposes" on their web site. The <strike>Wonderswan</strike> ODROID runs Android 1.5 (sounds like they will have a 2.0 update soon), has an HVGA screen, a Samsung CPU at 833mhz, a touch screen, orientation sensors, and all the other standard stuff. What sets it apart is the game-focused form factor, a real directional pad, and four face buttons. The thing is clearly designed for games.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieuDRyyr8OF-OuwH9Yw-Ok8w8jy52N04yX8fkqhRwLAQepC4sU45aFrU9hBhwtMIPnBR0oqPrHKVYMUxv227udtei2RkoynhO5rC4kdIceqt6BvmQHSKZNgpPuh-37LbemvAEr2f8Jov8/s1600-h/Picture+12.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieuDRyyr8OF-OuwH9Yw-Ok8w8jy52N04yX8fkqhRwLAQepC4sU45aFrU9hBhwtMIPnBR0oqPrHKVYMUxv227udtei2RkoynhO5rC4kdIceqt6BvmQHSKZNgpPuh-37LbemvAEr2f8Jov8/s320/Picture+12.png" /></a><br />
</div><br />
My ODROID arrived in the mail yesterday, direct from South Korea. It's clearly prototype hardware; the thing is made out of light plastic and looks fairly cheap. There's a very strange power button that doubles as a hold switch and screen orientation switch, and I keep accidentally hitting the capacitive volume controls where I expect shoulder buttons to be. The directional pad is actually pretty bad compared to what you'd find on gaming hardware like the Nintendo DS or Playstation Portable, but it's better than your average phone. The thing can talk to adb, it came with an SD card already installed, and supporting the A/B/X/Y buttons is trivially easy (they just map to regular keyboard events).<br />
<br />
But the reason that the ODROID is the best device for playing Replica Island isn't just because of the game-like form factor and controls. Unlike most other Android devices, the ODROID is a combination of fast CPU and medium resolution screen. The devices with larger screens tend to be fill-rate bound; though the Nexus One and Droid have extremely capable CPUs and GPUs, the high resolution screens on those devices work against them when it comes to games (it's almost impossible to break 30 fps on those devices via the GPU, though they can crunch really complex scenes at that speed without breaking a sweat). On the other hand, the Magic/Dream class of devices have the same HVGA resolution, but tend to be CPU bound--rendering is fast but I spend quite a lot of time running the game simulation. The ODROID has neither problem--its CPU is pretty fast and its GPU has no problem filling the HVGA display. As a result, Replica Island is silky smooth on the ODROD--a constant, reliable 60fps. Add that to the game-ready control scheme and you have a pretty great game experience.<br />
<br />
That's part of what's so awesome about the run-everywhere Android approach. Replica Island works on all Android devices, pretty much by default. It's fun to play (well, I think it's pretty cool) on most average phones. But when some crazy company wants to make some crazy device that's good at one thing, they can do so without requiring any changes to the applications themselves. In this case, the ODROID is a surprisingly solid game device, at least for games like mine (though other games, particularly 3D games with complex scenes, are probably faster on devices like the Nexus One). And supporting it costs me literally nothing; I just loaded it up and it worked. That's pretty neat.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com28tag:blogger.com,1999:blog-2127023973845776737.post-85754845650484097022010-01-13T23:43:00.000-08:002010-01-13T23:43:33.515-08:00The Elusive Perfect Platformer CameraI've come to believe that platformers live and die by their camera system. The camera system is the code that decides how the player should be centered in the frame. The camera must somehow track the player as he moves such that the player can see what's coming. That might seem like a simple problem, but it's not. In fact, I'll go out on a limb and say that a bad (or even mediocre) camera system can ruin a 2D scrolling platformer game.<br />
<br />
I mentioned in a <a href="http://replicaisland.blogspot.com/2010/01/tuning-with-metrics-redux.html">previous post</a> that the data coming back from my play testers showed them dying in droves in bottomless pits. I guessed that this had to do with the camera system scrolling up and down, and I was right; a review of the camera code revealed a lot of room for improvement, and after some tuning and bug fixing, I think the experience is much improved.<br />
<br />
But this experience again drove home the point I made in the intro paragraph: that the camera in a 2D scrolling platformer has the potential to affect the play experience dramatically--it has to be as perfect as possible. I've made a lot of side-scrollers before, and I should know this, but I was still surprised by how much play was improved by a few simple camera tweaks.<br />
<br />
If you are ever at a loss about what to do when it comes to 2D platformer design, refer back to Super Mario Bros. It's like the bible of platforming games--every problem that you might encounter has already been solved, and it's probably been solved in a way that works better than whatever you came up with. At least, that's been my experience. Take a look at this video from Super Mario Bros. 3. Pay attention to the amount of vertical scrolling that the game does when the player gets close to the top of the screen.<br />
<br />
<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/2SfvhZgD7Bk&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/2SfvhZgD7Bk&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br />
<br />
You can see that the game almost never scrolls vertically. The really interesting case is around 0:56, where the level (which has previously refused to scroll vertically) scrolls up in one very specific point to get the secret 1up. It's like vertical scrolling is only allowed in very specific situations. You can also see this sort of logic at work when Mario grabs the tanuki suit and starts to fly--immediately the game begins to follow him vertically.<br />
<br />
Now compare the camera movement in Mario to the video below. This is Frogger Advance: The Great Quest, a GBA game that I worked on all the way back in 2001. <br />
<br />
<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/REwqs7hrSbw&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/REwqs7hrSbw&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br />
<br />
Quite a difference, right? The camera is all over the place, but despite all of the motion it's pretty much impossible to see where you are going. Part of the problem is that Frogger himself is really big; he takes up so much space on the screen that the camera really has to move just to keep him in the frame. This is a leading camera--it's supposed to always show you the direction that that you are moving. But in practice the physics are so fast that even if the camera rushes to show what's coming up, the player doesn't have time to react. When we made this game we understood that players were dying because they couldn't see where they would fall after a jump, but we didn't understand what to do about it. If you watch this video, you'll see the player use Frogger's float move to slow his falling motion down; this move was added explicitly to combat fall-into-pit deaths. A better solution would have been to try to reduce the amount of movement of the camera by designing levels that don't need to scroll vertically and reducing the size of the main character.<br />
<br />
For Replica Island, my camera algorithm is based on the concept of a "window." I actually thought of it as a sphere when I wrote it, but my good friend and ultra-veteran platformer author <a href="http://greggman.com/">gman</a> pointed out that it's more accurate to think of a window. The center of the screen is defined by the center of the window, so when the window moves, the game scrolls. The rule that the camera's target (the player) must always remain within the window. When the player crosses out of the bounds of the window, the camera must move the window so that it contains the player at his new position. However, as long as the player stays within the window the camera does not move. So the player is able to cause scrolling in a particular direction by pushing up against a side of the window.<br />
<br />
To fix the levels in which huge numbers of users were dying, I adjusted the bounds of the window so that almost no scrolling occurs in the Y axis until the player approaches the top of the screen. The camera also does not allow the player to move below the middle of the screen. So now a small jump causes no vertical camera movement, but hopping off a ledge keeps the player right in the center of the display. This makes seeing what's below you a lot easier than before.<br />
<br />
But the heuristic wasn't good enough on its own, so I've also added a special object that, when visible, biases the camera in one direction or another. This lets me put camera hints in the map in areas that I know to be particularly problematic. <br />
<br />
Finally, on a few levels I squeezed the level size down so that there's almost no vertical scrolling at all. This makes these levels feel a bit more like Mario, as the game almost never scrolls up and down. This makes the jumping puzzles actually fun, rather than one leap of faith after another.<br />
<br />
So far I'm pretty happy with the results, but the real test will be to compare this new version of the code and levels with the data that I presented before; if my theory is right, the number of deaths from falls should be dramatically reduced. If I'm wrong, well, it'll be another round of iteration. It's worth it though; bad cameras are the death of 2D scrolling games.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com8tag:blogger.com,1999:blog-2127023973845776737.post-410091529567929542010-01-03T16:52:00.000-08:002010-01-03T16:53:52.474-08:00Game Play VideoHere's some footage of an early level in Replica Island. Sorry about the audio.<br />
<br />
<object width="480" height="385"><param name="movie" value="http://www.youtube.com/v/LB-wO_xe_a8&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/LB-wO_xe_a8&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object><br />
<br />
I think I've figured out a pretty decent pipeline for recording game play (this was the test), so more video is on the way.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com7tag:blogger.com,1999:blog-2127023973845776737.post-42368905107961570362010-01-01T08:20:00.000-08:002010-01-01T08:20:57.710-08:00Tuning With Metrics ReduxA while back I posted about the system I devised for <a href="http://replicaisland.blogspot.com/2009/11/tuning-play-with-automatic-and.html">recording player deaths and plotting them on level images</a> to find trouble spots. Since then I've improved the system a little and written some more tools to crunch the data I am receiving. Several hundred players have now participated in testing Replica Island, so I have quite a lot of feedback to process. The goal, of course, is to use anonymous statistics about play (specifically, where players died) to smooth out the difficulty curve and find frustration spikes.<br />
<br />
Rendering death locations on level maps is a good way to go about tuning individual levels, but what about the game as a whole? I now have enough data to look at how players move through the game. Maybe I can draw some conclusions about how the game is paced and how smoothly the difficulty increases.<br />
<br />
The first thing I did is graph the number of deaths for each level. That graph looks like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQPsGK6UGW5Gnu_TlaGLcI5yDhS7oUHdqWJFRcEU_sfm8Yvz2bGQrKwz_BVWXhMR88obOZSDzKkeCqOEMgT4yEaWk5aXjtwAdSLGCQ22lVkQ6LsIQloc9EavNhibfJrUr3_6ncQ0QwbqQ/s1600-h/deaths_per_level.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQPsGK6UGW5Gnu_TlaGLcI5yDhS7oUHdqWJFRcEU_sfm8Yvz2bGQrKwz_BVWXhMR88obOZSDzKkeCqOEMgT4yEaWk5aXjtwAdSLGCQ22lVkQ6LsIQloc9EavNhibfJrUr3_6ncQ0QwbqQ/s400/deaths_per_level.png" /></a><br />
</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;">As you can see, a few levels jump right out: levels 17, 21, 39, 32, and 34 all look like spikes in the death graph. My theory here is that the game should get harder at almost exactly the same rate that the player gets better, so I would expect the number of deaths per level to stay uniform across the entire game. And actually, this graph suggests that with the exception of the outliers mentioned above, I'm doing an ok job at keeping the difficulty increasing at a constant rate. After fixing the very hard levels, I can see that the middle of the game, from around level 11 to level 24, there are some very easy levels as well. I should probably go back and mix those up a bit to increase the difficulty. So far, so good. Looks like this is a pretty good metric for assessing difficulty.<br />
</div><br />
But one thing this data doesn't tell me is how frustrating these levels are. Just because a player died more than once doesn't mean that the level was frustrating; consider a poorly constructed level in which the player isn't in immediate danger but simply cannot progress. In a case like that, the level could be extremely frustrating without the player dying a lot, and this graph wouldn't catch it.<br />
<br />
Let me tell you more about the data I have. I collect a few basic fields for each event. They are:<br />
<br />
<ul><li>event type - the type of event that occurred. Usually "death."</li>
<li>x, y - the position in the level in which the event occurred.</li>
<li>time - the time since the level started, in seconds, until the event.</li>
<li>level - the level in which the event occurred.</li>
<li>session - a unique session key associated with the user (based on a random number generator).</li>
</ul><div>For the first version of the game that I sent out to testers, only death events were recorded. In the most recent version I added a new type: level completion events. When a level is completed, it causes that event to be logged to the server. With level completion events in place I can now track the total amount of time that a player spent on any given level by summing all of the time entries from death events associated with that user on that level and adding the result to the level complete time event. Do this for every user and average the results and I can get a graph of how long each level takes to complete including restarts from death. That graph looks like this:</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgToX1A7kSgI92dBc4Git6vbQ4cmIkIWRGb5OLHWnOIZWT6wRf_P2do5mZ20YFxjQ5BRwcssxqYCfHlHBpB63-QIf8Sg1YzNB8OUwr9cA3606pf8QJFto1TIb6T9SR88oZDMqmUHKF4RKU/s1600-h/time_per_level.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgToX1A7kSgI92dBc4Git6vbQ4cmIkIWRGb5OLHWnOIZWT6wRf_P2do5mZ20YFxjQ5BRwcssxqYCfHlHBpB63-QIf8Sg1YzNB8OUwr9cA3606pf8QJFto1TIb6T9SR88oZDMqmUHKF4RKU/s400/time_per_level.png" /></a><br />
</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div>Now, Replica Island is a game on a phone. It needs to be playable in short bursts. So my levels are all designed with the idea that you can finish them in around 5 minutes. And looking at the graph, it looks like my levels are pretty much in-line with that expectation. Levels 32 and 34, which were outliers on the deaths per level chart, are again outliers here; those levels must really suck. I mean, it's the end of the game, so the levels are supposed to be pretty hard, but the average player dies 8 times on level 32 and spends a total of almost 20 minutes trying to complete it. That's probably super frustrating. That level needs work.</div><div><br />
</div><div>I can also see that there's something very wrong with level 18. People are completing it in almost no time whatsoever, even though the other graph shows that most people die 2.25 times on that level. And again, with the exception of the very long outliers, I can see that the levels in the end are sometimes too easy; ideally all the bars on this graph should rarely vary from the 5 minute mark.</div><div><br />
</div><div>Of course, these are all averages. Some players might spend a very, very long time on a particular level (and actually, with my expanded view of the data, I can see some poor souls really are getting stuck for hours). But on the other hand, I can't design levels for every level of difficulty; hitting the perfect median that is challenging for novices but not boring for pros is hard, but I think that averages are a good guideline.</div><div><br />
</div><div>So now I know which levels are probably worth investigating. A cursory look at the levels identified by the death event graph revealed an interesting pattern: a lot of people seem to be dying by falling down pits. Replica Island, like many other side-scroller games before it, features pits that you must jump across. If you fall down a bit and off the bottom of the level, it's game over. It turns out that many of the levels in which people are dying in droves are levels that feature a lot of pits, like this one:</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMYq24CuHoTOFeQRzTAluEXaJn9aj4fZQdy1CGAkmzwmskp1txnqU3x6eo2KP2bNcOD9TXNokQCna2XWKlhTYsURRY67CIscQZPUIVExuZ1VbHRMmXpYG93UTU64Vr8nr-ecckPJHeZp8/s1600-h/pits_of_death_level.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMYq24CuHoTOFeQRzTAluEXaJn9aj4fZQdy1CGAkmzwmskp1txnqU3x6eo2KP2bNcOD9TXNokQCna2XWKlhTYsURRY67CIscQZPUIVExuZ1VbHRMmXpYG93UTU64Vr8nr-ecckPJHeZp8/s640/pits_of_death_level.jpg" /></a><br />
</div><div><br />
</div><div>Every little dot on this image is a player who died, and 99% of them are in pits. So it seems like I might have a problem with pits that affects a lot of different players. Rather than make that assumption, however, I graphed it:</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCUxu23CR1ilNehUIckFkuL9AIgahuC9Y0KLGyiPtZpQ5noNUt1PLt5mRQAcef9k3CMXyhOkYXvc-jozhl7UIHQnpI9sr-KK1Nub2F6kNznop_eddrIEoq1xr166bbWLoEZOCDpENiVGg/s1600-h/death_from_pits.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCUxu23CR1ilNehUIckFkuL9AIgahuC9Y0KLGyiPtZpQ5noNUt1PLt5mRQAcef9k3CMXyhOkYXvc-jozhl7UIHQnpI9sr-KK1Nub2F6kNznop_eddrIEoq1xr166bbWLoEZOCDpENiVGg/s400/death_from_pits.png" /></a><br />
</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div>I can detect a pit death from my data because the y coordinate in my x,y field goes negative only in this case. And yep, it looks like levels with a lot of pits are deadly. </div><div><br />
</div><div>So how pervasive is this problem? I know by looking at the death event graph and the completion time graph that I've got some outlier levels to deal with, and this latest graph tells me that some of the levels are affected by some specific problem with pits. So what's the relationship between the pits and the overall level flow that I'm trying to control? Let's graph it and find out!</div><div><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCHrtwUIIxB7N2wBfCS2_vL0uCp5Ck97KNBFDrZSlWFfZBFiRkpdZKOA20w9Rf6TiJUpwBS4nBMyOkWiO9d5GNJ2enY3sjO-mfSig0tXg0wdgLyrxRdhgm99ozsYij45Unt0qHrdudvOE/s1600-h/combined_view.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCHrtwUIIxB7N2wBfCS2_vL0uCp5Ck97KNBFDrZSlWFfZBFiRkpdZKOA20w9Rf6TiJUpwBS4nBMyOkWiO9d5GNJ2enY3sjO-mfSig0tXg0wdgLyrxRdhgm99ozsYij45Unt0qHrdudvOE/s400/combined_view.png" /></a><br />
</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;">This is pretty neat, right? I can tell that in several cases, in particular level 34, the pit problem is leading to a lot of deaths which is causing the level to take forever to complete. I can also see that the pit issue isn't my only problem: problematic level 32 has almost no pits, and yet the number of deaths and the time to complete that level is still way out of range compared to the rest of the game.<br />
</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">In case you are interested, the problem with pits is actually pretty simple: my camera moves too much in the vertical direction. Classic platformers that involve a lot of jumping, like <i>Super Mario Bros.</i>, are very careful to limit the amount of vertical movement required by the camera when making jumps. You can always see the landing point before you leave the ground in the <i>Mario</i> games, which makes jumping over pits a fun challenge rather than a frustrating penalty. But in Replica Island, the protagonist <a href="http://replicaisland.blogspot.com/2009/09/why-android-flies.html">can fly</a>, which requires the camera to track in the vertical axis a lot more aggressively. That means that if you are falling you might not actually realize that there isn't going to be any ground below you until it's too late. The solution to this problem is probably to make the camera smarter about pits, and maybe to throw some visual indicator into the mix as well. We'll see--I have a couple of solutions in mind but it'll take another round of testing to verify them.<br />
</div><div class="separator" style="clear: both; text-align: left;"><br />
</div><div class="separator" style="clear: both; text-align: left;">Anyway, based on an extremely simple packet of data from a sample of several hundred players, I can pretty accurately pinpoint levels that need polish and game mechanics that are sources for error. As I push forward to a public release, my goal should be to first fix the mechanical issue (the pit problem), and then polish levels like #32 that are broken for some other reason. That's a much better task list than just some gut feeling about the quality of my levels.<br />
</div>Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com5tag:blogger.com,1999:blog-2127023973845776737.post-20487437519753196982009-12-08T19:24:00.000-08:002009-12-08T19:26:48.108-08:00Some ScreenshotsI've posted some screenshots of the Replica Island beta build over at <a href="http://replicaisland.net/">replicaisland.net</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/screenshots/beta/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://replicaisland.net/img/screenshots/beta/5.png" /></a><br />
</div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/screenshots/beta/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://replicaisland.net/img/screenshots/beta/3.png" /></a><br />
</div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: center;"><a href="http://replicaisland.net/img/screenshots/beta/6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://replicaisland.net/img/screenshots/beta/6.png" /></a><br />
</div><br />
<br />
There are several more in the set, so <a href="http://replicaisland.net/">check it out!</a> This is a beta build so things may change a bit before release, but this is what the game looks like as of right now.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com7tag:blogger.com,1999:blog-2127023973845776737.post-44046303568445580812009-11-11T07:11:00.000-08:002009-11-11T07:15:03.567-08:00It's Not the Size That CountsOne of the concerns that I occasionally run across when working with Android game developers is that Android handsets don't have enough storage space for applications. Some folks, such as John Biggs over at MobileCrunch, see this as an obstacle to "real" game development. In his <a href="http://www.mobilecrunch.com/2009/10/29/here-are-all-the-great-android-games-the-answer-is-simpler-than-we-think/" id="ohxi" target="_blank" title="article on the subject">article on the subject</a>, Biggs notes that <i>Myst</i> for iPhone takes up 727 mb, making it too large for any of the existing Android handsets. His conclusion is that the lack of space on Android devices is preventing game developers from bringing "real" games to Android.<br />
<br />
<br />
<blockquote class="webkit-indent-blockquote" style="border: none; margin: 0 0 0 40px;">"The real culprit behind the lack of Android apps isn't lack of developer adoption or a difficult SDK – it's the ludicrous 256MB limit on app storage for most current Android phones and Android 2.0 itself. The OS also does not support the installation of apps on removable storage like SD cards, further ruining chances for more effusive and expansive titles. "<br />
</blockquote><br />
<br />
Actually, Android imposes no size limit on application space; each device maker may choose what kind of storage and how much they want to put in their devices, and there is already a bit of variation across the handsets currently on the market. The current partition space is the result of manufacturer preference, not some characteristic intrinsic to the Android OS. <br />
<br />
While it's true that apps can't be installed on the SD card, and that the amount of available application space on existing Android handsets is smaller than some other devices, I think that this argument is based on the assumption that "better" games must take up more space. I'm here to tell you that there's no strong correlation between game quality, or even game length, and application size.<br />
<br />
<b>Level of Detail</b><br />
<b><span style="font-weight: normal;">If we take a look back at video games over the last thirty years, we can see that, with each new generation, games have required more space than before. Take the <i>Super Mario</i> series. <i>Super Mario Bros.</i> (1985) for the Nintendo Entertainment System shipped on cartridges that could only hold around 40k of data. <i>Super Mario World</i> (1990) for the Super Nintendo shipped on a 512k cart. <i>Super Mario 64</i> (1996) for the Nintendo 64 ran in 8 mb, and <i>Super Mario Sunshine</i> (2002) for the GameCube came on a single 1.5 gb mini disc.</span></b><br />
<br />
The reason that this increase in space is necessary is that the target resolution of the host device has increased over time. The original <i>Super Mario Bros.</i> was designed for a 256 x 240 pixel display, while <i>Super Mario Sunshine</i> was designed for 640 x 480, the standard SDTV resolution. This increase in resolution required that art assets also grow in size; extra pixel density isn't useful if you don't actually have content to fill it with. <br />
<br />
This is true whether you're talking about 2D or 3D. In a modern 3D game, resolution is a function of texture detail and model complexity. Texture data includes normal maps, height maps, bump maps, reflection maps, and all kinds of other image data that has increased in resolution (or only recently become viable due to increases in storage space) since previous generations. If you are a gamer with a good memory, you might remember Hideo Kojima talking about how Solid Snake's mustache in <i>Metal Gear Solid 4</i> <a href="http://www.1up.com/do/minisite?cId=3146394" target="_blank">contains the same number of polygons</a> as an entire enemy character in <i>Metal Gear Solid 3</i>; presumably, the increase in complexity was necessitated by the move to HD televisions. <br />
<br />
So there <i>is</i> a correlation between application size and target screen size. Just not one between application size and game quality. <br />
<br />
<span style="font-weight: bold;">Running the Numbers</span><br />
To drive this point home, I took a look at the top games on three different digital distribution networks: Microsoft's Xbox Live Arcade, Apple's iTunes Store, and Android Market. Xbox Live Arcade games are written to be run on HD television sets with 1080p resolution. That's 1920 x 1080, or 2,073,600 pixels. iPhone apps and most existing Android apps are written to run at HVGA resolution, which is 320 x 480, or 153,600 individual pixels. So the XBLA games are running at a resolution roughly 13 times greater than that of the iPhone or Android devices like the T-Mobile G1 or myTouch 3G. So, if we assume that all things are equal and that cost of detail increases at a linear rate (neither of which are very true, but bear with me here), we can reasonably expect about a 13x difference in the physical size of the same content on an HD device compared to HVGA device.<br />
<br />
Below is a graph comparing the average application size for these three networks. To calculate this average I used the sizes of the top 20 games (as of Nov. 3, 2009) from each network.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO1eaCZVdikLBllD1SmJPHL64jGhva9mNFkovvJ7WG5mblghvYntEmVCFl0soxwQk_5Y9tZpwY4qGm4qxXd9WTtgNLnEsk-cMw5JdMfMC5cnMsCC-zNh9AXwfuYgk9G8wOXTMjt61uYsE/s1600-h/game_relative_sizes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO1eaCZVdikLBllD1SmJPHL64jGhva9mNFkovvJ7WG5mblghvYntEmVCFl0soxwQk_5Y9tZpwY4qGm4qxXd9WTtgNLnEsk-cMw5JdMfMC5cnMsCC-zNh9AXwfuYgk9G8wOXTMjt61uYsE/s640/game_relative_sizes.png" /></a><br />
</div><br />
<br />
The average top Xbox Live Arcade game is 277 mb. Interestingly, paid games on both iTunes Store and Android Market tend to be a little larger than free games, perhaps because the cost of generating art assets requires the developer to sell the app rather than give it away. In any case, the average iPhone game is 21.5 mb, which is about 12.8x times smaller than the XBLA average--pretty dead on with our resolution-based expectation. The average Android game is even smaller--around 1 mb--though as on the iPhone, paid games tend to be larger than free games.<br />
<br />
Though the Android games are smaller in aggregate, there certainly are a number of titles at or above the 10 mb mark on Android Market. This is within the same range as the top 20 iPhone games; <i>Myst</i> at 727 mb is clearly an outlier (and, as an aside, it's larger than all but two of the top XBLA games). You can fit a bunch of top-tier iPhone games in a G1, and even more in a myTouch or Droid.<br />
<br />
It's interesting that the Android games are so much smaller. The most popular free apps are tiny--the average of the top 20 free games is only around 300k. This might have to do with how popular games are ranked, or the fact that paid apps have been available on Market for a much shorter amount of time than on Apple's platform. Or it might be something else; <a href="http://labyrinth.codify.se/" target="_blank">Labyrinth Lite</a>, which is available on both platforms, is less than half the size on Android compared to its iPhone version. At any rate, even if the Android games were larger, the average user would still be able to fit a bunch of them on their phone.<br />
<br />
<b>So, Size Doesn't Matter?</b><br />
I don't mean to suggest that size has absolutely nothing to do with game content. There are some types of games, like <i>Myst</i>, that are unusually data-heavy. But content quality isn't a function of size; <i>Super Mario World's</i> tiny 512k footprint is evidence of that. And Android <i>should</i> support installing apps to the SD card (though I suspect that some game developers would be uncomfortable with that option). In the mean time, apps are free to stream data over the network or cache resources to the SD card (as the <a href="http://grammerjack.blogspot.com/2009/10/gles-quake-port-of-quake-to-android.html" target="_blank">Android Quake port</a> does), though admittedly this isn't as elegant a solution as true SD card support. Still, it's an easy solution for big games like <i>Myst.</i><br />
<br />
While we're on the topic, 727 mb for <i>Myst</i> is a fairly surprising number. The original version of <i>Myst</i> targeted 640 x 480 displays at 256 colors and filled a single CD-ROM; even assuming that the iPhone version is using 24-bit color versions of the original graphics, the reduced screen resolution of the iPhone combined with advances in CPU speed and image compression technology should make it fairly easy to cram the game into less space than it originally required. After all, they fit <i>Resident Evil 2</i>, a 2-disc CD-ROM game for the Sony Playstation, <a href="http://www.gamasutra.com/view/feature/3148/postmortem_angel_studios_.php" target="_blank">into a single 64 mb N64 cartridge</a>!<br />
<br />
Plus, a game like <i>Myst</i> should be trivial to stream over the network; the spatial layout of each room is known and so room images close to the player's current location can be streamed in the background. Even if the developer elected not to cache those images to the SD card, 16 mb of application heap is a lot of room for <a href="http://en.wikipedia.org/wiki/Myst#Development" target="_blank">80k images</a>, so it should be easy to stream far enough ahead into runtime memory that the player never experiences a loading pause.<br />
<br />
The point I am trying to make is not that the <i>Myst</i> iPhone developers did something wrong, just that there are many ways to implement this sort of game and not all of them come with huge space requirements. I suspect that <i>Myst</i> on iPhone is as large as it is because that was the simplest way to port the game, not because the game itself cannot be made any smaller. And there's value in that--giving developers as many options as possible to make applications is the number #1 reason that Android should support installation of apps to the SD card. But the claim that this sort of game is impossible on Android platforms is, I think, wrong.<br />
<br />
<b>Wrap Up</b><br />
Game quality has almost nothing to do with application size. For most games, physical size is most directly linked to the size of the screen and the resolution that the host device is running at. By looking at a selection of popular titles, we can see that on HVGA devices (such as the iPhone, or myTouch 3G) there is a fairly small range within which most games of significant scope reside. Looking at the top iPhone games, that range is well within the capabilities of existing Android devices. And while there are exceptions, they are few and far between; the existence of outliers, especially those that are large because of implementation decisions rather than necessity, is not sufficient to damn an entire platform. <br />
<br />
Should Android support installing applications to the SD card? Yeah, sure, that'd be great. Is the lack of that functionality blocking games from working on existing Android devices? Not in the slightest. <br />
<br />
And for the record, Replica Island is about 4.8 mb, which is a little heavier than I'd like, but not heavy enough that I'm going to spend time doing something about it. It has over 40 levels.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com7tag:blogger.com,1999:blog-2127023973845776737.post-84011207045353058822009-11-08T21:28:00.000-08:002009-11-08T21:28:04.220-08:00Tuning Play With Automatic and Anonymous Player MetricsAs the developer of an "indy" game (meaning I don't have the backing of a big publisher, though Google is nice enough to give me 1 day per week of work time to spend on this), I have a problem that a lot of us indy developers share: play testing is hard. Play testing is the tried-and-true method of determining the quality of a given game design by watching users who are not experts play. When I worked in the game industry we'd bring kids in to the office, plunk them down in front of a TV that had a game machine and VCR hooked up, hand them a controller, and record their entire play session. This is hugely valuable information because problematic areas in the design are almost immediately identifiable. Nothing settles a design question faster than watching a real user fail to understand what you are trying to get them to do over and over again.<br />
<br />
But as an indy developer, I don't have an office, I don't have a pool of play testers to draw from, and I can't easily connect my Android device to a VCR. My experience has been that play testing can vastly improve the quality of a game, so I really don't want to skimp on it. And the common refrain repeated by developers of universally loved, amazingly high-quality games is that they tested and iterated and tested and iterated and tested and iterated again until their game felt right.<br />
<br />
For example, Naughty Dog, the rockstar developers behind the <i>Crash Bandicoot</i> series, the <i>Jak and Daxter</i> series, and most recently, <i>Drake's Fortune</i>, has been talking about the value of play testing for years. Back in the Playstation 1 days they pioneered methods for recording metrics about player style so that their levels could be tuned to increase in difficulty gradually. On top of that, their Crash Bandicoot series is a poster child for Dynamic Difficulty Adjustment (DDA), an automated system for dialing a game's difficulty up or down as the player plays to keep the level of challenge constant (full disclosure: I worked on a couple of Crash games for GBA at Vicarious Visions). Their goal is for the user to never see the game over screen; if the player dies repeatedly in the same spot, that indicates a failure of the design. With DDA and play testing, Naughty Dog has been extremely successful in smoothing out these "difficulty cliffs" and, as a result, their games get a lot of love.<br />
<br />
So with these lessons in mind I released a build of Replica Island to Google employees about two weeks ago. Since I can't stand over every player's shoulder and watch them play, I asked for people to tell me about bugs that they encountered and provided a very short survey form at the end. Though a lot of people downloaded the game and tried it out, very few bothered to fill out the survey and even fewer wrote me directly about their thoughts. I learned a few things about my game but I did not collect not nearly the amount of information I need to tune my level design and game play decisions for a mass audience.<br />
<br />
Fortunately, I also built some automated metric recording functionality into the game. Taking a page from Naughty Dog's book, I decided to focus on death locations: every time a player dies, I want to know about it. Looking at the aggregate results over a number of players should reveal hotspot areas where lots of people died; these areas are the first candidates for polish and tuning. So I added a very simple function to ping a web server every time the player dies and restarts the level. The ping carries no information about the user himself: just which level he is playing, where he died, and a session identifier so I can see if people died multiple times in the same place. After I released the game to Google employees the data began to roll in, and after a while I started to have enough information to be statistically relevant.<br />
<br />
Once I had the data, I needed to decide how to use it. This is still a work in progress, but here's how I'm using it so far. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg7J-ZUFTCnf5yo1UdyxWZguXPV92v9O7Mr0mflg4O1W5pFRbyrG1rKEoqlA45OziV8Xt1E0vNCWbau91VaUkqwM6CdvTR3f5EhfiB5-lpXKcPhbu6lQbkv1u-t-OBluy7S65jMhPqi6A/s1600-h/player_metrics.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg7J-ZUFTCnf5yo1UdyxWZguXPV92v9O7Mr0mflg4O1W5pFRbyrG1rKEoqlA45OziV8Xt1E0vNCWbau91VaUkqwM6CdvTR3f5EhfiB5-lpXKcPhbu6lQbkv1u-t-OBluy7S65jMhPqi6A/s640/player_metrics.png" /></a><br />
</div><br />
This is an image of an early level in Replica Island. The little colored dots represent death events from various users (each user is a unique color). As you can see, there's a couple of obvious spots where a lot of users died. (Actually, this is only part of the data I have received so far--the quality of the results increases as more players respond).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGMgy7l4u01_dzuO_FsLokGzS_xh2ZyOKhJlHcDMWIDvYQFnUEXR7G7mph-nOUMUv6Q-UV9OwqA0urxZ3MgyKoXZE6AoMRRajs2aDVqKH8TnrkHByvnIcByJx6FBA_MqKhI-8w6e9uj1w/s1600-h/player_metrics_zoom.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGMgy7l4u01_dzuO_FsLokGzS_xh2ZyOKhJlHcDMWIDvYQFnUEXR7G7mph-nOUMUv6Q-UV9OwqA0urxZ3MgyKoXZE6AoMRRajs2aDVqKH8TnrkHByvnIcByJx6FBA_MqKhI-8w6e9uj1w/s640/player_metrics_zoom.png" /></a><br />
</div><br />
This spot is where the player encounters an enemy robot, one of the first in the game. This is a significant example because it takes place so early in the level; deaths in this location mean that the user got hit here at least three times, as there's nothing earlier in this level that can reduce the player's life. Also, now that the data has singled this location out, I can see why it's problematic; the low ceiling makes the Android's butt-stomp move (which is the only attack he has in the game) hard to pull off for novice players, and the slope likely causes people who are not used to the physics to roll down right into the enemy. This is a failure of the level design: I really don't want first-time players to die after playing a few seconds into the first or second level. Fortunately, the solution is simple: in this case I can just remove the enemy from that location.<br />
<br />
Though this type of metric reporting and visualization is pretty rudimentary, it has already proven extremely valuable in identifying problematic level segments. It's also confirmed one of the conclusions that I drew from the survey feedback: nobody reads on-screen text, so in order to teach the player how to play I need to use big, visual indicators and design levels to require specific moves. <br />
<br />
I am so happy with these results that I'm thinking of expanding the level and frequency of the events that I report; it would be great to know where players take the most damage, or which pits they fall down the most, or how long player spent traversing specific level segments. Now that the framework for event reporting and visualization is in place, it is pretty easy to add new types of events to it.<br />
<br />
In fact, I could even release the game with this system turned on. It's probably a good idea to let the user opt-out if they want (even though I don't transmit anything personal), and I'd rather do my test and polish before the game is released, but leaving these metric reporting systems in place for the full release is very tempting. It would give me real, concrete data to tune and improve Replica Island even after it has been released.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com4tag:blogger.com,1999:blog-2127023973845776737.post-21734879828653107722009-11-08T19:51:00.000-08:002009-11-08T19:51:54.744-08:00Android Game Development ResourcesI get a lot of questions about when the Replica Island source will be released. Believe me, I'd like to see it finished more than anybody else in the world. If I had a date I could guarantee I'd give it, but I don't. It's close, though; I'm in final bug and optimization phase now. The laws of physics are simply not in my favor.<br />
<br />
But in the mean time, there's a bunch of other code and resources that you can check out if you are looking for a way to get started on Android game development. Here's a few that I found:<br />
<br />
- The Rokon game engine (<a href="http://stickycoding.com/">blog</a> and <a href="http://code.google.com/p/rokon/">source</a>).<br />
- The <a href="http://code.google.com/p/cloak/">Cloak open source game framework</a>. <br />
- An <a href="http://grammerjack.blogspot.com/2009/10/gles-quake-port-of-quake-to-android.html">Android Quake port</a>.<br />
- Source to <a href="http://code.google.com/p/alienbloodbath/">Alien Blood Bath</a>, a side-scrolling action game. <br />
- AGE engine <a href="http://code.google.com/p/age/">source</a>. <br />
- <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/SpriteMethodTest">SpriteMethodTest source</a>, a sample which shows how to render sprites using Canvas and various OpenGL ES methods and profiles them.<br />
<br />
With the exception of SpriteMethodTest (which I wrote), I don't have any connection to any of these projects, so your milage with them may vary. However, they all look promising and I think that there's plenty of information contained within them that is useful to the budding Android game developer.<br />
<br />
If you have other links or resources that you've found useful, please post them here!Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com29tag:blogger.com,1999:blog-2127023973845776737.post-48802067723709372342009-10-13T06:10:00.000-07:002009-10-13T06:10:06.567-07:00The Main Loop as a Graph TraversalIn an <a href="2009/09/aggregate-objects-via-components.html">earlier post</a>, I talked about treating game objects as a list of game components, with each component implementing a specific feature of the object. That way, I can call GameObject.update() and get a different result for each game object instance depending on which components have been inserted.<br />
<br />
In fact, in Replica Island I apply that same idea to the entire game loop. The whole simulation step can be completed with a single call to MainLoop.update(). Though it's not a game object, the MainLoop object is similar to GameObject it that it simply contains a list of things to update. Those things might be GameObjects, or they might be some other kind of object that can be updated--the main loop doesn't have to know. Things that are inserted into that list will be polled every frame; that's all the MainLoop object does.<br />
<br />
For example, there's a bit of code that needs to work out the current state of all the hardware inputs--touch screen, trackball, keyboard, d-pad, etc. That code needs to run every frame, but it's not a game object. As long as it has the right base class and implements update(), it just needs to be added to the MainLoop's list to be run every frame. Then there's the renderer; once all the game objects are finished this object needs to send the draw commands for the frame to the rendering thread (also described in a <a href="2009/10/rendering-with-two-threads.html">previous post</a>), so it too can be inserted at the end of the MainLoop's list.<br />
<br />
And in fact, systems that need to update every frame can themselves manage other systems that need to update every frame.<br />
<br />
For example, game objects can be added directly to the MainLoop's list, but with one or two exceptions I never do that. Instead, a special system, called the GameObjectManager, is run by MainLoop every frame, and that object contains a list of GameObjects which it runs in its update() function. The reason that I have inserted this GameObjectManager class is that not all game objects should be updated every frame. Only a subset--the set that are within some radius from the camera--should be run. The rest should remain inactive to save CPU cycles. So the GameObjectManager, when updated by MainLoop, selects a subset of the GameObjects that it controls and updates them based on the position of the camera.<br />
<br />
If you hadn't guessed already, the structure I am describing here is a tree. The MainLoop is the root node of this tree, and its children are things like the input system, render system, and GameObjectManager--bits of code that need to run every frame. The GameObjectManager has all of the game objects as its children, but it stipulates that not all of its children will be visited every traversal. The game objects themselves contain game components as their children; the game components are the leaf nodes of this kind of tree. So, to run the simulation step for the current frame, I just traverse the tree.<br />
<br />
Actually, to be precise the structure that I am describing is a graph. The reason it must be a graph rather than a tree is that the structure allows for instancing of subgraphs and of cross-level traversals. For example, certain types of GameComponents that don't need to track state from frame to frame can be shared across multiple game objects; in that case, only one instance of the component exists but it is inserted into a number of different game object instances. In graph terms, shared GameComponents are nodes with multiple parents. However, for the general case the structure behaves like a tree, and so it's pretty safe to think about it that way.<br />
<br />
I like using a graph to describe all the work that must be done in a frame because it's an extremely flexible way to set up a main loop. The MainLoop object hasn't changed since I originally wrote it; though the number of objects that it contains has increased, the management code itself has remained the same. For the next game, I can rip out the individual systems that I don't need any longer and insert new ones without altering any of the main program architecture.<br />
<br />
This type of graph structure can also give you precise control over how your simulation step is run. Say you want to pause the game, but you need key systems (such as the renderer) to continue operating so that you can run the pause UI graphics. With a tree or graph system, you can insert "pausable" nodes into the tree and append to them children that should stop when the game is paused. At runtime these nodes will simply not traverse their children if the game is paused. This kind of control is hard to thread into a game that is already up and running using traditional hard-coded methods; it usually results in a lot of switches on the g_paused variable littered throughout the code base. With a graph, none of the actual simulation code needs to change--only the graph structure is modified to accommodate pausing.<br />
<br />
Another advantage is that it's pretty easy to drive this sort of system with data. Though I haven't done this in Replica Island yet, on previous games I've worked with systems in which the entire runtime for the game is loaded from a file in the form of a main loop graph; you can see how such a structure would be pretty easy to describe in XML, and you could even use Java's reflective properties to automatically instantiate the various systems that live in the tree. Once the graph is described in data, you can change it easily from game to game, or even from level to level if necessary, all with general-purpose infrastructure code. I've not done that with Replica Island yet, but I will eventually--probably after the game ships. <br />
<br />
Game graphs are not specific to Android, but I use them a lot and I find them a pretty powerful (and generally underrated) pattern for managing real-time update loops. Like the GameComponent system, they leave the door open to future revision by separating data structures from code. This kind of system is also pretty simple to write (my entire graph is based on two core classes, a node and a group node). Of course, for small projects they are probably overkill--it is likely faster and less error prone to just write a traditional main loop and update the code every time you need to change something. But for medium or large projects, or projects based on a codebase that is intended to be reusable across many different titles, game graphs are a pretty neat way to structure your frame.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com14tag:blogger.com,1999:blog-2127023973845776737.post-12919664449640130382009-10-02T06:59:00.000-07:002009-10-02T07:00:01.788-07:00Rendering With Two ThreadsThe Replica Island renderer is based heavily on the GLSurfaceView class that ships with the Android SDK. I've made a couple of modifications but the code is pretty similar to the regular version: a derivation of GLSurfaceView.Renderer that draws the frame gets called every frame, followed by a call to eglSwapBuffers() to actually display the rendered frame. <br />
<br />
GLSurfaceView provides a way to run user code in the same thread as the renderer. This makes writing games pretty easy; you can just implement a Runnable, implement a Renderer, stick them both into a GLSurfaceView and get stuff moving around on the screen. Indeed, it's more than sufficient for many applications; my <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/SpriteMethodTest">SpriteMethodTest</a> demo works this way just fine.<br />
<br />
But for Replica Island I took a different approach. The problem with the single GLSurfaceView thread is that eglSwapBuffers() must block on the hardware until the previous frame finishes drawing. That means that even if you have nothing to draw, a call to eglSwapBuffers() takes 16.67ms to complete. (And of course, if you have a lot to draw, it could take a lot longer).<br />
<br />
Now, just in case you are not used to thinking in terms of milliseconds, here's a quick primer. To achieve the magical "60 frames per second" that many games strive for, you need to have a new frame displayed to the user every 16.67 ms. If you go for 30 fps, you have ~32 ms to complete a frame. All your game code, plus all your OpenGL code, plus the actual time it takes to draw the frame must fit within 16.67 ms to achieve 60fps. <br />
<br />
In Replica Island, the game code is fairly heavy-weight. I have all that collision to run, plus updates of all the active entities on the screen, plus sound playback and all that jazz. Turns out that it's usually more work to calculate a single simulation step than it is to actually draw the frame. Since this code takes time to execute, the 16 ms block that eglSwapBuffers() incurs makes it really hard to hit 60 fps. What I really want to be able to do is run game code while eglSwapBuffers() is blocking; that way I can pipeline the game updates while the hardware is busy drawing the frame.<br />
<br />
So I split the game code off into a separate thread. This makes three threads, by the way: the main UI thread that all Activities have by default, the GLSurfaceView render thread, and this new game thread (actually, there are a few more that are generated by the system for things like orientation sensor updates, but they don't affect the equation much). Now my game code and my renderer can run asynchronously, and I win back some of that time spent in eglSwapBuffers().<br />
<br />
Now comes the tricky part. I have two threads running in parallel that need to sync up once a frame so that the game thread can tell the render thread what to do. There's a lot of ways to go about synchronizing these two threads, but I went with a double buffer solution. The game thread fills up a buffer of commands to draw the next frame, and when it is ready it waits for the render thread to begin the next frame. At that point, the buffer is passed to to the render, which can then go off and draw the next frame asynchronously. The buffer that was used to draw the last frame is passed back to the game thread, which fills it up again the next frame. So drawing is the process of swapping these two buffers back and forth during a (hopefully short) choke point at which both threads stop and communicate.<br />
<br />
This solution was attractive to me because it was simple, and so far it seems to be plenty fast. However, another solution might be to have a queue that is shared by both threads, with the game thread pushing commands in one end and the renderer executing commands out of the other. In theory such a solution wouldn't need both threads to ever perfectly align--blocking would only occur when one thread or the other was starved. But I haven't done this yet because it is going to be significantly more complex than the double buffer. <br />
<br />
My render commands are objects that are allocated out of pools that the game thread owns, and must be returned to those pools when they have been drawn. In the double buffer system, the queue that is returned from the render thread contains commands that can be safely returned to their pools, but in the shared queue system there's no obvious way for the game thread to know how much has been drawn. I suppose there could be two shared queues, one in each direction, but that would still be a lot more complicated than what I have now. Right now almost no code outside of the buffer swap system knows about other threads; the pool objects and the objects they contain are not thread safe and, as it stands, don't need to be. <br />
<br />
Is my solution the best for Android apps? I don't know. It seems to work pretty well and it is uncomplicated, which are two points in its favor. Still, I'd like to give this shared queue idea a shot at some point; my gut tells me that it will be slightly faster than the double buffer (less blocking in the average case) but a lot more complex, which might make it not worth the effort. Programmer guts are, however, extremely unreliable, so I will probably give this method a shot after Replica Island ships.Chrishttp://www.blogger.com/profile/16079744366139636224noreply@blogger.com19