Easy Mobile User Guide
User%20Guide
User%20Guide
User Manual:
Open the PDF directly: View PDF
.
Page Count: 207 [warning: Documents this large are best viewed by clicking the View PDF Link!]


1.1
1.1.1
1.1.2
1.1.3
1.2
1.2.1
1.2.1.1
1.2.1.1.1
1.2.1.1.2
1.2.1.1.3
1.2.1.1.4
1.2.1.1.5
1.2.1.2
1.2.1.3
1.2.2
1.2.3
1.3
1.3.1
1.3.1.1
1.3.1.2
1.3.1.3
1.3.1.4
1.3.1.5
1.3.2
1.3.2.1
1.3.3
1.4
1.4.1
1.4.2
1.4.3
TableofContents
GettingStarted
Introduction
Requirements
UsingEasyMobile
Advertising
ModuleConfiguration
SetupAdNetworks
AdColony
AdMob
Chartboost
Heyzap
UnityAds
AutomaticAdLoading
DefaultAdNetworks
Scripting
PlayMakerActions
GameServices
ModuleConfiguration
Android-SpecificSetup
AutoInitialization
Leaderboards&Achievements
ConstantsGeneration
SavedGames
Scripting
SavedGames
PlayMakerActions
GIF
Setup
Scripting
PlayMakerActions
2

1.5
1.5.1
1.5.1.1
1.5.1.2
1.5.1.3
1.5.1.4
1.5.1.5
1.5.2
1.5.3
1.6
1.6.1
1.6.2
1.7
1.7.1
1.7.1.1
1.7.2
1.8
1.8.1
1.8.1.1
1.8.1.2
1.8.1.3
1.8.1.4
1.8.1.5
1.8.2
1.8.3
1.9
1.9.1
1.9.1.1
1.9.1.2
1.9.2
1.10
1.11
1.12
In-AppPurchasing
ModuleConfiguration
EnableUnityIAP
TargetAndroidStore
ReceiptValidation
ProductManagement
ConstantsGeneration
Scripting
PlayMakerActions
Sharing
Scripting
PlayMakerActions
NativeAPIs
NativeUI
Scripting
PlayMakerActions
Notifications
ModuleConfiguration
AutoInitialization
RemoteNotificationSetup
AddingNotificationResources
CategoryManagement
ConstantsGeneration
Scripting
PlayMakerActions
Utilities
StoreReview
Configuration
Scripting
PlayMakerActions
ReleaseNotes[Basic]
ReleaseNotes[Pro]
UpgradeGuide
3

4

EasyMobileUserGuide
ThisdocumentistheofficialuserguideforEasyMobile,aUnitypluginfamilybySgLib
Games.
EasyMobileVersions
EasyMobilePro
EasyMobileBasic(comingsoon)
EasyMobileLite(Free)
ImportantLinks
OnlineDocumentation
SupportEmail
Forum
ConnectwithSgLibGames
UnityAssetStore
Facebook
Twitter
YouTube
DocumentUsageNotes
ThisdocumentistheofficialuserguideforallEasyMobileversions(Pro,Basicand
Lite)
ThewholedocumentisrelevanttotheProversion
AllchaptersarerelevanttotheBasicversion,exceptthoserelatingtoPro-onlyfeatures,
whichshouldbemarkedaccordingly
ThefollowingchaptersarerelevanttotheLiteversion,usersofthisversioncansafely
ignoreotherchapters:
Sharing
NativeAPIs>NativeUI
GettingStarted
5

GettingStarted
6

Introduction
EasyMobileisourattempttocreateamany-in-oneUnitypackagethatgreatlysimplifiesthe
implementationofdefactostandardfeaturesofmobilegamesincludingadvertising,in-app
purchasing,gameservices,notificationsandnativemobilefunctionalities.Itdoessoby
providingafriendlyeditorforsettingupandmanagingthings,andacross-platformAPI
whichallowsyoutoaccomplishmosttaskswithonlyonelineofcode.Italsoleverages
officialpluginswhereverpossible,e.g.GooglePlayGamespluginforUnity,toensure
reliabilityandcompatibilitywithoutreinventingthewheel.
EasyMobilesupportstwomajormobileplatforms:iOSandAndroid.
Thispluginiscurrentlydividedintothefollowingmodules:
Advertising
CompatiblewithAdColony,AdMob,Chartboost,HeyzapandUnityAds;evenmore
withAdMoborHeyzapmediation
Automaticadloading
Allowsusingmultipleadnetworksinonegame
Allowsdifferentadconfigurationsfordifferentplatforms
GameServices
LeveragesUnity'sGameCenterPlatformoniOSandGooglePlayGamespluginon
Android
Leaderboardsandachievements
SavedGames(Proversiononly)
GIF(Proversiononly)
Recordsscreen,playsrecordedclipsandexportsGIFimages
High-performance,mobile-friendlyGIFencoder
GiphyuploadAPIforsharingGIFtosocialnetworks
Introduction
7

In-AppPurchasing
LeveragesUnityIn-AppPurchasingservice
Customeditorforeasymanagementofproductcatalog
Receiptvalidation
Sharing
Sharestexts,URLsandimagesusingthenativesharingfunctionality
NativeAPIs
AccesstomobilenativeUIelementsincludingalertsand(Android)toasts
Morenativefunctionalitieswillbeaddedsoon
Notifications
Fully-customizablelocalnotifications
CompatiblewithOneSignal,afreeandpopularserviceforpushnotifications
SupportsnotificationchannelsandchannelgroupsonAndroidOandhigher
Utilities
StoreReview:providesaneffectivewaytoaskforreviewsandratingsusingthe
system-providedratingpromptofiOS(10.3+)andanative,highlycustomizable
popuponAndroid
Themodularapproachallowsyoutoenableonlythemodulesthatyouuse,whichhelps
avoidredundanciesandpotentialconflictswithexistingcodethatdoesthesamejobin
yourproject,ifany.
EasyMobileVersions
EasyMobilecomesin2differentversions:BasicandPro.EasyMobileBasicisthelower-
priceversionwhichcontainsmostofthecorefeaturesoftheplugin,exceptafewadvanced
functionalitiessuchasGIFandSavedGames.TheProversionisthepremiumoneand
haveallfeaturesavailable.Thisdocumentissharedbetweenbothversions,andPro-only
featureswillbemarkedaccordinglyinthecorrespondingchapters.
Introduction
8

Belowisafeature-comparisontableoftheBasicandProversionsofEasyMobile.
Introduction
9

Introduction
10

Introduction
11

Requirements
Unity5.3.0orabove.
Requirements
12

UsingEasyMobile
UsingEasyMobileinvolves3steps:
Configurethepluginusingthebuilt-inSettingsinterface
MakesureaninstanceoftheEasyMobileprefabisaddedtoyourfirstscene
MakeappropriateAPIcallsfromscript
Configuration
AfterimportingEasyMobile,therewillbeanewmenuaddedatWindow>EasyMobilefrom
whichyoucanaccesstheSettingsinterfaceandconfigurevariousmodulesoftheplugin.
UsingEasyMobile
13

TheSettingsinterfaceistheonlyplaceyougotoconfiguretheplugin.Hereyoucanenable
ordisablemodules,provideadscredentials,addleaderboards,createaproductcatalog,
etc.
AllthesesettingsarestoredintheEM_Settingsobject,whichisaScriptableObjectcreated
automaticallyafterimportingthepluginandislocatedatAssets/EasyMobile/Resources.You
canalsoaccessthisEM_Settingsclassfromscriptandviaitspropertiesaccessingeach
modulesettingsinruntime.
EasyMobilePrefab
FortheplugintofunctionproperlyitisrequiredthataninstanceoftheEasyMobileprefabis
addedtooneofthegamescenes.Theprefabisautomaticallycreatedwhenimportingthe
pluginandislocatedatitsrootfolder.Itwillhandletaskslikeinitializationandautomaticad
loading.
ItisadvisabletoaddtheEasyMobileprefabtothefirstsceneinyourgamesothatthe
moduleshavetimetoinitializebeforeyouactuallyusethem.Likewise,thiswillallowthe
automaticadloadingprocesstostartsoonandtheadswillbemorelikelyavailable
whenneeded.
UsingEasyMobile
14

ToaddanEasyMobileinstancetoyourscene,simplyright-clickintheHierarchywindowto
openthecontextmenu(asyouwouldwhencreatingUnitybuilt-inobjects),thenselectEasy
Mobile>EasyMobileInstance.Alternatively,youcanjustdragtheprefabtotheHierarchy
window,onlymakesuretomakeitaroot-levelobject(parentless).
Ifyou'reusingUnity5.6ornewer,you'llgetawarningifyoustartaniOSorAndroidbuild
withouthavingaddedtheEasyMobileinstancetooneofyourscenes.
UsingEasyMobile
15

Scripting
EasyMobileAPIiswritteninC#andisputunderthenamespaceEasyMobile.Therefore,
youneedtoaddthefollowingstatementtothetopofyourscriptinordertoaccessitsAPI
methods.
usingEasyMobile;
EasyMobile'sAPIiscross-platformsoyoucanusethesamecodebaseforbothiOS
andAndroid.
TestingUsingtheDemoApp
EasyMobilecomeswithademoappthatyoucanusetoquicklytesteachmodule's
operationafterconfiguring.ThedemoappiscontainedinfolderAssets/EasyMobile/Demo.
Tousethedemoapp,youneedtoaddaninstanceoftheEasyMobileprefabtothe
DemoHomescenelocatedintheAssets/EasyMobile/Demo/Scenesfolder.
UsingPlayMakerActions
UsingEasyMobile
16

Startingfromversion1.1.3,EasyMobileisofficiallycompatiblewithPlayMaker,withnearly
100customactionsreadytobeused.YoucaninstalltheseactionsfrommenuWindow>
EasyMobile>InstallPlayMakerActions.
EasyMobile'sDemoAppforPlayMaker
WheninstallingthePlayMakeractions,ademoappwillalsobeimportedat
Assets/EasyMobile/Demo/PlayMakerDemo.Thisdemoisacopyofourmaindemoapp,
rebuiltusingPlayMakeractionsinsteadofC#scripts.Youcantakeitasanexampletoget
aninsightintohowEasyMobile'sPlayMakeractionscanbeusedinpractice.
ApartfrominstallingPlayMaker(obviously),youneedtodoafewmoresetupstepsas
describedbelow,beforerunningthisdemoapp.
InstallingtheUnityUIadd-onforPlayMaker
OurPlayMakerdemoappusestheUnityUIsystem,soyouneedtoinstalltheUnityUIadd-
onforPlayMaker.
AddingtheEasyMobilePrefabInstance
Similartothemaindemoappdiscussedabove,youneedtoaddaninstanceoftheEasy
MobileprefabtotheDemoHome_PlayMakerscenelocatedinthe
Assets/EasyMobile/Demo/PlayMakerDemofolder.
ImportingPlayMakerGlobals
ThedemoappusesglobalPlayMakervariablesandevents,soyouneedtoimportthem
beforerunningtheapp.
Ifyouprojectisnewanddoesn'thaveanyPlayMakerGlobals(inthe
Assets/PlayMaker/Resourcesfolder),simplycopyourGlobalsoverbythesesteps:
Double-clickthePlayMakerGlobals.unitypackageinthe
Assets/EasyMobile/Demo/PlayMakerDemofolder.
LocatethenewlycreatedfilePlayMakerGlobals_EXPORTED.assetrightunderthe
Assetsfolder.
RenamethefiletoPlayMakerGlobals.assetandmoveittothe
Assets/PlayMaker/Resourcesfolder.
IfyouprojectalreadycontainssomeglobalPlayMakervariablesandevents(a
PlayMakerGlobals.assetfileexistsinthefolderAssets/PlayMaker/Resources),youcan
mergethesewithourdemo'sGlobalsusingPlayMaker'sImportGlobalstool:gotomenu
UsingEasyMobile
17

PlayMaker>Tools>ImportGlobalsandselectthePlayMakerGlobals.unitypackageinthe
Assets/EasyMobile/Demo/PlayMakerDemofolder.
UsingEasyMobile
18

Advertising
TheAdvertisingmodulehelpsyouquicklysetupandshowadsinyourgames.Here'resome
highlightsofthismodule:
Supportsmultiplenetworks
Thismoduleallowsshowingadsfrommostoftopadnetworks:AdColony,AdMob,
Chartboost,HeyzapandUnityAds
EvenmorenetworkscanbeusedviaAdMoborHeyzapmediation
Usingmultiplenetworksinonegame
It'spossibletousemultipleadneworksatthesametime,e.g.useAdMobfor
bannerads,whileusingChartboostforinterstitialadsandUnityAdsforrewarded
ads
Differentconfigurationsfordifferentplatformsareallowed,e.g.useUnityAdsfor
rewardedadsonAndroid,whileusingChartboostforthattypeofadsoniOS
Automaticadloading
Adswillbefetchedautomaticallyinthebackground;newadwillbeloadedifthe
lastonewasshown
ThetablebelowsummarizestheadtypessupportedbyEasyMobileforeachadnetwork.
AdNetwork BannerAd InterstitialAd RewardedAd Mediation
AdColony â—‰ â—‰
AdMob â—‰ â—‰ â—‰ â—‰
Chartboost â—‰ â—‰
Heyzap â—‰ â—‰ â—‰ â—‰
UnityAds ◉ ◉
Advertising
19

ModuleConfiguration
TousetheAdvertisingmoduleyoumustfirstenableit.GotoWindow>EasyMobile>
Settings,selecttheAdvertisingtab,thenclicktheright-handsidetoggletoenableandstart
configuringthemodule.
ModuleConfiguration
20

SetupAdNetworks
TheAdvertisingmoduleworkswithtopmobileadnetworks:AdMob,Chartboost,Heyzap
andUnityAds.Toshowadsfromacertainnetworkyouneedtoimportitsplugin(orenable
thecorrespondingUnityserviceincaseofUnityAds).EasyMobilewillautomaticallycheck
fortheavailabilityofthesepluginsandpromptyoutodownloadandimportthemifneeded.
Onlyimportpluginsfortheadnetworksyouusetonotincreasethebuildsize
unnecessarily.
SetupAdNetworks
21

ANoteonusingHeyzap
Heyzapcanserveadsfrommultipleothernetworksthankstoitsmediationfeature.Todoso
itrequires3rd-partySDKstobeimportedtogetherwithitsownplugin.IfyouuseHeyzap,
youshouldnotimportthestandalone-pluginsofothernetworks(AdColony,AdMob,
Chartboost,etc.),toavoidpotentialconflicts.Instead,usethemasmediatednetworksunder
HeyzapandimporttheircorrespondingpackagesprovidedattheHeyzapdownloadpage.
SetupAdNetworks
22

AdColony
CreateAdColonyAppsandZoneIds
ToshowadsfromAdColonyyouneedtocreateappsandadzonesinitsclientsportal.To
accesstheclientsportal,createanaccountandlogintoAdColonypage.
Intheclientsportal,selectMONETIZATIONtab,thenselecttheAppssub-tabandclickthe
SetupNewAppbutton.
Intheopenedpageentertherequiredinformationforyournewapp,e.g.appname,platform
andlocation.Youcanalsoselecttheadtypesthatyouwouldliketoallowinyourapp.Hit
Createwhenyou'redone,yourappwillbecreatedandyou'llberedirectedbacktotheApps
page.Selectyournewlycreatedapptorevealitsinformation,whichlookssimilartothe
picturebelow.NotetheAdColonyAppIDaswewilluseitlater.
Nowyourappisready,thenextstepistocreateadzonesforit.ClicktheSetupNewAd
Zoneatthebottomoftheappeditpagetocreateanewadzone.
IntheIntegrationsection,giveyouradzoneaname,optionalnotesandsetitsasactive.
NotetheZoneIDaswe'lluseitlater.
TheZoneIDwillappearonceyousaveyournewadzone.
SetupAdNetworks
23

IntheCreativeTypesection,selecttheVideooption.
IntheZoneTypesection,selectPreroll/Interstitialifyouwanttousethiszonefor
interstitialvideoads.Otherwise,selectValueExchange/V4VCtouseitforrewardedads.
SetupAdNetworks
24

IntheOptionssection,youcansetadailycaporasessioncaptolimitthenumberofads
servedtoauserperdayorpersession,respectively.IntheDevelopmentsection,youcan
choosetoshowtestadsonly(fordebugpurpose),don'tforgettodisablethisoptionwhen
yourappisreleased.
Nowyournewadzoneisfullyconfigured,clicktheSavebuttontosaveit.Repeatthe
processtocreateotheradzonestosuityourneeds.Typically,you'dwanttohave2ad
zones,oneforinterstitialadsandoneforrewardedads.Ifyou'retargetingmultiple
platforms,createanewappforeachplatform,andforeachappcreatethenecessaryad
zones.
ImportAdColonyPlugin
TohaveyourUnityappworkwithAdColonyyouneedtoimporttheAdColonypluginfor
Unity.IntheADCOLONYSETUPsectionoftheAdvertisingmodule,clicktheDownload
AdColonyPluginbuttontotoopenthedownloadpage.Downloadthepluginandimportitto
yourproject.
ConfigureAdColony
AfterimportingtheAdColonyplugin,theADCOLONYSETUPsectionwillbeupdatedas
below.
SetupAdNetworks
25

[iOS]AdColonyIds:entertheappIDandzoneIDscreatedintheAdColonyclients
portalfortheiOSapp
[Android]AdColonyIds:entertheappIDandzoneIDsfortheAndroidapp
ShowRewardedAdPrePopup:showtheAdColony'sdefaultpopupbeforearewarded
videostarts
ShowRewardedAdPostPopup:showtheAdColony'sdefaultpopupafterarewarded
videohasfinished
AdOrientation:selecttheorientationfortheadstomatchyourappsettings
SetupAdNetworks
26

AdMob
ImportAdMobPlugin
ToshowadsfromAdMobyouneedtoimporttheGoogleMobileAdsplugin.IntheADMOB
SETUPsection,clicktheDownloadGoogleMobileAdsPluginbuttontotoopenthe
downloadpage.Downloadthepluginandimportittoyourproject.
ConfigureAdMob
AfterimportingtheGoogleMobileAdsplugin,theADMOBSETUPsectionwillbeupdated
asbelow.
SetupAdNetworks
27

EnterAdMobIDs
FirstyouneedtoentertherequiredappIDandadunitIDsforeachplatform.
TofindtheAppIDforyourappfollowtheinstructionshere.
NotethatyouonlyneedtoprovidetheIDsoftheadunitsyouwanttouse,e.g.ifyou
onlyuseAdMobbanneradyoucanleavetheinterstitialadandrewardedadIDsempty.
Ifyou'renotfamiliarwithAdMob,followtheinstructionsheretocreateadunitsand
obtaintheadIDs;anadIDshouldhavetheformofca-app-pub-
0664570763252260xxxxxxxxxxx.
AdTargetingSettings
YoucanprovidetargetinginformationforyourappintheAdTargetingsub-section.These
settingswillbeappliedtoallAdMobadrequestsinyourapp.Youcanlearnmoreabout
AdMobadtargetinghere.
Gender:thetargetusergender
TagForChildDirectedTreatment:indicateswhetheryouwantGoogletotreatyour
contentaschild-directedwhenyoumakeanadrequestforthepurposesofChildren's
OnlinePrivacyProtectionAct(COPPA)
Extras:extrasettingsinformofkeyvaluepairs,e.g.forsetting
"max_ad_content_rating"
SetupAdNetworks
28

UsingAdMobwiththeDesignedforFamiliesprogram
Accordingtothisarticle,appsthatjoinintoGooglePlay'sDesignedforFamilies
programcanfallintotwocategories:
Primarilychild-directedapps:ifyourappisadmittedtotheprogramasa
primarilychild-directedapp,"AdMobwillautomaticallybeginservingDesignedfor
Families-compliantadsforalladrequestscomingfromtheapp",whichmeansyou
don'tneedtospecifythechilddirectedsettinginyourapp.
Mixed-audienceapps:ifyourapptargetsbothchildandadultaudiences,you
needtosettheextrakey"is_designed_for_families"totrueandtagyourappfor
child-directedtreatment.YoucandothatinEasyMobilesettingsasbelow.
OverridingAdMobtargetingsettingsinscript
YoucanoverrideAdMobtargetingsettingsinscriptbysettingtheproperty
EM_Settings.Advertising.AdMobTargeting.Allsubsequentadrequestswillbesentwith
thenewsettings.
UsingAdMobTestMode
ToenableAdMob'stestmode,simplychecktheEnableTestModeoptionandentertheIDs
ofyourtestingdevicesintotheTestDeviceIdsarray.
SetupAdNetworks
29

YoucanfindtheIDofyourtestdevicebybuildingandrunningtheEasyMobiledemo
apponthatdevice.RemembertoaddtheEasyMobileprefabtotheDemoHomescene
beforestartingthebuild.
AndroiddeviceID
InUnity,buildtheEasyMobiledemoappforAndroidplatform
Installandrunthedemoapponyourtestingdevice
OpenTerminal(Mac)orCmd(Windows)andtypein
adblogcat-sAds
(ifyou'reonWindows,youmayneedtoaddtheAndroidSDKpathtotheWindows
SystemPATH)
Inthedemoapp,selectADVERTISINGandthenclicktheSHOWBANNERAD
button
ObservetheoutputlogcatintheTerminal/Cmdandlocatealinesimilartotheone
inthefollowingimage,thevaluebetweenthedoublequotesisyourdeviceID
iOSdeviceID
InUnity,buildtheEasyMobiledemoappforiOSplatform
OpenthegeneratedprojectinXcodeandrunitonyourtestingdevice
Type'google'intothefilterboxoftheXcodeConsole,andfindyourdeviceID
betweenthedoublequotesashighlightedinthefollowingimage
[iOS]CocoaPodsRequirement
TheGoogleMobileAdspluginforUnityemploysCocoaPodstoautomaticallyimportthe
necessaryframeworkstothegeneratedXcodeprojectwhenaniOSbuildisperformedin
Unity.ThereforeyouneedtoinstallCocoaPodstoyourMac:pleasegoto
SetupAdNetworks
30

https://cocoapods.org/forinstallinstructions,aswellasformoreinformationabout
CocoaPods.NotethatyouonlyneedtoinstallCocoaPods,everythingelsewillbedone
automaticallybytheGoogleMobileAdsplugin.
WhenbuildingforiOSinUnity,CocoaPodswillautomaticallycreateanXcode
workspace(with.xcworkspaceextension)inthegeneratedXcodeproject.Youshould
alwaysopenthisworkspaceinsteadofthenormalprojectfile(with.xcodeproj
extension).
SetupAdNetworks
31

Chartboost
ImportChartboostPlugin
ToshowadsfromChartboostyouneedtoimporttheChartboostpluginforUnity.Inthe
CHARTBOOSTSETUPsection,clicktheDownloadChartboostPluginbuttontoopenthe
downloadpage.Downloadthepluginandimportittoyourproject.
ConfigureChartboost
AfterimportingChartboostplugin,theCHARTBOOSTSETUPsectionwillbeupdatedas
below.
ClicktheSetupChartboostbuttontoopenChartboost'ssettingstool.
SetupAdNetworks
32

ProvidetheAppIDsandAppSignaturesforyourtargetedplatforms.Remembertoclickthe
SetupAndroidSDKbuttonifyou'rebuildingforAndroid.
ToobtaintheAppIdandAppSignatureyouneedtoaddyourapptotheChartboost
dashboard.Ifyou'renotfamiliarwiththeprocesspleasefollowtheinstructionshere.
Afteraddingtheapp,gotoAPPSETTINGS>BasicSettingstofinditsAppIDandApp
Signature.
READ_PHONE_STATEpermissiononAndroid
SetupAdNetworks
33

TheChartboostSDKincludestheREAD_PHONE_STATEpermissiononAndroid,to"handle
videoplaybackwheninterruptedbyacall",asstatedinitsmanifest.READ_PHONE_STATE
permissionrequiresyourapptohaveaprivacypolicywhenuploadedtoGooglePlay.Since
thispermissionisnotmandatorytoruntheChartboostSDK,youcansafelyremoveitifyou
arenotreadytoprovidetherequiredprivacypolicy.Toremovethepermission,openthe
AndroidManifest.xmlfilelocatedatAssets/Plugins/Android/ChartboostSDKfolder,then
deletethecorrespondingline(orcommentitoutasbelow).
<!--ExcludetheREAD_PHONE_STATEpermissionbecauseitrequiresaprivacypolicy-->
<!--<uses-permissionandroid:name="android.permission.android.permission.READ_PHONE_S
TATE"/>-->
TestingNotes
PleasenotethattoshowadsfromChartboostyouneedtoeithercreateapublishing
campaignorenabletheTestModeforyourapp.
Tocreateapublishingcampaignfollowtheinstructionshere
ToenableTestModefollowtheinstructionhere
SetupAdNetworks
34

Heyzap
Asmentionedearlier,ifyouuseHeyzap'smediationwithothernetworks(AdColony,
AdMob,Chartboost,etc.),youshouldnotimportthestandalone-pluginsofthose
networks,toavoidpotentialconflicts.Instead,importtheircorrespondingpackages
providedattheHeyzapdownloadpage.
ImportHeyzapPlugin
ToshowadsfromHeyzapyouneedtoimporttheHeyzappluginforUnity.IntheHEYZAP
SETUPsection,clicktheDownloadHeyzapPluginbuttontoopenthedownloadpage.
InthedownloadpageselectyourpreferrednetworkstousewithHeyzapmediation.The
Heyzapdynamicdocumentationwillupdateautomaticallytoreflectyourselections.
FollowtheinstructionsprovidedbyHeyzaptodownloadandimportitspluginaswellas
otherrequired3rd-partyplugins.AlsogothroughtheIntegrationNotessectionbelowto
avoidproblemsthatmayoccurduringtheintegrationof3rd-partynetworks.
Ifyouhaven'talready,useHeyzap'sIntegrationWizardtosetupthe3rd-partynetworks
tousewithmediation.
SetupAdNetworks
35

ConfigureHeyzap
AfterimportingHeyzapplugin,theHEYZAPSETUPsectionwillbeupdatedasbelow.You
cannowenteryourpublisherIdtotheHeyzapPublisherIdfield.
HeyzapMediationTestSuite
TheHeyzapplugincomeswithaconvenientTestSuitethatyoucanusetotesteachofthe
networksyouselectedformediation.TousethisTestSuite,simplychecktheShowHeyzap
TestSuiteoptionintheHEYZAPSETUPsection.
BelowistheTestSuiteinterfaceoniOS.
SetupAdNetworks
36

IntegrationNotes
ThissectiondiscussessomenotesthatyoushouldtakewhenusingHeyzapmediationwith
variousothernetworks.
FacebookAudienceNetwork(Android-specific)
TheFacebookAudienceNetworkpackagecontainsanandroid-support-v4.jar_fileunder
Assets/Plugins/Androidfolder.Ifyouprojectalreadycontainsasupport-v4-xx.x.x.aarfile
underthatsamefolder,feelfreetoremove(orexcludeitwhenimporting)thejarfileoritwill
causethe"Unabletoconvertdex..."errorwhenbuildingduetoduplicatelibraries.
AppLovin(Android-specific)
AsinstructedintheHeyzapdocumentation,youneedtoaddtheAppLovinSDKkeytoits
AndroidManifest.xmlfilelocatedatAssets/Plugins/Android/AppLovinfolder.Simplyaddthe
followinglineinsidethe<application>taginthemanifest,replacingYOUR_SDK_KEYwith
youractualAppLovinSDKkey.
<meta-dataandroid:name="applovin.sdk.key"android:value="YOUR_SDK_KEY"/>
ThismanifestalsoincludestheREAD_PHONE_STATEpermission,whichrequiresyourapp
tohaveaprivacypolicywhenuploadedtoGooglePlay.Thispermissionisnotmandatoryto
runtheAppLovinSDK,thereforeyoucansafelyremoveitifyouarenotreadytoprovidethe
requiredprivacypolicy.Toremovethepermission,simplydeletethecorrespondinglinefrom
themanifestorcommentitoutasbelow.
<!--ExcludetheREAD_PHONE_STATEpermissionbecauseitrequiresaprivacypolicy-->
<!--<uses-permissionandroid:name="android.permission.READ_PHONE_STATE"/>-->
TheminSdkVersionProblem(Android-specific)
ThecurrentHeyzapSDKrequiresaminSdkVersionof10,whilesomeother3rd-party
pluginsmayrequireaversionof11orabove.Ifyougetabuilderrorincludingthisline
Unabletomergeandroidmanifests...
andthisline
Mainmanifesthas<uses-sdkandroid:minSdkVersion='x'>butlibraryusesminSdkVersion=
'y'
SetupAdNetworks
37

wherex<y,itmeansyouneedtoincreasetheminSdkVersionoftheapp.Todosogoto
Edit>ProjectSettings>Player,thenselecttheAndroidsettingstabandincreaseits
MinimumAPILeveltotherequiredone(whichis'y'inthisexample).
SetupAdNetworks
38

UnityAds
EnableUnityAdsService
TouseUnityAdsservice,youmustfirstsetupyourprojectforUnityServices.
ToshowadsfromUnityAdsyouneedtoenablethecorrespondingservice.EasyMobilewill
automaticallycheckfortheservice'savailabilityandwarnyoutoenableitifneeded.Below
istheUNITYADSSETUPsectionwhenUnityAdsisnotenabled.
ToenableUnityAdsswitchtheplatformtoiOSorAndroid,thengotoWindow>Services
andselecttheAdstab.
SetupAdNetworks
39

Intheopenedconfigurationwindow,clickthetoggleattheright-handsidetoenableUnity
Adsservice.Youmayneedtoanswerafewquestionsaboutyourgame.
SetupAdNetworks
40

TheUNITYADSSETUPsectionwillbeupdatedafterUnityAdshasbeenenabled.
TestingNotes
ItisadvisabletoenablethetestmodeofUnityAdsduringdevelopment.Thiswillensure
there'salwaysanadreturnedwheneverrequested.Toenabletestmodesimplecheckthe
EnabletestmodeoptionwithintheAdsconfigurationwindow.
Remembertodisablethistestmodewhencreatingyourreleasebuild.
SetupAdNetworks
41

AutomaticAdLoading
AutomaticadloadingisafeatureoftheAdvertisingmodule.Itregularlychecksforthe
availabilityofdefaultads,andperformsloadingifneeded,tomakesurethatadsarealways
readywhentheyneedtobeshown.YoucanconfigurethisfeatureintheAUTOAD-
LOADINGCONFIGsection.
Defaultadsareadsloadedfromdefaultnetworks,seeDefaultAdNetworkssection.
Auto-LoadDefaultAds:uncheckthistodisableautomaticadloadingfeature,youcan
loadadsmanuallyfromscript,seeScriptingsectionforinstructionsonthis
AdCheckingInterval:changethisvaluetodeterminehowfrequentlythemoduleshould
performadsavailabilitycheck,thesmallerthemorefrequently
AdLoadingInterval:theminimumtimebetweentwoadloadingrequests
AutomaticAdLoading
42

DefaultAdNetworks
YoucanselectdefaultadnetworksforeachplatformintheDEFAULTADNETWORKS
section.Youcanhavedifferentnetworksfordifferentadtypesanddifferentselectionsfor
differentplatforms.Ifyoudon'twanttouseacertaintypeofad,simplysetitsnetworkto
None.
Payattentiontothewarningsandimporttherequiredpluginsifyouhaven'talready.
DefaultAdNetworks
43

Scripting
ThissectionprovidesaguidetoworkwiththeAdvertisingAPIusingthedefaultadnetworks
configuredinthemodulesettings.
YoucanaccesstheAdvertisingmoduleAPIviatheAdvertisingclassunderthe
EasyMobilenamespace.
BannerAds
ToshowabanneradyouneedtospecifyitspositionusingtheBannerAdPositionenum.The
bannerwillbedisplayedonceitisloaded.
//Showbannerad
Advertising.ShowBannerAd(BannerAdPosition.Bottom);
Tohidethecurrentbannerad(itcanbeshownagainlater):
//Hidebannerad
Advertising.HideBannerAd();
Todestroythecurrentbannerad(anewonewillbecreatedonthenextbanneradshowing):
//Destroybannerad
Advertising.DestroyBannerAd();
InterstitialAds
Themethodtoshowaninterstialadrequiresittobealreadyloaded.Thereforeyoushould
checkforthead'savailabilitybeforeshowingit.
//Checkifinterstitialadisready
boolisReady=Advertising.IsInterstitialAdReady();
//Showitifit'sready
if(isReady)
{
Advertising.ShowInterstitialAd();
}
Scripting
44

AnInterstitialAdCompletedeventwillbefiredwheneveraninterstitialadisclosed.Youcan
listentothiseventtotakeappropriateactions,e.g.resumethegame.
//Subscribetotheevent
voidOnEnable()
{
Advertising.InterstitialAdCompleted+=InterstitialAdCompletedHandler;
}
//Theeventhandler
voidInterstitialAdCompletedHandler(InterstitialAdNetworknetwork,AdLocationlocation
)
{
Debug.Log("Interstitialadhasbeenclosed.");
}
//Unsubscribe
voidOnDisable()
{
Advertising.InterstitialAdCompleted-=InterstitialAdCompletedHandler;
}
RewardedAds
Themethodtoshowarewardedadrequiresittobealreadyloaded.Thereforeyoushould
checkforthead'savailabilitybeforeshowingit.
//Checkifrewardedadisready
boolisReady=Advertising.IsRewardedAdReady();
//Showitifit'sready
if(isReady)
{
Advertising.ShowRewardedAd();
}
ARewardedAdCompletedeventwillbefiredwheneverarewardedadhascompleted.You
shouldlistentothiseventtorewardtheuserforwatchingthead.Otherwise,a
RewardedAdSkippedeventwillbefirediftheadisskippedbeforefinishing(andtheuser
thereforeisnotentitledtothereward).
Scripting
45

//Subscribetorewardedadevents
voidOnEnable()
{
Advertising.RewardedAdCompleted+=RewardedAdCompletedHandler;
Advertising.RewardedAdSkipped+=RewardedAdSkippedHandler;
}
//Unsubscribeevents
voidOnDisable()
{
Advertising.RewardedAdCompleted-=RewardedAdCompletedHandler;
Advertising.RewardedAdSkipped-=RewardedAdSkippedHandler;
}
//Eventhandlercalledwhenarewardedadhascompleted
voidRewardedAdCompletedHandler(RewardedAdNetworknetwork,AdLocationlocation)
{
Debug.Log("Rewardedadhascompleted.Theusershouldberewardednow.");
}
//Eventhandlercalledwhenarewardedadhasbeenskipped
voidRewardedAdSkippedHandler(RewardedAdNetworknetwork,AdLocationlocation)
{
Debug.Log("Rewardedadwasskipped.TheusershouldNOTberewarded.");
}
RemoveAds
Insomecasesyouneedtoremove/stopshowingadsinyourgame,e.g.whentheuser
purchasesthe"RemoveAds"product.Toremoveads:
//Removeadspermanently
Advertising.RemoveAds();
TheRemoveAdsmethodwilldestroythebanneradifoneisbeingshown,andprevent
futureadsfrombeingloadedandshownexceptrewardedads,sincetheyareunobtrusive
andonlyshownattheuserdiscretion.
NotethattheRemoveAdsmethodusesUnity'sPlayerPrefstostoretheadremoval
statuswithnoencryption/scrambling.
AnAdsRemovedeventwillbefiredafteradshavebeenremoved.Youcanlistentothis
eventandtakeappropriateactions,e.gupdatetheUI.
Scripting
46

//Subscribetotheevent
voidOnEnable()
{
Advertising.AdsRemoved+=AdsRemovedHandler;
}
//Theeventhandler
voidAdsRemovedHandler()
{
Debug.Log("Adswereremoved.");
//Unsubscribe
Advertising.AdsRemoved-=AdsRemovedHandler;
}
Youcanalsocheckatanytimeifadswereremovedornot.
//Determineifadswereremoved
boolisRemoved=Advertising.IsAdRemoved();
Finally,youcanalsorevoketheadremovingstatusandallowadstobeshownagain.
//Revokeadremovingstatusandallowshowingadsagain
Advertising.ResetRemoveAds();
ManualAdLoading
Normallyyoudon'tneedtoworryaboutloadingadsiftheautomaticadloadingfeatureis
enabled(seeConfigureAdvertisingModulesection).Otherwise,ifyouchoosetodisable
thisfeature,youcanloadadsmanuallyfromscript.
Itisadvisabletoloadanadasfarinadvanceofshowingitaspossibletoallowtimefor
theadtobeloaded.
Toloadaninterstitialad:
//Loadaninterstitialad
Advertising.LoadInterstitialAd();
Toloadarewardedad:
//Loadarewardedad
Advertising.LoadRewardedAd();
Scripting
47

WorkingwithNon-DefaultAdNetworks
Besidethedefaultadnetworks,youcanalsoloadandshowadsfromnon-defaultnetworks,
thuscreatingamoresophisticatedadnetworkcombinationinyourapp.Notethateach
methodtoloadorshowadalwayshasavariantthatallowsyoutospecifythetargetad
networkexplicitly.Forexamplethere're2variantsoftheLoadInterstitialAdmethod.One
takesnoargumentandloadsthedefaultinterstitialad.Theotherloadsaninterstitialadfrom
anetworkspecifiedexplicitly.IfyouhaveAdMobasthedefaultinterstitialadnetwork,you
canloadandshowinterstitialadfromthenon-defaultAdColonylikebelow.
TheAutomaticAdLoadingfeaturewon'thandlenon-defaultads,soyouneedtoload
adsmanuallybeforeshowingthem.
//Thismethodshowsaninterstitialadfromthedefaultnetwork(i.e.AdMobinthise
xample).
//Defaultadsareloadedautomaticallysoyouwon'tneedtoloadthemmanually,
//unlessAutomaticAdLoadingisdisabled.
if(Advertising.IsInterstitialAdReady())
Advertising.ShowInterstitialAd();
...
//Non-defaultadsarenotloadedautomatically,soyouneedtoloadthemmanuallybef
oretheycanbeshown.
//Youshoulddothisearlytoallowsufficienttimeforanadtobeloadedbeforesho
wingit.
//Inthisexample,we'llloadandshowaninterstitialadfromthenon-defaultnetwor
kAdColony.
Advertising.LoadInterstitialAd(InterstitialAdNetwork.AdColony,AdLocation.Default);
...
//ChecksifanAdColonyinterstitialadisreadyandshowsit.
if(Advertising.IsInterstitialAdReady(InterstitialAdNetwork.AdColony,AdLocation.Defau
lt))
Advertising.ShowInterstitialAd(InterstitialAdNetwork.AdColony,AdLocation.Default)
;
Scripting
48

PlayMakerActions
ThePlayMakeractionsoftheAdvertisingmodulearegroupinthecategoryEasyMobile-
AdvertisinginthePlayMaker'sActionBrowser.
PleaserefertotheAdvertisingDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
49

PlayMakerActions
50

GameServices
TheGameServicesmodulehelpsyouquicklyimplementleaderboardsandachievements
foryourgame.ItworkswiththeGameCenternetworkoniOSandGooglePlayGames
servicesonAndroid.Here'resomehighlightsofthismodule:
Leveragesofficialplugins
ThismoduleisbuiltontopofUnity'sGameCenterPlatformoniOSandGooglePlay
GamespluginonAndroid
GameCenterPlatformisonepartoftheUnityEngineitselfwhiletheotheristhe
officialGooglePlayGamespluginforUnity,soreliabilityandcompatibilitycanbe
expected
Easymanagementofleaderboardsandachievements
EasyMobile'scustomeditorfeaturesafriendlyinterfacethathelpyoueasilyadd,
editorremoveleaderboardsandachievements
GameServices
51

ModuleConfiguration
TousetheGameServicesmoduleyoumustfirstenableit.GotoWindow>EasyMobile>
Settings,selecttheGameServicestab,thenclicktheright-handsidetoggletoenableand
startconfiguringthemodule.
ModuleConfiguration
52

Android-SpecificSetup
ImportGooglePlayGamespluginforUnity
Asstatedearlier,thismoduleisbuiltontopofGooglePlayGamesPluginonAndroid.
Thereforeyouneedtoimportittousethemoduleonthisplatform.EasyMobilewill
automaticallydetecttheavailabilityofthepluginandpromptyoutoimportitifneeded.Below
isthemodulesettingsinterfaceonAndroidplatformwhenGooglePlayGamespluginhasn't
beenimported.
ClicktheDownloadGooglePlayGamesPluginbuttontoopenthedownloadpage,then
downloadthepackageandimportittoyourproject.Oncetheimportcompletesthemodule
interfacewillbeupdatedandreadyforyoutostartwiththeconfiguration.
Sincewe'renotusingGooglePlayGamespluginoniOS,theNO_GPGSsymbolwillbe
definedforiOSplatformautomaticallyafterthepluginisimportedinordertodisableit.
SetupGooglePlayGames
TosetupGooglePlayGamesplugin,youneedtoobtainthegameresourcesfromthe
GooglePlayDeveloperConsole.
ThegameresourcesareavailableafteryouconfiguredyourgameontheGooglePlay
DeveloperConsole.Ifyou'renotfamiliarwiththeprocess,pleasefollowtheinstructions
oncreatingaclientID,aswellasleaderboardsandachievements.
Android-SpecificSetup
53

Togetthegameresources,logintoyourGooglePlayDeveloperConsole,selectGame
servicestabsthenselectyourgame.NextgototheAchievementstabandclickontheGet
Resourceslabelatthebottomofthelist.
CopyallthexmlcontentfromtheAndroidtab.
Android-SpecificSetup
54

GobacktoUnity,inthe[ANDROID]GOOGLEPLAYGAMESSETUPsection,pastethe
obtainedxmlresoucesintotheAndroidXMLResourcesarea,thenclickSetupGoogle
PlayGames.
Android-SpecificSetup
55

Afterthesetuphascompleted,anewfilenamedEM_GPGSIdswillbecreatedat
Assets/EasyMobile/Generated.ThisfilecontainstheconstantsoftheIDsofallthe
leaderboardsandachievementsinyourAndroidgame.
EnableGooglePlayGamesDebugLog
ToenableGooglePlayGamesdebuglog,simplychecktheGPGSDebugLogoptioninthe
[ANDROID]GOOGLEPLAYGAMESSETUPsection.
Android-SpecificSetup
56

AutoInitialization
AutoinitializationisafeatureoftheGameServicesmodulethatinitializestheservice
automaticallywhenthemodulestarts.Initializationisrequiredbeforeanyotheractionscan
bedone,e.g.reportingscores.
Duringtheinitialization,thesystemwilltrytoauthenticatetheuserbypresentingalogin
popup.
OniOS,thispopupwillshowupwhentheappgetsfocus(broughttoforeground)forthe
first3times.Iftheuserrefusestologinallthese3times,theOSwillignoresubsequent
authenticationcallsandstoppresentingtheloginpopup(toavoiddisturbingtheuser).
Otherwise,iftheuserhasloggedinsuccessfully,futureauthenticationwilltakeplace
silentlywithnologinpopuppresented.
OnAndroid,weemployasimilarapproachbutyoucanconfigurethemaximumnumber
ofauthenticationrequestsbeforeignoringsubsequentones.
YoucanconfiguretheautoinitializationfeaturewithintheAUTO-INITCONFIGsection.
AutoInit:uncheckthisoptiontodisabletheautoinitializationfeature,youcanstartthe
initializationmanuallyfromscript(seetheScriptingsection)
AutoInitDelay:howlongafterthemodulestartthattheinitializationshouldtakeplace
[Android]MaxLoginRequests:maximumnumberofauthenticationrequestsallowedon
Android,beforeignoringsubsequentones(incasetheuserrefusestologin)
"Modulestart"referstothemomenttheStartmethodofthemodule'sassociated
MonoBehavior(attachedtotheEasyMobileprefab)runs.
AutoInitialization
57

Leaderboards&Achievements
Thissectionprovidesaguidetomanageleaderboardsandmanagementsforyourgame.
BeforeYouBegin
Itisassumedthatyoualreadyconfiguredyourgameforthetargetedgamingnetworks,
i.e.GameCenterandGooglePlayGames.Ifyou'renotfamiliarwiththeprocess,
here'resomeusefullinks:
ConfigureforGooglePlayGames(Android)
CreatingaClientIDforyougame
Addingleaderboards
Addingachievements
ConfigureforGameCenter(iOS)
AddingleaderboardsandachievementsiniTunesConnect
IntheLEADERBOARDSETUPandACHIEVEMENTSETUPyoucanadd,editorremove
leaderboardsandachievements.
AddaNewLeaderboardorAchievement
ToaddanewleaderboardclicktheAddNewLeaderboardbutton(orAddNewAchievement
buttonincaseofanachievement).
Anewemptyleaderboard(orachievement)willbeadded.
Fillintherequiredinformationoftheleaderboard(orachievement):
Name:thenameofthisleaderboard(orachievement),thisnamecanbeusedwhen
reportingscorestothisleaderboard(orunlockingthisachievement)
iOSId:theIDofthisleaderboard(orachievement)asdeclarediniTunesConnect
Leaderboards&Achievements
58

AndroidId:theIDofthisleaderboard(orachievement)asdeclaredinGooglePlay
DeveloperConsole
GooglePlayGames'leaderboardsandachievementshavegeneratedIDswhichcanbe
difficulttomemorizeandcumbersometocopy-and-paste,especiallyiftherearemany
ofthem.Thankfully,whenyousetupGooglePlayGames,theconstantsoftheseIDs
aregeneratedautomatically(rememberthatEM_GPGSIdsfile?),allowingEasyMobile
toshowanicedropdownofalldefinedleaderboardandachievementIDsforyouto
choosefrom.
RemoveaLeaderboardorAchievement
Toremovealeaderboard(orachievement),simplyclickthe[-]buttonattherighthandside.
ArrangeLeaderboardsorAchievements
Youcanusethetwoarrow-upandarrow-downbuttonstomovealeaderboard(or
achievement)upwardordownwardwithinitsarray.
Leaderboards&Achievements
59

Leaderboards&Achievements
60

ConstantsGeneration
ConstantsgenerationisafeatureoftheGameServicesmodule.Itreadsthenamesofall
theaddedleaderboardsandachievementsandgeneratesastaticclassnamed
EM_GameServicesConstantsthatcontainstheconstantsofthesenames.Later,youcan
usetheseconstantswhenreportingscorestoaleaderboardorunlockinganachievementin
scriptinsteadoftypingthenamesdirectly,thushelppreventruntimeerrorsduetotyposand
thelikes.
Togeneratetheconstantsclass(youshoulddothisafteraddingallrequiredleaderboards
andachievements),clicktheGenerateConstantsClassbuttonwithintheCONSTANTS
CLASSGENERATIONsection.
Whentheprocesscompletes,afilenamedEM_GameServicesConstantswillbecreatedat
Assets/EasyMobile/Generated.
ConstantsGeneration
61

SavedGames
SavedGamesfeatureisavailableonEasyMobileProonly.
Savinggamedataisamongthemostdesirablefeaturesofvideogamesingeneral,and
mobilegamesinparticular.Nowadays,it'snotuncommonforausertoownmorethanone
mobiledevice,beitphoneortablet.Beingabletostartagameononedevice,andthen
continueplayingonanotherdevicewithoutlosinganyprogressbringsaseamless-ifnot
natural-userexperience.
TheSavedGamesfeatureofEasyMobilemakesitpossible-andeasy-tosaveaplayer's
gamedatatothecloudandsynchronizeitacrossmultipledevices.Savinguserdatatothe
cloudalsomeansthattheirgameprogressionispreservedandcanberestoredincases
suchasreinstallationordevicefailure.
OniOS,thegamedataissavedtoiCloudviatheGameCenter(GameKit)API.On
Android,itissavedtoGoogleDriveviatheGooglePlayGameServicesAPI(GPGS).
UnderstandingSavedGames
Asavedgameconsistsoftwoparts:
Anunstructuredbinaryblob-thiscanrepresentwhateverdatayoudeemrelevantto
yourgame,andyourgameisresponsibleforgeneratingandintepretingit.
Structuredmetadata-additionalpropertiesassociatedwiththebinarydataandprovide
informationaboutthisdata.
Thetablebelowdescribescommonsavedgameproperties.
SavedGames
62

Property Description
Name Adeveloper-suppliedshortnameofthesavedgame
ModificationDate Atimestampcorrespondingtothelastmodificationofthesaved
game
DeviceName [iOSonly]Thenameofthedevicethatcommittedthesavedgame
data
Description [GPGSonly][Optional]Adeveloper-supplieddescriptionofthe
savedgame
CoverImageURL [GPGSonly][Optional]TheURLofthePNGcoverimageofthe
savedgame
TotalTimePlayed [GPGSonly][Optional]Adeveloper-suppliedvalue(inmilisesconds)
representingtheplayedtimeofthesavedgame
IsOpen Whetherthesavedgameis"Open".Asavedgamecanonlybe
readorwrittentoifitisopen.
It'suptoyoutodecidehowandwhenuserscansaveagame.Dependingonyourgame
design,youmightwanttoallowonlyasinglesavedgame,oryoumightwanttoallowthe
playertocreatemultiplesavedgameswithdifferentnames(sotheycan,forexample,go
backtovariouscheckpointsandtrydifferentactions).
TheUnderlyingCloudServices
Asmentionedearlier,savedgamesarestoredoniCloud(iOS/GameCenter)andGoogle
Drive(Android/GPGS).Therefore,it'smandatorythattheuserhasaniCloudorGoogle
accounttousethefeatureonthecorrespondingplatform.
OniOS,thesavedgamesaretiedtotheuser'siCloudaccount,nottheGameCenter
account.
OnAndroid,theGoogleDriveassociatedwiththeuser'sGoogleaccountthatwas
authenticatedwithGPGSisused.
Limitations
iOS(iCloud/GameCenter) Android(GoogleDrive/GPGS)
Nohardlimitonthenumberofsaved
games
Nohardlimitonthenumberofsaved
games
Thesizeofasavedgamedataislimited
totheamountofavailablespaceinthe
user'siCloudaccount)
GPGScurrentlyenforcesizelimitson
binarydataandcoverimagesizesof3MB
and800KBrespectively.
SavedGames
63

Youshouldalwaysstrivetominimizetheamountofdatabeingsaved.Thispreventsthe
userfromrunningoutofspaceanddecreasestheamountoftimerequiredtofetchor
saveagamefile.Alsonotethatthegamesavingoperationmayfailifthere'snot
enoughroomintheiCloudorGoogleDriveaccountoftheuser.
OfflineSupport
Yourgamecanstillreadandwritetoasavedgamewhentheplayer'sdeviceisoffline,but
willnotbeabletosyncwiththecloudservicesuntilnetworkconnectivityisestablished.
Oncereconnected,thesynchronizationwillbedoneautomaticallyandasynchronously.
ConflictResolution
Whenauserplaysyourgameonmultipledevicesandusesthesavedgamesfeature,it's
notuncommontohavemultiplesavedgameswiththesamenameandfromdifferent
devices,thuscreatingconflicts.Theseconflictstypicallyoccurwhenaninstanceofyour
gameisunabletoreachthecloudservicewhileattemptingtosyncthesavegamedata,or
whenitupdatesthesavedgamedataonthecloudwithoutloadingthelatestdatafirst.In
general,thebestwaytoavoiddataconflictsistoalwaysloadthelatestdatafromthecloud
servicewhenyourgamestartsuporresumes,andsavedatatotheservicewithreasonable
frequency.However,itisnotalwayspossibletoavoiddataconflicts.Yourapplicationshould
makeeveryefforttohandleconflictstopreserveusers'dataaswellasmaintainagooduser
experience.Fortunately,theSavedGamesAPIcanhelpyouresolvetheseconflicts
automaticallyusingseveraldefaultresolutionstrategies.Italsoprovidesrelevantmethodsto
helpyouimplementyourownresolutionstrategytobettersuityourneeds.
InthisGameOn!-SavedGamesIn-Depth(Part2)YouTubevideobyGoogleDevelopers
you'llfindin-depthexplanationonconflictsbetweensavedgames,howtheyhappen,howto
resolvethemaswellasotherimportantconcepts.ThevideoisdedicatedtotheSaved
GamesfeatureofGooglePlayGamesServices,buttheconceptsarealsoapplicableto
GameCenter.Amustwatch.
UsefulLinks
1. SavingAGame-GameCenterProgrammingGuide
2. SavedGames-GooglePlayGameServices
3. GameOn!-SavedGamesIn-Depth(Part2)YouTubevideo
SavedGamesConfiguration
SavedGames
64

TheSavedGamesfeaturecanbeconfiguredintheSAVEDGAMESCONFIGsectioninthe
GameServicemodulesettings.
EnableSavedGames:youmustenabletheSavedGamesfeaturebeforeusingit
ConflictResolutionStrategy:thedefaultstrategyusedbytheautomaticconflict
resolutionfeature
[Android]DataSource:wherethegamedatacanbefetchedfrom,onlyapplicableon
Android/GooglePlayGameServicesplatform
iOS-SpecificSetup
TousetheSavedGamesserviceoniOS,youmustenabletheiCloudcapabilityforyourapp
intheXcodeproject.MakesuretheiCloudDocumentsserviceisselected.
Also,forthefeaturetofunctionontheiriOSdevices,theusersmustsignedintotheiriCloud
accountandhavetheiCloudDriveserviceenabledintheSettingsapp.
SavedGames
65

Android-SpecificSetup
Again,onAndroidweemploytheSavedGamesfeatureprovidedbytheGooglePlayGame
Services.Therefore,youneedtoenablethisfeatureforyourappintheGooglePlay
Console.Selectyourapp,thenselecttheGameServicestabandenablethefeatureinthe
Gamedetailstab.
SavedGames
66

Notethatyouneedtowaitatleast24hoursafterenablingtheSavedGamesservicefor
ittobeavailable.Attemptingtoauthenticateduringthistimemaycausetheappto
crash.
SavedGames
67

Scripting
ThissectionprovidesaguidetoworkwiththeGameServicesAPI.
YoucanaccesstheGameServicesmoduleAPIviatheGameServicesclassunderthe
EasyMobilenamespace.
Initialization
Initializationisrequiredbeforeanyotheraction,e.g.reportingscores,canbedone.Itshould
onlybedoneoncewhentheappisloaded.IfyouhaveenabledtheAutoinitialization
feature,youdon'tneedtoinitializeinscript(seeAutoInitializationsection).Otherwise,if
youchoosetodisablethatfeature,youcanstarttheinitializationinacoupleofways.
Managedinitialization:thismethodrespectstheMaxLoginRequestsvalueonAndroid
(seeAutoInitializationsection),whichmeansitwillignoreallsubsequentcallsonce
theuserhasdismissedtheloginpopupforanumberoftimedeterminedbyMaxLogin
Requests
Unmanagedinitialization:thismethodsimplyinitializesthemodule,onAndroiditshows
theloginpopupeverytimeaslongastheuserhasn'tbeenauthenticated
OniOS,thesystemautomaticallylimitsthemaximumnumberofloginrequeststo3no
matterwhichmethodisused.
Tousethemanagedinitializationmethod:
//ManagedinitrespectstheMaxLoginRequestsvalue
GameServices.ManagedInit();
Tousetheunmanagedinitializationmethod:
//Unmanagedinit
GameServices.Init();
Notethattheinitializationshouldbedoneearlyandonlyonce,e.g.youcanputitintheStart
methodofaMonoBehaviour,preferablyasingletononesothatitwon'trunagainwhenthe
scenereloads.
Scripting
68

//InitializationintheStartmethodofaMonoBehaviourscript
voidStart()
{
//ManagedinitrespectstheMaxLoginRequestsvalue
GameServices.ManagedInit();
//Dootherstuff...
}
AUserLoginSucceededeventwillbefiredwhentheinitializationcompletesandtheuser
loginssuccessfully.Otherwise,aUserLoginFailedeventwillbefiredinstead.Youcan
optionallysubscribetotheseeventsandtakeappropriateactionsdependedontheuser
loginstatus.
//SubscribetoeventsintheOnEnablemethodofaMonoBehaviorscript
voidOnEnable()
{
GameServices.UserLoginSucceeded+=OnUserLoginSucceeded;
GameServices.UserLoginFailed+=OnUserLoginFailed;
}
//Unsubscribe
voidOnDisable()
{
GameServices.UserLoginSucceeded-=OnUserLoginSucceeded;
GameServices.UserLoginFailed-=OnUserLoginFailed;
}
//Eventhandlers
voidOnUserLoginSucceeded()
{
Debug.Log("Userloggedinsuccessfully.");
}
voidOnUserLoginFailed()
{
Debug.Log("Userloginfailed.");
}
YoucanalsocheckifthemodulehasbeeninitializedatanypointusingtheIsInitialized
method.
//Checkifinitializationhascompleted(theuserhasbeenauthenticated)
boolisInitialized=GameServices.IsInitialized();
Leaderboards
Scripting
69

Thissectionfocusesonworkingwithleaderboards.
ShowLeaderboardUI
ToshowthedefaultleaderboardUI(thesystemviewofleaderboards):
//ShowleaderboardUI
GameServices.ShowLeaderboardUI();
Youshouldcheckiftheinitializationhasfinished(theuserhasbeenauthenticated)before
showingtheleaderboardUI,andtakeappropriateactionsiftheuserisnotloggedin,e.g.
showanalertorstartanotherinitializationprocess.
//CheckforinitializationbeforeshowingleaderboardUI
if(GameServices.IsInitialized())
{
GameServices.ShowLeaderboardUI();
}
else
{
#ifUNITY_ANDROID
GameServices.Init();//startanewinitializationprocess
#elifUNITY_IOS
Debug.Log("CannotshowleaderboardUI:TheuserisnotloggedintoGameCenter.")
;
#endif
}
ToshowtheUIofaspecificleaderboard,simplypassthenameoftheleaderboardintothe
ShowLeaderboardUImethod.Youcanalsooptionallyspecifythetimescope:
//ShowaspecificleaderboardUI
GameServices.ShowLeaderboardUI("YOUR_LEADERBOARD_NAME");
//ShowaspecificleaderboardUIintheWeektimescope
GameServices.ShowLeaderboardUI("YOUR_LEADERBOARD_NAME",TimeScope.Week);
ReportScores
Toreportscorestoaleaderboardyouneedtospecifythenameofthatleaderboard.
Itisstronglyrecommendedthatyouusetheconstantsofleaderboardnamesinthe
generatedEM_GameServicesConstantsclass(seeGameServicesConstants
Generationsection)insteadoftypingthenamesdirectlyinordertopreventruntime
errorsduetotyposandthelikes.
Scripting
70

//Reportascoreof100
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.ReportScore(100,EM_GameServicesConstants.Sample_Leaderboard);
LoadLocalUser'sScore
Youcanloadthescoreofthelocaluser(theauthenticateduser)onaleaderboard,todoso
youneedtospecifythenameoftheleaderboardtoloadscorefromandacallbacktobe
calledwhenthescoreisloaded.
//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
...
//Loadthelocaluser'sscorefromthespecifiedleaderboard
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadLocalUserScore(EM_GameServicesConstants.Sample_Leaderboard,OnLocalUs
erScoreLoaded);
...
//Scoreloadedcallback
voidOnLocalUserScoreLoaded(stringleaderboardName,IScorescore)
{
if(score!=null)
{
Debug.Log("Yourscoreis:"+score.value);
}
else
{
Debug.Log("Youdon'thaveanyscorereportedtoleaderboard"+leaderboardNam
e);
}
}
LoadScores
Youcanloadasetofscoresfromaleaderboardwithwhichyoucanspecifythestartposition
toloadscore,thenumberofscorestoload,aswellasthetimescopeanduserscope.
Scripting
71

//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
...
//Loadasetof20scoresstartingfromrank10inTodaytimescopeandGlobalusers
cope
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadScores(
EM_GameServicesConstants.Sample_Leaderboard,
10,
20,
TimeScope.Today,
UserScope.Global,
OnScoresLoaded
);
...
//Scoresloadedcallback
voidOnScoresLoaded(stringleaderboardName,IScore[]scores)
{
if(scores!=null&&scores.Length>0)
{
Debug.Log("Loaded"+scores.Length+"fromleadeboard"+leaderboardName);
foreach(IScorescoreinscores)
{
Debug.Log("Score:"+score.value+";rank:"+score.rank);
}
}
else
{
Debug.Log("Noscoreloaded.");
}
}
Youcanalsoloadthedefaultsetofscores,whichcontains25scoresaroundthelocaluser's
scoreintheAllTimetimescopeandGlobaluserscope.
Scripting
72

//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
...
//Loadthedefaultsetofscores
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadScores(EM_GameServicesConstants.Sample_Leaderboard,OnScoresLoaded);
...
//Scoresloadedcallback
voidOnScoresLoaded(stringleaderboardName,IScore[]scores)
{
if(scores!=null&&scores.Length>0)
{
Debug.Log("Loaded"+scores.Length+"fromleadeboard"+leaderboardName);
foreach(IScorescoreinscores)
{
Debug.Log("Score:"+score.value+";rank:"+score.rank);
}
}
else
{
Debug.Log("Noscoreloaded.");
}
}
GetAllLeaderboards
Youcanobtainanarrayofallleaderboardscreatedinthemodulesettingsinterface:
//GetthearrayofallleaderboardscreatedintheGameServicemodulesettings
//Leaderboardistheclassrepresentingaleaderboardasdeclaredinthemodulesetti
ngs
//TheGameServicespropertyofEM_Settingsclassholdsthesettingsofthismodule
Leaderboard[]leaderboards=EM_Settings.GameServices.Leaderboards;
//Printallleaderboardnames
foreach(Leaderboardldbinleaderboards)
{
Debug.Log("Leaderboardname:"+ldb.Name);
}
Achievements
Thissectionfocusesonworkingwithachievements.
Scripting
73

ShowAchievementUI
ToshowtheachievementsUI(thesystemviewofachievements):
//ShowachievementsUI
GameServices.ShowAchievementsUI();
Youshouldcheckiftheinitializationhasfinished(theuserhasbeenauthenticated)before
showingtheachievementsUI,andtakeappropriateactionsiftheuserisnotloggedin,e.g.
showanalertorstartanotherinitializationprocess.
//CheckforinitializationbeforeshowingachievementsUI
if(GameServices.IsInitialized())
{
GameServices.ShowAchievementsUI();
}
else
{
#ifUNITY_ANDROID
GameServices.Init();//startanewinitializationprocess
#elifUNITY_IOS
Debug.Log("CannotshowachievementsUI:TheuserisnotloggedintoGameCenter."
);
#endif
}
RevealanAchievement
Torevealahiddenachievement,simplyspecifyitsname.
Asinthecaseofleaderboards,itisstronglyrecommendedthatyouusetheconstants
ofachievementnamesinthegeneratedEM_GameServicesConstantsclassinsteadof
typingthenamesdirectly.
//Revealahiddenachievement
//EM_GameServicesConstants.Sample_Achievementisthegeneratednameconstant
//ofanachievementnamed"SampleAchievement"
GameServices.RevealAchievement(EM_GameServicesConstants.Sample_Achievement);
UnlockanAchievement
Tounlockanachievement:
Scripting
74

//Unlockanachievement
//EM_GameServicesConstants.Sample_Achievementisthegeneratednameconstant
//ofanachievementnamed"SampleAchievement"
GameServices.UnlockAchievement(EM_GameServicesConstants.Sample_Achievement);
ReportIncrementalAchievement'sProgress
Toreporttheprogressofanincrementalachievement:
//Reportarogressof50%foranincrementalachievement
//EM_GameServicesConstants.Sample_Incremental_Achievementisthegeneratednameconst
ant
//ofanincrementalachievementnamed"SampleIncrementalAchievement"
GameServices.ReportAchievementProgress(EM_GameServicesConstants.Sample_Incremental_Ach
ievement,50.0f);
GetAllAchievements
Youcanobtainanarrayofallachievementscreatedinthemodulesettingsinterface:
//GetthearrayofallachievementscreatedintheGameServicemodulesettings
//Achievementistheclassrepresentinganachievementasdeclaredinthemodulesett
ings
//TheGameServicepropertyofEM_Settingsclassholdsthesettingsofthismodule
Achievement[]achievements=EM_Settings.GameServices.Achievements;
//Printallachievementnames
foreach(Achievementacminachievements)
{
Debug.Log("Achievementname:"+acm.Name);
}
LoadUserProfiles
Youcanloadtheprofilesoffriendsofthelocal(authenticated)user.Whentheloading
completestheprovidedcallbackwillbeinvoked.
Scripting
75

//PutthisontopofthefiletouseIUserProfile
UnityEngine.SocialPlatforms;
...
//Loadthelocaluser'sfriendlist
GameServices.LoadFriends(OnFriendsLoaded);
...
//Friendsloadedcallback
voidOnFriendsLoaded(IUserProfile[]friends)
{
if(friends.Length>0)
{
foreach(IUserProfileuserinfriends)
{
Debug.Log("Friend'sname:"+user.userName+";ID:"+user.id);
}
}
else
{
Debug.Log("Couldn'tfindanyfriend.");
}
}
YoucanalsoloaduserprofilesbyprovidingtheirIDs.
Scripting
76

//PutthisontopofthefiletouseIUserProfile
UnityEngine.SocialPlatforms;
...
//LoadtheprofilesoftheuserswithprovidedIDs
//idArrayisthe(string)arrayoftheIDsoftheuserstoloadprofiles
GameServices.LoadUsers(idArray,OnUsersLoaded);
...
//Usersloadedcallback
voidOnUsersLoaded(IUserProfile[]users)
{
if(users.Length>0)
{
foreach(IUserProfileuserinusers)
{
Debug.Log("User'sname:"+user.userName+";ID:"+user.id);
}
}
else
{
Debug.Log("Couldn'tfindanyuserwiththespecifiedIDs.");
}
}
SignOut
Tosigntheuserout,simplycalltheSignOutmethod.Notethatthismethodisonlyeffective
onAndroid.
//SigntheuseroutonAndroid
GameServices.SignOut();
Scripting
77

SavedGamesScripting
SavedGamesfeatureisavailableonEasyMobileProonly.
ThissectionprovidesaguidetoworkwiththeSavedGamesAPIoftheGameServices
module.
YoucanaccesstheSavedGamesAPIviatheSavedGamespropertyofthe
GameServicesclassundertheEasyMobilenamespace.
Workingwithsavedgamesinvolvesthefollowingoperations:
Operation Description
Open
Asavedgamemustbeopenedbeforeitcanbeusedforreadorwrite
operation.Ifyouattempttoopenanon-existingsavedgame,anewone
willbecreatedandwillbeopenedautomatically.Youmustresolveany
conflictsassociatedwithasavedgamewhenopeningit.Youcanhave
theconflictsresolvedautomaticallyusingoneofthedefaultstrategies,or
implementyourownstrategytoresolvethemmanually.
Write
Updatethedataassociatedwithasavedgame,thesavedgamemustbe
openbeforewriting,anditwillbeclosedautomaticallyaftertheoperation
hasfinished.
Read Retrievethedataassociatedwithasavedgame,thesavedgamemust
beopen.
Delete Deleteasavedgamefromthecloudservice
OpenaSavedGame
YoucanopenasavedgameusingeithertheOpenWithAutomaticConflictResolutionorthe
OpenWithManualConflictResolutionmethod.Bothmethodsopenasavedgamewiththe
specifiedname,orcreateanewoneifnoneexists.Thesavedgamereturnedintheir
callbackswillbeopenwhichmeansitcanbeusedforreadorwriteoperation.Thedifference
betweenthetwoiswhethersavedgameconflicts,ifany,willberesolvedautomaticallyor
manually.
IfthecurrentplatformisGooglePlayGameServices,thesemethodsusethedata
sourcespecifiedinthemodulesettings.
OpenWithAutomaticConflictResolution
SavedGames
78

Asitsnamesuggests,whenopeningasavedgameusingthismethod,anyoutstanding
conflictswillberesolvedautomaticallyusingtheresolutionstrategyspecifiedinthemodule
settings.
usingEasyMobile;
...
privateSavedGamemySavedGame;
//Openasavedgamewithautomaticconflictresolution
voidOpenSavedGame()
{
//Openasavedgamenamed"My_Saved_Game"andresolveconflictsautomatically
ifany.
GameServices.SavedGames.OpenWithAutomaticConflictResolution("My_Saved_Game",O
penSavedGameCallback);
}
//Opensavedgamecallback
voidOpenSavedGameCallback(SavedGamesavedGame,stringerror)
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgameopenedsuccessfully!");
mySavedGame=savedGame;//keepareferenceforlateroperatio
ns
}
else
{
Debug.Log("Opensavedgamefailedwitherror:"+error);
}
}
OpenWithManualConflictResolution
Ifthesavedgamebeingopenedhasoutstandingconflicts,theywillberesolvedmanually
usingthespecifiedconflictresolutionfunction.Thisfunctionmustbeimplementedbyyou
anditiswhereyouprovideyourcustomconflictresolutionstrategy,incasenoneofthe
defaultstrategiessuitsyourneeds.Thefunctionwillbeinvokedautomaticallywhena
conflictisencounteredwhileopeningasavedgameandcanbeinvokedmultipletimesifthe
savedgamehasmorethanoneoutstandingconflict.Thereforeitmustbedesignedto
handlemultipleinvocations.
TheconflictresolutionfunctionreceivestheBaseandRemoteversionsoftheconflicting
savedgame(pleasecheckoutthisGameOn!-SavedGamesIn-Depth(Part2)YouTube
videobyGoogleDevelopersforanexcellentexplanationontheconceptsof"base"and
SavedGames
79

"remote").Thesepassedsavedgamesareallopen.IfOpenWithManualConflictResolution
wasinvokedwithprefetchDataOnConflictsettotrue,thebinarydataassociatedwiththese
savedgameswillloadedandpassedtotheconflictresolutionfunctiontoo.Usethereturn
valueofthisfunctiontodeterminewhetherthebaseortheremotewillbechosenasthe
canonicalversionofthesavedgame.
Thecallbackwillbeinvokedwhenallconflicts(ifany)havebeenresolvedandtheoperation
finishes.
usingEasyMobile;
...
privateSavedGamemySavedGame;
//Openasavedgamewithmanualconflictresolution
voidOpenSavedGame()
{
//Openasavedgamenamed"My_Saved_Game"andresolveanyoutstandingconflic
tsmanuallyusing
//thespecifiedresolutionfunction.
GameServices.SavedGames.OpenWithManualConflictResolution(
"My_Saved_Game",
true,//prefetchDataOnConflict
MyConflictResolutionFunction,
OpenSavedGameCallback
);
}
//Theconflictresolutionfunction.
//baseGameandremoteGameareallopen.
//IfOpenWithManualConflictResolutionwasinvokedwithprefetchDataOnConflictsetto
true,
//baseDataandremoteDatawillcontainthebinarydataassociatedwithbaseGameandr
emoteGamerespective.
//Theywillbenullotherwise.
//Inthisfunctionyoucanperformrequiredcalculation,comparisonbetweentwoversi
ons,etc.todecide
//whichonewillbethecanonicalversionofthesavedgame.Usethereturnvalueto
indicateyourdecision.
SavedGameConflictResolutionStrategyMyConflictResolutionFunction(SavedGamebaseGame,b
yte[]baseData,
SavedGameremoteGame,b
yte[]remoteData)
{
{
//Performwhateverrequiredcalculation,comparison,etc.onthetwo
versions
//andtheirassociateddatatohelpyoudecidewhichversionshouldb
echosen.
...
SavedGames
80

//Afterdeterminingthecanonicalversion,usethereturnvaluetoin
dicateyourchoice
returnSavedGameConflictResolutionStrategy.UseBase;//usethe
baseversion
//Ifyouwanttoselecttheremoteversioninstead,justchangeitto
//returnSavedGameConflictResolutionStrategy.UseRemote;
}
}
//Opensavedgamecallback
voidOpenSavedGameCallback(SavedGamesavedGame,stringerror)
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgameopenedsuccessfully!");
mySavedGame=savedGame;//keepareferenceforlateroperatio
ns
}
else
{
Debug.Log("Opensavedgamefailedwitherror:"+error);
}
}
Incaseyouwanttomergethedatafromdifferentversions,simplyspecifyeitherthe
baseortheremoteasthechosenversion.Onceallconflictsareresolvedandthe
savedhasbeenopenedsuccessfully,performawriteoperationusingthemergedata.
WritingSavedGameData
Tocommitnewdatatoasavedgame,usetheWriteSavedGameDatamethod.Asmention
earlier,thesavedgamemustbeopenbeforewritingortheoperationwillfail.Whenthis
methodcompletessuccessfully,thedataisdurablypersistedtodiskandwilleventuallybe
uploadedthethecloud(inpractice,thisprocesshappensveryquicklyunlessthedevice
doesn'thaveanetworkconnection).Aftertheoperationfinishes,thesavedgamewillbe
closedautomatically.Thisistoforceittobeopenedonceagain(thusresolvingany
outstandingconflicts)beforeanothercommitcanbemade.
SavedGames
81

usingEasyMobile;
...
//Updatesthegivenbinarydatatothespecifiedsavedgame
voidWriteSavedGame(SavedGamesavedGame,byte[]data)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforwriting
GameServices.SavedGames.WriteSavedGameData(
savedGame,
data,
(SavedGameupdatedSavedGame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenwrittensuccessfully!");
}
else
{
Debug.Log("Writingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeatthe
process.
Debug.Log("Youmustopenthesavedgamebeforewritingtoit.");
}
}
Besidethebinarydata,youcanalsoupdatethemetadata(properties)ofasavedgame.
JustusetheoverloadingversionofWriteSavedGameDatathatacceptsa
SavedGameInfoUpdatestruct.
Somesavedgamepropertiesareonlyavailableonacertainplatform,pleasereview
theGameService>ModuleConfiguration>SavedGamesectionfordetailed
information.
SavedGames
82

usingEasyMobile;
...
//UpdatesthebinarydataANDthepropertiesofasavedgame
voidWriteSavedGame(SavedGamesavedGame,byte[]data)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforwriting
//Preparetheupdatedmetadataofthesavedgame
SavedGameInfoUpdate.Builderbuilder=newSavedGameInfoUpdate.Builder();
builder.WithUpdatedDescription("New_Description");
builder.WithUpdatedPlayedTime(TimeSpan.FromMinutes(30));//updatetheplay
edtimeto30minutes
SavedGameInfoUpdateinfoUpdate=builder.Build();
GameServices.SavedGames.WriteSavedGameData(
savedGame,
data,
infoUpdate,//updatesavedgameproperties
(SavedGameupdatedSavedGame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenwrittensuccessfully!");
}
else
{
Debug.Log("Writingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeatthe
process.
Debug.Log("Youmustopenthesavedgamebeforewritingtoit.");
}
}
ReadingSavedGameData
Toreadasavedgamedata,usetheReadSavedGameDatamethod.Thesavedgamemust
beopenbeforereading.Thecallbackwillbeinvokedwhentheoperationfinishesandwill
receivetheretrieveddataasabytearray,whichcanbeemptyifthesavedgamehasno
datacommittedpreviously.
SavedGames
83

usingEasyMobile;
...
//Retrievesthebinarydataassociatedwiththespecifiedsavedgame
voidReadSavedGame(SavedGamesavedGame)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforreading
GameServices.SavedGames.ReadSavedGameData(
savedGame,
(SavedGamegame,byte[]data,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenretrievedsuccessfully!");
//Hereyoucanprocessthedataasyouwish.
if(data.Length>0)
{
//Dataprocessing
...
}
else
{
Debug.Log("Thesavedgamehasnodata!");
}
}
else
{
Debug.Log("Readingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeatthe
process.
Debug.Log("Youmustopenthesavedgamebeforereadingitsdata.");
}
}
DeletingASavedGame
Todeleteasavedgame,simplycalltheDeleteSavedGamemethod.
SavedGames
84

usingEasyMobile;
...
//Deletesasavedgame
voidDeleteSavedGame(SavedGamesavedGame)
{
GameServices.SavedGames.DeleteSavedGame(savedGame);
}
FetchAllSavedGames
Whenimplementthesavedgamesfeatureinyourgame,chancesareyouwillwanttoshow
theuseralistofexistingsavedgamesforthemtochoosefrom.Insuchcase,youcanuse
theFetchAllSavedGamesmethodtoretrieveallknowsavedgames.Acallbackwillbe
invokedwhenthemethodcompletes,receivinganarrayofsavedgameswhichcanbe
emptyifnosavedgamewascreatedbefore.Notethatallthereturnedsavedgamesare
NOTopen.
IfthecurrentplatformisGooglePlayGameServices,thismethodretrievessaved
gamesfromthedatasourcespecifiedinthemodulesettings.
usingEasyMobile;
...
//Fetchesallknowsavedgames.
voidFetchSavedGames()
{
GameServices.SavedGames.FetchAllSavedGames(
(SavedGame[]games,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Fetchedsavedgamessuccessfully!Got"+games.Length+"
savedgames.");
//HereyoucanshowaUItodisplaythesesavedgamestotheuser...
}
else
{
Debug.Log("Fetchingsavedgamesfailedwitherror"+error);
}
}
);
}
SavedGames
85

[Android]Built-inSavedGameUI
OnAndroid,theSavedGamesfeatureofGooglePlayGameServicesoffersabuilt-inUI
fromwhichtheusercanopen,selectordeleteasavedgames.YoucanshowthisUIby
callingtheShowSelectSavedGameUImethod.AcallbackwillbeinvokedwhentheUIis
closed,receivingtheselectedsavedgameifany.
Thismethodisano-oponiOS/GameCenterplatform.
usingEasyMobile;
...
//ShowstheGPGSbuilt-insavedgameUI.
voidShowGPGSSavedGameUI()
{
GameServices.SavedGames.ShowSelectSavedGameUI(
"SelectSavedGame",//UItitle
5,//maximumnumberofdisplayedsavedgames
true,//allowcreatingsavedgames
true,//allowdeletingsavedgames
(SavedGamegame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Youselectedsavedgame:"+game.Name);
}
else
{
Debug.Log(error);
}
}
);
}
SavedGames
86

PlayMakerActions
ThePlayMakeractionsoftheGameServicesmodulearegroupinthecategoryEasyMobile
-GameServicesinthePlayMaker'sActionBrowser.
PleaserefertotheGameServicesDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
87

SavedGames
ThePlayMakeractionsoftheSavedGamesfeaturearegroupinthecategoryEasyMobile-
SavedGamesinthePlayMaker'sActionBrowser.
PleaserefertotheGameServicesDemo_SavedGames_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
88

PlayMakerActions
89

GIF
GIFmoduleisavailableonEasyMobileProonly.
TheGIFmoduleprovidesyouconvenienttoolstorecordscreenactivitiesintoashortclip,
playtherecordedclipandexportitintoaGIFimage.YoucanthenuploadtheGIFfileto
hostingsiteslikeGiphyandfinallyshareitsURLtosocialnetworks.Inshort,thismodule
helpsyoueasilyaddtheGIFsharingfeaturetoyourgame,whichallowstheusertoshare
animatedGIFimagesofthegameplay,insteadofstillscreenshots,tosocialnetworks
includingFacebookandTwitter.Thefollowingpictureillustratesatypicalworkflowofsuch
feature.
Here'resomehighlightsofthismodule:
Highperformance,mobile-friendlyGIFgenerator
Lowoverheadscreen/camerarecorder
GIFgenerationisdoneinnativecode(iOSandAndroid)onaseparatethreadto
allowfastexportingwhileminimizingimpacttothemainthread.Exportcallbacks
arestillcalledfrommainthreadthough,soyoucansafelyaccessUnityAPIinthe
callbackhandlers
Flexible,fullycontrollableprocess
Youhavefullcontrolonthesizes,length,framerate,loopmodeandqualityofthe
exportedGIF
Youcanalsosetthepriorityoftheexportingthreadtobestsuityourneeds
HighqualityGIF
ExportedGIFemploysGIF89aformatanduses256-colorlocalpalettes(one
paletteperframe)
FrameimagedataisLWZcompressed
WorksinUnityeditor
GIF
90

GIFexportingalsoworksintheeditor,mostlyfortestingpurpose.Onmobiles,the
exportingisdoneinnativecode,whileineditoritisdoneinmanagedcodeusing
anadaptedversionoftheMomentsplugin(seetheAcknowledgementsection)
EasyGIFsharing
ThismodulealsoprovidesGiphyAPIforuploadingGIFimagestoGiphy,sothat
theycanbesharedandplayedonmajorsocialnetworksincludingFacebookand
TwitterusingtheGiphyhostedURLs
Acknowledgement
TherecorderusedinthismoduleisadaptedfromtherecorderoftheMomentspluginby
Chman(ThomasHourdel).Also,inUnityeditor,GIFgenerationisdoneusinganadapted
versionofthisplugin.
GIF
91

Setup
Thissectionexplainsthevariouscomponents,objectsandconceptsinvolvedinclip
recording,clipplayingandGIFexporting.Italsoprovidesaguideoncreatingand
configuringrelevantobjectsandcomponents.
TheRecordercomponent
TheRecordercomponentrecordsthecontentrenderedbyacameraandreturnsthe
recordedclip.Tostartrecording,simplyaddaRecordercomponenttothecamerathat
rendersthecontentyou'reinterestedinrecording(normallythiswillbetheMainCamera).To
addthecomponenttoacamera,selectthatcameraintheHierarchy,thenclickAdd
Component>EasyMobile>Recorder.
OncetheRecordercomponentisaddedtothecamera,youcanstartconfiguringitinthe
inspectortodeterminehowtherecordedclip(andasaresult,theexportedGIF)willbelike.
AutoHeight:whethertheclipheightshouldbecomputedautomaticallyfromthe
Setup
92

specifiedwidthandthecamera'saspectratio,whichisusefultomakesuretheexported
GIFhasacorrectaspectratio
Width:thewidthoftherecordedclipinpixels
Height:theheightoftherecordedclipinpixels
FramesPerSecond:theframerateoftheclip
Length:thecliplengthinseconds;therecorderautomaticallydiscardsoldcontentto
preservethislength,e.g.ifyousetthisvalueto3seconds,onlylast3secondsofthe
recordingwillbestoredintheresultedclip,therestwillbediscarded
EstimatedVRamUsage:theestimatedmemoryusedforrecording,calculatedbasedon
theabovesettings
CurrentState:thecurrentstatusoftherecorder,whichiseitherStoppedorRecording
Nowthattherecorderisconfigured,youcanstartandstopitsrecordingactivityfromscript
(seetheScriptingsection).Oncetherecordingisstopped,therecordedclipwillbereturned
forplaybackofGIFexporting.
RecordingtheUI
TorecordtheUI(Canvascontent),youneedtosettheCanvasRenderModetoWorld
SpaceorScreenSpace-Camera,andsettheRenderCameratotheonecontaining
theRecordercomponentinthelattercase.
Recordingmultiplecompositedcameras
Ifyourscenecontainsmultiplecamerasbeingcomposited(usingCamera.depthand
Clearflags),youcanaddtheRecordercomponenttothetop-mostcamera,soit
captureswhatevercontentbeingcompositedandshownbythatcamera.
TheAnimatedClipclass
RecordedclipsarerepresentedbytheAnimatedClipclass,whichhasfollowingproperties:
Width:thewidthoftheclipinpixels
Height:theheightoftheclipinpixels
FramePerSecond:theframerateoftheclip
Length:thelengthoftheclipinseconds
Frames:anarrayofframes,eachframeisaRenderTextureobject
Playback
EasyMobileprovidestwobuilt-inobjectsdedicatedforplayingrecordedclips:theClip
PlayerandClipPlayerUIobjects.Youcancreatethemfromthecontextmenu(asyou
wouldwithotherUnitybuilt-inobjects),configurethemintheinspector,andstartorstoptheir
Setup
93

playingactivityfromscript(seetheScriptingsection).
ClipPlayer
TheClipPlayerisanon-UIobject,whichisbasicallyaQuadobjectequippedwitha
ClipPlayercomponent.Itismeanttobeusedinsidethegameworld.TocreateaClipPlayer
object,right-clickintheHierarchywindowtoopenthecontextmenu,thenselectEasy
Mobile>ClipPlayer.
EachClipPlayerobjectcontainsaClipPlayercomponent.
TheonlyparameterofthiscomponentistheScaleMode,whichcantakeoneof3values:
None:don'tadjusttheobjectsizes
AutoHeight:keepsthecurrentheightoftheobject(theYcomponentofitslocalScale),
andadjustthewidth(theXcomponentofitslocalScale)tomatchtheaspectratioofthe
clipbeingplayed
AutoWidth:keepsthecurrentwidthoftheobject(theXcomponentofitslocalScale),
andadjusttheheight(theYcomponentofitslocalScale)tomatchtheaspectratioof
theclipbeingplayed
ClipPlayerUI
Setup
94

TheClipPlayerUI,asitnameimplies,isaUIobjectlivinginsideaCanvas.Itistheobjectto
usewhenyouwanttoplayaclipinsidetheUI.ItisbasicallyaRawImageobjectequipped
withaClipPlayerUIcomponent.TocreateaClipPlayerUIobject,right-clickintheHierarchy
windowtoopenthecontextmenu,thenselectEasyMobile>ClipPlayer(UI).
EachClipPlayerUIobjectcontainsaClipPlayerUIcomponent.
TheonlyparameterofthiscomponentistheScaleMode,whichcantakeoneof3values:
None:don'tadjusttheobjectsizes
AutoHeight:keepsthecurrentheightoftheobject(theHeightvalueinitsRect
Transform),andadjustthewidth(theWidthvalueinitsRectTransform)tomatchthe
aspectratiooftheclipbeingplayed
AutoWidth:keepsthecurrentwidthoftheobject(theWidthvalueinitsRect
Transform),andadjusttheheight(theHeightvalueinitsRectTransform)tomatchthe
aspectratiooftheclipbeingplayed
CustomClipPlayer
Besidethetwobuilt-inclipplayersprovidedbyEasyMobile,youcanconstructyourown
playertoserveyourspecificneeds.Tomakeitconsistentwithotherplayers,andcompatible
withEasyMobileAPI,thisplayershouldcontainascriptimplementingtheIClipPlayer
interface,whichisresponsibleforapplyingtheframes(RenderTextures)oftheclip,atthe
requiredframerate,towhatevertexture-displayingcomponentitisequippedwith.
Setup
95

Setup
96

Scripting
ThissectionprovidesaguidetoworkwiththeGIFAPI.Atthisstage,it'sassumedthatyou
havesetuparecorderforthecamerayouwanttorecord,andcreatedanappropriateclip
playertoplaytherecordedclip.Ifyou'renotfamiliarwiththeseconcepts,pleasereviewthe
Setupsection.
YoucanaccesstheGIFmoduleAPIviatheGifclassundertheEasyMobile
namespace.AsforGiphyAPI,usetheGiphyclass.
Recording
Tostartrecordingonthecreatedrecorder,usetheStartRecordingmethod.Youcandothis
assoonasthegamestarts;therecorderonlystoresafewlastseconds(specifiedbythe
LengthparameterintheRecorderinspector)oftherecording,andautomaticallydiscardsthe
rest.
//UsetheEasyMobilenamespace
usingEasyMobile;
...
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
...
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
Tostoprecording,simplycalltheStopRecordingmethod,passingtherelevantrecorder.The
methodreturnsanAnimatedClipobject,whichcanbeplayedorexportedintoaGIFimage
afterward.Tocontinuethepreviousexample:
Scripting
97

//UsetheEasyMobilenamespace
usingEasyMobile;
...
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
...
//Asuitabletimetostoprecordingmaybewhenthegameends(theplayerdies)
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//Dootherstuff...
}
Playback
Toplayarecordedclipusingapre-createdclipplayer,usethePlayClipmethod.This
methodreceivesasargumentanIClipPlayerinterface,whichisimplementedbyboth
ClipPlayerandClipPlayerUIclasses,thereforeitworkswithbothClipPlayerandClipPlayer
UIobject.ThesecondargumentisanAnimatedClipobject.Otherargumentsincludean
optionaldelaytimebeforetheplayingstarts,andtheloopingmode.Youcanpause,resume
andstoptheplayerusingthePausePlayer,ResumePlayerandStopPlayermethods,
respectively.
Tocontinuethepreviousexample:
//UsetheEasyMobilenamespace
usingEasyMobile;
Scripting
98

...
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Supposeyou'vecreatedaClipPlayerUIobject(ClipPlayerwillalsowork)
//Dragthepre-createdclipplayertothisfieldintheinspector
publicClipPlayerUIclipPlayer;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
...
//Asuitabletimetostoprecordingmaybewhenthegameends(theplayerdies)
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//Playtherecordedclip!
PlayMyClip();
}
...
//Thismethodplaystherecordedcliponthecreatedplayer,
//withnodelaybeforeplaying,andloopindefinitely.
voidPlayMyClip()
{
Gif.PlayClip(clipPlayer,myClip);
}
//Thismethodplaystherecordedcliponthecreatedplayer,
//withadelayof1secondsbeforeplaying,andloopindefinitely,
//(youcansetloop=falsetoplaythecliponlyonce)
voidPlayMyClipWithDelay()
{
Gif.PlayClip(clipPlayer,myClip,1f,true);
}
//Thismethodpausestheplayer.
Scripting
99

voidPausePlayer()
{
Gif.PausePlayer(clipPlayer);
}
//Thismethodun-pausestheplayer.
voidUnPausePlayer()
{
Gif.ResumePlayer(clipPlayer);
}
//Thismethodstopstheplayer.
voidStopPlayer()
{
Gif.StopPlayer(clipPlayer);
}
GIFExport
ToexporttherecordedclipintoaGIFimage,usetheExportGifmethod.Intheeditor,the
exportedGIFfilewillbestoredrightundertheAssetsfolder;onmobiledevices,thestorage
locationisApplication.persistentDataPath.Youcanspecifythefilenameandthequalityof
theGIFimageaswellasthepriorityoftheexportingthread.Thequalitysettingaccepts
valuesfrom1to100(inputswillbeclampedtothisrange).Biggervalueswillresultinbetter
lookingGIFs,butwilltakeslightlylongerprocessingtime;80isgenerallyagoodvaluein
termsoftime-qualitybalance.Thismethodhastwocallbacks:oneiscalledrepeatedly
duringtheprocessandreceivestheprogressvalue(0to1),theotheriscalledwhenthe
exportcompletesandreceivesthefilepathofthegeneratedimage.ThoughtheGIF
generationprocessisdoneinaseparatethread,thesecallbacksareguaranteedtobe
calledfromthemainthread,soyoucansafelyaccessallUnityAPIfromwithinthem.
IntherarecasethatyouwanttocontroltheloopingmodeoftheexportedGIF(thedefaultis
loopindefinitely),usethevariantofExportGifthathasaloopparameter(notethatsomeGIF
playersmayignorethissetting):
loop<0:disablelooping(playonce)
loop=0:loopindefinitely
loop>0:loopanumberoftimes
Inthefollowingexample,we'llexportaGIFimagefromtherecordedclipreturnedafterthe
recordinghasstopped.
//UsetheEasyMobilenamespace
usingEasyMobile;
...
Scripting
100

//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
...
//Asuitabletimetostoprecordingcanbewhenthegameends(theplayerdies)
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//ExportGIFimagefromtheresultedclip
ExportMyGif();
}
...
//ThismethodexportsaGIFimagefromtherecordedclip.
voidExportMyGif()
{
//Parametersetup
stringfilename="myGif";//filename,noneedthe".gif"extension
intloop=0;//-1:noloop,0:loopindefinitely,>0:loopaset
numberoftimes
intquality=80;//80isagoodvalueintermsoftime-qualitybalan
ce
System.Threading.ThreadPrioritytPriority=System.Threading.ThreadPriority.Normal
;//exportingthreadpriority
Gif.ExportGif(myClip,
filename,
loop,
quality,
tPriority,
OnGifExportProgress,
OnGifExportCompleted);
}
Scripting
101

//ThiscallbackiscalledrepeatedlyduringtheGIFexportingprocess.
//Itreceivesareferencetooriginalclipandaprogressvaluerangingfrom0to1.
voidOnGifExportProgress(AnimatedClipclip,floatprogress)
{
Debug.Log(string.Format("Exportprogress:{0:P0}",progress));
}
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimag
e.
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
{
Debug.Log("AGIFimagehasbeencreatedat"+path);
}
DisposingofAnimatedClip
Internally,eachAnimatedClipobjectconsistsofanarrayofRenderTexture,a"nativeengine
object"type,whichisnotgarbagecollectedasnormalmanagedtypes.Thatmeansthese
rendertextureswon'tbe"destroyed"automaticallywhentheircontainingclipisgarbage
collected(theclipobjectdoesgetcollected,buttherendertexturesitreferencesdon't,thus
creatingmemoryleaks).Totakecareofthisissue,wehavetheAnimatedClipimplementthe
IDisposableinterfaceandprovidetheDisposemethodtoreleasetherendertextures,as
Unityrecommended.YoumustcallthisDisposemethod,preferablyassoonasyou'redone
withusingaclip(afterplayingorexportingGIF),tomakesuretherendertexturesare
properlyreleasedandnotcausememoryissues.
We'llextendtheOnGifExportCompletedcallbackhandlerofthepreviousexampletodispose
therecordedclipassoonaswe'vegeneratedaGIFimagefromit.
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimag
e.
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
{
Debug.Log("AGIFimagehasbeencreatedat"+path);
//We'vedoneusingtheclip,disposeittosavememory
if(clip==myClip)
{
myClip.Dispose();
myClip=null;
}
}
GIFSharing
Scripting
102

NowthataGIFimagehasbeencreated,youmaywanttoshareit(becauseit'snotfun
otherwise,isit?).AcommonapproachistofirstuploadtheimagetoGiphy,apopularGIF
hostingsite,andthensharethereturnedURLtoothersocialnetworkslikeFacebookand
Twitter,usingEasyMobile'sNativeSharingfeature(seetheNativeSharing>Scripting
section,inparticulartheShareURLmethod).
AccordingtoGiphyAPIdocumentation,hostedGiphyURLsaresupportedandplayon
everymajorsocialnetwork.
UploadtoGiphy
TouploadaGIFimagetoGiphy,usetheUploadmethodoftheGiphyclass.Youcanupload
alocalimageonyourdevice,oranimagehostedonline,providedthatyouhaveitsURL.
Beforedoingso,you'llneedtopreparetheuploadcontentbycreatinga
GiphyUploadParamsstruct.Inthisstructyou'llspecifyeitherthefilepathofthelocalimage,
ortheURLoftheonlineimagetoupload.Notethatifbothparametersareprovided,the
localfilepathwillbeusedovertheURL.Withinthisstructyoucanalsospecifyother
optionalparameterssuchasimagetags,thesourceoftheimage(e.g.yourwebsite),or
marktheimageasprivate(onlyvisiblebyyouonGiphy).TheUploadmethodhasthree
callbacks:thefirstoneiscalledrepeatedlyduringtheuploadprocess,receivingaprogress
value(0to1);thesecondoneiscalledoncetheuploadhascompleted,receivingtheURLof
theuploadedimage;andthelastonewillbecallediftheuploadhasfailed,receivingthe
errormessage.Allcallbacksarecalledfromthemainthread.
GiphyBetaandProductionKey
TheUploadmethodhastwovariants:oneusingGiphy'spublicbetakey,andtheother
usingyourownchannelusernameandproductionAPIkey.Thepublicbetakeyis
meanttobeusedindevelopmentonly.AccordingtoGiphyUploadAPIdocumentation,
itis"subjecttoratelimitconstraints",andthey"donotencourageliveproduction
deploymentstousethepublickey".IfyouhavecreatedaGiphychannelandwantto
uploadGIFimagesdirectlytothatchannel,you'llneedtorequestanUploadProduction
Key,thenprovidethatkeyandyourchannelusernametotheUploadmethod.
We'llextendtheaboveexample,andmodifytheOnGifExportCompletedcallbackhandlerto
uploadtheGIFimagetoGiphyonceitiscreated.We'lldemonstratetwocases:uploadusing
thepublicbetakeyanduploadusingyourownproductionkey.
...
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimag
e.
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
Scripting
103

{
Debug.Log("AGIFimagehasbeencreatedat"+path);
//We'vedoneusingtheclip,disposeittosavememory
if(clip==myClip)
{
myClip.Dispose();
myClip=null;
}
//TheGIFimagehasbeencreated,nowwe'lluploadittoGiphy
//Firstpreparetheuploadcontent
varcontent=newGiphyUploadParams();
content.localImagePath=path;//thefilepathofthegeneratedGIFimage
content.tags="easymobile,sglibgames,unity";//optionalimagetags,comma
-delimited
content.sourcePostUrl="YOUR_WEBSITE_ADDRESS";//optionalimagesource,e.g.
yourwebsite
content.isHidden=false;//optionalhiddenflag,settotruetomarktheimag
easprivate
//UploadtheimagetoGiphyusingthepublicbetakey
UploadToGiphyWithBetaKey(content);
}
//ThismethoduploadsaGIFimagetoGiphyusingthepublicbetakey,
//noneedtospecifyanyusernameorAPIkeyhere.
voidUploadToGiphyWithBetaKey(GiphyUploadParamscontent)
{
Giphy.Upload(content,OnGiphyUploadProgress,OnGiphyUploadCompleted,OnGiphyUpload
Failed);
}
//ThismethoduploadsaGIFimagetoyourownGiphychannel,
//usingyourchannelusernameandproductionkey.
voidUploadToGiphyWithProductionKey(GiphyUploadParamscontent)
{
Giphy.Upload("YOUR_CHANNEL_USERNAME","YOUR_PRODUCTION_KEY",
content,
OnGiphyUploadProgress,
OnGiphyUploadCompleted,
OnGiphyUploadFailed);
}
//Thiscallbackiscalledrepeatedlyduringtheuploadingprocess.
//Itreceivesaprogressvaluerangingfrom0to1.
voidOnGiphyUploadProgress(floatprogress)
{
Debug.Log(string.Format("Uploadprogress:{0:P0}",progress));
}
//Thiscallbackiscalledoncetheuploadinghascompleted.
//ItreceivestheURLoftheuploadedimage.
Scripting
104

voidOnGiphyUploadCompleted(stringurl)
{
Debug.Log("TheGIFimagehasbeenuploadedsuccessfullytoGiphyat"+url);
}
//Thiscallbackiscallediftheuploadhasfailed.
//Itreceivestheerrormessage.
voidOnGiphyUploadFailed(stringerror)
{
Debug.Log("UploadingtoGiphyhasfailedwitherror:"+error);
}
DisplaytheGiphyAttributionMarks
TorequestaProductionKey,Giphyrequireyoutodisplaythe"PoweredbyGiphy"
attributionmarkswhenevertheirAPIisutilizedinyourapp,andprovidescreenshotsofyour
attributionplacementwhensubmittingforthekey.Totakecareofthis,weprovidethestatic
IsUsingAPIbooleanpropertyinsidetheGiphyclass.Thispropertywillbetrueaslongas
GiphyAPIisinuse,toletyouknowwhentoshowtheirattributionmarks.Youcandisplay
theattributionlogousinganImageoraSpriteobject,thenpollthispropertyinsidethe
Update()function,andactivateordeactivatetheobjectaccordingly.
YoucandownloadGiphy'sofficialattributionmarkshere.
//Dragtheobjectdisplayingtheattributionmarkstothisfieldintheinspector
publicGameObjectattribution;
...
voidUpdate()
{
attribution.SetActive(Giphy.IsUsingAPI);
}
ShareGiphyURLs
AfteruploadingyourGIFimagetoGiphyandobtainitsURL,youcansharethisURLusing
theShareURLmethodoftheMobileNativeShareclass.Intheexamplebelow,we'llmodify
theOnGiphyUploadCompletedcallbackhandlerofthepreviousexampletostorethe
returnedURLintoaglobalvariable,whichcanbeusedforlatersharing.
Scripting
105

...
//GlobalvariabletoholdtheGiphyURLoftheuploadedGIF
stringgiphyURL;
...
//Thiscallbackiscalledoncetheuploadinghascompleted.
//ItreceivestheURLoftheuploadedimage.
voidOnGiphyUploadCompleted(stringurl)
{
Debug.Log("TheGIFimagehasbeenuploadedsuccessfullytoGiphyat"+url);
//StoretheURLintoourglobalvariable
giphyURL=url;
}
...
//ThismethodsharestheURLusingthenativesharingutilityoniOSandAndroid
publicvoidShareGiphyURL()
{
if(!string.IsNullOrEmpty(giphyURL))
{
MobileNativeShare.ShareURL(giphyURL);
}
}
Scripting
106

PlayMakerActions
ThePlayMakeractionsoftheGIFmodulearegroupinthecategoryEasyMobile-Gifinthe
PlayMaker'sActionBrowser.
PleaserefertotheGifDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
107

PlayMakerActions
108

In-AppPurchasing
TheIn-AppPurchasingmodulehelpsyouquicklysetupandselldigitalproductsinyour
game.Here'resomehighlightsofthismodules:
LeveragesUnityIn-AppPurchasingservice
ThismoduleisbuiltontopofUnityIAPservice,apowerfulservicethatsupports
mostappstoresincludingiOSAppStore,GooglePlay,AmazonApps,Samsung
GALAXYAppsandTizenStore
UnityIAPistightlyintegratedwiththeUnityengine,socompatibilityandreliability
canbeexpected
Easymanagementofproductcatalog
EasyMobile'scustomeditorfeaturesafriendlyinterfacethathelpsyoueasilyadd,
editorremoveproducts
Receiptvalidation
Localreceiptvalidationthatoffersextrasecurity
In-AppPurchasing
109

ModuleConfiguration
TousetheIn-AppPurchasingmoduleyoumustfirstenableit.GotoWindow>EasyMobile
>Settings,selecttheIn-AppPurchasingtab,thenclicktheright-handsidetoggletoenable
andstartconfiguringthemodule.
ModuleConfiguration
110

EnableUnityIAP
TheIn-AppPurchasingmodulerequiresUnityIAPservicetobeenabled.Itwillautomatically
checkfortheservice'savailabilityandpromptyoutoenableitifneeded.Belowisthe
modulesettingsinterfacewhenUnityIAPisdisabled.
TouseUnityIn-AppPurchasingservice,youmustfirstsetupyourprojectforUnity
Services.
ToenableUnityIAPservicegotoWindow>ServicesandselecttheIn-AppPurchasingtab.
EnableUnityIAP
111

Intheopenedconfigurationwindow,clickthetoggleattheright-handsideortheEnable
buttontoenableUnityIAPservice.
EnableUnityIAP
112

Adialogwindowwillappearaskingafewquestionsaboutyourgameinordertoensure
COPPAcompliance.
NextclicktheImportbuttontoimporttheUnityIAPpackagetoyourproject.
EnableUnityIAP
113

Afterimporting,thereshouldbeaUnityPurchasingfolderaddedunderAssets/Plugins
folder.
EnablingUnityIAPservicewillautomaticallyenabletheUnityAnalyticsservice(ifit'snot
enabledbefore),thisisarequirementtouseUnityIAP.GobacktotheServicespaneland
makesurethatbothIn-AppPurchasingandAnalyticsservicesarenowenabled.
EnableUnityIAP
114

AfterenablingUnityIAPservice,thesettingsinterfaceoftheIn-AppPurchasingmodulewill
beupdatedandreadyforyoutostartconfiguring.
EnableUnityIAP
115

EnableUnityIAP
116

TargetAndroidStore
Ifyou'rebuildingforAndroidplatform,youneedtospecifyyourtargetstore.Inthe
[ANDROID]TARGETSTOREsectionselectyourtargetstorefromthedropdown.
TargetAndroidStore
117

ReceiptValidation
Thereceiptvalidationfeatureprovidesextrasecurityandhelpspreventpreventfraudulent
usersfromaccessingcontenttheyhavenotpurchased.ThisfeatureemploysUnityIAP's
localreceiptvalidation,whichmeansthevalidationtakesplaceonthetargetdevice,without
theneedtoconnecttoaremoteserver.
ReceiptvalidationisavailableforApplestoresandGooglePlaystore.Pleasefindmore
informationaboutUnityIAP'sreceiptvalidationhere.
ObfuscatingEncryptionKeys
Toenablereceiptvalidation,youmustfirstcreateobfuscatedencryptionkeys.Thepurpose
ofthisobfuscatingprocessistopreventafraudulentuserfromaccessingtheactualkeys,
whichareusedforthevalidationprocess.Toobfuscateyourencryptionkeys,gotoWindow
>UnityIAP>ReceiptValidationObfuscator.
IntheopenedIAPObfuscatorwindow,pasteinyourGooglePlaypublickeyandhitthe
Obfuscatesecretsbutton.AccordingtoUnitydocumentation,thiswillobfuscatebothApple's
rootcertificate(bundlewithUnityIAP)andtheprovidedGooglePlaypublickeyandcreate
twoC#filesAppleTangleandGooglePlayTangleat
Assets/Plugins/UnityPurchasing/generated.Thesefilesarerequiredforthereceiptvalidation
process.
ReceiptValidation
118

ToobtaintheGooglePlaypublickeyforyourapp,logintoyourGooglePlayDeveloper
Console,selectyourapp,thennavigatetotheServices&APIssectionandfindyour
keyunderthesectionlabeledYOURLICENSEKEYFORTHISAPPLICATION.
Notethatyoudon'tneedtoprovideaGooglePlaypublickeyifyou'reonlytargeting
Applestores.
EnableReceiptValidation
Aftercreatingtheobfuscatedencryptionkeys,youcannowenablereceiptvalidationforyour
game.OpentheIn-AppPurchasingmodulesettings,thenintheRECEIPTVALIDATION
sectioncheckthecorrespondingoptionsforyourtargetedstores.
ReceiptValidation
119

ProductManagement
InthePRODUCTSsectionyoucaneasilyadd,editorremoveyourIAPproducts.
AddaNewProduct
Toaddanewproduct,clicktheAddNewProductbutton.
Anewemptyproductwillbeadded.
Fillintherequiredinformationforyournewproduct:
Name:theproductname,canbeusedwhenmakingpurchases
Type:theproducttype,canbeConsumable,Non-ConsumableorSubscription
Id:theunifiedproductidentifier,youshouldusethisIDwhendeclaringtheproducton
yourtargetedstores;otherwise,ifyouneedtohaveadifferentIDforthisproductona
certainstore,addittotheStore-SpecificIdsarray(seebelow)
ClickMoreifyouneedtoenterstore-specificIDsorfillinoptionalinformationforyour
product.
Price:theproductpricestringfordisplayingpurpose
Description:theproductdescriptionfordisplayingpurpose
ProductManagement
120

Store-SpecificIds:ifyouneedtouseadifferentproductID(thantheunifiedIDprovided
above)onacertainstore,youcanaddithere
AddingStore-SpecificID
ToaddanewIDtotheStore-SpecificIdsarray,increasethearraysizebyadjustingthe
numberintheright-handsidebox.Anewrecordwillbeaddedwhereyoucanselectthe
targetedstoreandenterthecorrespondingproductIDforthatstore.
Belowisasampleproductwithalltheinformationenteredincludingthetwostore-specific
IDs.
RemoveaProduct
Toremoveaproduct,simplyclickthe[-]buttonattherighthandside.
ArrangeProductList
Youcanusethetwoarrow-upandarrow-downbuttonstomoveaproductupwardor
downwardwithintheproductlist.
ProductManagement
121

SetupProductsforTargetedStores
BesidecreatingtheproductlistinUnity,youalsoneedtodeclaresimilarproductsforyour
targetedstores,e.g.ifyou'retargetingiOSAppStoreyouneedtocreatetheproductsin
iTunesConnect.Ifyou'renotfamiliarwiththeprocess,youcanfollowUnity'sinstructionson
configuringIAPforvariousstores,whichalsoincludeusefulinformationaboutIAPtesting.
OnGooglePlaystore,bothconsumableandnon-consumableproductsaredefinedas
Managedproduct.IfaproductissettoConsumabletypeinUnity,themodulewill
automaticallyhandletheconsumptionoftheproductonceitisboughtandmakeit
availableforpurchaseagain.
ProductManagement
122

ConstantsGeneration
ConstantsgenerationisafeatureoftheIn-AppPurchasingmodule.Itreadsalltheproduct
namesandgeneratesastaticclassnamedEM_IAPConstantsthatcontainstheconstantsof
thesenames.Later,youcanusetheseconstantswhenmakingpurchasesinscriptinstead
oftypingtheproductnamesdirectly,thushelppreventruntimeerrorsduetotyposandthe
likes.
Togeneratetheconstantsclass(youshoulddothisafterfinishingwithproductediting),click
theGenerateConstantsClassbuttonwithintheCONSTANTSCLASSGENERATION
section.
Whentheprocesscompletes,afilenamedEM_IAPConstantswillbecreatedat
Assets/EasyMobile/Generated.
ConstantsGeneration
123

Scripting
ThissectionprovidesaguidetoworkwiththeIn-AppPurchasingAPI.
YoucanaccesstheIn-AppPurchasingmoduleAPIviatheInAppPurchasingclass
undertheEasyMobilenamespace.
Initialization
ThemodulewillautomaticallyinitializeUnityIAPatstartwithoutyouhavingtodoanything.
AllfurtherAPIcallscanonlybemadeaftertheinitializationhasfinished.Youcancheckif
UnityIAPhasbeeninitialized:
//CheckifUnityIAPhasbeeninitialized
boolisInitialized=InAppPurchasing.IsInitialized();
ObtainProductList
Youcanobtainthearrayofallproductscreatedinthemodulesettingsinterface:
//GetthearrayofallproductscreatedintheIn-AppPurchasingmodulesettings
//IAPProductistheclassrepresentingaproductasdeclaredinthemodulesettings
IAPProduct[]products=InAppPurchasing.GetAllIAPProducts();
//Printallproductnames
foreach(IAPProductprodinproducts)
{
Debug.Log("Productname:"+prod.Name);
}
MakeaPurchase
Youcanpurchaseaproductusingitsname.
Itisstronglyrecommendedthatyouusetheconstantsofproductnamesinthe
generatedEM_IAPConstantsclass(seeIAPConstantsGenerationsection)insteadof
typingthenamesdirectlyinordertopreventruntimeerrorsduetotyposandthelikes.
Scripting
124

//Purchaseaproductusingitsname
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"S
ampleProduct"
InAppPurchasing.Purchase(EM_IAPConstants.Sample_Product);
APurchaseCompletedeventwillbefiredifthepurchaseissuccessful,otherwise,a
PurchaseFailedeventwillbefiredinstead.Youcanlistentotheseeventsandtake
appropriateactions,e.g.granttheuserdigitalgoodsifthepurchasehassucceeded.
Scripting
125

//SubscribetoIAPpurchaseevents
voidOnEnable()
{
InAppPurchasing.PurchaseCompleted+=PurchaseCompletedHandler;
InAppPurchasing.PurchaseFailed+=PurchaseFailedHandler;
}
//Unsubscribewhenthegameobjectisdisabled
voidOnDisable()
{
InAppPurchasing.PurchaseCompleted-=PurchaseCompletedHandler;
InAppPurchasing.PurchaseFailed-=PurchaseFailedHandler;
}
//Purchasethesampleproduct
publicvoidPurchaseSampleProduct()
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductname
d"SampleProduct"
InAppPurchasing.Purchase(EM_IAPConstants.Sample_Product);
}
//Successfulpurchasehandler
voidPurchaseCompletedHandler(IAPProductproduct)
{
//Compareproductnametothegeneratednameconstantstodeterminewhichproduct
wasbought
switch(product.Name)
{
caseEM_IAPConstants.Sample_Product:
Debug.Log("Sample_Productwaspurchased.Theusershouldbegranteditnow
.");
break;
caseEM_IAPConstants.Another_Sample_Product:
Debug.Log("Another_Sample_Productwaspurchased.Theusershouldbegrante
ditnow.");
break;
//Moreproductshere...
}
}
//Failedpurchasehandler
voidPurchaseFailedHandler(IAPProductproduct)
{
Debug.Log("Thepurchaseofproduct"+product.Name+"hasfailed.");
}
CheckforProductOwnership
Scripting
126

Youcancheckifaproductisownedbyspecifyingitsname.Aproductisconsidered"owned"
ifitsreceiptexistsandpassesthereceiptvalidation(ifenabled).
//Checkiftheproductisownedbytheuser
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"S
ampleProduct"
boolisOwned=InAppPurchasing.IsProductOwned(EM_IAPConstants.Sample_Product);
Consumableproducts'receiptsarenotpersistedbetweenapprestarts,thereforethis
methodonlyreturnstrueforthoseproductsinthesessionthey'repurchased.
Inthecaseofsubscriptionproducts,thismethodsimplychecksifaproducthasbeen
bought(subscribed)beforeandhasareceipt.Itdoesn'tcheckifthesubscriptionis
expiredornot.
RestorePurchases
Non-consumableandsubscriptionproductsarerestorable.Appstoresmaintainapermanent
recordofeachuser'snon-consumableandsubscriptionproducts,sothatheorshecanbe
grantedtheseproductsagainwhenreinstallingyourgame.
ApplenormallyrequiresaRestorePurchasesbuttontoexistinyourgame,sothattheusers
canexplicitlyinitiatethepurchaserestorationprocess.Onotherplatforms,e.g.GooglePlay,
therestorationisdoneautomaticallyduringthefirstinitializationafterreinstallation.
Duringtherestorationprocess,aPurchaseCompletedeventwillbefiredforeach
ownedproduct,asiftheuserhasjustpurchasedthemagain.Thereforeyoucanreuse
thesamehandlertogranttheusertheirproductsasnormalpurchases.
OniOS,youcaninitiateapurchaserestorationasbelow.
//Restorepurchases.ThismethodonlyhaseffectoniOS.
InAppPurchasing.RestorePurchases();
ARestoreCompletedeventwillbefirediftherestorationissuccessful,otherwise,a
RestoreFailedeventwillbefiredinstead.Notethattheseeventsonlymeanthesuccessor
failureoftherestorationitself,whilethePurchaseCompletedeventwillbefiredforeach
restoredproduct,asnotedearlier.Youcanlistentotheseeventsandtakeappropriate
actions,e.g.informtheusertherestorationresult.
TheRestoreCompletedandRestoreFailedeventsareonlyraisedoniOS.
Scripting
127

//SubscribetoIAPrestoreevents,theseeventsarefiredoniOSonly.
voidOnEnable()
{
InAppPurchasing.RestoreCompleted+=RestoreCompletedHandler;
InAppPurchasing.RestoreFailed+=RestoreFailedHandler;
}
//Successfulrestorationhandler
voidRestoreCompletedHandler()
{
Debug.Log("Allpurchaseshavebeenrestoredsuccessfully.");
}
//Failedrestorationhandler
voidRestoreFailedHandler()
{
Debug.Log("Thepurchaserestorationhasfailed.");
}
//Unsubscribe
voidOnDisable()
{
InAppPurchasing.RestoreCompleted-=RestoreCompletedHandler;
InAppPurchasing.RestoreFailed-=RestoreFailedHandler;
}
AdvancedScripting
Thissectiondescribesthemethodstoaccomplishtasksbeyondthebasiconessuchas
makingpurchasesorrestoring.Thesetasksincluderetrievingproductlocalizeddata,
readingproductreceipts,refreshingreceipts,etc.
MostofthemethodsdescribedinthissectionareonlyavailableonceEasyMobile'sIAP
moduleandUnityIAPserviceareenabled,whichisindicatedbythedefinitionofthe
symbolEM_UIAP.Therefore,youshouldalwayswraptheuseofthesemethodsinside
acheckfortheexistingofthissymbol.
Also,thetypesexposedinthesemethodsareonlyavailablewhentheUnityIAP
packageisimported,andyoushouldincludetheUnityEngine.Purchasingand
UnityEngine.Purchasing.Securitynamespacesatthetopofyourscriptforthesetypes
toberecognized.
GetUnityIAP'sProductobject
Scripting
128

Thein-appproductsarerepresentedinUnityIAPbytheProductclass,whichisdifferent
fromEasyMobile'sIAPProductclass,whosemainpurposeisforsettingsanddisplaying.
ThisProductclassistheentrypointtoaccessproduct-relateddataincludingitsmetadata
andreceipt,whichispopulatedautomaticallybyUnityIAP.ToobtaintheProductobjectofan
in-appproduct,calltheGetProductmethodwiththeproductname.
#ifEM_UIAP
usingUnityEngine.Purchasing;
#endif
...
//ObtaintheProductobjectofthesampleproductandprintitsdata
publicvoidGetSampleProduct()
{
#ifEM_UIAP
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductname
d"SampleProduct"
ProductsampleProduct=InAppPurchasing.GetProduct(EM_IAPConstants.Sample_Product)
;
if(sampleProduct!=null)
{
Debug.Log("AvailableToPurchase:"+sampleProduct.availableToPurchase.ToStri
ng());
if(sampleProduct.hasReceipt)
{
Debug.Log("Receipt:"+sampleProduct.receipt);
}
}
#endif
}
GetProductLocalizedData
Youcangetaproduct'smetadataretrievedfromtargetedappstores,e.g.localizedtitle,
descriptionandprice.Thisinformationisparticularlyusefulwhenbuildingastorefrontinyour
gamefordisplayingthein-appproducts.Togetthelocalizeddataofaproduct,callthe
GetProductLocalizedDataandspecifytheproductname.Thefollowingexampleiterates
throughtheproductlistandretrievethelocalizeddataofeachitem.
Scripting
129

#ifEM_UIAP
usingUnityEngine.Purchasing;
#endif
...
//Iteratethroughtheproductlistandgetthelocalizeddataretrievedfromthetarg
etedappstore.
//NotethecheckfortheEM_UIAPsymbol.
voidPrintProductsMetadata()
{
#ifEM_UIAP
//GetallproductscreatedintheIn-AppPurchasingmodulesettings
IAPProduct[]products=EM_Settings.InAppPurchasing.Products;
foreach(IAPProductprodinproducts)
{
//Getproductlocalizeddata.
ProductMetadatadata=InAppPurchasing.GetProductLocalizedData(prod.Name);
if(data!=null)
{
Debug.Log("Localizedtitle:"+data.localizedTitle);
Debug.Log("Localizeddescription:"+data.localizedDescription);
Debug.Log("Localizedpricestring:"+data.localizedPriceString);
}
}
#endif
}
ReadReceipts
Thissectionsdescribesmethodstoworkwithreceipts.Currently,UnityIAPonlysupports
parsingreceiptsfromApplestoresandGooglePlaystore.
Notethatforthereceiptreadingmethodstowork,youneedtoenablereceiptvalidation
feature(seetheReceiptValidationsection).
AppleAppReceipt
OniOS,youcangettheparsedAppleAppReceiptforyourappusingthe
GetAppleAppReceiptmethod.
Scripting
130

#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
...
//ReadtheAppReceiptoniOS.Receiptvalidationisrequired.
voidReadAppleAppReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
AppleReceiptappReceipt=InAppPurchasing.GetAppleAppReceipt();
//Printthereceiptcontent.
if(appReceipt!=null)
{
Debug.Log("AppVersion:"+appReceipt.appVersion);
Debug.Log("BundleID:"+appReceipt.bundleID);
Debug.Log("Numberofpurchasedproducts:"+appReceipt.inAppPurchaseRecei
pts.Length);
}
}
#endif
}
AppleInAppPurchaseReceipt
OniOS,youcangettheparsedAppleInAppPurchasereceiptforaparticularproduct,using
theGetAppleIAPReceiptmethodwiththenameoftheproduct.
Scripting
131

#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
...
//ReadtheInAppPurchasereceiptofthesampleproductoniOS.
//Receiptvalidationisrequired.
voidReadAppleInAppPurchaseReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproduct
named"SampleProduct".
AppleInAppPurchaseReceiptreceipt=InAppPurchasing.GetAppleIAPReceipt(EM_IAPC
onstants.Sample_Product);
//Printthereceiptcontent.
if(receipt!=null)
{
Debug.Log("ProductID:"+receipt.productID);
Debug.Log("OriginalPurchaseDate:"+receipt.originalPurchaseDate.ToShor
tDateString());
Debug.Log("OriginalTransactionID:"+receipt.originalTransactionIdentif
ier);
Debug.Log("PurchaseDate:"+receipt.purchaseDate.ToShortDateString());
Debug.Log("TransactionID:"+receipt.transactionID);
Debug.Log("Quantity:"+receipt.quantity);
Debug.Log("CancellationDate:"+receipt.cancellationDate.ToShortDateStri
ng());
Debug.Log("SubscriptionExpirationDate:"+receipt.subscriptionExpiratio
nDate.ToShortDateString());
}
}
#endif
}
GooglePlayReceipt
OnAndroid,youcangettheparseGooglePlayreceiptforaparticularproduct,usingthe
GetGooglePlayReceiptmethodwiththenameoftheproduct.
Scripting
132

#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
...
//ReadtheGooglePlayreceiptofthesampleproductonAndroid.
//Receiptvalidationisrequired.
voidReadGooglePlayReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.Android)
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproduct
named"SampleProduct".
GooglePlayReceiptreceipt=InAppPurchasing.GetGooglePlayReceipt(EM_IAPConstan
ts.Sample_Product);
if(receipt!=null)
{
Debug.Log("PackageName:"+receipt.packageName);
Debug.Log("ProductID:"+receipt.productID);
Debug.Log("PurchaseDate:"+receipt.purchaseDate.ToShortDateString());
Debug.Log("PurchaseState:"+receipt.purchaseState.ToString());
Debug.Log("TransactionID:"+receipt.transactionID);
Debug.Log("PurchaseToken:"+receipt.purchaseToken);
}
}
#endif
}
RefreshAppleAppReceipt
AppleprovidesamechanismtofetchanewAppReceiptfromtheirservers,typicallyused
whennoreceiptiscurrentlycachedinlocalstorageSKReceiptRefreshRequest.Youcan
refreshtheAppReceiptoniOSusingtheRefreshAppleAppReceiptmethod.Notethatthis
willprompttheuserfortheirpassword.
Scripting
133

//FetchanewAppleAppReceiptoniOS.Thiswillprompttheuserfortheirpassword.
voidRefreshAppleAppReceipt()
{
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
InAppPurchasing.RefreshAppleAppReceipt(SuccessCallback,ErrorCallback);
}
}
voidSuccessCallback(stringreceipt)
{
Debug.Log("AppReceiptrefreshedsuccessfully.Newreceipt:"+receipt);
}
voidErrorCallback()
{
Debug.Log("AppReceiptrefreshingfailed.");
}
Scripting
134

PlayMakerActions
ThePlayMakeractionsoftheIn-AppPurchasingmodulearegroupinthecategoryEasy
Mobile-In-AppPurchasinginthePlayMaker'sActionBrowser.
PleaserefertotheInAppPurchasingDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
135

PlayMakerActions
136

Sharing
TheSharingmodulehelpsyoueasilysharetextsandimagestosocialnetworksincluding
Facebook,TwitterandGoogle+usingthenativesharingfunctionality.Inaddition,italso
providesconvenientmethodstocapturethescreenshotstobeshared.
BelowarethesharinginterfacesoniOSandAndroid,respectively.

[iOS]RequestPhotoLibraryAccessPermission
SinceiOS10,inordertousethe"SaveImage"featureofthesharingutility,theappneeds
toaskforuserpermissionbeforeitcanaccessthephotolibrary.Failuretodosowillcause
theapptocrashassoonastheuserselectstheoption.Torequestthephotolibraryaccess
Sharing
137

permission,youneedtoaddthePrivacy-PhotoLibraryUsageDescriptionandPrivacy-
PhotoLibraryAdditionsUsageDescriptionpropertiestotheInfo.plistofyourXcode
project.
Asofthiswriting,outtestsshowthatoniOS10,thePrivacy-PhotoLibraryUsage
Descriptionpropertyisrequired.WhileiOS11asksforthePrivacy-PhotoLibrary
AdditionsUsageDescriptionproperty.Thereforeit'srecommendedtoaddboth
propertiesifyourtargetplatformsincludingiOS10andabove.
InyourgeneratedXcodeprojectopentheInfo.plistfile.
Clickthe+buttonontherightofInformationPropertyListtoaddanewkey.
ScrolldowntofindthePrivacy-PhotoLibraryUsageDescriptionkey.
Sharing
138

Enteravalueforthekey,thismessagewillbedisplayedastheapprequestsaccess
permissionwhentheuserselectsthe"SaveImage"option.
RepeattheprocesstoaddthePrivacy-PhotoLibraryAdditionsUsageDescription
property.
Sharing
139

[Android]EnableExternalWritePermission
ForthismoduletofunctiononAndroid,itisnecessarytoenablethepermissiontowriteto
externalstorage.Todoso,gotoEdit>ProjectSettings>Player,selectAndroidsettingstab,
thenlocatetheConfigurationsectionandsettheWritePermissiontoExternal(SDCard).
Sharing
140

Scripting
ThissectionprovidesaguidetoworkwithSharingAPI.
YoucanaccesstheSharingmoduleAPIviatheSharingclassundertheEasyMobile
namespace.
CaptureScreenshots
Tocapturethedevice'sscreenshot,youhaveafewoptions.
CaptureandSaveaScreenshotasPNGImage
Tocaptureandsaveascreenshotofthewholedevicescreen,simplyspecifythefilename
tobesaved.ThisscreenshotwillbesavedasaPNGimageinthedirectorypointedby
Application.persistentDataPath.Notethatthismethod,aswellasotherscreenshotcapturing
methods,needstobecalledattheendofaframe(whentherenderinghasdone)foritto
produceaproperimage.Thereforeyoushouldcallitwithinacoroutineafter
WaitForEndOfFrame().
//Coroutinethatcapturesandsavesascreenshot
IEnumeratorSaveScreenshot()
{
//Waituntiltheendofframe
yieldreturnnewWaitForEndOfFrame();
//TheSaveScreenshot()methodreturnsthepathofthesavedimage
//Theprovidedfilenamewillbeaddeda".png"extensionautomatically
stringpath=Sharing.SaveScreenshot("screenshot");
}
Youcanalsocapturesandsavesjustaportionofthescreen:
//Coroutinethatcapturesandsavesaportionofthescreen
IEnumeratorSaveScreenshot()
{
//Waituntiltheendofframe
yieldreturnnewWaitForEndOfFrame();
//Capturetheportionofthescreenstartingat(50,50),
//hasawidthof200andaheightof400pixels.
stringpath=Sharing.SaveScreenshot(50,50,200,400,"screenshot");
}
Scripting
141

CaptureaScreenshotintoaTexture2D
InsomecasesyoumaywanttocaptureascreenshotandobtainaTexture2Dobjectofit
insteadofsavingtodisk,e.g.tocreateaspritefromthetextureanddisplayitin-game.
//CoroutinethatcapturesascreenshotandgeneratesaTexture2Dobjectofit
IEnumeratorCaptureScreenshot()
{
//Waituntiltheendofframe
yieldreturnnewWaitForEndOfFrame();
//CreateaTexture2DobjectofthescreenshotusingtheCaptureScreenshot()metho
d
Texture2Dtexture=Sharing.CaptureScreenshot();
}
Similartothecaseabove,youcanalsocaptureonlyaportionofthescreen.
//CoroutinethatcapturesaportionofthescreenshotandgeneratesaTexture2Dobjec
tofit
IEnumeratorCaptureScreenshot()
{
//Waituntiltheendofframe
yieldreturnnewWaitForEndOfFrame();
//CreateaTexture2DobjectofthescreenshotusingtheCaptureScreenshot()metho
d
//Thecapturedportionstartsat(50,50)andhasawidthof200,aheightof400
pixels.
Texture2Dtexture=Sharing.CaptureScreenshot(50,50,200,400);
}
Notethatscreenshotcapturingshouldbedoneattheendoftheframe.
Sharing
Toshareanimageyoualsohaveafewoptions.Youcanalsoattachamessagetobe
sharedwiththeimage.
DuetoFacebookpolicy,pre-filledmessageswillbeignoredwhensharingtothis
network,i.e.sharingmessagesmustbewrittenbytheuser.
ShareaSavedImage
Youcanshareasavedimagebyspecifyingitspath.
Scripting
142

//Shareasavedimage
//Supposewehavea"screenshot.png"imagestoredinthepersistentDataPath,
//we'llconstructitspathfirst
stringpath=System.IO.Path.Combine(Application.persistentDataPath,"screenshot.png")
;
//Sharetheimagewiththepath,asamplemessageandanemptysubject
Sharing.ShareImage(path,"Thisisasamplemessage");
ShareaTexture2D
YoucanalsoshareaTexture2Dobjectobtainedsomepointbeforethesharingtime.
Internally,thismethodwillalsocreateaPNGimagefromtheTexture2D,saveittothe
persistentDataPath,andfinallysharethatimage.
//ShareaTexture2D
//sampleTextureisaTexture2Dobjectcapturedsometimebefore
//ThismethodsavesthetextureasaPNGimagenamed"screenshot.png"inpersistentDa
taPath,
//thensharesitwithasamplemessageandanemptysubject
Sharing.ShareTexture2D(sampleTexture,"screenshot","Thisisasamplemessage");
ShareaText
Youcanshareatext-onlymessageusingtheShareTextmethod.NotethatFacebook
doesn'tallowpre-filledsharingmessages,sothetextwillbediscardedwhensharingtothis
particularnetwork.
//Shareatext
Sharing.ShareText("HellofromEasyMobile!");
ShareaURL
ToshareaURL,usetheShareURLmethod.OnnetworkslikeFacebookorTwitter,a
summaryofthepagewillbeshownifthesharedURLpointstoawebsite.URLsarealso
usefultoshareGIFimageshostedonsiteslikeGiphy(seetheGIF>Scriptingsection).
//ShareaURL
Sharing.ShareURL("www.sglibgames.com");
Scripting
143

Scripting
144

PlayMakerActions
ThePlayMakeractionsoftheSharingmodulearegroupinthecategoryEasyMobile-
SharinginthePlayMaker'sActionBrowser.
PleaserefertotheSharingDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
145

PlayMakerActions
146

NativeAPIs
TheNativeAPIsmoduleallowsaccesstomobilenativefunctionalities.Thefirstfeature
availableisnativeUI.Morefunctionalitieswillbeaddedsoon.
NativeAPIs
147

NativeUI
TheNativeUIfeatureallowsyoutoaccessnativemobileUIelementsincludingalertsand
(Android)toasts.
Alerts
Alertsareusefulinprovidingtheuserscontextualinformation,askingforconfirmationor
promptingthemtomakeaselectionoutofseveraloptions.Analertcanhaveone,twoor
threebuttonswithit.
BelowarethethreetypesofalertoniOS.

AndbelowarethethreetypesofalertonAndroid.

Toasts
NativeUI
148

Toastsareshortmessagesdisplayedatthebottomofthescreen.Theyautomatically
disappearafteratimeout.ToastsareavailableonAndroidonly.Belowisasampletoast
message.
NativeUI
149

Scripting
ThissectionprovidesaguidetoworkwithNativeUIAPI.
YoucanaccesstheNativeUIAPIviatheNativeUIclassundertheEasyMobile
namespace.
Alerts
AlertsareavailableonbothiOSandAndroidplatformandcanhaveuptothreebuttons.
Simple(one-button)alertsareusefulingivingtheusercontextualinformation.Toshowa
simplealertwiththedefaultOKbutton,youonlyneedtoprovideatitleandamessagefor
thealert:
//ShowasimplealertwithOKbutton
NativeUI.AlertPopupalert=NativeUI.Alert("SampleAlert","Thisisasamplealertwit
hanOKbutton.");
Youcanalsoshowaone-buttonalertwithacustombuttonlabel.
//Showanalertwithabuttonlabeledas"Gotit"
NativeUI.AlertPopupalert=NativeUI.Alert(
"SampleAlert",
"Thisisasamplealertwithacustombutton.",
"Gotit"
);
Two-buttonalertscanbeusefulwhenneedingtoaskforuserconfirmation.Toshowatwo-
buttonalert,youneedtospecifythelabelsofthesetwobuttons.
//Showatwo-buttonalertwiththebuttonslabeledas"Button1"&"Button2"
NativeUI.AlertPopupalert=NativeUI.ShowTwoButtonAlert(
"SampleAlert",
"Thisisatwo-buttonalert.",
"Button1",
"Button2"
);
Three-buttonalertscanbeusedtopresenttheuserwithseveraloptions,atypicalusageofit
istoimplementtheRateUspopup.Toshowathree-buttonalert,youneedtospecifythe
labelsofthethreebuttons.
Scripting
150

//Showathree-buttonalertwiththebuttonslabeledas"Button1","Button2"&"But
ton3"
NativeUI.AlertPopupalert=NativeUI.ShowThreeButtonAlert(
"SampleAlert",
"Thisisathree-buttonalert.",
"Button1",
"Button2",
"Button3"
);
Wheneveranalertisshown,aNativeUI.AlertPopupobjectisreturned,whenthealertis
closed,thisobjectwillfireanOnCompleteeventandthendestroyitself.Theargumentof
thiseventistheindexoftheclickedbutton.Youshouldlistentothiseventandtake
appropriateactiondependingonthebuttonselected.
//ShowathreebuttonalertandhandleitsOnCompleteevent
NativeUI.AlertPopupalert=NativeUI.ShowThreeButtonAlert(
"SampleAlert",
"Thisisathree-buttonalert.",
"Button1",
"Button2",
"Button3"
);
//Subscribetotheevent
if(alert!=null)
{
alert.OnComplete+=OnAlertCompleteHandler;
}
//Theeventhandler
voidOnAlertCompleteHandler(intbuttonIndex)
{
switch(buttonIndex)
{
case0:
//Button1wasclicked
break;
case1:
//Button2wasclicked
break;
case2:
//Button3wasclicked
break;
default:
break;
}
}
Scripting
151

Onlyonealertpopupcanbeshownatatime.Anycalltoshowanalertwhileanotheroneis
beingdisplayedwillbeignored.Youcancheckifanalertisbeingshownusingthe
IsShowingAlertmethod.
//Checkifanalertisbeingdisplayed.
boolisShowingAlert=NativeUI.IsShowingAlert();
Toasts
Toastisashortmessagedisplayedatthebottomofthescreenandautomaticallydisappears
afteratimeout.ToastsareavailableonlyonAndroidplatform.Toshowatoastmessage:
//Showasampletoastmessage
NativeUI.ShowToast("ThisisasampleAndroidtoast");
Scripting
152

PlayMakerActions
ThePlayMakeractionsoftheNativeAPIsmodulearegroupinthecategoryEasyMobile-
NativeAPIsinthePlayMaker'sActionBrowser.
PleaserefertotheNativeUIDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/ModulesforanexampleonhowNativeUI
actionscanbeused.
PlayMakerActions
153

PlayMakerActions
154

Notifications
TheNotificationsmodulehelpsyouquicklyimplementnotificationsfeatureinyourapp.
Here'resomehighlightsofthismodule:
Remote(push)notification
ThemoduleiscurrentlycompatiblewithOneSignal,afree,popularcross-platform
pushnotificationdeliveryservice.
Localnotification
One-timeandrepeatnotifications.
Fully-customizablenotifications:sounds,icons,badge,light,vibration,lock-screen
visibility,etc.(featureavailabilityvariesbetweeniOSandAndroid)
Supportsnotificationcustomactionbuttons.
NotificationCategory
UnifiesAndroidnotificationcategory(channel)andiOSnotificationcategory,makes
iteasytocustomizeandorganizenotificationsinyourapp.
FullysupportsnotificationchannelsintroducedandrequiredsinceAndroidO,while
maintainingbackward-compatibilitywitholderAndroidversions,wherenotification
channelsdon'texist.
FullysupportsnotificationchannelgroupsintroducedsinceAndroidO.
Friendlyeditor
Makesiteasytosetupremotenotificationservice,managecategories,adding
resources,etc.
EasyMobile'snotificationAPIworksoniOS10ornewer(93%ofdevicesbyJan2018)
andAndroidAPI14ornewer(99.37%ofdevicesbyFeb2018).
ABriefIntroductionofNotifications
Anotificationisbasicallyamessagethatisdisplayedbythesystemoutsideofyourapp'sUI
toprovidetheuserwithsometimelyinformationaboutyourapp.Theusercanopena
notificationtobringyourapptoforegroundandtakefurtheractionsiftheywish.
Ifyourappisinforegroundatthemomentthenotificationisdelivered,thenthe
notificationwon'tbeposted(inotherwords,itwillbesilenced).Instead,itsdatawillbe
senttotheappdirectlyinformofanevent.
Notifications
155

Notificationsappeartousersindifferentlocationsandformats,plustheirappearancevaries
slightlybetweeniOSandAndroid,andamongdifferentversionsoftheseplatforms.
Dependingonthecurrentstateofyourdevice,notificationscanappearinthestatusbar
(Android),thenotificationcenter(iOS),orthelock-screen.Belowarethetypicalanatomies
ofiOSandAndroidnotifications.
iOSNotificationAnatomy
Here'satypicaliOSnotificationwithbasicdetails.
1. Smallicon:providedbythesystem
2. Appname:providedbythesystem
3. Title:thenotificationtitleprovidedbyyou
4. Subtitle:theoptionalnotificationsubtitleprovidedbyyou
5. Body:themainnotificationmessageprovidedbyyou
AndroidNotificationAnatomy
Here'satypicalAndroidnotificationwithbasicdetails.
1. Smallicon:required,providedbyyou(ifnovalidiconspecified,EasyMobile
automaticallyusesthefallbackicon,whichisabell)
2. Appname:providedbythesystem
3. Timestamp:providedbythesystem
4. Largeicon:optional,providedbyyou(thisisusuallyusedonlyforcontactphotos;do
Notifications
156

notuseitforyourappicon)
5. Title:thenotificationtitleprovidedbyyou
6. Body:themainnotificationmessageprovidedbyyou
YoucanlearnmoreaboutiOSnotificationshereandAndroidnotificationshere.
NotificationCustomActions
Besidebasiccontent,youcanoptionallyattachadditionalactionbuttonstothenotification
forspecifictasks.UsinguniqueactionIDs,yourappcanacknowledgetheactionselectedby
theuserandresponseaccordingly,e.g.openaparticularUIfortheusertoperformfurther
interaction.
AnotificationoniOScanhaveupto4actionbuttons.However,thenumberofactions
actuallydisplayeddependsonhowandwherethenotificationisdisplayed.Forexample,
bannersdisplaynomorethantwoactions.Here'sanexampleofaniOSnotificationwith2
actionbuttons,ActionAandActionB.
AnotificationonAndroidcanhaveupto3actionbuttons.Here'satypicalAndroid
notificationwith2actionbuttons,REPLYandARCHIVE.
OnAndroid,EasyMobilesupportsnotificationactionsonAPI23ornewer.
LocalVs.RemoteNotifications
Localnotificationsarenotificationsscheduledbyyourapplocally.Yourappconfiguresthe
notificationcontent,specifiesatriggercondition,andpassesthesedetailstothesystem,
whichthenhandlesthedeliveryofthenotificationwhenthetriggerconditionismet.
Notifications
157

One-timevs.Repeatlocalnotifications
Aonetimenotificationisdeliveredonlyonce.Arepeatnotificationisdeliveredonce
everytimetherepeatintervalpassed.Bothnotificationtypessurvivedevicereboots.If
thedeliverytimeispastatthemomentthedevicehasfinishedreboot,thenotification
willbedeliveredimmediately.
Remotenotificationsaresent(pushed)touserdevicesfromaremoteserver(beityourown
serverora3rdpartyserverlikeOneSignal's)viatheApplePushNotificationservice(APNs)
oniOS,ortheGoogleCloudMessagingservice(GCM)onAndroid.
NotificationCategories
EasyMobile'scross-platformnotificationcategoryunifiesAndroidnotification
channel/categoryandiOSnotificationcategory,providingasimpleandconvenientwayto
customizeandorganizenotificationsinyourapp.Youcanusecategorytocontrolmultiple
aspectsofnotificationsincludingimportance,light,vibration,soundandactionbuttons
(configurationvariesbetweenplatforms,e.g.importanceandlightareAndroid-only).All
notificationspostedtothesamecategorysharethesamecustomization.Everytimeyou
scheduleanotification,simplysetthecategoryitbelongstoandallthesettingsofthat
categorywillbeappliedtothenotificationautomatically.Usingcategoriesisthereforeamore
intuitiveandefficientwaytocustomizeandorganizenotifications.Youcanhavemultiple
categoriesinyourapp,eachcontrolsadifferenttypeofnotifications.Forexample,youcan
haveacategorydedicatedforgameeventnotifications,andanothercategoryforuser(chat)
messagenotifications.
NotificationCategoriesoniOS
OniOS,cross-platformcategoriesautomaticallytranslatetonativenotificationcategories,
takingonlytheinformationoncustomactionsbecauseiOSnotificationcategoriesaremostly
responsibleforconfiguringcustomactions.Abouttheothersettingsofthecross-platform
category,thoseareapplicableoniOS(e.g.sound)willautomaticallybeappliedwhena
notificationofthiscategoryisscheduled.
iOSnotificationcategoriesarenotvisibletousers.Youcanlearnmoreaboutthem
here.
NotificationCategoriesonAndroid
OnAndroid8.0Oreo(APIlevel26),anewfeatureisintroducedwhichisnotification
channels/categories.Startinginthisversion,allnotificationsmustbeassignedtoachannel
oritwillnotappear.Notificationchannelsoffertheabilitytogroupnotifications,andallow
Notifications
158

userstocontrolthevisualandauditoryoptionsofeachchannelfromtheAndroidsystem
settings.
TheAndroiduserinterfacereferstochannelsas"categories".
OnAndroid8.0andnewer,EasyMobile'scross-platformnotificationcategories
automaticallytranslatetonativenotificationchannels.Mostsettingsareapplicableto
Androidchannels,exceptthecustomactions(Androidnotificationchannelsarenot
responsibleforcustomactions).Whenalocalnotificationisscheduled,itwillbeassignedto
thenativenotificationchannel,andthecustomactions(ifany)foundinitscross-platform
categoryareautomaticallyaddedwhenconstructingthenotification.
OnAndroid7.1(APIlevel25)andlower,there'renonotificationchannels.Whenalocal
notificationisscheduled,individualsettingswillbereadfromitscross-platformcategoryand
appliedautomaticallywhenconstructingthenotification.
YoucanlearnmoreaboutAndroidnotificationchannels/categorieshere.
DefaultNotificationCategory
Notifications
159

FornotificationstoworkconsistentlyacrossiOSandAndroidplatform,yourapprequiresat
leastonenotificationcategory.EasyMobilehasabuilt-indefaultcategory.Ifyou're
schedulingalocalnotificationandnotspecifyinganycategory,thenthedefaultonewillbe
used.Thisdefaultcategorycanbecustomizedfromthemodulesettings,butitcannotbe
removed.
NotificationCategoryGroups
Youcanorganizeyournotificationcategoriesintocategorygroups.OnAndroidcategory
groupsdirectlytranslatetonativechannelgroups.Belowisanexampleofhownotification
channelsareorganizedintogroupsinthesystemsettingsonAndroid.
Notificationcategorygroupsdon'thaveanyeffectoniOS.
Notifications
160

ModuleConfiguration
TousetheNotificationsmoduleyoumustfirstenableit.GotoWindow>EasyMobile>
Settings,selecttheNotificationstab,thenclicktheright-handsidetoggletoenableandstart
configuringthemodule.
ModuleConfiguration
161

AutoInitialization
AutoinitializationisafeatureoftheNotificationsmodulethatinitializestheservice
automaticallywhenthemodulestarts.YoucanconfigurethisfeatureintheAUTO-INIT
CONFIGsection.
OniOS,apopupwillappearduringthefirstinitializationfollowingtheappinstallationto
askfortheuser'spermissiontoenablenotificationsforyourgame.
AutoInit:uncheckthisoptiontodisabletheautoinitializationfeature,youcanstartthe
initializationmanuallyfromscript(seeScriptingsection)
AutoInitDelay:howlongafterthemodulestartthattheinitializationshouldtakeplace
"Modulestart"referstothemomenttheStartmethodofthemodule'sassociated
MonoBehavior(attachedtotheEasyMobileprefab)runs.IfyouaddtheEasyMobile
prefabinstancetothefirstsceneofyourgamethenthismomentismostlyidenticalto
thelaunchtimeoftheapp.
AutoInitialization
162

RemoteNotificationSetup
EnableRemoteNotifications
Toenableremote/pushnotificationsforyourapp,selectavalidserviceproviderfromthe
PushNotificationServicedropdowninREMOTENOTIFICATIONSETUPsection.Currently
onlyOneSignalserviceissupported,butmorearetobeadded.
SetupOneSignalPushNotificationService
BeforeYouBegin
BeforesettingupOneSignalinUnity,youmustfirstgenerateappropriatecredentialsfor
yourtargetedplatforms.Ifyou'renotfamiliarwiththeprocess,pleasefollowtheguides
listedhere.Youshouldalsofollowtheinstructionsincludedinthatdocumenton
performingnecessarysetupwhenbuildingforeachplatform.
UsingOneSignalservicerequirestheOneSignalpluginforUnity.EasyMobilewill
automaticallycheckfortheavailabilityofthepluginandpromptyoutoimportitifneeded
(seetheabovescreenshot).YoucanclicktheDownloadOneSignalPluginbuttontoopen
thedownloadpagefortheplugin.OnceitisimportedintoUnitythesettingsinterfacewillbe
updatedandreadyforyoutostartconfiguring.
InfactallyouneedtodoisenteryourOneSignalAppId.
RemoteNotificationSetup
163

RemoteNotificationSetup
164

AddingNotificationResources
AndroidNotificationResources
Androidnotificationresourcesincludenotificationiconsandcustomnotificationsounds.
Customnotificationsounds:ifyoudon'twanttousethesystem'sdefaultnotification
sound,youcanprovidecustomsoundstobeplayedwhenyournotificationsare
delivered.
Smallnotificationicons:smalliconsorstatusbariconsarerequiredandwillbeused
torepresentnotificationsfromyourappinthestatusbar.StartingwithAndroid5,the
systemforcessmallnotificationiconstobeallwhitewhenyourapptargetsAndroidAPI
21+.Ifyoudon'tmakeacorrecticon,itwillmostlikelybedisplayedasthefallbackicon
(bell)orsolidwhiteiconinthestatusbar.
Largenotificationicons:largenotificationsiconsareoptionalandwillshowupat
differentpositionsonthenotificationdependingontheAndroidversion(seebelow
screenshot).Ifyoudonotspecifyalargeicon,thesmalliconwillbeused.
AddingNotificationResources
165

PreparingAndroidNotificationIcons
It'sadvisabletousetheAndroidAssetStudiotoquicklyandeasilygeneratesmalliconswith
thecorrectsettings.Notethatthedefaultsmalliconsmustbenamedic_stat_em_default
sothatEasyMobilecanrecognizeit.Belowisanexampleofaresfoldercontainingthe
defaultsmalliconsgeneratedbytheAndroidAssetStudio.Laterwewillimportthisfolderto
Unitysothattheiconscanbeusedinyourproject.
Ifyouwanttohaveadefaultlargeicon,createa256x256icon,nameit
ic_large_em_defaultandplaceitinthedrawable-xxxhdpifolderofthesameresfolder
generatedpreviously.
AddingNotificationResources
166

Non-DefaultNotificationIcons
Besidethedefaulticons,youcanaddmorecustomiconswhichcanbeusedwhen
schedulingdifferenttypesofnotifications.JustusetheAndroidAssetStudioto
generatesmallicons,andcreatelargeiconsatthecorrectsizes(256x256).Then
mergethemintothesameresfolderthatcontainsthedefaulticons,makingsurethe
iconsgointotheircorrectsubfolders.Whenschedulinganotification,youcanselect
thesecustomiconsusingtheirnames.
PreparingAndroidNotificationSounds
Toaddcustomnotificationsounds,createafoldernamedrawinsidetheresfolderthat
containsthedefaultnotificationiconsandplacethesoundfilesthere.Laterthesecustom
soundscanbespecifiedinyourprojectwiththeirnames.Belowisanexampleresfolderthat
containsdefaulticons,customiconsandcustomsoundsforAndroidnotifications.
AddingNotificationResources
167

ImportingAndroidNotificationResources
Afterconstructingtheresfolderwithalltherequirediconsandsounds,thenextstepis
importthefolderintoyourUnityprojectsotheresourcescanbeusedinyourapp.Inthe
ANDROIDNOTIFICATIONRESOURCESsection,clicktheImportResFolderbutton,then
selectthegeneratedresfoldertoimport.That'sit!
iOSNotificationResources
OniOS,notificationiconsareprovidedbythesystem,butyoucanstillhavecustom
notificationsounds.ToaddacustomsoundtoyouriOSapp,simplyplacethesoundfile
anywhereinyourUnityproject,buildyourprojectforiOSplatform,andthendragthesound
filetotheXcodeprojectroot(remembertoselectCopyitemsifneededontheXcodeimport
dialog).
AddingNotificationResources
168

NotesonNotificationSounds
It'srecommendedtohavenotificationsoundsin.wavformatsothattheycanbeused
onbothAndroidandiOS.
MakesuresoundnamesareconsistentacrossiOSandAndroid.
Notificationsoundsmustbelessthan30secondstomakesuretheycanbeplayedon
bothAndroidandiOS.
AddingNotificationResources
169

CategoryManagement
NotificationCategoryGroups
IntheCATEGORYGROUPSsectionyoucanadd,editorremovegroupsforthenotification
categoriesinyourapp.
1. Groupcontent:whereyoufillingroupinformation
2. Addbutton:usethisbuttontoaddanewgroup
3. Moveupbutton:movethegroupup,forarrangementpurpose
4. Deletebutton:usethisbuttontoremovethecurrentgroup
5. Movedownbutton:movethegroupdown,forarrangementpurpose
Acategorygroupcontentincludesfollowingfields:
Name:thegroupname,mustnotbeempty
Id:thegroupID,mustnotbeempty;acategoryspecifiesthegroupitbelongstousing
thisID
NotificationCategories
YoucanmanagethenotificationcategoriesinyourappwithintheCATEGORIESsection.
CategoryManagement
170

Acategorycontentincludesfollowingfields:
Name:categoryname,onlyvisibleonAndroiddevices,thisisrequired
Id:categoryID,thisisrequired;anotificationspecifiesthecategoryitbelongstousing
thisID
Description:optionalcategorydescription,onlyvisibleonAndroiddevices
GroupId:theidentifierofthegroupthiscategorybelongsto
EnableBadge:[Androidonly]whetherthenotificationsofthiscategorycanappearas
badgesinaLauncherapplication
Importance:[Androidonly]howmuchinterruptive-visuallyandaudibly-the
notificationsofthiscategoryshouldbe
Lights:[Androidonly]determineshowthenotificationlightshouldbedisplayed,on
devicesthatsupportthisfeature
LightColor:[Androidonly]thecolorofthenotificationlight,onlyconfigurablewhen
LightsissettoCustom
Vibration:[Androidonly]determineshowthedeviceshouldvibratewhenanotification
arrives
VibrationPattern:[Android]thevibrationpatternofthedevice,onlyconfigurablewhen
VibrationissettoCustom
Lock-screenVisibility:[Androidonly]determineshownotificationsshouldbedisplayed
onlock-screen
Sound:determinewhetherthedefaultsound,oracustomsound,ornosoundatall
shouldbeplayedwhenanotificationarrives
CategoryManagement
171

SoundName:thefilename(withextension)ofthecustomnotificationsound,only
configurableifSoundissettoCustom
Actionbuttons:thecustomactionbuttonstobeattachedtothenotificationsofthis
category,eachactionbuttonrequiresthefollowingfields:
Id:actionID,usedtodistinguishbetweenactions
Title:actiontitle,usedtodisplaytheactionbuttononthenotification
DefaultCategory
Yourappmusthaveatleastonenotificationcategory,andEasyMobileprovidesabuilt-in
defaultcategory.YoucancustomizeitintheDefaultCategorysub-sectionofthe
CATEGORIESsection.Youcan'tremovethedefaultcategory.
UserCategories
Besidethedefaultcategory,youcanaddasmanymorecategoriesasyouwish.Wecall
theseusercategories,andtheycanbemanagedintheUserCategoriessub-sectionofthe
CATEGORIESsection.
1. Categorycontent:whereyoufillincategoryinformation
2. Addbutton:usethisbuttontoaddanewusercategory
3. Moveupbutton:movethecategoryup,forarrangementpurpose
4. Deletebutton:usethisbuttontoremovethecurrentusercategory
5. Movedownbutton:movethecategorydown,forarrangementpurpose
CategoryManagement
172

CategoryManagement
173

ConstantsGeneration
ConstantsgenerationisafeatureoftheNotificationsmodule.ItreadstheIDsofalluser
categoriesaddedandgeneratesastaticclassnamedEM_NotificationsConstantsthat
containstheconstantsoftheseIDs.Later,youcanusetheseconstantswhenschedulinga
localnotificationinscriptinsteadoftypingtheIDsdirectly,thushelppreventruntimeerrors
duetotyposandthelikes.
Togeneratetheconstantsclass(youshoulddothisafteraddingallrequireduser
categories),clicktheGenerateConstantsClassbuttonwithintheCONSTANTSCLASS
GENERATIONsection.
Whentheprocesscompletes,afilenamedEM_NotificationsConstantswillbecreatedat
Assets/EasyMobile/Generated.
ConstantsGeneration
174

Scripting
ThissectionprovidesaguidetoworkwiththeNotificationsAPI.
YoucanaccesstheNotificationsmoduleAPIviatheNotificationsclassunderthe
EasyMobilenamespace.
Initialization
Beforenotificationscanbeused,themoduleneedstobeinitialized.Thisinitializationshould
onlybedoneoncewhentheappisloaded,andbeforeanyothercallstotheAPIaremade.
IfyouhaveenabledtheAutoinitializationfeature(seeModuleConfigurationsection),you
don'tneedtostarttheinitializationfromscript.Otherwise,ifyouchoosetodisablethat
feature,youcaninitializetheserviceusingtheInitmethod.
//Initializepushnotificationservice
Notifications.Init();
Notethattheinitializationshouldbedoneearlyandonlyonce,e.g.youcanputitintheStart
methodofaMonoBehaviour,preferablyasingletonsothatitwon'trunagainwhenthe
scenereloads.
//InitializationintheStartmethodofaMonoBehaviourscript
voidStart()
{
//Initializepushnotificationservice
Notifications.Init();
//Dootherstuff...
}
YoucancheckifthemodulehasbeeninitializedatanypointusingtheIsInitializedmethod.
//Checkifinitializationhascompleted.
boolisInitialized=Notifications.IsInitialized();
WorkingwithLocalNotifications
ConstructingaNotificationContent
Scripting
175

Beforeschedulinganotification,youmustfirstprepareitscontent.Todothissimplycreatea
newNotificationContentobjectandfillitwithappropriateinformation.
//Constructthecontentofanewnotificationforscheduling.
NotificationContentPrepareNotificationContent()
{
NotificationContentcontent=newNotificationContent();
//Providethenotificationtitle.
content.title="DemoNotification";
//Youcanoptionallyprovidethenotificationsubtitle,whichisvisibleoniOSo
nly.
content.subtitle="DemoSubtitle";
//Providethenotificationmessage.
content.body="Thisisademonotification.";
//Youcanoptionallyattachcustomuserinformationtothenotification
//informofakey-valuedictionary.
content.userInfo=newDictionary<string,object>();
content.userInfo.Add("string","OK");
content.userInfo.Add("number",3);
content.userInfo.Add("bool",true);
//YoucanoptionallyassignthisnotificationtoacategoryusingthecategoryID
.
//Ifyoudon'tspecifyanycategory,thedefaultonewillbeused.
//Notethatit'srecommendedtousethecategoryIDconstantsfromtheEM_Notific
ationsConstantsclass
//ifithasbeengeneratedbefore.Inthisexample,UserCategory_notification_cat
egory_testisthe
//generatedconstantofthecategoryID"notification.category.test".
content.categoryId=EM_NotificationsConstants.UserCategory_notification_category_
test;
//Ifyouwanttousedefaultsmalliconandlargeicon(onAndroid),
//don'tsetthesmallIconandlargeIconfieldsofthecontent.
//Ifyouwanttousecustomiconsinstead,simplyspecifytheirnameshere(witho
utfileextensions).
content.smallIcon="YOUR_CUSTOM_SMALL_ICON";
content.largeIcon="YOUR_CUSTOM_LARGE_ICON";
returncontent;
}
SchedulingLocalNotifications
Toschedulealocalnotification,preparethenotificationcontentandfeedittothe
ScheduleLocalNotificationmethod.
Scripting
176

One-TimeLocalNotifications
Youcanscheduleanotificationtobedeliveredataspecifictime(inthefuture).Ifthe
specifiedtimeisinthepast,thenotificationwillbedeliveredimmediately.
usingSystem;//forDateTime
...
//Scheduleanotificationtobedeliveredby08:08AM,08August2018.
voidScheduleLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//Setthedeliverytime.
DateTimetriggerDate=newDateTime(2018,08,08,08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(triggerDate,content);
}
Insteadofatriggerdate,youcanalsoscheduleaone-timenotificationtobedeliveredafter
somedelaytime.
usingSystem;
...
//Scheduleanotificationtobedeliveredafter08hours,08minutesand08seconds.
voidScheduleLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//SetthedelaytimeasaTimeSpan.
TimeSpandelay=newTimeSpan(08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(delay,content);
}
RepeatLocalNotifications
Ifyouwantthenotificationtorepeatautomatically,scheduleittobedeliveredaftersome
delaytime,andthenspecifyafixedrepeatinterval.Repeatintervalcanbeoneofthe
followingvalues:
None:norepeat
Scripting
177

EveryMinute:notificationrepeatsonceeveryminute
EveryHour:notificationrepeatsonceeveryhour
EveryDay:notificationrepeatsonceeveryday
EveryWeek:notificationrepeatsonceeveryweek
usingSystem;
...
//Scheduleanotificationtobedeliveredafter08hours,08minutesand08seconds,
//thenrepeatonceeveryday.
voidScheduleRepeatLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//SetthedelaytimeasaTimeSpan.
TimeSpandelay=newTimeSpan(08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(delay,content,NotificationRepeat.EveryDa
y);
}
ManagingPendingLocalNotifications
GetPendingLocalNotifications
Togetallpending(scheduledbutnotyetdelivered)localnotifications,usethe
GetPendingLocalNotificationsmethod.Thependingnotificationswillbereturnedasanarray
ofNotificationRequestobjectsviaacallback.Eachpendingnotificationrequestisidentified
byitsrequestID.
Scripting
178

//Getsallpendinglocalnotificationrequests.
voidGetPendingLocalNotifications()
{
Notifications.GetPendingLocalNotifications(GetPendingLocalNotificationsCallback);
}
//Callback.
voidGetPendingLocalNotificationsCallback(NotificationRequest[]pendingRequests)
{
foreach(varrequestinpendingRequests)
{
NotificationContentcontent=request.content;//notificationcontent
Debug.Log("NotificationrequestID:"+request.id);//notificationreques
tID
Debug.Log("Notificationtitle:"+content.title);
Debug.Log("Notificationbody:"+content.body);
}
}
CancelaPendingLocalNotification
Tocancelaparticularpendinglocalnotification,calltheCancelPendingLocalNotification
methodwiththerequestIDofthenotificationtobecanceled.Cancelednotificationswillno
longerbedelivered.
//CancelsapendinglocalnotificationwithknownrequestID.
Notifications.CancelPendingLocalNotification("REQUEST_ID_TO_CANCEL");
CancelAllPendingLocalNotifications
Tocancelallpendinglocalnotifications,simplycalltheCancelAllPendingLocalNotifications
method.
//Cancelsallpendinglocalnotifications.
Notifications.CancelAllPendingLocalNotifications();
WorkingwithRemoteNotifications
Remotenotificationsaresentfromaremoteserverandaretypicallyscheduledfroma
website,e.g.OneSignal'sdashboard.Withinyourapp,youonlyneedtowritecodetohandle
whenaremotenotificationisdeliveredandopened.
HandlingOpenedNotifications
Scripting
179

Wheneveralocalorremotenotificationisopened-eitherbytheusertappingonit(default
openaction)orselectinganactionbutton-yourappwillbebroughttoforegroundandthen
aLocalNotificationOpened_ora_RemoteNotificationOpenedeventwillberaised.Youcan
subscribetotheseeventsandtakeappropriateactionsaccordingtoyourappdesign.It's
recommendedtosubscribetotheeventsassoonasyourappisloaded,e.g.inthe
OnEnablemethodofaMonoBehaviourscriptinyourfirstscene.Otherwiseyouriskmissing
them.
Ifyourappisinforegroundwhenanotificationisdelivered-beitlocalorremote-then
thenotificationisnotposted(notdisplayed)andtheLocalNotificationOpenedor
RemoteNotificationOpenedeventwillberaisedimmediatelyasifthenotificationwas
openedwiththedefaultaction.
//Subscribestonotificationevents.
voidOnEnable()
{
Notifications.LocalNotificationOpened+=OnLocalNotificationOpened;
Notifications.RemoteNotificationOpened+=OnRemoteNotificationOpened;
}
//Unsubscribesnotificationevents.
voidOnDisable()
{
Notifications.LocalNotificationOpened-=OnLocalNotificationOpened;
Notifications.RemoteNotificationOpened-=OnRemoteNotificationOpened;
}
//Thishandlerwillbecalledwhenalocalnotificationisopened.
voidOnLocalNotificationOpened(LocalNotificationdelivered)
{
//TheactionIdwillbeemptyifthenotificationwasopenedwiththedefaultacti
on.
//OtherwiseitcontainstheIDoftheselectedactionbutton.
if(!string.IsNullOrEmpty(delivered.actionId))
{
Debug.Log("ActionID:"+delivered.actionId);
}
//Whetherthenotificationisdeliveredwhentheappisinforeground.
Debug.Log("Isappinforeground:"+delivered.isAppInForeground.ToString());
//Getsthenotificationcontent.
NotificationContentcontent=delivered.content;
//Takefurtheractionsifneeded...
}
//Thishandlerwillbecalledwhenaremotenotificationisopened.
voidOnRemoteNotificationOpened(RemoteNotificationdelivered)
Scripting
180

{
//TheactionIdwillbeemptyifthenotificationwasopenedwiththedefaultacti
on.
//OtherwiseitcontainstheIDoftheselectedactionbutton.
if(!string.IsNullOrEmpty(delivered.actionId))
{
Debug.Log("ActionID:"+delivered.actionId);
}
//Whetherthenotificationisdeliveredwhentheappisinforeground.
Debug.Log("Isappinforeground:"+delivered.isAppInForeground.ToString());
//Getsthenotificationcontent.
NotificationContentcontent=delivered.content;
//IfOneSignalserviceisinused(currentlyit'stheonlypushnotification
//servicesupported),youcanaccesstheoriginalOneSignalpayload.
OneSignalNotificationPayloadpayload=delivered.oneSignalPayload;
//Takefurtheractionsifneeded...
}
RemovingDeliveredNotifications
Normallydeliverednotificationsareclearedautomaticallywhenthey'reopened.Youcan
manuallyclearallpreviouslyshownnotificationsofyourappfromthenotificationcenteror
statusbarwiththeClearAllDeliveredNotificationsmethod.
//Clearalldeliverednotifications(localandremote).
Notifications.ClearAllDeliveredNotifications();
Scripting
181

PlayMakerActions
ThePlayMakeractionsoftheNotificationsmodulearegroupinthecategoryEasyMobile-
NotificationsinthePlayMaker'sActionBrowser.
PleaserefertotheNotificationsDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
182

PlayMakerActions
183

Utilities
TheUtilitiesmoduleisaplacetoholdusefulmiscellaneousfeatures.Thefirstfeatureadded
tothismoduleisStoreReview.
Utilities
184

StoreReview
Ratingsandreviewscanhaveacrucialimpactontheperformanceofanapponappstores.
Thereforeit'sacommonpracticetoaskusersforratingswhenappropriate.TheStore
Reviewfeaturegivesyouanefficientwaytodothatusinganativeandhighlycustomizable
ratingdialog.
Thisratingdialoghasdifferentappearancesandbehaviorsdependedontheplatformitis
beingused.
iOS10.3andnewer
OniOS10.3ornewer,thesystem-providedratingdialogisemployed.Thisdialogisbuilt-in
toiOSsinceits10.3release,andisthepreferredmethodtosolicituserratingsonthis
platform.
Youcanfindmoreinformationaboutthisbuilt-inratingpromptat
https://developer.apple.com/ios/human-interface-guidelines/interaction/ratings-and-
reviews/
It'sworthnotingthattheSubmitbuttononthisratingpopupwillbedisabledwhileyour
appisstillinsandboxmode.Itwillbefunctioningnormallywhentheappisactuallylive
onAppStore.
iOSBefore10.3
OniOSolderthan10.3,atypical3-buttonalertisusedastheratingprompt.Thisismainly
forbackward-compatibilitypurpose,sincethenewbuilt-inratingpromptispreferredandwill
beusedonthemajorityofiOSdevicesinthenearfuture,giventhehighadoptionrateof
newiOSversions.
StoreReview
185

Unlikethebuilt-indialog,youcancustomizethetitle,messageandbuttonlabelsofthisalert
tosuityourneeds.Thedefaultbehaviorofthisratingpromptisdescribedbelow.
Don'tAskAgain:closeandnevershowthispromptagain
RemindMeLater:closethisalert
RateNow!:openthe"WriteAReview"pageofthecurrentappontheAppStore,the
promptwillneverbedisplayedagain
Android
OnAndroid,webuiltanative,customalertthatemploystheRatingBarcomponenttoform
theratingdialog.Thepicturebelowillustrateshowthisdialoglooksandbehaves.
StoreReview
186

Theideaistoasktheuserhowtheywouldratetheapp,andthedialogwillupdateitself
basedonthegivenrating.Youcanseta"minimumacceptedrating"value,whichisthe
lowestnumberofstarsexpectedforyourapp.Anyratinglowerthanthisvalueisconsidered
abadrating,andviceversa.Iftheuserisgivingagoodrating,wewilltakethemtothestore
todotheactualratingandreview.Otherwise,wewillsuggestthemtosendafeedbackto
yoursupportemailinstead.Thedefaultbehaviorofthisratingdialogisdescribedbelow.
Again,youcandiscardthisdefaultbehaviorandimplementacustomoneifyouwish.
Don'tAskAgain:closeandnevershowthispromptagain
NotNow/Cancel:closethisprompt
SendFeedback:openemailclientfortheusertosendfeedbacktoyouremailaddress
RateNow!:opentheproductpageoftheappontheGooglePlayStore,thepromptwill
neverbedisplayedagain
OnAndroidoriOSolderthan10.3,youcandiscardthedefaultbehavioroftherating
dialogandgiveyourownbehaviorimplementationifyouwish.
DisplayPolicy
Itisuptoyoutodecidewhentoshowtheratingpromptinyourgametomaximizeits
effectivenesswhilemaintainingthebestuserexperience.Generally,itisadvisabletonot
annoytheuserbyaskingrepeatedlyortoofrequently.Forthatpurpose,theratingrequest
featureprovidesafewgeneralconstraintstohelpregulatethedisplayoftheratingprompt.
Youarefreetoconfigurethesevaluesappropriatelytosuityourneeds.Theseconstraints
include:
AnnualCap:themaxiumnumberofrequestsallowedeachyear
DelayAfterInstallation:therequiredwaitingtime(days)sinceappinstallationbeforethe
firstratingrequestcanbemade
Cooling-OffPeriod:theminimuminterval(days)requiredbetweentwoconsecutive
requests
OniOS10.3andnewer(wherethebuilt-inratingpromptisused),theAnnualCapis
overwrittenbytheOSandwillalwaysbesetto3.
FortheDelayAfterInstallationconstrainttofunctionproperly,itisrequiredthatan
instanceoftheEasyMobileprefabisaddedtothefirstsceneofyourgame(sothatit
canrecordtheinstallationtimestamp).
DialogContentandLocalization
StoreReview
187

Thedefaultcontent(texts)oftheratingdialogcanbeenteredinthesettingsUI(seethe
Configurationsection).Thiscontentcanbealteredinruntime(seetheScriptingsection),
sothatyoucanusethisfeatureinconjunctionwithanotherlocalizationplugintofullylocalize
yourpopup.
StoreReview
188

Configuration
YoucanconfiguretheStoreReviewfeaturefromtheUtilitiesmodule.GotoWindow>Easy
Mobile>Settings,thenselecttheUtilitiestabtorevealit.
Inthe[STOREREVIEW]REQUESTDIALOGCONFIGsection,youcancustomizethe
appearance,behavioranddisplayconstraintsoftheratingdialog.
DefaultDialogContent:thedefaulttextsoftheratingdialogusedonAndroidandiOS
Configuration
189

olderthan10.3(oniOS10.3ornewerthiscontentisgovernedbythesystem)
MinimumAcceptedRating:thelowestnumberofstarsrequiredtobeconsideredasa
goodrating,youcansetitto0todisablethefeedbackfeature(acceptallratings);note
thatthisisonlyapplicableonAndroid
SupportEmail:youremailaddressforreceivingfeedback
iOSAppId:yourappIdontheAppleAppStore,thisisrequiredtoopenthereview
pageoftheapponiOSolderthan10.3
AnnualCap:themaxiumnumberofrequestsallowedeachyear
DelayAfterInstallation:therequiredwaitingtime(days)sinceappinstallationbeforethe
firstratingrequestcanbemade
Cooling-OffPeriod:theminimuminterval(days)requiredbetweentwoconsecutive
requests
IgnoreConstraintsInDevelopment:ignorealldisplayconstraintssotheratingpopup
canbeshowneverytimeinDevelopmentbuilds(unlessitwasdisabledbefore)
Configuration
190

Scripting
ThissectionprovidesaguidetoworkwiththeStoreReviewAPI.
YoucanaccesstheStoreReviewAPIviatheStoreReviewclassundertheEasyMobile
namespace.
RequestRating
Toshowtheratingdialogusingitsdefaultcontentandretainitsdefaultbehavior,usethe
RequestRatingmethodwithoutanyparameter.Notethatthismethodisano-opiftherating
dialoghasbeendisabled,oroneofdisplayconstraintsisnotsatisfied.Youshouldcallthis
methodwhenitmakessenseintheexperienceflowofyourapp,tomaximizethe
effectivenessoftherequest.
OniOS10.3ornewer,theactualdisplayoftheratingdialogisgovernedbyAppStore
policy.Whenyourappisstillinsandbox/developmentmode,thedialogisalways
displayedfortestingpurpose.However,itwon'tbeshowninanappthatyoudistribute
usingTestFlight.
//Showtheratingdialogwithdefaultbehavior
StoreReview.RequestRating();
Tocheckiftheratingdialoghasbeendisabled(becausetheuserselectedDon'tAskAgain
oralreadygavearating):
//Checkiftheratingdialoghasbeendisabled
boolisDisabled=StoreReview.IsRatingRequestDisabled();
Togetthenumberofusedandremainingrequestsinthecurrentyear:
//Getthenumberofrequestsusedthisyear
intusedRequests=StoreReview.GetThisYearUsedRequests();
//Getthenumberofunusedrequeststhisyear
intunusedRequests=StoreReview.GetThisYearRemainingRequests();
Togetthetimestampofthelastrequest:
Scripting
191

//Getthetimewhenthelastratingpopupisshown
DateTimelastTime=StoreReview.GetLastRequestTimestamp();
Tocheckifit'seligibletoshowtheratingdialog(whichmeansithasn'tbeendisabledandall
displayconstraintsaresatisfied):
//Checkifit'seligibletoshowtheratingdialogandthenshowit
if(StoreReview.CanRequestRating())
{
StoreReview.RequestRating();
}
LocalizetheRatingDialog
Tolocalizethecontentoftheratingdialog,simplycreateanewRatingDialogContenttohold
thetranslatedtexts(whichyoumayobtainfromastandardlocalizationplugin),andpassitto
theRequestRatingmethod.
//CreateaRatingDialogContentobjecttoholdthetranslatedcontentofthedialog
varlocalized=newRatingDialogContent(
YOUR_LOCALIZED_TITLE+RatingDialogContent.PRODUCT_NAME_PLACEHOLDER,
YOUR_LOCALIZED_MESSAGE+RatingDialogContent.PRODUCT_NAME_PLACEHOLDER+"?
",
YOUR_LOCALIZED_LOW_RATING_MESSAGE,
YOUR_LOCALIZED_HIGH_RATING_MESSAGE,
YOUR_LOCALIZED_POSTPONE_BUTTON_LABEL,
YOUR_LOCALIZED_REFUSE_BUTTON_LABEL,
YOUR_LOCALIZED_RATE_BUTTON_LABEL,
YOUR_LOCALIZED_CANCEL_BUTTON_LABEL,
YOUR_LOCALIZED_FEEDBACK_BUTTON_LABEL
);
//Showtheratingpopupwiththelocalizedtexts
StoreReview.RequestRating(localized);
AnyinstanceofRatingDialogContent.PRODUCT_NAME_PLACEHOLDER(literalvalue
"$PRODUCT_NAME")willbeautomaticallyreplacedbytheactualproductname(given
inPlayerSettings)bytheRequestRatingmethod.
RequestRatingwithCustomBehavior
OnAndroidoriOSolderthan10.3,youcandiscardthedefaultbehavioroftheratingdialog
andprovideyourownimplementationtosuityourneeds(again,oniOS10.3ornewerwe
employthenativeratingpromptwhosebehaviorisgovernedbythesystemitself).Thiscan
Scripting
192

beusefulincaseswhenyouwanttoperformadditionaltaskslikerecordingthenumberof
userswhogavegoodratings(maybeforanalyticspurpose).Todoso,simplycallthe
RequestRatingmethodpassingacallbackinwhichthecustombehaviorisimplemented.
Thiscallbacktakesasinputanenumvaluerepresentingtheuseraction,whichyoucanuse
todecidewhateveractionshouldbetaken.NotethatyoucanusetheDisableRatingRequest
methodtopreventtheratingdialogfrombeingdisplayedinthefuture,iftheuserselects
"Don'tAskAgain"option.AlsonotethatyoucanpassanullRatingDialogContentobjectto
usethedefaultcontent,otherwisecreateanewobjectasdescribedintheLocalizedthe
RatingDialogsectionabove.
Fromtheanalyticspointofview,it'sworthnotingthattheratinggivenintherating
dialogonAndroidismerelyasuggestionofhowtheuserwouldratetheapp.There's
currentlynoreliablewaytoverifyifitistheactualratinggivenontheappstoresornot.
//Showratingdialogwithacallbackforcustombehavior
//PassingnullfortheRatingDialogContentparametertousethedefaultcontent
StoreReview.RequestRating(null,RatingCallback);
//Theratingcallback
privatevoidRatingCallback(StoreReview.UserActionaction)
{
switch(action)
{
caseStoreReview.UserAction.Refuse:
//Don'taskagain.Disabletheratingdialog
//topreventitfrombeingshowninthefuture.
StoreReview.DisableRatingRequest();
break;
caseStoreReview.UserAction.Postpone:
//UserselectsNotNow/Cancelbutton.
//Thedialogautomaticallycloses.
break;
caseStoreReview.UserAction.Feedback:
//Badrating,useroptstosendfeedbackemail.
break;
caseStoreReview.UserAction.Rate:
//Goodrating,userwantstorate.
break;
}
}
Scripting
193

PlayMakerActions
ThePlayMakeractionsoftheUtilitiesmodulearegroupinthecategoryEasyMobile-
UtilitiesinthePlayMaker'sActionBrowser.
PleaserefertotheUtilitiesDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowthese
actionscanbeused.
PlayMakerActions
194

PlayMakerActions
195

ReleaseNotes[Basic]
ReleasenotesofEasyMobileBasic.
Version1.0.0p1
BugFixes
Notificationsmodule:
Fixedabugthatmaycauselocalnotificationstonotbescheduledcorrectlyifthe
triggerdateisnotspecifiedinlocaltimezone.
Version1.0.0
Firstrelease.
ReleaseNotes[Basic]
196

ReleaseNotes[Pro]
ReleasenotesofEasyMobilePro.
Version1.2.1
Changes
GameServicesmodule:
ImprovedPlayMakeractionsforSavedGamesAPI.
ImprovedPlayMakerdemosceneforSavedGamesfeature.
Editor:
UpdatedtoGooglePlayServicesResolver1.2.69.
Version1.2.0p1
BugFixes
Notificationsmodule:
Fixedabugthatmaycauselocalnotificationstonotbescheduledproperlyifthe
triggerdateisnotspecifiedinlocaltimezone.
Version1.2.0
Thisisamajorupdateinwhichwe'readdingbrandnewfeatures,revampingthewholeAPI
aswellasfixingsomeknownissues.Wealsorenamedsomemodulesandrevisedrelated
wordingtobetterpresenttheplugincontent.Mostimportantly,withthisupdatewe're
restructuringtheEasyMobileproductline.Specifically:
TheexistingEasyMobileversionwillnowbeEasyMobilePro,whichisthepremium
versionandcontainsalltheavailablefeaturesoftheplugin.CurrentEasyMobileusers
thereforewillowntheProversionautomatically.
AnewversionnamedEasyMobileBasicwillbeintroducedatalowerpricethanthePro
ReleaseNotes[Pro]
197

version.Itwillcontainallthecorefeaturesbutwithoutafewadvancedonessuchas
GIFandSavedGames.FordetailsaboutfeaturedifferencesbetweenProandBasic
versionspleaseseetheFeatureComparisontable.
TheEasyMobile:GIFToolsversionwillbedeprecated.
NewFeatures
GameServicesmodule:
AddedthebrandnewfeatureSavedGames,whichalloweasysynchronizationof
gamedatatocloudservicesincludingiCloud(iOS)andGoogleDrive(Android).
AllowsspecifyinganoptionaltheWebClientIDwhensetupGooglePlayGames.
NewPlayMakeractionsforSavedGamesfeature.
Notificationsmodule:
Addedsupportforfully-customizablelocalnotifications.
FullycompatiblewithAndroid8.0notificationchannelsandchannelgroups.
NewPlayMakeractionsforlocalnotifications.
NativeAPIsmodule:
NativeUIfeature:addedamethodtocheckwhetheranalertisbeingdisplayed.
Changes
Advertisingmodule:
RemovedIsShowingBannerAdandGetActiveBannerAdNetworksmethods
becausethere'scurrentlynoreliablewaytoobtainthisinformationthatwouldwork
consistentlyacrossallthesupportednetworks.
Scripting:
Introducedmuchofcoderefactoringtoenhancestability,maintainability,scalability
andreadability.
RenamedmajorclassestomaketheAPImoreintuitive;specifically,each
module/featurenowhasamainclasswiththesamename,whereitsAPIcanbe
accessed.
Removedthefeaturethatautomaticallydisablesdebuglogsinproductionbuilds.
Editor:
UpgradedtoGooglePlayServicesResolverversion1.2.64.0.
BugFixes
GIFmodule:
FixedGiphyuploaderror"401Unauthorized".
ReleaseNotes[Pro]
198

Version1.1.5p2
NewFeatures
Advertisingmodule:
AdManagerclassnowexposesaRewardedAdSkippedevent,whichisraisedwhen
arewardedadwasclosedbeforefinishing.
BugFixes
Editor:
ReplacedtheoldChartboostSDKdownloadURLwithanewworkingone.
Version1.1.5p1
BugFixes
PlayMakerActions:
FixedaminorerrorinthescriptforMobileNativeShare_CaptureScreenshotaction.
Version1.1.5
NewFeatures
Editor:
IncorporatedtheGooglePlayServicesResolverforUnitypluginforAndroid
dependenciesmanagement.
AddedtheImportPlayServicesResolveritemtoEasyMobilemenuformanual
importofthisresolverifneeded(normallyitwillbeimportedautomaticallyupon
importingEasyMobile)
Changes
Editor:
EasyMobile'snativecodeisnowstaticallyincludedinfolder
Assets/EasyMobile/Pluginsfolder,ratherthanbeingimportedautomaticallyfrom
ReleaseNotes[Pro]
199

scriptintoAssets/Pluginsfolderasbefore.Thisenhancestheplugin'srobustness
asitpreventsbuilderrorsduetounintendedremovalofpluginfilesinthe
Assets/Pluginsfolder.
RemovedtheReimportNativePackageitemfromEasyMobilemenu(asaresult
oftheabovechange).
BugFixes
NativeSharingmodule:
FixedabugcausingimagesharingtofailonAndroid7(Nougat)andabove.Image
sharingontheseplatformsnowusesFileProvidertocomplywiththenewAndroid
securityrequirements.
Notes
*Sincethepluginstructurechangesquitealotinthisversion,youneedtodosomecleanup
beforeimportingthenewplugin.PleaseseetheUpgradeGuidesectionformoredetails.
Version1.1.4b
Changes
GIFmodule:
OptimizedmemoryusagewhenexportingGIF.
Version1.1.4a
BugFixes
In-AppPurchasingmodule:
UpdatededitorscriptstobecompatiblewithUnityIAPversion1.14.0.
Notes
*Ifyou'reupgradingEasyMobilefromanexistingprojectthatusesIAPmodule,youneedto
upgrade(re-import)UnityIAPpackagetoo.PleaseseetheUpgradeGuidesectionformore
details.
ReleaseNotes[Pro]
200

Version1.1.4
NewFeatures
GameServicemodule:
AddedanewmethodtoshowtheUIofaspecificleaderboardinan(optional)time
scope.
In-AppPurchasingmodule:
AddedanewmethodtogetallIAPproductscreatedinthemodulesettings.
Utilitiesmodule-RatingRequestfeature:
Addednewdisplayconstraints:delayafterinstallation&cooling-offperiod.
Addedanoptiontoignoredisplayconstraintswhileindevelopmentmode.
Addednewmethodstogetthetimestampofthelastrequest,thenumberof
requestsusedinthecurrentyear,etc.
Addedtheabilitytoupdatethedialogcontentinruntimeforlocalizationpurposes
(seetheuserguidefordetails).
Editor:
[Android]leaderboard&achievementIDsarenowsortedalphabeticallyinthe
settingsUI.
We'venowgotalittlecuteAboutwindowwhereyoucanquicklyfindouttheversion
ofyourEasyMobile:)
Changes
GameServicemodule:
UserAuthenticatedeventisnowofficiallyremoved.
Version1.1.3
NewFeatures
IntroducingbrandnewPlayMakeractions!
ReleaseNotes[Pro]
201

EasyMobileisnowcompatiblewithPlayMaker,startingwithnearly100custom
actionscoveringallmodules!
Utilitiesmodule:
AddednewmethodGetAnnualRequestLimittogettheannualcapoftherating
requestpopupfromscript
Changes
GameServicemodule:
AddedoptionalcallbacktoReportScore,RevealAchievement,UnlockAchievement
&ReportAchievementProgresstoacknowledgeiftheoperationsucceedsornot
BugFixes
Editor:
FixedabugonUnity5.6+causingEasyMobileprefabinstancetonotbedetected
properlyifthecontainingsceneisnotactive->afalse"EasyMobileInstanceNot
Found"alertisshownbeforebuilding
Version1.1.2
Changes
In-AppPurchasingmodule:
Updatedthereceiptvalidationmethodtohandlecaseswhentheinputreceiptis
nullorempty.
Version1.1.1
NewFeatures
In-AppPurchasingmodule:
AddednewmethodstoreadreceiptsfromApplestoresandGooglePlaystore
AddedanewmethodtorefreshAppleAppReceipt
ReleaseNotes[Pro]
202

Version1.1.0
Thisisamajorreleasewithmanynewfeaturesandimprovements!
NewFeatures
IntroducingbrandnewmoduleGIF!
Lowoverheadscreen/camerarecorder
Built-inplayersforplaybackofrecordedclips
Highperformance,mobile-friendlyGIFimagegenerator
GiphyuploadAPIforsharingGIFimagestosocialnetworks
NativeSharingmodule:
AddedShareTextandShareURLmethodstoMobileNativeShareclass
Editor:
AddedanewcontextmenuforcreatingEasyMobileinstanceandotherbuilt-in
objectsintheHierarchywindow
AddednewitemReimportNativePackagetoEasyMobilemenu
[Unity5.6+]AddedawarningpopupwhichisshownwhenaniOSorAndroidbuild
startswhilenoEasyMobileinstancewasaddedtoanyscene
Version1.0.4
NewFeatures
GameServicemodule:
AddedSignOutmethodtoGameServiceManagerclass.
Version1.0.3
Thisupdateintroducesimportantimprovementsandbugfixes.
NewFeatures
Advertisingmodule:
ReleaseNotes[Pro]
203

AdMobrewardedadisnowsupported
Addedsupportfornewadnetwork:AdColony
Changes
Advertisingmodule:
AdeventsarenowraisedfrommainthreadwhenusingAdMob
RewardedAdCompletedeventisnowraisedaftertheadisclosed,toensurea
consistentbehavioracrossdifferentadnetworks
BugFixes
NativeSharingmodule:
FixedapotentialmemoryleakissuecausedbytheSaveScreenshotmethodofthe
MobileNativeShareclass
Version1.0.2
NewFeatures
IntroducingwholenewmoduleUtilities:
ThefirstfeatureofthismoduleisRatingRequest,aneffectivewaytoaskfor
ratingusinganativeandhighlycustomizable"ratemyapp"popup.
GameServicemodule:
UpdatedGameServiceManagerclass,introducingnewevents
UserLoginSucceededandUserLoginFailed;_UserAuthenticated_eventisnow
obsolete.
Version1.0.1
Changes
GameServicemodule:
Updatedscriptstobecompatiblewithversion0.9.37oftheGooglePlayGames
ReleaseNotes[Pro]
204

pluginforUnity.
Version1.0.0
Firstrelease.
ReleaseNotes[Pro]
205

UpgradeGuide
Thissectiondescribestherequiredactionsyoumayneedtotakewhenupgradingtoa
certainversionofEasyMobile.PleasevisitthisplacebeforeupgradingEasyMobiletoavoid
unnecessaryissues.
Upgradingtoversion1.2.0
Version1.2.0isamajorupdateinwhichEasyMobilehasbeenrenamedtoEasyMobilePro
andlotsofimprovementsandmodificationswereintroduced,mostnotablyAPIchanges.If
you'reupgradingfromanolderversionto1.2.0,westronglyrecommendremovingtheold
versioncompletelybeforeimportingthenewonetoavoidpotentialissues.Pleasefollow
thesesteps:
BackuptheAssets/EasyMobile/ResourcesandAssets/EasyMobile/Generatedfolders
andsavethemsomewheresafe.
RemovethewholeAssets/EasyMobilefolder.
Removethefile/foldernamedcom.sglib.easymobile.easy-mobile-1.0.2infolder
Assets/Plugins/Android.
ImportEasyMobilePro1.2.0.
CopythebackedupResourcesandGeneratedfoldersbacktoAssets/EasyMobile
folders.
GotomenuAssets>PlayServicesResolver>AndroidResolver>ForceResolve.
Ifyou'reusingGameServicesmoduleonAndroid,runSetupGooglePlayGamesonce
againinthesettingsUI.
Optionallyupdateyourscriptstofixwarningsduetooldclassesbeingdeprecated(they
stillfunctionnormally,we'rejustintroducingnewclasseswithdifferentnamestomake
theAPImoreintuitive).
Upgradingtoversion1.1.5ornewer
Sinceversion1.1.5,EasyMobileincorporatestheGooglePlayServicesResolverforUnity
pluginforAndroiddependenciesmanagement,aswellasmovesallnativecodeintothe
Assets/EasyMobile/Pluginsfolder.Ifyou'reupgradingfromanolderversiontoversion1.1.5
ornewer,pleaseremovethefollowingfilesbeforeimportingthenewpackagetoavoid
potentialissues:
UpgradeGuide
206

Assets/Plugins/Android/easy-mobile.aar.
Assets/Plugins/Android/libs/armeabi-v7a/libeasymobile.so
Assets/Plugins/Android/libs/x86/libeasymobile.so
Assets/Plugins/iOS/libEasyMobile.a
Upgradingtoversion1.1.4aornewer
Sinceversion1.14.0,theUnityIAPpackagehasmadechangestoitsAPIthatcausesome
conflictswithEasyMobileeditorscripts.Weaddressedthisprobleminversion1.1.4a.If
you'reupgradingfromanolderversionto1.1.4a,andyourprojectusestheIn-App
Purchasingmodule,youneedtoupgrade(re-import)theUnityIAPpackagetoversion1.14.0
ornewertoavoidincompatibilityissues.
Upgradingtoversion1.1.0ornewer
Ifyou'reupgradingfromanolderversiontoversion1.1.0ornewer,you'llneedto:
1. RemovetheEasyMobile/Demofolder
2. RemovetheEasyMobile/Scriptfolder
3. Importthenewversion
UpgradeGuide
207