2013:
Razer Hangout July 15th (1:00:00)
Virtual Controller Example (41:37) |
InAppPurchases - In-App-Purchase Example
SetResolutions - Set Resolutions Example
VirtualController - Virtual Controllers Example Using OUYA-Everywhere Input
-
Download the latest MonoGame release.
-
Download the MonoGame Dependencies and unpack into the
ThirdParty\Dependencies
folder. -
Run
Protobuild.exe
in the root source folder to generate the build solutions. -
Compile
MonoGame.Framework.Android.sln
inRelease
mode. -
Copy the compiled
MonoGame libaries
into your C# project.
MonoGame.Framework\bin\Android\AnyCPU\Release\Lidgren.Network.dll
MonoGame.Framework\bin\Android\AnyCPU\Release\MonoGame.Framework.dll
MonoGame.Framework\bin\Android\AnyCPU\Release\MonoGame.Framework.Net.dll
- Learn
MonoGame
by browsing the MonoGame Samples.
The auto update process requires that you use the same keystore in your app and every update.
If you lose your keystore, it requires users to uninstall the game and redownload to get the next update.
If you keep using the same keystore after that, the auto updating will continue as intended.
In MonoGame, unload your project and edit the CSPROJ. Add the following settings to your valid keystore:
<AndroidKeyStore>True</AndroidKeyStore>
<AndroidSigningKeyStore>PathToYourKeyStoreFile\key.keystore</AndroidSigningKeyStore>
<AndroidSigningStorePass>my_password_here</AndroidSigningStorePass>
<AndroidSigningKeyAlias>Aranda</AndroidSigningKeyAlias>
<AndroidSigningKeyPass>same_password_here</AndroidSigningKeyPass>
It's important that your key signing, your package name, bundle identifiers, developer id, and the account that submits the game all matches.
The above has to true for the in-app-purchases to function, since decryption depends on using the right signing key.
MonoGame has an environmental build settings for advanced users to configure.
ouya/Resources/environment.txt
These settings aren't for every game, but the following environmental tweaks reduce the amount of garbage collection thrashing.
MONO_GC_PARAMS=soft-heap-limit=512m,nursery-size=64m,evacuation-threshold=66,major=marksweep,concurrent-sweep
Right-click
the solution and select the Restore NuGet Packages
menu item if you see an error about This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105..
MonoGame Download - http://www.monogame.net/
(old) ODK Bindings for C# - https://github.com/slygamer/ouya-csharp
Mono for Android - http://xamarin.com/monoforandroid
Installation Instructions - http://docs.xamarin.com/guides/android/getting_started/installation
MonoGame GIT for latest tools - https://github.com/mono/MonoGame
MonoGame OUYA Examples - https://github.com/ouya/ouya-sdk-examples
MonoGame Content Builder - https://github.com/mono/MonoGame/wiki/MonoGame-Content-Builder
Docs and Tutorials - http://www.monogame.net/documentation
Binding JARs - http://docs.xamarin.com/guides/android/advanced_topics/java_integration_overview/binding_a_java_library_(.jar)/
In order to use MonoGame
3.5, the min API level has to be set to 16
, target API level 21
, and compile API level 21
.
MonoGame
3.4 added proper pause/resume support which allows textures and other content to reload when the game is reopened from the launcher.
- In Visual Studio, right-click the
MonoGame
project and select properties.
A target API of 16
or below will crash on launch with the unhandled exception System.MissingMethodException: Method 'AudioManager.GetProperty' not found
.
The target API of 21
is necessary starting with ODK 2.1.0
or better for Android TV
support on Forge TV
.
Since OUYA uses API level 16
, you'll have to use a MonoGame
version prior to 3.4
to avoid runtime issues with OpenAL
.
The Virtual Controller example shows 4 images of the OUYA Controller which moves axises and highlights buttons when the physical controller is manipulated.
The In-App-Purchase example uses the ODK to access gamer info, purchasing, and receipts.
OUYA-Everywhere Input
remaps controllers as if they are an OuyaController
.
The screensaver will be disabled in the Activity OnCreate
event by adding the OuyaInputView
which also provides OUYA-Everywhere Input
.
#if MONOGAME_3_4
SetContentView((View)_game.Services.GetService(typeof(View)));
#endif
// Add OUYA-Everywhere Input and disable screensaver
using (var ignore = new TV.Ouya.Sdk.OuyaInputView(this))
{
// do nothing
}
After checking input, be sure to invoke ClearButtonStates
at the end of the draw/update to clear the controller pressed/released states for the next frame.
protected override void Draw(GameTime gameTime)
{
// draw things...
base.Draw (gameTime);
OuyaInput.ClearButtonStates();
}
MonoGame
uses the same axis
constants that are used in the Java
Cortex
SDK.
OuyaController.AXIS_LS_X
OuyaController.AXIS_LS_Y
OuyaController.AXIS_RS_X
OuyaController.AXIS_RS_Y
OuyaController.AXIS_L2
OuyaController.AXIS_R2
MonoGame
uses the same button
constants that are used in the Java
Cortex
SDK.
OuyaController.BUTTON_O
OuyaController.BUTTON_U
OuyaController.BUTTON_Y
OuyaController.BUTTON_A
OuyaController.BUTTON_L1
OuyaController.BUTTON_L3
OuyaController.BUTTON_R1
OuyaController.BUTTON_R3
OuyaController.BUTTON_DPAD_DOWN
OuyaController.BUTTON_DPAD_LEFT
OuyaController.BUTTON_DPAD_RIGHT
OuyaController.BUTTON_DPAD_UP
OuyaController.BUTTON_MENU
GetAxis
returns the axis value given the playerNum
(0, 1, 2, or 3) and an axis
constant.
float GetAxis(int playerNum, int axis);
float lx = OuyaInput.GetAxis(0, OuyaController.AXIS_LS_X);
float ly = OuyaInput.GetAxis(0, OuyaController.AXIS_LS_Y);
float rx = OuyaInput.GetAxis(0, OuyaController.AXIS_RS_X);
float ry = OuyaInput.GetAxis(0, OuyaController.AXIS_RS_Y);
float l2 = OuyaInput.GetAxis(0, OuyaController.AXIS_L2);
float r2 = OuyaInput.GetAxis(0, OuyaController.AXIS_R2);
GetButton
returns the button state given the playerNum
(0, 1, 2, or 3) and a button
constant.
GetButton
returns true if the button
is currently pressed
or returns false if the button
is currently released
.
bool GetButton(int playerNum, int button)
if (OuyaInput.GetButton(0, OuyaController.BUTTON_O))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_U))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_Y))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_A))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_L1))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_L3))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_R1))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_R3))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_DPAD_DOWN))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_DPAD_LEFT))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_DPAD_RIGHT))
{
}
if (OuyaInput.GetButton(0, OuyaController.BUTTON_DPAD_UP))
{
}
GetButton
can also be used without the playerNum
parameter to find if a button was pressed on any
controller.
bool GetButton(int button)
if (OuyaInput.GetButton(OuyaController.BUTTON_O))
{
}
GetButtonDown
returns the last button state given the playerNum
(0, 1, 2, or 3) and a button
constant.
GetButtonDown
returns true if the last frame detected a pressed
event.
BUTTON_MENU
should be detected using GetButtonDown
.
bool GetButtonDown(int playerNum, int button);
if (OuyaInput.GetButtonDown(0, OuyaController.BUTTON_MENU))
{
}
GetButtonDown
can also be used without the playerNum
parameter to find if a button pressed
event happened on any
controller.
bool GetButtonDown(int button)
if (OuyaInput.GetButtonDown(OuyaController.BUTTON_O))
{
}
GetButtonUp
returns the last button state given the playerNum
(0, 1, 2, or 3) and a button
constant.
GetButtonUp
returns true if the last frame detected a released
event.
BUTTON_MENU
should be detected using GetButtonUp
.
bool GetButtonUp(int playerNum, int button);
if (OuyaInput.GetButtonDown(0, OuyaController.BUTTON_MENU))
{
}
GetButtonUp
can also be used without the playerNum
parameter to find if a button released
event happened on any
controller.
bool GetButtonUp(int button)
if (OuyaInput.GetButtonUp(OuyaController.BUTTON_O))
{
}
ButtonData
gives you button names and button textures for the OuyaController button codes on the OUYA
, MOJO
, and Xiaomi
consoles.
public OuyaController.ButtonData GetButtonData(int button)
{
return OuyaController.GetButtonData(button);
}
ButtonData
has a string
name that returns the console specific button name. I.e. on OUYA the OuyaController.BUTTON_O
name is O
versus on the Xiaomi
console as A
.
public string GetButtonName(int button)
{
OuyaController.ButtonData buttonData = OuyaController.GetButtonData(button);
if (null == buttonData) {
return string.Empty;
}
return buttonData.ButtonName;
}
ButtonData
has a BitmapDrawable
that can be converted to a Texture2D
to be drawn in a SpriteBatch
.
public Texture2D GetButtonTexture(int button)
{
OuyaController.ButtonData buttonData = OuyaController.GetButtonData(button);
if (null == buttonData)
{
return null;
}
BitmapDrawable drawable = (BitmapDrawable)buttonData.ButtonDrawable;
if (null == drawable)
{
return null;
}
Bitmap bitmap = drawable.Bitmap;
if (null == bitmap)
{
return null;
}
using (MemoryStream ms = new MemoryStream ())
{
bitmap.Compress (Bitmap.CompressFormat.Png, 100, ms);
ms.Position = 0;
return Texture2D.FromStream (GraphicsDevice, ms);
}
}
The following examples are within the context of a class that extends Microsoft.Xna.Framework.AndroidGameActivity
.
Your game Activity
will need to pass OnActivityResult
events to the OuyaFacade
.
protected override void OnActivityResult (int requestCode, Result resultCode, Intent data)
{
if (null != ouyaFacade) {
ouyaFacade.ProcessActivityResult (requestCode, (int)resultCode, data);
}
}
By extending RequestGamerInfoListener
, the OnSuccess
and OnFailure
events can be overloaded and will be invoked when the asynchronous RequestGamerInfo
method completes.
public class CustomRequestGamerInfoListener : RequestGamerInfoListener
{
private const string TAG = "CustomRequestGamerInfoListener";
public override void OnSuccess(Java.Lang.Object jObject) {
GamerInfo gamerInfo = jObject.JavaCast<GamerInfo>();
if (null == gamerInfo) {
Log.Error (TAG, "GamerInfo is null!");
} else {
Log.Info (TAG, "OnSuccess uuid=" + gamerInfo.Uuid + " username=" + gamerInfo.Username);
}
}
public override void OnFailure(int errorCode, String errorMessage, Bundle optionalData) {
Log.Error (TAG, "OnFailure errorCode=" + errorCode + " errorMessage=" + errorMessage);
}
}
RequestGamerInfoListener requestGamerInfoListener = new CustomRequestGamerInfoListener();
// first parameter is a reference to the Activity
ouyaFacade.RequestGamerInfo(this, requestGamerInfoListener);
By extending RequestProductsListener
, the OnSuccess
and OnFailure
events can be overloaded and will be invoked when the asynchronous RequestProductList
method completes.
public class CustomRequestProductsListener : RequestProductsListener
{
private const string TAG = "CustomRequestProductsListener";
public override void OnSuccess(Java.Lang.Object jObject) {
JavaList<Product> products = jObject.JavaCast<JavaList<Product>> ();
if (null == products) {
Log.Error (TAG, "Products are null!");
} else {
Log.Info (TAG, "OnSuccess Count="+products.Count);
}
}
public override void OnFailure(int errorCode, String errorMessage, Bundle optionalData) {
Log.Error (TAG, "OnFailure errorCode=" + errorCode + " errorMessage=" + errorMessage);
}
}
RequestProductsListener requestProductsListener = new CustomRequestProductsListener();
// sample purchasables
string[] purchasables =
{
"long_sword",
"sharp_axe",
"cool_level",
"awesome_sauce",
"__DECLINED__THIS_PURCHASE",
};
List<Purchasable> products = new List<Purchasable>();
foreach (string identifier in purchasables)
{
Purchasable purchasable = new Purchasable(identifier);
products.Add(purchasable);
}
// first parameter is a reference to the Activity
ouyaFacade.RequestProductList(this, products, requestProductsListener);
By extending RequestPurchaseListener
, the OnSuccess
, OnFailure
, and OnCancel
events can be overloaded and will be invoked when the asynchronous RequestPurchase
method completes.
public class CustomRequestPurchaseListener : RequestPurchaseListener
{
private const string TAG = "CustomRequestPurchaseListener";
public override void OnSuccess(Java.Lang.Object jObject) {
PurchaseResult purchaseResult = jObject.JavaCast<PurchaseResult>();
if (null == purchaseResult) {
Log.Error (TAG, "PurchaseResult is null!");
} else {
Log.Info (TAG, "OnSuccess identiifer"+purchaseResult.ProductIdentifier);
}
}
public override void OnFailure(int errorCode, String errorMessage, Bundle optionalData) {
Log.Error (TAG, "OnFailure errorCode=" + errorCode + " errorMessage=" + errorMessage);
}
public override void OnCancel() {
Log.Error (TAG, "Purchase was cancelled");
}
}
RequestPurchaseListener requestPurchaseListener = new CustomRequestPurchaseListener();
Purchasable purchasable = new Purchasable("long_sword"); // the identifier being purchased
// first parameter is a reference to the Activity
ouyaFacade.RequestPurchase(this, purchasable, requestPurchaseListener);
By extending RequestReceiptsListener
, the OnSuccess
, OnFailure
, and OnCancel
events can be overloaded and will be invoked when the asynchronous RequestReceipts
method completes.
public class CustomRequestReceiptsListener : RequestReceiptsListener
{
private const string TAG = "CustomRequestReceiptsListener";
public override void OnSuccess(Java.Lang.Object jObject) {
JavaCollection<Receipt> receipts = jObject.JavaCast<JavaCollection<Receipt>> ();
if (null == receipts) {
Log.Error (TAG, "Receipts are null!";
} else {
Log.Info (TAG, "Request Receipts: OnSuccess Count="+receipts.Count);
}
}
public override void OnFailure(int errorCode, String errorMessage, Bundle optionalData) {
Log.Error (TAG, "OnFailure errorCode=" + errorCode + " errorMessage=" + errorMessage);
}
public override void OnCancel() {
Log.Error (TAG, "Receipt request was cancelled");
}
}
RequestReceiptsListener requestReceiptsListener = new CustomRequestReceiptsListener();
// first parameter is a reference to the Activity
ouyaFacade.RequestReceipts(this, requestReceiptsListener);
A common mistake is to leave sounds and music playing after the application has ended.
If you used the XNA MediaPlayer
object, be sure to Stop()
when the user quits
the application.
Microsoft.Xna.Framework.Media.MediaPlayer.Stop();
Place the Xiaomi libraries in the following destinations:
Assets/MiGameCenterSDKService.apk
MiGameCenterSDKService.apk
should be set as AndroidAsset
.
SDK_MIBOX_2.0.1.jar
should be set as AndroidJavaLibrary
.
Jars/SDK_MIBOX_2.0.1.jar
Some of the AndroidManifest.xml
options can be edited in the Project Options
on your MonoGame project. To add all the permissions, browse to your project Properties
folder to edit the raw AndroidManifest.xml
.
MonoGame
supports initialization strings similar to Java
syntax to make the game compatible with OUYA Everywhere devices.
-
tv.ouya.developer_id
- The developer UUID can be found in the developer portal after logging in. -
com.xiaomi.app_id
- The Xiaomi App Id is provided by the content team, emailofficehours@ouya.tv
to obtain your key. -
com.xiaomi.app_key
- The Xiaomi App Key is provided by the content team, emailofficehours@ouya.tv
to obtain your key. -
tv.ouya.product_id_list
- The product id list is a comma separated list of product ids that can be purchased in the game.
Bundle developerInfo = new Bundle();
byte[] applicationKey = null;
// load the application key from assets
try {
AssetManager assetManager = ApplicationContext.Assets;
AssetFileDescriptor afd = assetManager.OpenFd("key.der");
int size = 0;
if (null != afd) {
size = (int)afd.Length;
afd.Close();
using (Stream inputStream = assetManager.Open("key.der", Access.Buffer))
{
applicationKey = new byte[size];
inputStream.Read(applicationKey, 0, size);
inputStream.Close();
}
}
} catch (Exception e) {
Log.Error (TAG, string.Format("Failed to read application key exception={0}", e));
}
if (null != applicationKey) {
Log.Debug (TAG, "Read signing key");
developerInfo.PutByteArray (OuyaFacade.OUYA_DEVELOPER_PUBLIC_KEY, applicationKey);
} else {
Log.Error (TAG, "Failed to authorize with signing key");
Finish ();
return;
}
string[] purchasables =
{
"long_sword",
"sharp_axe",
"cool_level",
"awesome_sauce",
"__DECLINED__THIS_PURCHASE",
};
developerInfo.PutString(OuyaFacade.OUYA_DEVELOPER_ID, "00000000-0000-0000-0000-000000000000");
developerInfo.PutString(OuyaFacade.XIAOMI_APPLICATION_ID, "0000000000000");
developerInfo.PutString(OuyaFacade.XIAOMI_APPLICATION_KEY, "000000000000000000");
developerInfo.PutStringArray(OuyaFacade.OUYA_PRODUCT_ID_LIST, purchasables);
_ouyaFacade = OuyaFacade.Instance;
_ouyaFacade.Init(this, developerInfo);
MonoGame
uses standard Android localization for localized string resources.
String resources must be included in the project and marked as an AndroidResource
.
Strings can be placed in designated folders which are automatically selected using the current language.
-
Resources/Values/Strings.xml
(Default) -
Resources/Values-de/Strings.xml
(Dutch) -
Resources/Values-en/Strings.xml
(English) -
Resources/Values-es/Strings.xml
(Spanish) -
Resources/Values-fr/Strings.xml
(French) -
Resources/Values-it/Strings.xml
(Italian) -
Resources/Values-zh-rCN/Strings.xml
(Simplified Chinese)
Within the Activity
or using a reference to the Activity
, the resource strings can be found.
The current Locale will automatically select the right String
resource.
private String GetStringResource(String name) {
Resources resources = Resources;
if (null == resources) {
return String.Empty;
}
int id = resources.GetIdentifier (name, "string", PackageName);
if (id <= 0) {
return "";
}
return resources.GetString(id);
}
Use the content view
to disable the screensaver
in MonoGame
from the Activity
before running your customized Microsoft.Xna.Framework.Game
. This parallels the Java example using the Xamarin
auto-generated bind syntax.
public class Activity1 : Microsoft.Xna.Framework.AndroidGameActivity
{
protected override void OnCreate(Bundle bundle)
{
var g = new Game1();
SetContentView(g.Window);
// Add OUYA-Everywhere Input
using (var ignore = new TV.Ouya.Sdk.OuyaInputView(this))
{
// do nothing
}
// Get the Content View to disable the screensaver
View content = FindViewById (Android.Resource.Id.Content);
if (null != content) {
// Disable screensaver
content.KeepScreenOn = true;
}
g.Run();
}
}