Thursday, October 18, 2012

What happens in the final stage of development?

We're in the process of getting Trick Shot Bowling into the Windows 8 release.  Here are the notes of what we've done in the past month or so to get ready.

Done
* Fix/check pin sound modulation
* Fix gray text
* Fix minimap arrows
* Make interrupt pause removal not add to the gui stack
* Fix touch input for throwing the ball
* Fix ad spacing issues with new admob
* (No repro) Fix sound delay
* Icons
* Sidebar pause menu
* High/low graphics settings with lower res reflection
* Prevent gutterball sound in the ball pit
* Tweak gutterball sound zone, possible to hit without leaving the lane.
* Prevent gutterball sound from continuing to play if you skip
* Portrait
* Make pins heavier
* Make a 4 player game, exit the app, restart the app, start a new game with 4 players without clicking any add/remove players buttons.  Scorecard only has a player name for player 1.
*Play halfway through a game. Going back to the main menu and starting a new game does not reset the scorecard.
*in trick shot mode, pins sometimes do not disappear from the alley (may be related to skip button in some cases)
*in trick shot mode (at least), pins sometimes are not fully stabilized at the beginning of a ball (may be related to skip button, less certain in this case) (may or may not happen only between balls in the same frame)
* Stop the gutterball sound when the ball hits the ballpit
* Have two players with different balls.  Bowl a trick shot game with second ball.  Exit. Start a new non-trick shot game.  Player 1 uses player 2’s ball.
* Throw a ball down the lane, wait for it to hit the ball pit and immediately bring up the pause menu, go to ball select, wait for the skip button to show up, and then go back.  You will get stuck on the skip screen.
* In the store, select a ball (other than the 8lb and 44lb balls) and resume the game. Go back to the store and select the ball that has the same weight as the previously selected ball. Note that the rotating display ball model has not changed. This bug is probably related to the fact that the two balls share the same ball model. - Entity properties not properly being applied when a duplicate mat is in the scene.
*ball changes color visibly in the back of the alley when the player for the next ball is using a different ball color
* Make a low graphics option that doesn’t draw at full screen
* Ball textures
* Second page of balls
* Make ball properties do stuff (spin, speed)
* Modify pin icon to include a dollar sign
* Make sensitivity slider
* User was having trouble with the input being too sensitive to use. The arrow may be resetting too quickly, making spin too hard for new players.
* “Press any key to skip” -- should say “Press a key or touch the screen to skip.”
* Make trick shot alley screenshot be of trick shot alley
* Make starting pins be 0
* Remove framerate counter
* Link to tutorial from pause.
* “Draw an arc” in tutorial -- fix too-literal wording
* DDS the ball textures and ball icons
* Remove IAP page from win8 menus
* It appears to be possible to make the guide arrows disappear in the tutorial. Repro steps unknown.
* Implement openURL
* Text background during tutorial has a little white line at the top
* Ball pricing
* Final score gui was only showing first 8 frames, and no player name.
* It is also possible for a player to be using one ball but show a different ball’s icon. I am not sure what the repro steps are yet. (might be related to per-frame per-ent stuff in the shader)
* Store screenshots
* Promo images

Saturday, April 28, 2012

Porting to the Blackberry Playbook


A few months ago RIM decided to add native C++ support to the Playbook.  Our ears perked up a little.  Then they gave out a ton of Playbooks to developers at GDC and we managed to snag a couple.  We're always working on a dozen things at once so we didn't do anything with them.  Then we ran into some RIM folks at MassDiGI, who pointed out that there aren't any 3d snowboarding games out!  Time to port!!!

The Playbook SDK is a custom install of Eclipse, which we're pretty familiar with from Android development.  It really didn't take long to get a sample Hello World app up and running.  One point of confusion I ran into is there are template C apps, but the only option for C++ is an empty app.  I tried modifying one of the C apps to use C++ and never got anywhere.  I ended up starting over with an empty C++ app and adding a basic empty class to get it to compile.  It took me the better portion of a day to figure out how to get it to recognize C++ classes without compile errors, which 5 minutes with someone who knows what he's doing could have prevented.

Next up was getting all our platform-independent code to compile.  This consists of right clicking the navigator window, import files from file system, and clicking the "create links in workspace" checkbox under advanced.  This imports the files as symbolic links, so no need to duplicate!  Eclipse likes to have everything live under the project directory so I was a little worried about that part.

Then I had to set the include directories.  It took me a long time to find the right place, though it sounds fairly obvious.  Right click the project and go to properties.  Then under "C++ Build" click "Settings", "QCC Compiler", and "Preprocessor".  If I added directories from the workspace it didn't follow the symbolic links, so instead I had to add from file system.  When I did this, Katie couldn't compile because we were including files in "/users/Yrgol/...".  The fix for this is to include relative to the "${PWD}" directory.  After that, we had our engine compiling!  Time to add the platform layer.

The platform layer was actually the simplest one that I've seen.  Our entire Blackberry-specific codebase is 1500 lines long.  We had to add libjpeg and libpng texture loaders, and alut sound loaders.  We used sample code to initialize our OpenGL context.  The main loop is very simple and reminds me of old Windows programming, where we spin in a for loop and ask for system events every frame.  This is way easier than plugging into the event model that iOS and Android use.

It seems like the Playbook NDK is a little more sensitive compared to other platforms, which is good and bad.  Porting exposed several null pointer accesses that somehow weren't causing crashes elsewhere.  It also exposed a leak in how we managed textures!  I won't be neglecting to call glDeleteTextures anymore.

Assets are added to the project by creating a directory under your project directory on the file system, and dropping your files in.  There's an extra step of letting the packager know which files you want to include by editing the "bar-descriptor.xml" file.  Big Mountain Snowboarding has about 400 data files, and I entered them by hand before finding the correct way to do this.  Double click the bar-descriptor.xml file, then click the assets tab.  When I do this nothing shows up, but if I resize the window magic appears.  You then have a GUI with an easy way to add all your data files.

Lots of debugging later and it's time to submit, but not so fast!  First you have to get your publishing account approved, which seems to be a manual process.  If I had known this I would have applied at the start of the port instead of the end.  First you have to submit a request, wait 48 hours, then submit some documentation, and wait another 48 hours.  I don't think their account approval team works weekends.

The submission process itself is fairly pain-free.  The only real annoyance was I had to create a PayPal account in order to sell apps.  I'm a bit wary due to various stories of PayPal deciding to just seize other developers money in the past, but no choice.  I'll try not to leave too much in the PayPal account at any time if we actually make any sales.

You have to create a large promo graphic, and upload up to 50(!) screenshots at no larger than 640x640.  I'm not sure why the 640 limit exists considering the playbook is 1024x600, but it shouldn't be that big a deal.  It would be nice if they also included an option to include a video like Google Market.  I can't say anything about being on the app store yet or if anyone actually buys the apps considering only one of our apps has been out for less than a day.

Sunday, March 11, 2012

Lights-out mode in Android

As I noted in the last post, Google and players like it when you use the "lights out" option to hide the status bar during games. I spent a couple hours today figuring out how to do that.

1) Wrap all of this code in a check to see if we are on Android 3 or higher so we can still run on 2.3.

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {

2) Find the right view. We create a GLView in our startup code, so this part is easy for us. Otherwise you'd want to add an android id to the top view in the layout file, and then use findViewById to find it.

(in layout definition) android:id="@+id/RootView"
(in startup code) View root=findViewById(R.id.RootView);

3) Toggle the visibility flag to SYSTEM_UI_FLAG_LOW_PROFILE

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

4) Set up a visibility listener to re-hide the menu bar after a delay when it becomes visible again. Otherwise when you touch the bar it will stay visible forever. We add the delay in because things go wonky if you rehide in the unhide notification.

view.setOnSystemUiVisibilityChangeListener(

new View.OnSystemUiVisibilityChangeListener()

{

public void onSystemUiVisibilityChange(int visibility)

{

if (visibility == 0)

{

Runnable rehideRunnable = new Runnable()

{

public void run() {

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

}

};

Handler rehideHandler = new Handler();

rehideHandler.postDelayed(rehideRunnable, 2000);

}

}

});

Wednesday, March 7, 2012

Tips every Android developer should know, or things we're doing wrong and need to fix in our Android builds.

A couple Google engineers, Dan Galpin and Ian Lewis, gave a talk at GDC this morning on things people do that prevent their apps from getting featured. Here are my notes on what they said.


1) Don't run in compatibility mode. Google hates the menu button on newer Android devices, so set the target SDK version to the newest (15).

2) Games that use the lights out menu option are cool. This is View.Status_Bar_Hidden.

3) Don't override the basic button behavior like volume or home or power. The back button is fair game though. Back should be treated as the escape key and not a quick exit button because it is easy to accidentally press it on honeycomb and ice cream sandwich.

4) Don't use a "do you want to quit" button when hitting back from the main menu. Just exit.

5) Don't play music on the lock screen when coming back from sleep. This one has driven us nuts trying to figure out. You have to overload onWindowFocusChanged as well as the sleep functions, and they can be called in any order.

6) Gracefully cleanup your OGL contexts.

7) For in-app purchases, don't assume your app will be open when the confirmation comes through.

8) Always have a tablet promo graphic. They scale this down to use it for feature spots on phones. This is the big banner that shows up when viewing the game on the web or on a tablet.

9) Localizing the market text is recommended, with the languages EFIGS-CJK.

Tuesday, February 28, 2012

Black bars on the Kindle Fire

The Kindle Fire is a little different from all other Android devices. 1.30 Big Mountain Snowboarding was just released on the Amazon app store so we could support those users. It has a bug where there is a black bar at the top of the screen and all the button presses are off unless the game is launched from landscape with the power cord facing right.

This is because Kindle Fire is the only device I've heard of which calls onSurfaceChanged twice.

public void onSurfaceChanged(GL10 gl, int w, int h) {

if (!mWasInitialized){

// game init

mWasInitialized = true;

}

else {

// resize the game window

}

}

1.30.1 has been uploaded and fixes this issue. I would update the game description if Amazon allowed developers to write their own promo text. Instead the best I can do is add a discussion at the bottom of the page with the workaround and wait for patch approval.

Monday, November 14, 2011

Apple and non-public APIs

We recently got a rejection from Apple for using a non-public API. Google was not a big help on this one, so here's the story of how we tracked it down.

We found that your app uses one or more non-public APIs, which is not in compliance with the App Store Review Guidelines. The use of non-public APIs is not permissible because it can lead to a poor user experience should these APIs change.

We found the following non-public API/s in your app:

appName
setAppName:
Step 1: Find in files

There's a dropdown when typing into the search bar in XCode that lets you search in frameworks. Unfortunately we got no hits with this search, so that tells us the private calls are not in our code.

Step 2: Command line tools
From the rejection mail:

Additionally, one or more of the above-mentioned APIs may reside in a static library included with your application. If you do not have access to the library's source, you may be able to search the compiled binary using "strings" or "otool" command line tools. The "strings" tool can output a list of the methods that the library calls and "otool -ov" will output the Objective-C class structures and their defined methods. These techniques can help you narrow down where the problematic code resides.

otool and strings are tools that seem to be on the global path, so you can type them into the command line from any directory. The trick is you have to figure out what to run them on. XCode 4 tends to put things in strange directories, so here's a shortcut to finding the actual file you want.


Find your app under "Products" in XCode and right click on it to select "Show in Finder". This will open up a finder window where your .app file lives. If you run otool or strings on this file it will not work. Right click on your .app file and select "Show Package Contents". Then find your executable file and copy it to somewhere accessible.

Once in Terminal and in the right directory, the two commands to use are:
otool -ov ScribbleWormHD
strings ScribbleWormHD
These will output a ton of stuff. You likely won't be able to navigate it without using command-F to search for the strings that Apple complained about. In our case otool did not give us anything useful. It would have been easy to track down the problem if it had, because otool will tell you what called the offending function. Strings just outputs a list of every string used in your project. We managed to get a hit with that, so at least we had a way to test if any changes we made would fix the problem.

* NOTE: strings did not work for us at all in Snow Leopard. It only worked in Lion.

Step 3: Head Scratching

So the calls to setAppName were not in our code, and we had no tool telling us directly where the calls were. Sometimes debugging degenerates into informed trial and error. We only have two middleware libraries in Scribble Worm: Flurry and Admob. We started with Flurry on a hunch and removed it from our project as a test before running strings again. This time we were clean.

So we went to the Flurry site and found that they are on version 3.0 while we shipped with 2.8. One upgrade later and another run of strings and we were ready to submit.

Saturday, November 5, 2011

Design Patterns in GHEngine, Part 1

One of the major design goals in our engine is to have as small as a codebase as possible. To do this, we want to re-use as much code as possible. A Golden Hammer in programming is a tool that gets overused and applied to many problems even when it is not a good fit. We have a small set of design tools that are so generic we can use them in all sorts of places. It's a good model if you can have a unified design: avoid duplication by extracting the shared work and making it available to all users. The danger of course is we can end up with the bad definition of golden hammer if we're not careful.

The Controller

class Controller {

virtual update(void);

virtual onActivate(void);

virtual onDeactivate(void);

}

A Controller is something that updates. Our renderer, physics, and input handling are controllers. We can store these controllers in a sorted list for updating, and we can dynamically add or remove controllers on the fly. Instead of having a big update function where we say "do this, then do this, then….", we have a single function that looks like this:

void runFrame() {

for each Controller, call update();

}

Note that Controller does not take any arguments at all. Once you start adding arguments, you start making assumptions about all possible Controller subclasses, and that list can grow to the point where you are passing a lot of stuff that most controllers don't care about. If a Controller needs to access something like the current time, we pass in a time value reference in the constructor.

By having this dynamic list we can avoid iterating over objects that don't need to do anything. If an entity like a player needs an update it can add a Controller to the list. If an entity like a tree does not need to do any work, it can avoid the list. Just touching the memory of each entity in a large world can become a serious performance issue even if no work is being done.

State Machine

class StateMachine {

addTransition(trans, state);

removeTransition(trans, state);

setState(state);

}

class Transition {

virtual activate(void);

virtual deactivate(void);

}

A StateMachine is used to change behavior based on whatever influences we want. Instead of defining each state as a class, we define a state as a set of Transitions into and out of that state. The most common Transition used is a ControllerTransition, which is used to add Controllers to the update list. An example Transition is a MusicTransition, which plays a song while it is active. We can create one of these and add it to the main menu state, then whenever the main menu is shown music plays.

class MusicTransition : public Transition {

activate() { start playing music }

deactivate() { stop playing music }

}

Another use for StateMachine is players. We can use a ControllerTransition to have an input handler active while the player is in the alive state. Then when the player dies we shift him to the dead state, and the ControllerTransition removes the input handler from the update list.

Events

class Message {

identifier messageType

payload messageArguments

}

class MessageListener {

virtual void handleMessage(Message);

}

class EventManager {

addListener(MessageListener, message identifier);

removeListener(MessageListener, message identifier);

broadcastMessage(Message);

}

Events are something that happened, that something else cares about. They are an alternative to having a bunch of different managers that know about the inner workings of some set of objects. We also use them for communicating between threads and for sending information over a network, as they can be stored up and sent later. The real flexibility comes into play when we start data driving message loading, adding listeners that change state to state machines, and allowing out GUI to send loaded messages.