Easy Mobile User Guide
USER_GUIDE
USER_GUIDE_FOR_EASY_MOBILE
USER_GUIDE
User Manual:
Open the PDF directly: View PDF .
Page Count: 228 [warning: Documents this large are best viewed by clicking the View PDF Link!]
- About
- Introduction
- Using Easy Mobile
- Introduction
- Settings
- Scripting
- PlayMaker Actions
- Introduction
- Settings
- Scripting
- Saved Games
- PlayMaker Actions
- Introduction
- Setup
- Scripting
- PlayMaker Actions
- Introduction
- Settings
- Scripting
- Advanced Scripting
- PlayMaker Actions
- Introduction
- Native UI
- PlayMaker Actions
- Introduction
- Settings
- Scripting
- PlayMaker Actions
- Introduction
- Settings
- Scripting
- PlayMaker Actions
- Introduction
- Scripting
- PlayMaker Actions
- Introduction
- Store Review
- PlayMaker Actions
- Release Notes
- Upgrade Guide
- Troubleshooting
1.1
1.2
1.2.1
1.2.2
1.2.3
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.3.5
1.3.6
1.3.7
1.3.8
1.3.9
2.1
2.1.1
2.1.2
2.1.3
2.2
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.2.8
2.2.9
2.2.10
2.2.11
2.3
TableofContents
GETTINGSTARTED
About
Introduction
Highlights
What'sintheBox
BasicvsPro
UsingEasyMobile
Overview
MainMenu
GlobalSettings
Initializing
ScriptingAPI
Testing
UsingtheDemoApp
UsingPlayMakerActions
Requirements
ADVERTISING
Introduction
AdPlacements
DefaultvsNon-DefaultAds
GDPRCompliance
Settings
AutomaticAdLoading
DefaultAdNetworks
SetupAdColony
SetupAdMob
SetupChartboost
SetupAudienceNetwork
SetupHeyzap
SetupironSource
SetupMoPub
SetupTapjoy
SetupUnityAds
Scripting
2
2.3.1
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.4
3.1
3.2
3.2.1
3.2.2
3.2.3
3.2.4
3.3
3.3.1
3.3.2
3.3.3
3.3.4
3.3.5
3.4
3.4.1
3.4.1.1
3.4.1.2
3.4.2
3.4.2.1
3.4.2.2
3.4.2.3
3.4.2.4
3.4.2.5
3.4.2.6
3.5
4.1
4.2
WorkingwithConsent
BannerAds
InterstitialAds
RewardedAds
RemovingAds
ManualAdLoading
WorkingwithNon-DefaultAds
AdNetworkClients
PlayMakerActions
GAMESERVICES
Introduction
Settings
Android-SpecificSetup
AutoInitialization
Leaderboards&Achievements
ConstantsGeneration
Scripting
Initialization
Leaderboards
Achievements
UserProfile
SignOut
SavedGames
Settings
iOSSetup
AndroidSetup
Scripting
Opening
Writing
Reading
Deleting
Fetching
Built-inUI
PlayMakerActions
GIF
Introduction
Setup
3
4.2.1
4.2.2
4.3
4.3.1
4.3.2
4.3.3
4.3.4
4.3.5
4.4
5.1
5.2
5.2.1
5.2.2
5.2.3
5.2.4
5.2.5
5.2.6
5.2.7
5.3
5.3.1
5.3.2
5.3.3
5.3.4
5.3.5
5.3.6
5.3.7
5.4
5.4.1
5.4.2
5.4.3
5.4.4
5.5
6.1
6.2
6.2.1
Recorder
AnimatedClip
Scripting
Recording
Playback
ExportingGIF
DisposingAnimatedClip
SharingGIF
PlayMakerActions
IN-APPPURCHASING
Introduction
Settings
EnablingUnityIAP
TargetAndroidStore
AppleAsk-To-Buy
ApplePromotionalPurchases
ReceiptValidation
ProductManagement
ConstantsGeneration
Scripting
Initialization
ObtainingProductList
MakingPurchases
CheckingOwnership
RestoringPurchases
AppleAsk-To-Buy
ApplePromotionalPurchases
AdvancedScripting
GettingUnityIAPProduct
GettingProductLocalizedData
GettingSubscriptionInfo
WorkingwithReceipts
PlayMakerActions
NATIVEAPIs
Introduction
NativeUI
Scripting
4
6.2.1.1
6.2.1.2
6.3
7.1
7.1.1
7.1.2
7.1.3
7.1.4
7.2
7.2.1
7.2.2
7.2.3
7.2.4
7.2.5
7.3
7.3.1
7.3.2
7.3.3
7.3.4
7.3.5
7.3.6
7.3.7
7.4
8.1
8.1.1
8.1.2
8.1.3
8.1.4
8.2
8.2.1
8.3
8.3.1
8.3.2
8.3.3
8.4
Alerts
Toasts
PlayMakerActions
NOTIFICATIONS
Introduction
UnderstandingNotifications
Localvs.Remote
NotificationCategories
GDPRCompliance
Settings
AutoInitialization
RemoteNotificationSetup
AddingNotificationResources
CategoryManagement
ConstantsGeneration
Scripting
WorkingwithConsent
Initialization
LocalNotifications
RemoteNotifications
HandlingOpenedNotifications
RemovingDeliveredNotifications
BadgeNumber
PlayMakerActions
PRIVACY
Introduction
ConsentManagementSystem
ConsentDialog
EEARegionChecking
ProposedWorkflow
Settings
ConsentDialogComposer
Scripting
EEARegionChecking
WorkingwithConsentDialog
ManagingGlobalConsent
PlayMakerActions
5
EasyMobileProUserGuide
ThisdocumentistheofficialuserguidefortheProversionoftheEasyMobilepluginforUnitybySgLibGames.
EasyMobileVersions
EasyMobilePro
EasyMobileBasic
EasyMobileLite(Free)
ImportantLinks
Website
APIReference
SupportEmail
Forum
ConnectwithSgLibGames
UnityAssetStore
Facebook
Twitter
YouTube
About
7
Introduction
Todaymobilegameshavemany"defactostandard"featuresrangingfromadvertising,in-apppurchasing,game
services,notificationstosharingandratingsystem.Theseareconsidered"necessaryevils"bymanydevelopers
though:required,butnotnecessarilyfuntodo.Moreimportantly,ittakestime(alot)toimplementthem.Thatmeans
developerswouldneedtorepeattheboring,time-consumingtaskofintegratingthesefeaturesinalmosteverymobile
gamestheyaregoingtobuild,insteadoffocusingtheirenergyondoingwhatwe'reallingamedevelopmentfor:
creatingfun!
EasyMobileisourattempttosolvethisproblem.Itisamany-in-oneUnitypluginthatgreatlysimplifiesthe
implementationofthedefactostandardfeatureseverymobilegameneeds.Itispackedwithready-to-use
componentsthatyoucanjust"plug"intoyourproject,andhavethenecessaryevilstakencareof.We'vebeen
spendingthousandsofhourscreatingthisproduct,soyoucansavethatsameamountoftime andspendyourtime
onrealgamedevelopment!
EasyMobilesupportstwomajormobileplatforms:iOSandAndroid.
Highlights
UsabilityisournumberoneprioritywhendesigningEasyMobile,hencethename.Othertopprioritiesincluding
robustness,flexibilityandversatility.Withthatinmind,wecomeupwithaproductthatboaststhefollowinghighlights:
Cross-platformAPI:EasyMobile'sscriptingAPIallowsyoutoaccomplishmosttaskswithonlyonelineofcode.
Anditiscross-platform,whichmeansyoucanwritecodeonceanddeployonbothiOSandAndroid.
FriendlyEditor:EasyMobilecomeswithasimpleyetpowerful(andbeautiful!)built-ineditorthatallowsyouto
easilycontroleverythingfromoneplace.
Automation:Muchmorethanamerecollectionofmethods,EasyMobileoperatesquietlyinthebackgroundand
automatesmanychoressuchasserviceinitializationandadloading.
ModularDesign:EasyMobile’sfunctionalitiesaregroupedintomodulesthatcanbeenabledordisabled
separately,soyoucanusewhatyouwantandnotbeworriedaboutredundanciesorcontradictionswithexisting
code.
LeveragingOfficialSDKs:EasyMobileleveragesofficial3rd-partySDKsofinterest,e.g.GooglePlayGames
pluginforUnity,integratingthemwithitsowncodetoformacoherentsystemthatmaximizesusabilityand
reliability-withoutreinventingthewheel!
VisualScripting :EasyMobileisnotonlyforcoders-itisfullycompatiblewithUnity'smostpopularvisual
scriptingpluginPlaymaker,thankstomorethan100customactionsbuilt-intothepackage.
What'sintheBox?
Thispluginiscurrentlypackedwiththefollowingmodules:
Advertising
SupportsmostpopularadnetworksincludingAdColony,AdMob,Chartboost,FacebookAudienceNetwork,
Heyzap,ironSource,MoPub,TapjoyandUnityAds
EvenmoreadnetworkscanbeusedviamediationserviceprovidedbyAdMob,Heyzap,ironSource,MoPub
orTapjoy
1
2
Introduction
8
Automaticadloading
Allowsusingmultipleadnetworksinonebuild
Allowsdifferentadconfigurationsfordifferentplatforms
Allowshavingmultipleplacementsofeachadtype
Userconsentsupport(GDPRcompliant)
GameServices
WorkswithGameCenteroniOSandGooglePlayGamesonAndroid
Automaticauthentication
Customeditorforeasymanagementofleaderboardsandachievements
SavedGames
GIF
Recordsscreen,playsrecordedclipsandexportsGIFimages
High-performance,mobile-friendlyGIFencoder
GiphyuploadAPIforsharingGIFtosocialnetworks
In-AppPurchasing
LeveragesUnityIn-AppPurchasingservice
Customeditorforeasymanagementofproductcatalog
Autoinitialization
Receiptvalidation
Apple'sAskToBuy
Apple'sPromotionalPurchases
NativeAPIs
AccesstomobilenativeUIelementsincludingalertsand(Android)toasts
Morenativefunctionalitieswillbeaddedsoon
Notifications
Fully-customizablelocalnotifications
CompatiblewithOneSignalandFirebaseCloudMessaging,freeandpopularservicesforpushnotifications
SupportsnotificationchannelsandchannelgroupsonAndroidOandhigher
Userconsentsupport(GDPRcompliant)
Privacy
ProvidestoolsandresourcestohelpgettingcompliantwithuserprivacyregulationssuchasGDPR
Multi-purposenativeconsentdialogthatcanactasthecommoninterfaceforcollectinguserconsentforall
relevantservices,insteadofhavingmultipleinterfacesforvariousservices
Built-insystemtomanageconsentandcommunicateconsenttorelevantservices
3
Introduction
9
APItocheckifthecurrentdeviceisintheEuropeanEconomicArearegion(EEAregion,affectedbyGDPR)
Sharing
Sharestexts,URLsandimagesusingthemobilenativesharingfunctionality
Utilities
StoreReview:providesaneffectivewaytoaskforreviewsandratingsusingthesystem-providedrating
promptofiOS(10.3+)andanative,highlycustomizablepopuponAndroid
EasyMobile:BasicvsPro
EasyMobilecomesin2differentversions:BasicandPro.EasyMobileBasicisthelower-priceversionwhichcontains
mostofthecorefeaturesoftheplugin.TheProversionisthepremiumoneandhaveallfeaturesavailable.Belowisa
feature-comparisontableoftheBasicandProversionsofEasyMobile.
Introduction
10
Introduction
11
UsingEasyMobile
Overview
UsingEasyMobileinvolves3steps:
1. Configuringthepluginusingthebuilt-inSettingsinterface
2. Initializingthepluginruntime
3. MakingappropriateAPIcallsfromscript(orusingvisualscriptingactions)
MainMenu
AfterimportingEasyMobile,therewillbeanewmenuaddedatWindow>EasyMobilefromwhichyoucanopenthe
GlobalSettingsinterfacetoconfiguremodulesaswellasaccessotherresources.
GlobalSettings
TheGlobalSettingsinterfaceistheonlyplaceyougotoconfiguretheplugin.Hereyoucancontroleverything
includingtogglingmoduleonoroff,providingadscredentials,addingleaderboards,creatingaproductcatalog,etc.
UsingEasyMobile
13
AllthesesettingsarestoredintheEM_Settingsobject,whichisaScriptableObjectcreatedautomaticallyafter
importingthepluginandislocatedatAssets/EasyMobile/Resources.YoucanalsoaccessthisEM_Settingsclass
fromscriptandviaitspropertiesaccessingeachmodulesettingsinruntime.
UsingEasyMobile
14
Initializing
YoumustalwaysinitializeEasyMobilebeforeusingitsAPI.
SincetheinitializationmustbedonebeforetheEasyMobileAPIcanbeused,itisadvisabletodoitassoonasyour
applaunches.Thiswillalsoallowautomaticservicessuchasadloadingtostartsoonandtheadswillbemorelikely
availablewhenneeded.Practically,thismeansattachingtheinitializingscripttosomegameobjectinthefirstsceneof
yourapp.
ToinitializesimplycalltheInitmethodoftheRuntimeManagerclass.Thismethodisano-opiftheinitializationhas
beendonesoit'ssafetobecalledmultipletimes.YoucanalsocheckifEasyMobilehasbeeninitializedusingthe
IsInitializedmethod.
usingUnityEngine;
usingSystem.Collections;
usingEasyMobile;//includetheEasyMobilenamespacetouseitsscriptingAPI
publicclassEasyMobileInitializer:MonoBehaviour
{
//ChecksifEMhasbeeninitializedandinitializeitifnot.
//ThismustbedoneoncebeforeotherEMAPIscanbeused.
voidAwake()
{
if(!RuntimeManager.IsInitialized())
RuntimeManager.Init();
}
}
ScriptingAPI
AfterinitializingEasyMobileyoucanuseitsscriptingAPI,whichiswritteninC#.NotethattheAPIisputunderthe
namespaceEasyMobilesoyouneedtoaddthefollowingstatementtothetopofyourscriptstoaccessit.
//PutthisontopofyourscriptstouseEasyMobilescriptingAPI
usingEasyMobile;
UsingEasyMobile
15
EasyMobile'sAPIiscross-platformsoyoucanusethesamecodeforbothiOSandAndroid.Youcanfindthe
APIreferencehere.
Testing
MostofEasyMobile'sfunctionalitiesaremobile-specificwhichmaynotworkproperlywithintheUnityeditor.
ThereforeyoushouldalwaysperformtestingonactualiOSorAndroiddevices.
UsingtheDemoApp
EasyMobilecomeswithademoappthatyoucanusetoquicklytesteachmodule'soperationafterconfiguring,even
beforewritinganycode!ThedemoappislocatedatfolderAssets/EasyMobile/Demo.Itsscenesareinthesubfolder
Scenes,youcanbuildallscenesoronlyscenesthatyouwanttotest.ItsscriptsareinthesubfolderScripts,which
youcanrefertoforanexampleofhowEasyMobileAPIcanbeused.
UsingPlayMakerActions
EasyMobileisofficiallycompatiblewithPlayMaker,withmorethan100customactionsreadytobeused.Youcan
installtheseactionsfrommenuWindow>EasyMobile>InstallPlayMakerActions.
WheninstallingthePlayMakeractions,ademoappwillalsobeimportedat
Assets/EasyMobile/Demo/PlayMakerDemo.Thisdemoisacopyofourmaindemoapp,rebuiltusingPlayMaker
actionsinsteadofC#scripts.YoucantakeitasanexampletogetaninsightintohowEasyMobile'sPlayMaker
actionscanbeusedinpractice.ApartfrominstallingPlayMaker(obviously),youneedtodoafewmoresetupsteps
asdescribedbelowbeforerunningthisdemoapp.
InstallingtheUnityUIadd-onforPlayMaker
OurPlayMakerdemoappusestheUnityUIsystem,soyouneedtoinstalltheUnityUIadd-onforPlayMaker.
ImportingPlayMakerGlobals
ThedemoappusesglobalPlayMakervariablesandevents,soyouneedtoimportthembeforerunningtheapp.
UsingEasyMobile
16
Ifyouprojectisnewanddoesn'thaveanyPlayMakerGlobals(intheAssets/PlayMaker/Resourcesfolder),simplycopy
ourGlobalsoverbythesesteps:
Double-clickthePlayMakerGlobals.unitypackageintheAssets/EasyMobile/Demo/PlayMakerDemofolder.
LocatethenewlycreatedfilePlayMakerGlobals_EXPORTED.assetrightundertheAssetsfolder.
RenamethefiletoPlayMakerGlobals.assetandmoveittotheAssets/PlayMaker/Resourcesfolder.
IfyouprojectalreadycontainssomeglobalPlayMakervariablesandevents(aPlayMakerGlobals.assetfileexistsin
thefolderAssets/PlayMaker/Resources),youcanmergethesewithourdemo'sGlobalsusingPlayMaker'sImport
Globalstool:gotomenuPlayMaker>Tools>ImportGlobalsandselectthePlayMakerGlobals.unitypackageinthe
Assets/EasyMobile/Demo/PlayMakerDemofolder.
WiththePlayMakerdemoapp,weperformtheEasyMobileinitializationusingaFSMinthe
DemoHome_PlayMakerscenesoyoushouldalwaysbuildthisscenewhenusingthisdemoapp.
Requirements
EasyMobilerequires:
Unity5.5.5ornewer.
iOS8.0ornewer.
Android4.0(APILevel14)ornewer.
UsingEasyMobile
17
Advertising:Introduction
TheAdvertisingmodulehelpsyouquicklysetupandshowadsinyourgames.Here'resomehighlightsofthismodule:
Supportsmultiplenetworks
Thismoduleallowsshowingadsfrommostoftopadnetworks:AdColony,AdMob,Chartboost,Facebook
AudienceNetwork,Heyzap,ironSource,MoPub,TapjoyandUnityAds
EvenmorenetworkscanbeusedviamediationserviceprovidedbyAdMob,Heyzap,ironSource,MoPubor
Tapjoy
Usingmultiplenetworksinonebuild
It'spossibletousemultipleadneworksatthesametime,e.g.useAdMobforbannerads,whileusing
ChartboostforinterstitialadsandUnityAdsforrewardedads
Differentconfigurationsfordifferentplatformsareallowed,e.g.useUnityAdsforrewardedadsonAndroid,
whileusingChartboostforthattypeofadsoniOS
Automaticadloading
Adswillbefetchedautomaticallyinthebackground;newadwillbeloadedifthelastonewasshown
ThetablebelowsummarizestheadtypessupportedbyEasyMobileforeachadnetwork.
AdNetwork BannerAd InterstitialAd RewardedAd Mediation
AdColony ◉ ◉
AdMob ◉ ◉ ◉ ◉
Chartboost ◉ ◉
AudienceNetwork ◉ ◉ ◉
Heyzap ◉ ◉ ◉ ◉
ironSource ◉ ◉ ◉ ◉
MoPub ◉ ◉ ◉ ◉
Tapjoy ◉◉◉
UnityAds ◉ ◉
AdPlacements
AnadplacementinEasyMobilerepresentsaspecific"location"inyourappwhereanadisserved.Forexamplethe
"GameOver"placementcanbedefinedasthe"location"inyourappwhereagameisover,andanadisserved.Anad
placementisnormallyassociatedwithanadunitwithaspecificadID.EasyMobilehasseveralbuilt-inadplacements
includingadefaultplacementandotherplacementssuchas"Startup","HomeScreen","MainMenu",etc.Youcanalso
createmorecustomadplacementstosuityourneeds.
Groupingadsintoplacementsprovidesanintuitivewaytoorganizeadsinyourappandsimplifiestheimplementation
offlexibleandsophisticatedadvertisingstrategies.Itallowshavingmorethanoneunitofacertainadtype(banner,
interstitialorrewardedad)ofthesameadnetwork.Forexample,youcanusethedefaultplacementtoshownormal
AdMobinterstitialads(andgetpaid!),whilehavinga"Startup"placementtoshowAdMobinterstitialhouseads(atapp
launch),thuscreatingafreecross-promotionsystemforyourapps!
Introduction
18
Adplacementsdon'tcomplicatethingsthough.Ifyouonlyneedabasicusageofadvertising,youcansimply
ignoreallplacementstuffwhenworkingwiththeAdvertisingAPI,andEasyMobilewillautomaticallyusethe
defaultplacement.Therefore,it'sonlynecessarytoprovideadIDsassociatedwiththedefaultplacementwhen
settingupadnetworks(someadnetworksmaynotevenrequiresuchIDs).Allotheradplacementsareoptional
andyouonlyneedtoconfigureifyouwanttouseanyofthem.
DefaultvsNon-DefaultAds
Adefaultadofacertaintypeistheadunitthatbelongstothedefaultnetworkforthatadtypeatthedefault
placement.Forexample,ifthedefaultinterstitialnetworkforthecurrentplatformisAdColony,thentheAdColony
interstitialadatthedefaultplacementisthedefaultinterstitialad.Therestareconsiderednon-defaultinterstitialads.
Thesameistrueforbannerandrewardedads.
GDPRCompliance
WerecommendyoutoreadthePrivacychapterfirsttogainacomprehensiveunderstandingofthetoolsand
resourcesofferedbyEasyMobiletohelpyourappgetcompliantwithGDPR,includingtheconsentdialogand
theconsentmanagementsystem.
AdvertisingisoneofthoseservicesaffectedbytheGDPR,becausemostadproviderscollectuserdatatoserve
personalizedads.Mostadnetworksrecommendrequestinguserconsentforsuchdatausageandservepersonalized
ornon-personalizedadsaccordingly.EasyMobileprovidesanative,multi-purposedialogforcollectinguserconsent
foralladnetworksoreachindividualadnetwork,aswellasotherrelevantservices,inaflexiblemanner.Italsoallows
youtoflexibilycommunicatethecollectedconsent(applytheconsent)totheAdvertisingmoduleeitheratthemodule
levelorthevendorlevel.
Allowingtheusertoprovideandmanageconsentforallservicesviaasingleinterface(dialog)isadvisablein
termsofuserexperience,becausetheusermayfinditirritatingbeingpresentedmultipledialogsasking
consentforvariousthings.
Consent Description Priority
Module
consent
CommonconsentappliedtoallsupportedadnetworksintheAdvertising
module Lower
Vendor
consent Consentappliedtoanindividualadnetwork,e.g.AdMob Higher
Consentisnormallyappliedduringtheinitializationofanadnetwork.Thereforeitisimportanttocollectconsent
beforeinitializingadnetworks.PracticallythismeanscollectingconsentbeforeinitializingtheEasyMobileruntime.If
there'savendorconsentspecifiedforthecurrentnetwork,itwillbeused.Otherwisethemoduleconsentwillbeused.
Ifneitherwasspecified,theglobalconsentwillbeused.Incasenoconsentprovidedatalllevels,theadnetwork
carriesoutitsinitializationwithoutapplyinganyconsentandwillservepersonalizedads(the"pre-GDPR"behavior).
ThetablebelowsummarizeshowEasyMobileconfigureseachnetworkaccordingtotheprovidedconsent.
Ad
Network ConsentGranted ConsentRevoked Consent
Unknown
AdColony SettingGdprRequiredto'true'and
GdprConsentStringto"1"
SettingGdprRequiredto'true'and
GdprConsentStringto"0"
Do
nothing
AdMob Donothing(keepservingpersonalized
adsasnormal)
Setting"npa"keyto"1"when
constructingAdRequesttoservenon-
personalizedads
Do
nothing
Chartboost Calling Calling Do
Introduction
19
Chartboost Chartboost.restrictDataCollection(false); Chartboost.restrictDataCollection(true); nothing
Audience
Network Donothing Donothing Do
nothing
Heyzap Calling
HeyzapAds.SetGdprConsent(true);
Calling
HeyzapAds.SetGdprConsent(false);
Do
nothing
ironSource CallingIronSource.setConsent(true); CallingIronSource.setConsent(false); Do
nothing
MoPub Calling
MoPub.PartnerApi.GrantConsent();
Calling
MoPub.PartnerApi.RevokeConsent();
Do
nothing
Tapjoy CallingTapjoy.SetUserConsent("1"); CallingTapjoy.SetUserConsent("0"); Do
nothing
UnityAds CallingSetGdprMetadata(true); CallingSetGdprMetadata(false); Do
nothing
Reference:
AdColony:https://github.com/AdColony/AdColony-Unity-SDK-3/wiki/GDPR
AdMob:https://developers.google.com/admob/unity/eu-consent
Chartboost:theC#sourcecodeoftheChartboostSDKforUnity
FacebookAudienceNetwork:https://developers.facebook.com/docs/audience-network/unity/(thedocumentation
doesn'tmentionanythingaboutconsent)
Heyzap:https://developers.heyzap.com/docs/unity_sdk_setup_and_requirements#step-7-adding-user-consent
ironSource:https://developers.ironsrc.com/ironsource-mobile/android/advanced-settings/#step-1
MoPub:https://developers.mopub.com/docs/unity/gdpr/
Tapjoy:https://dev.tapjoy.com/sdk-integration/
UnityAds:https://unityads.unity3d.com/help/legal/gdpr
Introduction
20
Advertising:Settings
TousetheAdvertisingmoduleyoumustfirstenableit.GotoWindow>EasyMobile>Settings,thenclickthetoggle
totheright-handsideoftheAdvertisingtabtoenableandstartconfiguringthemodule.
AutomaticAdLoading
AutomaticadloadingisahandyfeatureoftheAdvertisingmodule.Itregularlychecksfortheavailabilityofads,and
performsloadingifanadhasn'tbeenloadedorwasconsumed.Withautomaticadloading,youcanforgetabout
loadingadsmanuallyandrestassurethatadsarealwaysreadywhenevertheyareneeded.Youcanconfigurethis
featureintheAUTOAD-LOADINGCONFIGsection.
Settings
21
AutoAd-LoadingMode:
None:disabletheautoad-loading,youcanloadadsmanuallyfromscript,seeManualAdLoading
LoadDefaultAds:onlydefaultadsareloadedautomatically,non-defaultadsmustbeloadedmanually
LoadAllDefinedPlacements:thismodeallowsloadingofallplacements(defaultandnon-default)thatare
addedinthemodulesettings,providedthattheplacementsarewelldefined,meaningthateachhasavalid
associatedID(whereapplicable)andthecorrespondingSDKisimported;thisistherecommendedoption
AdCheckingInterval:changethisvaluetodeterminehowfrequentlythemoduleshouldperformadsavailability
check,thesmallervaluethemorefrequently
AdLoadingInterval:theminimumdurationrequiredbetweentwoadloadingrequests
DefaultAdNetworks
YoucanselectdefaultadnetworksforeachplatformintheDEFAULTADNETWORKSsection.Youcanhave
differentnetworksfordifferentadtypesanddifferentselectionsfordifferentplatforms.Ifyoudon'twanttousea
certaintypeofad,simplysetitsnetworktoNone.
Payattentiontothewarningsandimporttherequiredpluginsifyouhaven'talready.
Settings
22
Advertising:Settings|SetupAdColony
CreatingAdColonyAppsandZoneIds
ToshowadsfromAdColonyyouneedtocreateappsandadzonesinitsclientsportal.Toaccesstheclientsportal,
createanaccountandlogintoAdColonypage.
Intheclientsportal,selectMONETIZATIONtab,thenselecttheAppssub-tabandclicktheSetupNewAppbutton.
Intheopenedpageentertherequiredinformationforyournewapp,e.g.appname,platformandlocation.Youcan
alsoselecttheadtypesthatyouwouldliketoallowinyourapp.HitCreatewhenyou'redone,yourappwillbecreated
andyou'llberedirectedbacktotheAppspage.Selectyournewlycreatedapptorevealitsinformation,whichlooks
similartothepicturebelow.NotetheAdColonyAppIDaswewilluseitlater.
Nowyourappisready,thenextstepistocreateadzonesforit.ClicktheSetupNewAdZoneatthebottomofthe
appeditpagetocreateanewadzone.
IntheIntegrationsection,giveyouradzoneaname,optionalnotesandsetitsasactive.NotetheZoneIDaswe'll
useitlater.
TheZoneIDwillappearonceyousaveyournewadzone.
SetupAdColony
23
IntheCreativeTypesection,selecttheVideooption.
IntheZoneTypesection,selectPreroll/Interstitialifyouwanttousethiszoneforinterstitialvideoads.Otherwise,
selectValueExchange/V4VCtouseitforrewardedads.
SetupAdColony
24
IntheOptionssection,youcansetadailycaporasessioncaptolimitthenumberofadsservedtoauserperdayor
persession,respectively.IntheDevelopmentsection,youcanchoosetoshowtestadsonly(fordebugpurpose),
don'tforgettodisablethisoptionwhenyourappisreleased.
Nowyournewadzoneisfullyconfigured,clicktheSavebuttontosaveit.Repeattheprocesstocreateotherad
zonestosuityourneeds.Typically,you'dwanttohave2adzones,oneforinterstitialadsandoneforrewardedads.If
you'retargetingmultipleplatforms,createanewappforeachplatform,andforeachappcreatethenecessaryad
zones.
ImportingAdColonyPlugin
TohaveyourUnityappworkwithAdColonyyouneedtoimporttheAdColonypluginforUnity.IntheADCOLONY
sectionoftheAdvertisingmodule,clicktheDownloadAdColonyPluginbuttontotoopenthedownloadpage.
Downloadthepluginandimportittoyourproject.
ConfiguringAdColony
AfterimportingtheAdColonyplugin,theADCOLONYsectionwillbeupdatedasbelow.
SetupAdColony
25
AppID
InthissectionyoucanentertheappIDcreatedintheAdColonyclientsportalforeachplatform.
DefaultPlacement
HereyoucanentertheadIDstobeusedwiththedefaultplacementforeachplatform.ThesearetheonlyadIDs
requiredifyouarenotusinganycustomplacementsinyourapp.NotethatyouonlyneedtoprovideIDsforthead
typesyouwanttouse,e.g.ifyouonlyuseAdColonyinterstitialadsyoucanleavetherewardedadIDsempty.
CustomPlacements
HereyoucanoptionallyentertheadIDsassociatedwithnon-defaultadplacementstobeusedinyourapp.Youcan
haveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenewplacementsforyour
needs.
AdSettings
Thissectionincludesothersettingsincluding:
AdOrientation:thedefaultadorientationofyourapp.
ShowRewardedAdPrePopup:whethertoshowapopupbeforearewardedadstarts.
ShowRewardedAdPostPopup:whethertoshowapopupafterarewardedadhasfinished.
SetupAdColony
26
SetupAdColony
27
Advertising:Settings|SetupAdMob
ImportingAdMobPlugin
ToshowadsfromAdMobyouneedtoimporttheGoogleMobileAdsplugin.IntheADMOBsection,clickthe
DownloadGoogleMobileAdsPluginbuttontotoopenthedownloadpage.Downloadthepluginandimportittoyour
project.
ConfiguringAdMob
AfterimportingtheGoogleMobileAdsplugin,theADMOBsectionwillbeupdatedasbelow.
SetupAdMob
28
AppID
FirstyouneedtoentertherequiredAdMobappIDforeachplatform.TofindtheAppIDforyourappfollowthe
instructionshere.
UpdateyourAndroidManifest.xmlonAndroidplatform
ApartfromenteringtheappIDintheAdvertisingmodulesettings,startinginversion17.0.0oftheGoogle
MobileAdsSDKforAndroid,youasanAdMobpublisherarerequiredtoaddyourAdMobappIDinyour
AndroidManifest.xmlfile.Theaddedelementshouldlooklikebelow.
<manifest>
<application>
<!--TODO:ReplacewithyourrealAdMobappID-->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-################~##########"/>
</application>
</manifest>
YoucaneitheraddthisappIDtotheAssets/Plugins/Android/AndroidManifest.xmlfileifyouhaveone,orthe
Assets/Plugins/Android/GoogleMobileAdsPlugin/AndroidManifest.xmlfile.Inthelattercase,youmayneedto
repeattheprocessifyoure-importtheGoogleMobileAdsplugin.
Toknowifyou'reusingtheAndroidGoogleMobileAdsSDKversion17.0.0ornewer,navigatetoyour
Assets/Plugins/Androidfolderandchecktheversionoffollowingfiles(inthisscreenshotwearehavingversion
17.0.0,yoursmaybedifferent).
Youcanlearnmoreaboutthisrequirementhere.
DefaultPlacement
HereyoucanentertheadIDstobeusedwiththedefaultplacementforeachplatform.ThesearetheonlyadIDs
requiredifyouarenotusinganycustomplacementsinyourapp.NotethatyouonlyneedtoprovideIDsforthead
typesyouwanttouse,e.g.ifyouonlyuseAdMobbanneradsyoucanleavetheinterstitialandrewardedadIDs
empty.
Ifyou'renotfamiliarwithAdMob,followtheinstructionsheretocreateadunitsandobtaintheadIDs;anadID
shouldhavetheformofca-app-pub-0664570763252260xxxxxxxxxxx.
CustomPlacements
HereyoucanoptionallyentertheadIDsassociatedwithnon-defaultadplacementstobeusedinyourapp.Youcan
haveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenewplacementsforyour
needs.
TargetingSettings
YoucanprovidetargetinginformationforyourappintheTargetingSettingssection.Thesesettingswillbeappliedto
allAdMobadrequestsinyourapp.YoucanlearnmoreaboutAdMobadtargetinghere.
TagForChildDirectedTreatment:indicateswhetheryouwantGoogletotreatyourcontentaschild-directed
whenyoumakeanadrequestforthepurposesofChildren'sOnlinePrivacyProtectionAct(COPPA)
SetupAdMob
29
ExtraOptions:extrasettingsinformofkeyvaluepairs,e.g.forsetting"max_ad_content_rating"
UsingAdMobwiththeDesignedforFamiliesprogram
Accordingtothisarticle,appsthatjoinintoGooglePlay'sDesignedforFamiliesprogramcanfallintotwo
categories:
Primarilychild-directedapps:ifyourappisadmittedtotheprogramasaprimarilychild-directedapp,
"AdMobwillautomaticallybeginservingDesignedforFamilies-compliantadsforalladrequestscoming
fromtheapp",whichmeansyoudon'tneedtospecifythechilddirectedsettinginyourapp.
Mixed-audienceapps:ifyourapptargetsbothchildandadultaudiences,youneedtosettheextrakey
"is_designed_for_families"totrueandtagyourappforchild-directedtreatment.YoucandothatinEasy
Mobilesettingsasbelow.
OverridingAdMobtargetingsettingsinscript
YoucanoverrideAdMobtargetingsettingsinscriptbysettingtheproperty
EM_Settings.Advertising.AdMob.TargetingSettings.Allsubsequentadrequestswillbesentwiththenew
settings.
TestMode
ToenableAdMob'stestmode,simplychecktheEnableTestModeoptionandentertheIDsofyourtestingdevices
intotheTestDeviceIdsarray.
YoucanfindtheIDofyourtestdevicebybuildingandrunningtheEasyMobiledemoapponthatdevice.
RemembertoaddtheEasyMobileprefabtotheDemoHomescenebeforestartingthebuild.
AndroiddeviceID
InUnity,buildtheEasyMobiledemoappforAndroidplatform
Installandrunthedemoapponyourtestingdevice
OpenTerminal(Mac)orCmd(Windows)andtypein
adblogcat-sAds
(ifyou'reonWindows,youmayneedtoaddtheAndroidSDKpathtotheWindowsSystemPATH)
Inthedemoapp,selectADVERTISINGandthenclicktheSHOWBANNERADbutton
ObservetheoutputlogcatintheTerminal/Cmdandlocatealinesimilartotheoneinthefollowingimage,
thevaluebetweenthedoublequotesisyourdeviceID
iOSdeviceID
SetupAdMob
30
InUnity,buildtheEasyMobiledemoappforiOSplatform
OpenthegeneratedprojectinXcodeandrunitonyourtestingdevice
Type'google'intothefilterboxoftheXcodeConsole,andfindyourdeviceIDbetweenthedoublequotes
ashighlightedinthefollowingimage
NotesonRewardedAds
AdMobonlyallowsonerewardedadtobeloadedatatime.Thatmeanstheloadedadmustbeconsumedbefore
anotheradcanbeloaded.IfyouregistermultipleplacementsforAdMobrewardedad,onlyoneofthemcanbeused
atatime.Theautomaticadloadingfeaturefavorstherewardedadatthedefaultplacementandwillalwaysloadit
first.
BuildingNotes
TheGoogleMobileAdspluginforUnityemploysCocoaPodstoautomaticallyimportthenecessaryframeworkstothe
generatedXcodeprojectwhenaniOSbuildisperformedinUnity.ThereforeyouneedtoinstallCocoaPodstoyour
Mac:pleasegotohttps://cocoapods.org/forinstallinstructions,aswellasformoreinformationaboutCocoaPods.
NotethatyouonlyneedtoinstallCocoaPods,everythingelsewillbedoneautomaticallybytheGoogleMobileAds
plugin.
RecentversionsoftheGooglePlayServicesResolver(thatisbundledwithEasyMobile)handlesthe
installationofCocoaPodsforyouautomatically.
WhenbuildingforiOSinUnity,CocoaPodswillautomaticallycreateanXcodeworkspace(with.xcworkspace
extension)inthegeneratedXcodeproject.Youshouldalwaysopenthisworkspaceinsteadofthenormalprojectfile
(with.xcodeprojextension).
SetupAdMob
31
Advertising:Settings|SetupChartboost
ImportingChartboostPlugin
ToshowadsfromChartboostyouneedtoimporttheChartboostpluginforUnity.IntheCHARTBOOSTsection,click
theDownloadChartboostPluginbuttontoopenthedownloadpage.Downloadthepluginandimportittoyourproject.
ConfiguringChartboost
AfterimportingChartboostplugin,theCHARTBOOSTsectionwillbeupdatedasbelow.
Placements
AnEasyMobile'sadplacementwillbedirectlytranslatedintoaChartboost'sadlocationwiththesamenamein
runtime.ThereforeifyouwanttospecifyalocationwhenshowingaChartboostad,simplyshowtheadwithanad
placementofthesamename.There'snoneedtodeclareanyadIDstobeassociatedwiththeplacements.
Setup
ClicktheSetupChartboostbuttontoopenChartboost'sdedicatedsettingsinterface.
SetupChartboost
33
ProvidetheAppIDsandAppSignaturesforyourtargetedplatforms.RemembertoclicktheSetupAndroidSDK
buttonifyou'rebuildingforAndroid.
ToobtaintheAppIdandAppSignatureyouneedtoaddyourapptotheChartboostdashboard.Ifyou'renot
familiarwiththeprocesspleasefollowtheinstructionshere.
Afteraddingtheapp,gotoAPPSETTINGS>BasicSettingstofinditsAppIDandAppSignature.
AndroidREAD_PHONE_STATEPermissions
TheChartboostSDKincludestheREAD_PHONE_STATEpermissiononAndroid,to"handlevideoplaybackwhen
interruptedbyacall",asstatedinitsmanifest.READ_PHONE_STATEpermissionrequiresyourapptohavea
privacypolicywhenuploadedtoGooglePlay.SincethispermissionisnotmandatorytoruntheChartboostSDK,you
cansafelyremoveitifyouarenotreadytoprovidetherequiredprivacypolicy.Toremovethepermission,openthe
AndroidManifest.xmlfilelocatedatAssets/Plugins/Android/ChartboostSDKfolder,thendeletethecorrespondingline
(orcommentitoutasbelow).
SetupChartboost
34
<!--ExcludetheREAD_PHONE_STATEpermissionbecauseitrequiresaprivacypolicy-->
<!--<uses-permissionandroid:name="android.permission.android.permission.READ_PHONE_STATE"/>-->
TestingNotes
PleasenotethattoshowadsfromChartboostyouneedtoeithercreateapublishingcampaignorenabletheTest
Modeforyourapp.
Tocreateapublishingcampaignfollowtheinstructionshere
ToenableTestModefollowtheinstructionhere
BuildingNotes
IftheversionoftheimportedChartboostpluginincludesacopyofandroid-support-v4.jar,itmaycreatebuilderrorson
Androidplatformduetoconflictwiththecom.android.support-v4librarythatexistsintheAssets/Plugins/Android
folder.
Toavoidthisissue,removetheandroid-support-v4.jarinAssets/Plugins/Android/ChartboostSDK/libs/folder.
SetupChartboost
35
Advertising:Settings|SetupAudienceNetwork
ImportingFacebookAudienceNetworkPlugin
ToshowadsfromFacebookAudienceNetworkyouneedtoimporttheFacebookAudienceNetworkpluginforUnity.
IntheFACEBOOKAUDIENCENETWORKsection,clicktheDownloadFBAudiencePluginbuttontoopenthe
downloadpage.Downloadthepluginandimportittoyourproject.
ConfiguringFacebookAudienceNetwork
AfterimportingFacebookAudienceNetworkplugin,theFACEBOOKAUDIENCENETWORKsectionwillbeupdated
asbelow.
DefaultPlacement
HereyoucanentertheadIDstobeusedwiththedefaultplacementforeachplatform.ThesearetheonlyadIDs
requiredifyouarenotusinganycustomplacementsinyourapp.NotethatyouonlyneedtoprovideIDsforthead
typesyouwanttouse,e.g.ifyouonlyuseAudienceNetworkbanneradsyoucanleavetheinterstitialandrewarded
SetupAudienceNetwork
36
adIDsempty.
TocreateAudienceNetworkadunitsandgettheIDs,followtheinstructionshere.
CustomPlacements
HereyoucanoptionallyentertheadIDsassociatedwithnon-defaultadplacementstobeusedinyourapp.Youcan
haveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenewplacementsforyour
needs.
TestMode
Toenabletestmode,simplychecktheEnableTestModeoptionandenterthetestdeviceIDs.
FacebookAudienceNetworktestinginstructionscanbefoundhere.
Ifyourprojectisstillindevelopment,westronglyrecommendsettingupthetestingmodeproperlytoavoid"No
fill"error,especiallyoniOS.
AndroiddeviceID
ThedeviceIDisprintedinthedevicelogcat,youcanfollowtheinstructionsintheSetupAdMob/TestMode
sectiontofindit.NotethatyouwillneedtoimporttheGoogleMobileAdsplugin,whichisquiteawkwardifyou
don'tuseitinyourproject.HowevertheAudienceNetworkdocumentationislackingonthisdetailandusingthe
AdMobpluginisthesimplestworkaroundweknowfornow.
iOSdeviceID
FollowthesestepstofindthedeviceID:
InUnity,buildyourprojectforIOSplatform.
OpenthegeneratedprojectinXCode.
OpentheUnityAppController.mmfile.
ImporttheFBAudienceNetworkheaderintothatfile.
Addthisline[FBAdSettingssetLogLevel:FBAdLogLevelLog];intotheinitmethod,beforethereturn
statement.
SetupAudienceNetwork
37
RuntheapponyourdeviceandfindtheIDintheXCodeconsole.
SetupAudienceNetwork
38
BuildingNotes
Pleasefollowbuildinstructionshere:Android,iOS.
OnAndroid,ifyouencounterthe"Unabletoconvertclassesintodexformat"issuewhenbuildingyourgame,
theremightbeduplicatedfilesinyourprojectandyouneedtodeletethesupport-xxxfilesinthe
AudienceNetworkfolder.
SetupAudienceNetwork
39
SetupAudienceNetwork
40
Advertising:Settings|SetupHeyzap
ImportingHeyzapPlugin
ToshowadsfromHeyzapyouneedtoimporttheHeyzappluginforUnity.IntheHEYZAPsection,clicktheDownload
HeyzapPluginbuttontoopenthedownloadpage.
InthedownloadpageselectyourpreferrednetworkstousewithHeyzapmediation.TheHeyzapdynamic
documentationwillupdateautomaticallytoreflectyourselections.
FollowtheinstructionsprovidedbyHeyzaptodownloadandimportitspluginaswellasotherrequired3rd-party
plugins.AlsogothroughtheIntegrationNotessectionbelowtoavoidproblemsthatmayoccurduringtheintegration
of3rd-partynetworks.
Ifyouhaven'talready,useHeyzap'sIntegrationWizardtosetupthe3rd-partynetworkstousewithmediation.
ConfiguringHeyzap
AfterimportingHeyzapplugin,theHEYZAPsectionwillbeupdatedasbelow.
SetupHeyzap
41
PublisherID
HereyoucanentertherequiredpublisherIDtotheHeyzapPublisherIDfield.ThisIDcanbefoundintheAccount
DetailspageintheHeyzapdashboard.
Placements
AnEasyMobile'sadplacementwillbedirectlytranslatedintoaHeyzap'sadtagthatissametotheplacementname
inruntime.ThereforeifyouwanttospecifyatagwhenshowingaHeyzapad,simplyshowtheadwithanad
placementwhosenameisthetagyouwanttouse.There'snoneedtodeclareanyadIDstobeassociatedwiththe
placements.
TestMode
TheHeyzapplugincomeswithaconvenientTestSuitethatyoucanusetotesttheoperationofeachmediation
network.TousethisTestSuite,simplychecktheShowHeyzapTestSuiteoptionintheTestModesection.
BelowistheTestSuiteinterfaceoniOS(it'ssimilaronAndroid).
SetupHeyzap
42
MediationNotes
ThissectiondiscussessomenotesthatyoushouldtakewhenusingHeyzapmediationwithvariousothernetworks.
IfyouuseHeyzap'smediationfeaturewithothernetworks(AdColony,AdMob,Chartboost,etc.),youshouldnot
importthestandalone-pluginsofthosenetworks,toavoidpotentialconflicts.Instead,importtheircorresponding
adapterpackagesprovidedattheHeyzapdownloadpage.
FacebookAudienceNetwork(Android-specific)
TheFacebookAudienceNetworkpackagecontainsanandroid-support-v4.jar_fileunderAssets/Plugins/Android
folder.Ifyouprojectalreadycontainsasupport-v4-xx.x.x.aarfileunderthatsamefolder,feelfreetoremove(or
excludeitwhenimporting)thejarfileoritwillcausethe"Unabletoconvertdex..."errorwhenbuildingduetoduplicate
libraries.
AppLovin(Android-specific)
AsinstructedintheHeyzapdocumentation,youneedtoaddtheAppLovinSDKkeytoitsAndroidManifest.xmlfile
locatedatAssets/Plugins/Android/AppLovinfolder.Simplyaddthefollowinglineinsidethe<application>taginthe
manifest,replacingYOUR_SDK_KEYwithyouractualAppLovinSDKkey.
<meta-dataandroid:name="applovin.sdk.key"android:value="YOUR_SDK_KEY"/>
ThismanifestalsoincludestheREAD_PHONE_STATEpermission,whichrequiresyourapptohaveaprivacypolicy
whenuploadedtoGooglePlay.ThispermissionisnotmandatorytoruntheAppLovinSDK,thereforeyoucansafely
removeitifyouarenotreadytoprovidetherequiredprivacypolicy.Toremovethepermission,simplydeletethe
correspondinglinefromthemanifestorcommentitoutasbelow.
<!--ExcludetheREAD_PHONE_STATEpermissionbecauseitrequiresaprivacypolicy-->
<!--<uses-permissionandroid:name="android.permission.READ_PHONE_STATE"/>-->
TheminSdkVersionProblem(Android-specific)
ThecurrentHeyzapSDKrequiresaminSdkVersionof10,whilesomeother3rd-partypluginsmayrequireaversionof
11orabove.Ifyougetabuilderrorincludingthisline
Unabletomergeandroidmanifests...
andthisline
Mainmanifesthas<uses-sdkandroid:minSdkVersion='x'>butlibraryusesminSdkVersion='y'
wherex<y,itmeansyouneedtoincreasetheminSdkVersionoftheapp.TodosogotoEdit>ProjectSettings>
Player,thenselecttheAndroidsettingstabandincreaseitsMinimumAPILeveltotherequiredone(whichis'y'inthis
example).
SetupHeyzap
43
SetupHeyzap
44
Advertising:Settings|SetupironSource
ImportingironSourcePlugin
ToshowadsfromIronSourceyouneedtoimporttheIronSourceplugin.IntheIRONSOURCEsection,clickthe
DownloadIronSourcePluginbuttontotoopenthedownloadpage.Downloadthepluginandimportittoyourproject.
ConfiguringironSource
AfterimportingtheironSourceplugin,theIRONSOURCESETUPsectionwillbeupdatedasbelow.
AppKey
Firstyouneedtoprovidetheappkeyforeachplatform.
GototheironSourcedashboardtosetupyourappandgetthekey.Thenfollowtheseinstructionstosetup
mediationnetworksforyourproject.
Placements
SetupironSource
45
EasyMobile'sadplacementswillbedirectlytranslatedintoironSource'splacementsofthesamenameinruntime.If
youwanttospecifyanon-defaultplacementwhenshowinganironSourcead,simplyshowtheadwithoneofEasy
Mobile'sbuilt-inplacementsorcreateanewplacementforyourneeds.There'snoneedtodeclareanyadIDstobe
associatedwiththeplacements.
NotethattheplacementsyouusewithEasyMobilemustalsoexistonironSourcedashboard,otherwiseyou
havetocreatethemonthedashboardbeforeusing.Ifyoushowanadwithanundefinedplacement(the
placementdoesn'texistonironSourcedashboard)theadwillbeshownwiththedefaultplacementbut
correspondingadeventhandlerswillreceivetheundefinedplacementasargument,whichmaycause
unexpectedbehaviors.
AdvancedSettings
ironSourcehasanadditionalfeaturecalledsegmenttohelpyouserveadsthattargetaspecificaudience,further
informationcanbefoundhere.Toenabletheadvancedsettings,simplychecktheUseadvancedsettingsoptionand
enterrequiredthefields.
AddingIronSourceEventsPrefab
It'simportanttoaddtheIronSourceEventsPrefabfoundatfolderAsset/IronSource/Prefabstothefirstsceneinyour
app,otherwiseironSourceadeventswon'tbereceivedproperly.
MediationSetupNotes
Inordertointegrate3rd-partymediationnetworksyouneedtoperformtwosteps.
1. SetupthenetworksontheironSourcedashboard,followtheseinstructions.
2. GotothispageanddownloadthemediationadaptersthatyouwanttointegrateandimportthemintoyourUnity
Project.
ThecodetoworkwithironSourceadsremainsthesamewhetheryousetupmediationornot.Allmediating
worksaredonebytheironSourceSDKandtheadapters.
AdMobNetwork(Android-specific)
Asofversion4.2.1oftheironSource’sadapterforAdMob,youmayalsoneedtoimporttheGoogleMobileAdsplugin
intoyourprojecttomakeitworkproperly.
BuildingNotes
Android
SetupironSource
46
TheironSource'sinstructionsforAndroidcanbefoundhere.
Ifyouencounterthe"Noresourcefoundthatmatchesthegivenname(at'value'withvalue
'@integer/google_play_services_version')"error,commentoutthefollowinglines(177,178,179)intheironSource's
AndroidManifestthatcanbefoundatfolderAssets/Plugin/Android/IronSource/AndroidManifest.xml.
iOS
Theironsource'sintructionsforiOScanbefoundhere.
SetupironSource
47
Advertising:Settings|SetupMoPub
ImportingMoPubPlugin
ToshowadsfromMoPubyouneedtoimporttheMoPubpluginforUnity.IntheMOPUBADSsection,clickthe
DownloadMoPubPluginbuttontoopenthedownloadpage.
ConfiguringMoPub
AfterimportingtheMoPubplugin,theMOPUBADSsectionwillbeupdatedasbelow.
DefaultPlacement
HereyoucanentertheadIDstobeusedwiththedefaultplacementforeachplatform.ThesearetheonlyadIDs
requiredifyouarenotusinganycustomplacementsinyourapp.NotethatyouonlyneedtoprovideIDsforthead
typesyouwanttouse,e.g.ifyouonlyuseMoPubbanneradsyoucanleavetheinterstitialandrewardedadIDs
empty.
SetupMoPub
48
Ifyou'renotfamiliarwithMoPub,pleasefollowtheinstructionsheretosetupadunitsforyourapp.
CustomPlacements
HereyoucanoptionallyentertheadIDsassociatedwithnon-defaultadplacementstobeusedinyourapp.Youcan
haveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenewplacementsforyour
needs.
AdvancedSettings
EnableLocationPassing:checkthisifyouwanttoenablelocationsupportforbanners&interstitials.
UseAdvancedSetting:checkthistoenableMoPub'sadvancedsettings,includinginitializationwithcustom
configurations,whichcanreadmoreabouthere.
GDPRConsent
ThesesettingsareonlyusefulifyouwanttousetheGDPRsupportfeaturesprovidedbyMoPub(andthus,
specifictoMoPubonly).Ifyou'reusingtheconsentmanagementsystemandconsentdialogprovidedbyEasy
Mobiletomanageconsentforthewholeappyoushouldignorethesesettings.
AutoRequestConsent:checkthisboxifyouwanttheMopub'sGDPRconsentdialogtoshowautomatically
duringinitialization.
ForceGDPRApplicable:enablethistoshowtheMoPub'sGPDRconsentdialoginallregions(usefulfor
debuggingindevelopment).
BuildingNotes
Android
MoPubpluginmanuallyincludesandroid-support-v4jar,whichisalsorequiredbyEasyMobile(andmanyother
plugins)andwillbefetchedautomaticallybytheGooglePlayServicesResolver,thereforeyouneedtoremovethe
duplicatedfilebeforestartingbuildingtoavoiderrorsduetoduplication.NavigatetoPlugins/Android/mopub/libsand
deletethefilesthere.
SetupMoPub
49
iOS
MoPubpluginrequiresiOS8.0+andXcode9.0+.
MediationNotes
Newmediationnetworksneedtobeconfiguredandenabledcorrectlyinthedashboardbeforeyoucanshow
theminyourapp.Followthisinstructiontocreateandsetupnewnetworks.
Inordertointegratea3rd-partynetwork,youwillneedtoimportthatnetworkUnityplugin(youcanalsouseitsnative
plugin,butitcantakesomeextrawork,especiallyoniOS,soit'snotrecommended)andtheMoPubadapter(which
canbedownloadhere).
Android
Placetheadapter.jarfilesinPlugins/Android/mopub-support/libsfolder(directly,notinanysubfolder).
Ifthe3rd-partynetworkpluginuses.jarfiles,placetheminPlugins/Android/mopub-support/libs.Ifthey're.aarfiles,
placetheminPlugins/Android.Forexample:
SetupMoPub
50
TheremaybeconflictsbetweentheAndroidManifestofthesenetworkswiththeoneofMopub,causingbuild
errors.InsuchcaseyoushouldupdatetheAndroidManifestofthosenetworkstosolvetheconflictswhile
maintainingtheoneofMopub.
iOS
Justkeepalltheimportedfileswheretheyare.
SetupMoPub
51
Advertising:Settings|SetupTapjoy
ImportingTapjoyPlugin
ToshowadsfromTapjoyyouneedtoimporttheTapjoyUnityPlugin.IntheTAPJOYsection,clicktheDownload
TapJoyPluginbuttontotoopenthedownloadpage.Downloadthepluginandimportittoyourproject.
ConfiguringTapjoy
AfterimportingtheTapjoyplugin,theTAPJOYsectionwillbeupdatedasbelow.
BeforeconfiguringTapjoyplacementsinEasyMobilesettings,youhavetocreatethemontheTapjoy
dashboard.Ifyou'renotfamiliarwiththeprocess,followtheinstructionhere.Basically,you'llneedto:
GotoTapJoydashboardandcreateanewapp.
Setupatleastonevirtualcurrency.
Createsomecontentstoshowinyourapp.
Createsomeplacementsandassigncontentforthem.
DefaultPlacement
SetupTapjoy
52
Hereyoucanconfigurethedefaultplacementforeachplatform.NotethatintheIDfieldsyouwouldenterthenameof
theplacementscreatedontheTapjoydashboardwhosecontentisrelevanttothecorrespondingadtype(interstitial,
rewarded).Youonlyneedtoprovideplacementnamesfortheadtypesyouwanttouse,e.g.ifyouonlyuseTapjoy
rewardedadsyoucanleavetheinterstitialadfieldsempty.
CustomPlacements
HereyoucanoptionallyentertheTapjoyplacementnamesassociatedwithnon-defaultplacementstobeusedinyour
app.Youcanhaveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenew
placementsforyourneeds.
AutoReconnectSettings
AutoReconnect:shouldwereconnecttotheserverautomaticallyuntilconnected.Ifyouuncheckit,playersmight
neverbeabletoconnecttotheTapjoyserveriftheyopenedyourappwhentheirdeviceisoffline.
AutoReconnectInterval:autoreconnectcoroutinerefreshrate.
TapjoySetupInterface
ClicktheOpenTapJoySetupWindowbuttontoopentheTapjoy'ssetupwindow(youcanalsonavigatetothemenu
Window>Tapjoyandopenit).Awindowwillbeopenedasbelow.
SetupTapjoy
53
TapjoyrequiresaTapjoyUnityGameObjectwithsomecomponentsattachedtoit.The_Fix_buttonrightnextto
the"NoTapjoyUnityGameObjectinthisscene"warninglabelcancreatethatGameObjectforyou,makesureto
additinthescenethatappearsfirstinyourgame.
EntertheSDKKeyforeachplatform.
MakesuretheAuto-Connectfieldischecked.
Moredetailsaboutthesesettingscanbefoundhere.
BuildingNotes
Android
TapjoyneedsGooglePlayServiceslibrarytowork,butstartingfromversion11.10.1,theTapjoypluginforUnity
nolongerincludesitinthepackage,somakesuretoadditintoyourgame(youcanreadaboutthishere).
YouneedtomakesureyourgamehasavalidAndroidManifest.xmlplacedunderAssets/Plugins/Android(read
moreaboutithere).
SetupTapjoy
54
IfthereisnoAndroidManifest.xmlinthatfolder,youwillseesome"notfound"warninglabelsliketheimage
above.SimplyclicktheGenerateAndroidManifestintheTAPJOYsection,avalidfilewillbecreated
automatically.
IfyoualreadyhaveaAndroidManifestinthefolder,butsomeelementsaremissing,youcanatthemvia
Tapjoy'ssetupwindow.Clickallthefixbuttonthere(seetheimagebelow)andTapjoywilldothejob
automatically.
iOS
AfterexportingyourprojecttoXCode,addthefollowingintoyourInfo.plistfile(readmoreaboutithere).
<key>NSAppTransportSecurity</key>
<dict>
<!--Tosupportlocalhost-->
<key>NSAllowsLocalNetworking</key>
<true/>
<!--TocontinuetoworkforiOS9-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
SetupTapjoy
55
Advertising:Settings|SetupUnityAds
EnablingUnityAdsService
TouseUnityAdsservice,youmustfirstsetupyourprojectforUnityServices.
ToshowadsfromUnityAdsyouneedtoenablethecorrespondingservice.EasyMobilewillautomaticallycheckfor
theservice'savailabilityandwarnyoutoenableitifneeded.BelowistheUNITYADSsectionwhenUnityAdsisnot
enabled.
ToenableUnityAdsswitchtheplatformtoiOSorAndroid,thengotoWindow>ServicesandselecttheAdstab.
SetupUnityAds
56
Intheopenedconfigurationwindow,clickthetoggleattheright-handsidetoenableUnityAdsservice.Youmayneed
toanswerafewquestionsaboutyourgame.
SetupUnityAds
57
ConfiguringUnityAds
TheUNITYADSsectionwillbeupdatedafterUnityAdshasbeenenabled.
DefaultPlacement
TheadIDsassociatedwiththedefaultplacementareprefilledforyouautomatically.TheseIDsmatchthedefault
placementIDsgeneratedbytheUnityAdsservicewhenitisenabledandthereforecannotbechanged.
CustomPlacements
SetupUnityAds
58
HereyoucanoptionallyentertheadIDsassociatedwithnon-defaultadplacementstobeusedinyourapp.Youcan
haveanarbitrarynumberofcustomplacementsandcanusebuilt-inplacementsorcreatenewplacementsforyour
needs.NotethatyoumustnotreusethedefaultplacementIDswiththesecustomplacements.
BeforeenteringtheadIDsforcustomplacementsintheCustomPlacementssection,youmustcreatethese
placementsintheUnityDashboard.Inthedashboardselectyourproject,selecttheOperatetab,thenintheleft
navigationmenuselectMonetization>PlacementsandclicktheADDPLACEMENTbuttonatthetopright
corner.
TestingNotes
ItisadvisabletoenablethetestmodeofUnityAdsduringdevelopment.Thiswillensurethere'salwaysanad
returnedwheneverrequested.ToenabletestmodesimplechecktheEnabletestmodeoptionintheAdstabinthe
Serviceswindow.
Remembertodisablethistestmodewhencreatingyourreleasebuild.
SetupUnityAds
59
Advertising:Scripting
ThissectionprovidesaguidetoworkwiththeAdvertisingmodulescriptingAPI.
YoucanaccesstheAdvertisingmoduleAPIviatheAdvertisingclassundertheEasyMobilenamespace.
WorkingwithConsent
ModuleConsent
Thefollowingsnippetshowshowyougrant,revokeorreadthemodule-levelconsentoftheAdvertisingmodule.
//Grantsthemodule-levelconsentfortheAdvertisingmodule.
Advertising.GrantDataPrivacyConsent();
//Revokesthemodule-levelconsentoftheAdvertisingmodule.
Advertising.RevokeDataPrivacyConsent();
//Readsthecurrentmodule-levelconsentoftheAdvertisingmodule.
ConsentStatusmoduleConsent=Advertising.DataPrivacyConsent;
VendorConsent
Thefollowingsnippetshowshowyougrant,revokeorreadthevendor-levelconsentofanindividualnetworkinthe
Advertisingmodule.
InthisexampleweuseAdMobfordemonstrationpurpose,thesamecodecanbeusedforanyothernetwork.
//Grantsthevendor-levelconsentforAdMob.
Advertising.GrantDataPrivacyConsent(AdNetwork.AdMob);
//Revokesthevendor-levelconsentofAdMob.
Advertising.RevokeDataPrivacyConsent(AdNetwork.AdMob);
//Readsthecurrentvendor-levelconsentofAdMob.
ConsentStatusadmobConsent=Advertising.GetDataPrivacyConsent(AdNetwork.AdMob);
WorkingwithBannerAds
ToshowabanneradyouneedtospecifyitspositionusingtheBannerAdPositionenum.Thebannerwillbedisplayed
onceitisloaded.
//Showbannerad
Advertising.ShowBannerAd(BannerAdPosition.Bottom);
Tohidethecurrentbannerad(itcanbeshownagainlater):
//Hidebannerad
Advertising.HideBannerAd();
Todestroythecurrentbannerad(anewonewillbecreatedonthenextbanneradshowing):
//Destroybannerad
Advertising.DestroyBannerAd();
Scripting
60
WorkingwithInterstitialAds
Themethodtoshowaninterstialadrequiresittobealreadyloaded.Thereforeyoushouldcheckforthead's
availabilitybeforeshowingit.
//Checkifinterstitialadisready
boolisReady=Advertising.IsInterstitialAdReady();
//Showitifit'sready
if(isReady)
{
Advertising.ShowInterstitialAd();
}
AnInterstitialAdCompletedeventwillbefiredwheneveraninterstitialadisclosed.Youcanlistentothiseventtotake
appropriateactions,e.g.resumethegame.
//Subscribetotheevent
voidOnEnable()
{
Advertising.InterstitialAdCompleted+=InterstitialAdCompletedHandler;
}
//Theeventhandler
voidInterstitialAdCompletedHandler(InterstitialAdNetworknetwork,AdLocationlocation)
{
Debug.Log("Interstitialadhasbeenclosed.");
}
//Unsubscribe
voidOnDisable()
{
Advertising.InterstitialAdCompleted-=InterstitialAdCompletedHandler;
}
WorkingwithRewardedAds
Themethodtoshowarewardedadrequiresittobealreadyloaded.Thereforeyoushouldcheckforthead's
availabilitybeforeshowingit.
//Checkifrewardedadisready
boolisReady=Advertising.IsRewardedAdReady();
//Showitifit'sready
if(isReady)
{
Advertising.ShowRewardedAd();
}
ARewardedAdCompletedeventwillbefiredwheneverarewardedadhascompleted.Youshouldlistentothisevent
torewardtheuserforwatchingthead.Otherwise,aRewardedAdSkippedeventwillbefirediftheadisskipped
beforefinishing(andtheuserthereforeisnotentitledtothereward).
//Subscribetorewardedadevents
voidOnEnable()
{
Advertising.RewardedAdCompleted+=RewardedAdCompletedHandler;
Advertising.RewardedAdSkipped+=RewardedAdSkippedHandler;
}
Scripting
61
//Unsubscribeevents
voidOnDisable()
{
Advertising.RewardedAdCompleted-=RewardedAdCompletedHandler;
Advertising.RewardedAdSkipped-=RewardedAdSkippedHandler;
}
//Eventhandlercalledwhenarewardedadhascompleted
voidRewardedAdCompletedHandler(RewardedAdNetworknetwork,AdLocationlocation)
{
Debug.Log("Rewardedadhascompleted.Theusershouldberewardednow.");
}
//Eventhandlercalledwhenarewardedadhasbeenskipped
voidRewardedAdSkippedHandler(RewardedAdNetworknetwork,AdLocationlocation)
{
Debug.Log("Rewardedadwasskipped.TheusershouldNOTberewarded.");
}
RemovingAds
RemovingAdswithoutUpdatingConsent
Insomecasesyouneedtoremove/stopshowingadsinyourgame,e.g.whentheuserpurchasesthe"RemoveAds"
product.Toremoveads:
//Removeadspermanently
Advertising.RemoveAds();
TheRemoveAdsmethodwilldestroythebanneradifoneisbeingshown,andpreventfutureadsfrombeingloaded
andshownexceptrewardedads,sincetheyareunobtrusiveandonlyshownattheuserdiscretion.
NotethattheRemoveAdsmethodusesUnity'sPlayerPrefstostoretheadremovalstatuswithno
encryption/scrambling.
AnAdsRemovedeventwillbefiredafteradshavebeenremoved.Youcanlistentothiseventandtakeappropriate
actions,e.gupdatetheUI.
//Subscribetotheevent
voidOnEnable()
{
Advertising.AdsRemoved+=AdsRemovedHandler;
}
//Theeventhandler
voidAdsRemovedHandler()
{
Debug.Log("Adswereremoved.");
//Unsubscribe
Advertising.AdsRemoved-=AdsRemovedHandler;
}
Youcanalsocheckatanytimeifadswereremovedornot.
//Determineifadswereremoved
boolisRemoved=Advertising.IsAdRemoved();
RemovingAdsandRevokeConsents
Scripting
62
Becauserewardedadsarestillavailableafterremovingads(forgoodreason!),itmaybedesirableinsomecasesto
alsorevokeallconsentgrantedtotheAdvertisingmoduleaswellastheindividualnetwork.Forexample,youmay
offer"RemoveAds"asanin-apppurchasethattheusercanbuytoremoveintrusiveads(i.e.bannerandinterstitial
ads)andstoptheappfromcollectingtheirpersonaldataforadvertisingpurpose.Insuchcase,youcancallthe
RemoveAdsandpasstruetoitsrevokeConsentsparameter.Thisparameterisoptionalanddefaulttofalse.
//Removeadspermanentlyandalsorevoketheconsent
//oftheAdvertisingmoduleandalladnetworkssothat
//theystopcollectinguserdata.
Advertising.RemoveAds(true);//revokeConsentspassedastrue
Re-enablingAdsafterRemoval
Finally,youcanalsorevoketheadremovingstatusandallowadstobeshownagain.
//Revokeadremovingstatusandallowshowingadsagain
Advertising.ResetRemoveAds();
ManualAdLoading
WiththeAutomaticAdLoadingfeature,younormallydon'tneedtoworryaboutloadingads.However,ifyouwantto
disablethisfeatureandcontroltheloadingprocessyourself,youcandosowithmanualadloadinginscript.
Itisadvisabletoloadanadasfarinadvanceofshowingitaspossibletoallowampletimefortheadtobe
loaded.
Toloadaninterstitialad:
//Loadthedefaultinterstitialad.
Advertising.LoadInterstitialAd();
Toloadarewardedad:
//Loadthedefaultrewardedad.
Advertising.LoadRewardedAd();
WorkingwithNon-DefaultAds
Besidethedefaultads,youcanalsoshowadsfromnon-defaultnetworksatnon-defaultplacements,thus
implementingamoresophisticatedadstrategyinyourapp.Notethateachmethodtoloadorshowadalwayshasa
variantthatallowsyoutospecifythetargetadnetworkandplacementexplicitly.Forexamplethere're2variantsofthe
LoadInterstitialAdmethod.Onetakesnoargumentandloadsthedefaultinterstitialad.Theotherloadsaninterstitial
adfromaspecifiednetworkatanarbitraryplacement.
Ifyou'reusingthe"LoadAllDefinedPlacements"autoad-loadingmode,bothdefaultandnon-defaultadsare
loadedautomaticallysoyouneverneedtoworryaboutloadingads.
IfyouhaveAdMobasthedefaultinterstitialadnetwork,youcanloadandshowanon-defaultinterstitialadlikebelow.
//Thismethodshowsaninterstitialadfromthedefaultnetwork(i.e.AdMobinthisexample)
//atthedefaultplacement.DefaultadsareloadedautomaticallyunlessAutomaticAdLoadingisdisabled.
if(Advertising.IsInterstitialAdReady())
Advertising.ShowInterstitialAd();
Scripting
63
//Ifyou'reusingthe"LoadAllDefinedPlacements"autoad-loadingmode,non-defaultadsareloadedautomatic
allytoo.
//Hereweshowhowyoucanmanuallyloadanon-defaultinterstitialad,forillustrationpurpose.
//Inpractice,youdon'tneedtodothisunlesstheautoad-loadingisdisabledorsetto"LoadDefaultAds"m
ode.
//Inthisexample,we'llloadandshowaninterstitialadfromAdMobatthecustomplacementStartup.
//(Notethataninterstitialadatthedefaultplacementbutbelongstoanon-defaultnetwork
//wouldalsobeconsideredanon-defaultad).
//Youcan,forexample,associatethisplacementwithahouseadunittoshowcross-promotionads
//forotherappsinyourportfolio,thushavingafreecross-promotionsystem!
Advertising.LoadInterstitialAd(InterstitialAdNetwork.AdMob,AdPlacement.Startup);
//ChecksiftheAdMobinterstitialadatplacementStartupisreadyandshowsit.
if(Advertising.IsInterstitialAdReady(InterstitialAdNetwork.AdMob,AdPlacement.Startup))
Advertising.ShowInterstitialAd(InterstitialAdNetwork.AdMob,AdPlacement.Startup);
//Likewise,youcancheckifanon-defaultrewardedadisreadyandshowitlikebelow
//(assumethatIronSourceisnotsetasthedefaultnetworkforrewardedads).
if(Advertising.IsRewardedAdReady(RewardedAdNetwork.IronSource,AdPlacement.HomeScreen))
Advertising.ShowRewardedAd(RewardedAdNetwork.IronSource,AdPlacement.HomeScreen);
AdNetworkClients
Youcanaccesstheunderlayingclientsforthesupportedadnetworksusingthecorrespondingpropertiesofthe
Advertisingclass.
//AdColonyclient.
AdColonyClientImpladcolonyClient=Advertising.AdColonyClient;
//AdMobclient.
AdMobClientImpladmobClient=Advertising.AdMobClient;
//Chartboostclient.
ChartboostClientImplchartboostClient=Advertising.ChartboostClient;
//FacebookAudienceNetworkclient.
AudienceNetworkClientImplfbanClient=Advertising.AudienceNetworkClient;
//Heyzapclient.
HeyzapClientImplheyzapClient=Advertising.HeyzapClient;
//ironSourceclient.
IronSourceClientImplironSrcClient=Advertising.IronSourceClient;
//MoPubclient.
MoPubClientImplmopubClient=Advertising.MoPubClient;
//Tapjoyclient.
TapjoyClientImpltapjoyClient=Advertising.TapjoyClient;
//UnityAdsclient.
UnityAdsClientImplunityAdsClient=Advertising.UnityAdsClient;
Fromtheseclientsyoucanaccessnetwork-specificevents,e.gtheOnBannerAdClosedeventprovidedbythe
AdMob.Eventslikethisarespecifictoacertainadnetworkandnormallynotavailableinothernetworkswhichiswhy
theyarenotexposedintheAdvertisingclass(theAdvertisingclassonlyexposesacommonsubsetofeventsthatare
providedbyalladnetworkstoensureaconsistentbehavioracrossallnetworks).Notethatthesenetwork-specific
eventsareonlyavailableifthecorrespondingpluginofthatnetworkhasbeenimportedtoyourproject.Youcanuse
thescriptingsymbolsinthebelowtabletowrapcodesthataccesstheseeventtomakesuretheyonlygetcompiled
whenthepluginisavailable.ThesesymbolsaredefinedautomaticallybyEasyMobilewhenthecorrespondingad
pluginisimported,exceptUNITY_ADSwhichisdefinedbyUnitywhentheserviceisenabled.
Scripting
64
AdNetwork Symbol
AdColony EM_ADCOLONY
AdMob(GoogleMobileAds) EM_ADMOB
Chartboost EM_CHARTBOOST
FacebookAudienceNetwork EM_FBAN
Heyzap(Fyber) EM_HEYZAP
ironSource EM_IRONSOURCE
MoPub EM_MOPUB
Tapjoy EM_TAPJOY
UnityAds UNITY_ADS(definedbyUnity)
Scripting
65
Advertising:PlayMakerActions
ThePlayMakeractionsoftheAdvertisingmodulearegroupinthecategoryEasyMobile-Advertisinginthe
PlayMaker'sActionBrowser.
PleaserefertotheAdvertisingDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowtheseactionscanbeused.
PlayMakerActions
66
PlayMakerActions
67
GameServices:Introduction
TheGameServicesmodulehelpsyouquicklyimplementleaderboardsandachievementsforyourgame.Itworkswith
theGameCenternetworkoniOSandGooglePlayGamesservicesonAndroid.Here'resomehighlightsofthis
module:
Leveragesofficialplugins
ThismoduleisbuiltontopofUnity'sGameCenterPlatformoniOSandGooglePlayGamespluginonAndroid
GameCenterPlatformisonepartoftheUnityEngineitselfwhiletheotheristheofficialGooglePlayGames
pluginforUnity,soreliabilityandcompatibilitycanbeexpected
Easymanagementofleaderboardsandachievements
EasyMobile'scustomeditorfeaturesafriendlyinterfacethathelpyoueasilyadd,editorremove
leaderboardsandachievements
Introduction
68
GameServices:Settings
TousetheGameServicesmoduleyoumustfirstenableit.GotoWindow>EasyMobile>Settings,selecttheGame
Servicestab,thenclicktheright-handsidetoggletoenableandstartconfiguringthemodule.
Android-SpecificSetup
ImportingGooglePlayGamespluginforUnity
Asstatedearlier,thismoduleisbuiltontopofGooglePlayGamesPluginonAndroid.Thereforeyouneedtoimportit
tousethemoduleonthisplatform.EasyMobilewillautomaticallydetecttheavailabilityofthepluginandpromptyou
toimportitifneeded.BelowisthemodulesettingsinterfaceafterswitchingtoAndroidplatformiftheGooglePlay
Gamespluginhasn'tbeenimported.
Settings
69
ClicktheDownloadGooglePlayGamesPluginbuttontoopenthedownloadpage,thendownloadthepackageand
importittoyourproject.Oncetheimportcompletesthemoduleinterfacewillbeupdatedandreadyforyoutostart
withtheconfiguration.
Sincewe'renotusingGooglePlayGamespluginoniOS,theNO_GPGSsymbolwillbedefinedforiOS
platformautomaticallyafterthepluginisimportedinordertodisableit.
SetupGooglePlayGames
TosetupGooglePlayGamesplugin,youneedtoobtainthegameresourcesfromtheGooglePlayDeveloper
Console.
ThegameresourcesareavailableafteryouconfiguredyourgameontheGooglePlayDeveloperConsole.If
you'renotfamiliarwiththeprocess,pleasefollowtheinstructionsoncreatingaclientID,aswellas
leaderboardsandachievements.
Togetthegameresources,logintoyourGooglePlayDeveloperConsole,selectGameservicestabsthenselectyour
game.NextgototheAchievementstabandclickontheGetResourceslabelatthebottomofthelist.
Settings
70
CopyallthexmlcontentfromtheAndroidtab.
Settings
71
GobacktoUnity,intheGOOGLEPLAYGAMESSETUPsection,pastetheobtainedxmlresoucesintotheAndroid
XMLResourcesarea,thenclickSetupGooglePlayGames.
YoucanoptionallyprovideaWebClientIDbeforesettingupGooglePlayGamesifneeded.
Afterthesetuphascompleted,anewfilenamedEM_GPGSIdswillbecreatedatAssets/EasyMobile/Generated.This
filecontainstheconstantsoftheIDsofalltheleaderboardsandachievementsinyourAndroidgame.
WithintheGOOGLEPLAYGAMESSETUPsectionthere'reothersettingsincluding:
GPGSDebugLog:checkthistoenableGooglePlayGamesdebuglog.
GPGSPopupGravity:usethistocontrolthepositionofGooglePlayGamespopups(e.g.achievementpopup).
AutoInitialization
AutoinitializationisafeatureoftheGameServicesmodulethatinitializestheserviceautomaticallywhenthemodule
starts.Initializationisrequiredbeforeanyotheractionscanbedone,e.g.reportingscores.
Duringtheinitialization,thesystemwilltrytoauthenticatetheuserbypresentingaloginpopup.
OniOS,thispopupwillshowupwhentheappgetsfocus(broughttoforeground)forthefirst3times.Iftheuser
refusestologinallthese3times,theOSwillignoresubsequentauthenticationcallsandstoppresentingthelogin
popup(toavoiddisturbingtheuser).Otherwise,iftheuserhasloggedinsuccessfully,futureauthenticationwill
takeplacesilentlywithnologinpopuppresented.
OnAndroid,weemployasimilarapproachbutyoucanconfigurethemaximumnumberofauthentication
requestsbeforeignoringsubsequentones.
Settings
72
YoucanconfiguretheautoinitializationfeaturewithintheAUTO-INITCONFIGsection.
AutoInit:uncheckthisoptiontodisabletheautoinitializationfeature,youcanstarttheinitializationmanuallyfrom
script(seetheScriptingsection)
AutoInitDelay:howlongafterthemodulestartthattheinitializationshouldtakeplace
[Android]MaxLoginRequests:maximumnumberofauthenticationrequestsallowedonAndroid,beforeignoring
subsequentones(incasetheuserrefusestologin)
"Modulestart"referstothemomenttheStartmethodofthemodule'sassociatedMonoBehavior(attachedtothe
EasyMobileprefab)runs.
Leaderboards&Achievements
Thissectionprovidesaguidetomanageleaderboardsandmanagementsforyourgame.
BeforeYouBegin
Itisassumedthatyoualreadyconfiguredyourgameforthetargetedgamingnetworks,i.e.GameCenterand
GooglePlayGames.Ifyou'renotfamiliarwiththeprocess,here'resomeusefullinks:
ConfigureforGooglePlayGames(Android)
CreatingaClientIDforyougame
Addingleaderboards
Addingachievements
ConfigureforGameCenter(iOS)
AddingleaderboardsandachievementsiniTunesConnect
IntheLEADERBOARDSandACHIEVEMENTSyoucanadd,editorremoveleaderboardsandachievements.
AddingaNewLeaderboardorAchievement
ToaddanewleaderboardclicktheAddNewLeaderboardbutton(orAddNewAchievementbuttonincaseofan
achievement).
Anewemptyleaderboard(orachievement)willbeadded.
Fillintherequiredinformationoftheleaderboard(orachievement):
Name:thenameofthisleaderboard(orachievement),thisnamecanbeusedwhenreportingscorestothis
Settings
73
leaderboard(orunlockingthisachievement)
iOSId:theIDofthisleaderboard(orachievement)asdeclarediniTunesConnect
AndroidId:theIDofthisleaderboard(orachievement)asdeclaredinGooglePlayDeveloperConsole
GooglePlayGames'leaderboardsandachievementshavegeneratedIDswhichcanbedifficulttomemorize
andcumbersometocopy-and-paste,especiallyiftherearemanyofthem.Thankfully,whenyousetupGoogle
PlayGames,theconstantsoftheseIDsaregeneratedautomatically(rememberthatEM_GPGSIdsfile?),
allowingEasyMobiletoshowanicedropdownofalldefinedleaderboardandachievementIDsforyouto
choosefrom.
RemovingaLeaderboardorAchievement
Toremovealeaderboard(orachievement),simplyclickthe[-]buttonattherighthandside.
ArrangingLeaderboardsorAchievements
Youcanusethetwoarrow-upandarrow-downbuttonstomovealeaderboard(orachievement)upwardordownward
withinitsarray.
ConstantsGeneration
Settings
74
ConstantsgenerationisafeatureoftheGameServicesmodule.Itreadsthenamesofalltheaddedleaderboardsand
achievementsandgeneratesastaticclassnamedEM_GameServicesConstantsthatcontainstheconstantsofthese
names.Later,youcanusetheseconstantswhenreportingscorestoaleaderboardorunlockinganachievementin
scriptinsteadoftypingthenamesdirectly,thushelppreventruntimeerrorsduetotyposandthelikes.
Togeneratetheconstantsclass(youshoulddothisafteraddingallrequiredleaderboardsandachievements),click
theGenerateConstantsClassbuttonwithintheCONSTANTSCLASSGENERATIONsection.
Whentheprocesscompletes,afilenamedEM_GameServicesConstantswillbecreatedat
Assets/EasyMobile/Generated.
Settings
75
GameServices:Scripting
ThissectionprovidesaguidetoworkwiththeGameServicesmodulescriptingAPI.
YoucanaccesstheGameServicesmoduleAPIviatheGameServicesclassundertheEasyMobile
namespace.
Initialization
Initializationisrequiredbeforeanyotheraction,e.g.reportingscores,canbedone.Itshouldonlybedoneoncewhen
theappisloaded.IfyouhaveenabledtheAutoinitializationfeature,youdon'tneedtoinitializeinscript(seeAuto
Initializationsection).Otherwise,ifyouchoosetodisablethatfeature,youcanstarttheinitializationinacoupleof
ways.
Managedinitialization:thismethodrespectstheMaxLoginRequestsvalueonAndroid(seeAutoInitialization
section),whichmeansitwillignoreallsubsequentcallsoncetheuserhasdismissedtheloginpopupfora
numberoftimedeterminedbyMaxLoginRequests
Unmanagedinitialization:thismethodsimplyinitializesthemodule,onAndroiditshowstheloginpopupevery
timeaslongastheuserhasn'tbeenauthenticated
OniOS,thesystemautomaticallylimitsthemaximumnumberofloginrequeststo3nomatterwhichmethodis
used.
Tousethemanagedinitializationmethod:
//ManagedinitrespectstheMaxLoginRequestsvalue
GameServices.ManagedInit();
Tousetheunmanagedinitializationmethod:
//Unmanagedinit
GameServices.Init();
Notethattheinitializationshouldbedoneearlyandonlyonce,e.g.youcanputitintheStartmethodofa
MonoBehaviour,preferablyasingletononesothatitwon'trunagainwhenthescenereloads.
//InitializationintheStartmethodofaMonoBehaviourscript
voidStart()
{
//ManagedinitrespectstheMaxLoginRequestsvalue
GameServices.ManagedInit();
//Dootherstuff...
}
AUserLoginSucceededeventwillbefiredwhentheinitializationcompletesandtheuserloginssuccessfully.
Otherwise,aUserLoginFailedeventwillbefiredinstead.Youcanoptionallysubscribetotheseeventsandtake
appropriateactionsdependedontheuserloginstatus.
//SubscribetoeventsintheOnEnablemethodofaMonoBehaviorscript
voidOnEnable()
{
GameServices.UserLoginSucceeded+=OnUserLoginSucceeded;
GameServices.UserLoginFailed+=OnUserLoginFailed;
}
Scripting
76
//Unsubscribe
voidOnDisable()
{
GameServices.UserLoginSucceeded-=OnUserLoginSucceeded;
GameServices.UserLoginFailed-=OnUserLoginFailed;
}
//Eventhandlers
voidOnUserLoginSucceeded()
{
Debug.Log("Userloggedinsuccessfully.");
}
voidOnUserLoginFailed()
{
Debug.Log("Userloginfailed.");
}
YoucanalsocheckifthemodulehasbeeninitializedatanypointusingtheIsInitializedmethod.
//Checkifinitializationhascompleted(theuserhasbeenauthenticated)
boolisInitialized=GameServices.IsInitialized();
Leaderboards
Thissectionfocusesonworkingwithleaderboards.
ShowLeaderboardUI
ToshowthedefaultleaderboardUI(thesystemviewofleaderboards):
//ShowleaderboardUI
GameServices.ShowLeaderboardUI();
Youshouldcheckiftheinitializationhasfinished(theuserhasbeenauthenticated)beforeshowingtheleaderboard
UI,andtakeappropriateactionsiftheuserisnotloggedin,e.g.showanalertorstartanotherinitializationprocess.
//CheckforinitializationbeforeshowingleaderboardUI
if(GameServices.IsInitialized())
{
GameServices.ShowLeaderboardUI();
}
else
{
#ifUNITY_ANDROID
GameServices.Init();//startanewinitializationprocess
#elifUNITY_IOS
Debug.Log("CannotshowleaderboardUI:TheuserisnotloggedintoGameCenter.");
#endif
}
ToshowtheUIofaspecificleaderboard,simplypassthenameoftheleaderboardintotheShowLeaderboardUI
method.Youcanalsooptionallyspecifythetimescope:
//ShowaspecificleaderboardUI
GameServices.ShowLeaderboardUI("YOUR_LEADERBOARD_NAME");
//ShowaspecificleaderboardUIintheWeektimescope
GameServices.ShowLeaderboardUI("YOUR_LEADERBOARD_NAME",TimeScope.Week);
Scripting
77
ReportScores
Toreportscorestoaleaderboardyouneedtospecifythenameofthatleaderboard.
Itisstronglyrecommendedthatyouusetheconstantsofleaderboardnamesinthegenerated
EM_GameServicesConstantsclass(seeGameServicesConstantsGenerationsection)insteadoftypingthe
namesdirectlyinordertopreventruntimeerrorsduetotyposandthelikes.
//Reportascoreof100
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.ReportScore(100,EM_GameServicesConstants.Sample_Leaderboard);
LoadLocalUser'sScore
Youcanloadthescoreofthelocaluser(theauthenticateduser)onaleaderboard,todosoyouneedtospecifythe
nameoftheleaderboardtoloadscorefromandacallbacktobecalledwhenthescoreisloaded.
//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
//Loadthelocaluser'sscorefromthespecifiedleaderboard
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadLocalUserScore(EM_GameServicesConstants.Sample_Leaderboard,OnLocalUserScoreLoaded);
//Scoreloadedcallback
voidOnLocalUserScoreLoaded(stringleaderboardName,IScorescore)
{
if(score!=null)
{
Debug.Log("Yourscoreis:"+score.value);
}
else
{
Debug.Log("Youdon'thaveanyscorereportedtoleaderboard"+leaderboardName);
}
}
LoadScores
Youcanloadasetofscoresfromaleaderboardwithwhichyoucanspecifythestartpositiontoloadscore,the
numberofscorestoload,aswellasthetimescopeanduserscope.
//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
//Loadasetof20scoresstartingfromrank10inTodaytimescopeandGlobaluserscope
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadScores(
EM_GameServicesConstants.Sample_Leaderboard,
10,
20,
TimeScope.Today,
UserScope.Global,
OnScoresLoaded
);
Scripting
78
//Scoresloadedcallback
voidOnScoresLoaded(stringleaderboardName,IScore[]scores)
{
if(scores!=null&&scores.Length>0)
{
Debug.Log("Loaded"+scores.Length+"fromleadeboard"+leaderboardName);
foreach(IScorescoreinscores)
{
Debug.Log("Score:"+score.value+";rank:"+score.rank);
}
}
else
{
Debug.Log("Noscoreloaded.");
}
}
Youcanalsoloadthedefaultsetofscores,whichcontains25scoresaroundthelocaluser'sscoreintheAllTimetime
scopeandGlobaluserscope.
//PutthisontopofthefiletouseIScore
UnityEngine.SocialPlatforms;
//Loadthedefaultsetofscores
//EM_GameServicesConstants.Sample_Leaderboardisthegeneratednameconstant
//ofaleaderboardnamed"SampleLeaderboard"
GameServices.LoadScores(EM_GameServicesConstants.Sample_Leaderboard,OnScoresLoaded);
//Scoresloadedcallback
voidOnScoresLoaded(stringleaderboardName,IScore[]scores)
{
if(scores!=null&&scores.Length>0)
{
Debug.Log("Loaded"+scores.Length+"fromleadeboard"+leaderboardName);
foreach(IScorescoreinscores)
{
Debug.Log("Score:"+score.value+";rank:"+score.rank);
}
}
else
{
Debug.Log("Noscoreloaded.");
}
}
GetAllLeaderboards
Youcanobtainanarrayofallleaderboardscreatedinthemodulesettingsinterface:
//GetthearrayofallleaderboardscreatedintheGameServicemodulesettings
//Leaderboardistheclassrepresentingaleaderboardasdeclaredinthemodulesettings
//TheGameServicespropertyofEM_Settingsclassholdsthesettingsofthismodule
Leaderboard[]leaderboards=EM_Settings.GameServices.Leaderboards;
//Printallleaderboardnames
foreach(Leaderboardldbinleaderboards)
{
Debug.Log("Leaderboardname:"+ldb.Name);
}
Scripting
79
Achievements
Thissectionfocusesonworkingwithachievements.
ShowAchievementUI
ToshowtheachievementsUI(thesystemviewofachievements):
//ShowachievementsUI
GameServices.ShowAchievementsUI();
Youshouldcheckiftheinitializationhasfinished(theuserhasbeenauthenticated)beforeshowingtheachievements
UI,andtakeappropriateactionsiftheuserisnotloggedin,e.g.showanalertorstartanotherinitializationprocess.
//CheckforinitializationbeforeshowingachievementsUI
if(GameServices.IsInitialized())
{
GameServices.ShowAchievementsUI();
}
else
{
#ifUNITY_ANDROID
GameServices.Init();//startanewinitializationprocess
#elifUNITY_IOS
Debug.Log("CannotshowachievementsUI:TheuserisnotloggedintoGameCenter.");
#endif
}
RevealanAchievement
Torevealahiddenachievement,simplyspecifyitsname.
Asinthecaseofleaderboards,itisstronglyrecommendedthatyouusetheconstantsofachievementnamesin
thegeneratedEM_GameServicesConstantsclassinsteadoftypingthenamesdirectly.
//Revealahiddenachievement
//EM_GameServicesConstants.Sample_Achievementisthegeneratednameconstant
//ofanachievementnamed"SampleAchievement"
GameServices.RevealAchievement(EM_GameServicesConstants.Sample_Achievement);
UnlockanAchievement
Tounlockanachievement:
//Unlockanachievement
//EM_GameServicesConstants.Sample_Achievementisthegeneratednameconstant
//ofanachievementnamed"SampleAchievement"
GameServices.UnlockAchievement(EM_GameServicesConstants.Sample_Achievement);
ReportIncrementalAchievement'sProgress
Toreporttheprogressofanincrementalachievement:
//Reportarogressof50%foranincrementalachievement
//EM_GameServicesConstants.Sample_Incremental_Achievementisthegeneratednameconstant
//ofanincrementalachievementnamed"SampleIncrementalAchievement"
GameServices.ReportAchievementProgress(EM_GameServicesConstants.Sample_Incremental_Achievement,50.0f);
Scripting
80
GetAllAchievements
Youcanobtainanarrayofallachievementscreatedinthemodulesettingsinterface:
//GetthearrayofallachievementscreatedintheGameServicemodulesettings
//Achievementistheclassrepresentinganachievementasdeclaredinthemodulesettings
//TheGameServicepropertyofEM_Settingsclassholdsthesettingsofthismodule
Achievement[]achievements=EM_Settings.GameServices.Achievements;
//Printallachievementnames
foreach(Achievementacminachievements)
{
Debug.Log("Achievementname:"+acm.Name);
}
UserProfiles
Youcanloadtheprofilesoffriendsofthelocal(authenticated)user.Whentheloadingcompletestheprovided
callbackwillbeinvoked.
//PutthisontopofthefiletouseIUserProfile
UnityEngine.SocialPlatforms;
//Loadthelocaluser'sfriendlist
GameServices.LoadFriends(OnFriendsLoaded);
//Friendsloadedcallback
voidOnFriendsLoaded(IUserProfile[]friends)
{
if(friends.Length>0)
{
foreach(IUserProfileuserinfriends)
{
Debug.Log("Friend'sname:"+user.userName+";ID:"+user.id);
}
}
else
{
Debug.Log("Couldn'tfindanyfriend.");
}
}
YoucanalsoloaduserprofilesbyprovidingtheirIDs.
//PutthisontopofthefiletouseIUserProfile
UnityEngine.SocialPlatforms;
//LoadtheprofilesoftheuserswithprovidedIDs
//idArrayisthe(string)arrayoftheIDsoftheuserstoloadprofiles
GameServices.LoadUsers(idArray,OnUsersLoaded);
//Usersloadedcallback
voidOnUsersLoaded(IUserProfile[]users)
{
if(users.Length>0)
{
foreach(IUserProfileuserinusers)
{
Debug.Log("User'sname:"+user.userName+";ID:"+user.id);
}
}
else
Scripting
81
GameServices|SavedGames:Introduction
Savinggamedataisamongthemostdesirablefeaturesofvideogamesingeneral,andmobilegamesinparticular.
Nowadays,it'snotuncommonforausertoownmorethanonemobiledevice,beitphoneortablet.Beingabletostart
agameononedevice,andthencontinueplayingonanotherdevicewithoutlosinganyprogressbringsaseamless-if
notnatural-userexperience.
TheSavedGamesfeatureofEasyMobilemakesitpossible-andeasy-tosaveaplayer'sgamedatatothecloud
andsynchronizeitacrossmultipledevices.Savinguserdatatothecloudalsomeansthattheirgameprogressionis
preservedandcanberestoredincasessuchasreinstallationordevicefailure.
OniOS,thegamedataissavedtoiCloudviatheGameCenter(GameKit)API.OnAndroid,itissavedto
GoogleDriveviatheGooglePlayGameServicesAPI(GPGS).
UnderstandingSavedGames
Asavedgameconsistsoftwoparts:
Anunstructuredbinaryblob-thiscanrepresentwhateverdatayoudeemrelevanttoyourgame,andyourgame
isresponsibleforgeneratingandintepretingit.
Structuredmetadata-additionalpropertiesassociatedwiththebinarydataandprovideinformationaboutthis
data.
Thetablebelowdescribescommonsavedgameproperties.
Property Description
Name Adeveloper-suppliedshortnameofthesavedgame
ModificationDate Atimestampcorrespondingtothelastmodificationofthesavedgame
DeviceName [iOSonly]Thenameofthedevicethatcommittedthesavedgamedata
Description [GPGSonly][Optional]Adeveloper-supplieddescriptionofthesavedgame
CoverImageURL [GPGSonly][Optional]TheURLofthePNGcoverimageofthesavedgame
TotalTimePlayed [GPGSonly][Optional]Adeveloper-suppliedvalue(inmilisesconds)representingthe
playedtimeofthesavedgame
IsOpen Whetherthesavedgameis"Open".Asavedgamecanonlybereadorwrittentoifitis
open.
It'suptoyoutodecidehowandwhenuserscansaveagame.Dependingonyourgamedesign,youmightwantto
allowonlyasinglesavedgame,oryoumightwanttoallowtheplayertocreatemultiplesavedgameswithdifferent
names(sotheycan,forexample,gobacktovariouscheckpointsandtrydifferentactions).
TheUnderlyingCloudServices
Asmentionedearlier,savedgamesarestoredoniCloud(iOS/GameCenter)andGoogleDrive(Android/GPGS).
Therefore,it'smandatorythattheuserhasaniCloudorGoogleaccounttousethefeatureonthecorresponding
platform.
SavedGames
83
OniOS,thesavedgamesaretiedtotheuser'siCloudaccount,nottheGameCenteraccount.
OnAndroid,theGoogleDriveassociatedwiththeuser'sGoogleaccountthatwasauthenticatedwithGPGSis
used.
Limitations
iOS(iCloud/GameCenter) Android(GoogleDrive/GPGS)
Nohardlimitonthenumberofsavedgames Nohardlimitonthenumberofsavedgames
Thesizeofasavedgamedataislimitedtothe
amountofavailablespaceintheuser'siCloud
account)
GPGScurrentlyenforcesizelimitsonbinarydataand
coverimagesizesof3MBand800KBrespectively.
Youshouldalwaysstrivetominimizetheamountofdatabeingsaved.Thispreventstheuserfromrunningout
ofspaceanddecreasestheamountoftimerequiredtofetchorsaveagamefile.Alsonotethatthegame
savingoperationmayfailifthere'snotenoughroomintheiCloudorGoogleDriveaccountoftheuser.
OfflineSupport
Yourgamecanstillreadandwritetoasavedgamewhentheplayer'sdeviceisoffline,butwillnotbeabletosyncwith
thecloudservicesuntilnetworkconnectivityisestablished.Oncereconnected,thesynchronizationwillbedone
automaticallyandasynchronously.
ConflictResolution
Whenauserplaysyourgameonmultipledevicesandusesthesavedgamesfeature,it'snotuncommontohave
multiplesavedgameswiththesamenameandfromdifferentdevices,thuscreatingconflicts.Theseconflictstypically
occurwhenaninstanceofyourgameisunabletoreachthecloudservicewhileattemptingtosyncthesavegame
data,orwhenitupdatesthesavedgamedataonthecloudwithoutloadingthelatestdatafirst.Ingeneral,thebest
waytoavoiddataconflictsistoalwaysloadthelatestdatafromthecloudservicewhenyourgamestartsupor
resumes,andsavedatatotheservicewithreasonablefrequency.However,itisnotalwayspossibletoavoiddata
conflicts.Yourapplicationshouldmakeeveryefforttohandleconflictstopreserveusers'dataaswellasmaintaina
gooduserexperience.Fortunately,theSavedGamesAPIcanhelpyouresolvetheseconflictsautomaticallyusing
severaldefaultresolutionstrategies.Italsoprovidesrelevantmethodstohelpyouimplementyourownresolution
strategytobettersuityourneeds.
InthisGameOn!-SavedGamesIn-Depth(Part2)YouTubevideobyGoogleDevelopersyou'llfindin-depth
explanationonconflictsbetweensavedgames,howtheyhappen,howtoresolvethemaswellasotherimportant
concepts.ThevideoisdedicatedtotheSavedGamesfeatureofGooglePlayGamesServices,buttheconceptsare
alsoapplicabletoGameCenter.Amustwatch.
UsefulLinks
1. SavingAGame-GameCenterProgrammingGuide
2. SavedGames-GooglePlayGameServices
3. GameOn!-SavedGamesIn-Depth(Part2)YouTubevideo
SavedGames
84
SavedGames
85
GameServices|SavedGames:Settings
TheSavedGamesfeaturecanbeconfiguredintheSAVEDGAMESCONFIGsectionintheGameServicemodule
settings.
EnableSavedGames:youmustenabletheSavedGamesfeaturebeforeusingit
ConflictResolutionStrategy:thedefaultstrategyusedbytheautomaticconflictresolutionfeature
[Android]DataSource:wherethegamedatacanbefetchedfrom,onlyapplicableonAndroid/GooglePlayGame
Servicesplatform
iOSSetup
TousetheSavedGamesserviceoniOS,youmustenabletheiCloudcapabilityforyourappintheXcodeproject.
MakesuretheiCloudDocumentsserviceisselected.
Also,forthefeaturetofunctionontheiriOSdevices,theusersmustsignedintotheiriCloudaccountandhavethe
iCloudDriveserviceenabledintheSettingsapp.
Settings
86
Notethatyouneedtowaitatleast24hoursafterenablingtheSavedGamesserviceforittobeavailable.
Attemptingtoauthenticateduringthistimemaycausetheapptocrash.
Settings
88
GameServices|SavedGames:Scripting
ThissectionprovidesaguidetoworkwiththeSavedGamesscriptingAPIoftheGameServicesmodule.
YoucanaccesstheSavedGamesAPIviatheSavedGamespropertyoftheGameServicesclassunderthe
EasyMobilenamespace.
Workingwithsavedgamesinvolvesthefollowingoperations:
Operation Description
Open
Asavedgamemustbeopenedbeforeitcanbeusedforreadorwriteoperation.Ifyouattemptto
openanon-existingsavedgame,anewonewillbecreatedandwillbeopenedautomatically.
Youmustresolveanyconflictsassociatedwithasavedgamewhenopeningit.Youcanhavethe
conflictsresolvedautomaticallyusingoneofthedefaultstrategies,orimplementyourown
strategytoresolvethemmanually.
Write Updatethedataassociatedwithasavedgame,thesavedgamemustbeopenbeforewriting,
anditwillbeclosedautomaticallyaftertheoperationhasfinished.
Read Retrievethedataassociatedwithasavedgame,thesavedgamemustbeopen.
Delete Deleteasavedgamefromthecloudservice
OpeningSavedGame
YoucanopenasavedgameusingeithertheOpenWithAutomaticConflictResolutionorthe
OpenWithManualConflictResolutionmethod.Bothmethodsopenasavedgamewiththespecifiedname,orcreatea
newoneifnoneexists.Thesavedgamereturnedintheircallbackswillbeopenwhichmeansitcanbeusedforread
orwriteoperation.Thedifferencebetweenthetwoiswhethersavedgameconflicts,ifany,willberesolved
automaticallyormanually.
IfthecurrentplatformisGooglePlayGameServices,thesemethodsusethedatasourcespecifiedinthe
modulesettings.
OpenWithAutomaticConflictResolution
Asitsnamesuggests,whenopeningasavedgameusingthismethod,anyoutstandingconflictswillberesolved
automaticallyusingtheresolutionstrategyspecifiedinthemodulesettings.
//Putthisontopofthescript
usingEasyMobile;
//Tostoretheopenedsavedgame.
privateSavedGamemySavedGame;
//Openasavedgamewithautomaticconflictresolution
voidOpenSavedGame()
{
//Openasavedgamenamed"My_Saved_Game"andresolveconflictsautomaticallyifany.
GameServices.SavedGames.OpenWithAutomaticConflictResolution("My_Saved_Game",OpenSavedGameCallback);
}
//Opensavedgamecallback
voidOpenSavedGameCallback(SavedGamesavedGame,stringerror)
{
if(string.IsNullOrEmpty(error))
{
Scripting
89
Debug.Log("Savedgameopenedsuccessfully!");
mySavedGame=savedGame;//keepareferenceforlateroperations
}
else
{
Debug.Log("Opensavedgamefailedwitherror:"+error);
}
}
OpenWithManualConflictResolution
Ifthesavedgamebeingopenedhasoutstandingconflicts,theywillberesolvedmanuallyusingthespecifiedconflict
resolutionfunction.Thisfunctionmustbeimplementedbyyouanditiswhereyouprovideyourcustomconflict
resolutionstrategy,incasenoneofthedefaultstrategiessuitsyourneeds.Thefunctionwillbeinvokedautomatically
whenaconflictisencounteredwhileopeningasavedgameandcanbeinvokedmultipletimesifthesavedgamehas
morethanoneoutstandingconflict.Thereforeitmustbedesignedtohandlemultipleinvocations.
TheconflictresolutionfunctionreceivestheBaseandRemoteversionsoftheconflictingsavedgame(pleasecheck
outthisGameOn!-SavedGamesIn-Depth(Part2)YouTubevideobyGoogleDevelopersforanexcellent
explanationontheconceptsof"base"and"remote").Thesepassedsavedgamesareallopen.If
OpenWithManualConflictResolutionwasinvokedwithprefetchDataOnConflictsettotrue,thebinarydataassociated
withthesesavedgameswillloadedandpassedtotheconflictresolutionfunctiontoo.Usethereturnvalueofthis
functiontodeterminewhetherthebaseortheremotewillbechosenasthecanonicalversionofthesavedgame.
Thecallbackwillbeinvokedwhenallconflicts(ifany)havebeenresolvedandtheoperationfinishes.
//Putthisontopofthescript
usingEasyMobile;
//Tostoretheopenedsavedgame.
privateSavedGamemySavedGame;
//Openasavedgamewithmanualconflictresolution
voidOpenSavedGame()
{
//Openasavedgamenamed"My_Saved_Game"andresolveanyoutstandingconflictsmanuallyusing
//thespecifiedresolutionfunction.
GameServices.SavedGames.OpenWithManualConflictResolution(
"My_Saved_Game",
true,//prefetchDataOnConflict
MyConflictResolutionFunction,
OpenSavedGameCallback
);
}
//Theconflictresolutionfunction.
//baseGameandremoteGameareallopen.
//IfOpenWithManualConflictResolutionwasinvokedwithprefetchDataOnConflictsettotrue,
//baseDataandremoteDatawillcontainthebinarydataassociatedwithbaseGameandremoteGamerespective.
//Theywillbenullotherwise.
//Inthisfunctionyoucanperformrequiredcalculation,comparisonbetweentwoversions,etc.todecide
//whichonewillbethecanonicalversionofthesavedgame.Usethereturnvaluetoindicateyourdecision.
SavedGameConflictResolutionStrategyMyConflictResolutionFunction(SavedGamebaseGame,byte[]baseData,
SavedGameremoteGame,byte[]remoteData)
{
{
//Performwhateverrequiredcalculation,comparison,etc.onthetwoversions
//andtheirassociateddatatohelpyoudecidewhichversionshouldbechosen.
...
//Afterdeterminingthecanonicalversion,usethereturnvaluetoindicateyourchoice
returnSavedGameConflictResolutionStrategy.UseBase;//usethebaseversion
//Ifyouwanttoselecttheremoteversioninstead,justchangeitto
//returnSavedGameConflictResolutionStrategy.UseRemote;
}
Scripting
90
}
//Opensavedgamecallback
voidOpenSavedGameCallback(SavedGamesavedGame,stringerror)
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgameopenedsuccessfully!");
mySavedGame=savedGame;//keepareferenceforlateroperations
}
else
{
Debug.Log("Opensavedgamefailedwitherror:"+error);
}
}
Incaseyouwanttomergethedatafromdifferentversions,simplyspecifyeitherthebaseortheremoteasthe
chosenversion.Onceallconflictsareresolvedandthesavedhasbeenopenedsuccessfully,performawrite
operationusingthemergedata.
WritingSavedGameData
Tocommitnewdatatoasavedgame,usetheWriteSavedGameDatamethod.Asmentionearlier,thesavedgame
mustbeopenbeforewritingortheoperationwillfail.Whenthismethodcompletessuccessfully,thedataisdurably
persistedtodiskandwilleventuallybeuploadedthethecloud(inpractice,thisprocesshappensveryquicklyunless
thedevicedoesn'thaveanetworkconnection).Aftertheoperationfinishes,thesavedgamewillbeclosed
automatically.Thisistoforceittobeopenedonceagain(thusresolvinganyoutstandingconflicts)beforeanother
commitcanbemade.
//Putthisontopofthescript
usingEasyMobile;
//Updatesthegivenbinarydatatothespecifiedsavedgame
voidWriteSavedGame(SavedGamesavedGame,byte[]data)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforwriting
GameServices.SavedGames.WriteSavedGameData(
savedGame,
data,
(SavedGameupdatedSavedGame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenwrittensuccessfully!");
}
else
{
Debug.Log("Writingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeattheprocess.
Debug.Log("Youmustopenthesavedgamebeforewritingtoit.");
}
}
Besidethebinarydata,youcanalsoupdatethemetadata(properties)ofasavedgame.Justusetheoverloading
versionofWriteSavedGameDatathatacceptsaSavedGameInfoUpdatestruct.
Scripting
91
Somesavedgamepropertiesareonlyavailableonacertainplatform,pleasereviewtheGameService>
ModuleConfiguration>SavedGamesectionfordetailedinformation.
//Putthisontopofthescript
usingEasyMobile;
//UpdatesthebinarydataANDthepropertiesofasavedgame
voidWriteSavedGame(SavedGamesavedGame,byte[]data)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforwriting
//Preparetheupdatedmetadataofthesavedgame
SavedGameInfoUpdate.Builderbuilder=newSavedGameInfoUpdate.Builder();
builder.WithUpdatedDescription("New_Description");
builder.WithUpdatedPlayedTime(TimeSpan.FromMinutes(30));//updatetheplayedtimeto30minutes
SavedGameInfoUpdateinfoUpdate=builder.Build();
GameServices.SavedGames.WriteSavedGameData(
savedGame,
data,
infoUpdate,//updatesavedgameproperties
(SavedGameupdatedSavedGame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenwrittensuccessfully!");
}
else
{
Debug.Log("Writingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeattheprocess.
Debug.Log("Youmustopenthesavedgamebeforewritingtoit.");
}
}
ReadingSavedGameData
Toreadasavedgamedata,usetheReadSavedGameDatamethod.Thesavedgamemustbeopenbeforereading.
Thecallbackwillbeinvokedwhentheoperationfinishesandwillreceivetheretrieveddataasabytearray,whichcan
beemptyifthesavedgamehasnodatacommittedpreviously.
//Putthisontopofthescript
usingEasyMobile;
//Retrievesthebinarydataassociatedwiththespecifiedsavedgame
voidReadSavedGame(SavedGamesavedGame)
{
if(savedGame.IsOpen)
{
//Thesavedgameisopenandreadyforreading
GameServices.SavedGames.ReadSavedGameData(
savedGame,
(SavedGamegame,byte[]data,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Savedgamedatahasbeenretrievedsuccessfully!");
//Hereyoucanprocessthedataasyouwish.
Scripting
92
if(data.Length>0)
{
//Dataprocessing
...
}
else
{
Debug.Log("Thesavedgamehasnodata!");
}
}
else
{
Debug.Log("Readingsavedgamedatafailedwitherror:"+error);
}
);
}
else
{
//Thesavedgameisnotopen.Youcanoptionallyopenithereandrepeattheprocess.
Debug.Log("Youmustopenthesavedgamebeforereadingitsdata.");
}
}
DeletingSavedGame
Todeleteasavedgame,simplycalltheDeleteSavedGamemethod.
//Putthisontopofthescript
usingEasyMobile;
//Deletesasavedgame
voidDeleteSavedGame(SavedGamesavedGame)
{
GameServices.SavedGames.DeleteSavedGame(savedGame);
}
FetchingAllSavedGames
Whenimplementthesavedgamesfeatureinyourgame,chancesareyouwillwanttoshowtheuseralistofexisting
savedgamesforthemtochoosefrom.Insuchcase,youcanusetheFetchAllSavedGamesmethodtoretrieveall
knowsavedgames.Acallbackwillbeinvokedwhenthemethodcompletes,receivinganarrayofsavedgameswhich
canbeemptyifnosavedgamewascreatedbefore.NotethatallthereturnedsavedgamesareNOTopen.
IfthecurrentplatformisGooglePlayGameServices,thismethodretrievessavedgamesfromthedatasource
specifiedinthemodulesettings.
//Putthisontopofthescript
usingEasyMobile;
//Fetchesallknowsavedgames.
voidFetchSavedGames()
{
GameServices.SavedGames.FetchAllSavedGames(
(SavedGame[]games,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Fetchedsavedgamessuccessfully!Got"+games.Length+"savedgames.");
//HereyoucanshowaUItodisplaythesesavedgamestotheuser...
Scripting
93
}
else
{
Debug.Log("Fetchingsavedgamesfailedwitherror"+error);
}
}
);
}
[Android]Built-inSavedGameUI
OnAndroid,theSavedGamesfeatureofGooglePlayGameServicesoffersabuilt-inUIfromwhichtheusercan
open,selectordeleteasavedgames.YoucanshowthisUIbycallingtheShowSelectSavedGameUImethod.A
callbackwillbeinvokedwhentheUIisclosed,receivingtheselectedsavedgameifany.
Thismethodisano-oponiOS/GameCenterplatform.
//Putthisontopofthescript
usingEasyMobile;
//ShowstheGPGSbuilt-insavedgameUI.
voidShowGPGSSavedGameUI()
{
GameServices.SavedGames.ShowSelectSavedGameUI(
"SelectSavedGame",//UItitle
5,//maximumnumberofdisplayedsavedgames
true,//allowcreatingsavedgames
true,//allowdeletingsavedgames
(SavedGamegame,stringerror)=>
{
if(string.IsNullOrEmpty(error))
{
Debug.Log("Youselectedsavedgame:"+game.Name);
}
else
{
Debug.Log(error);
}
}
);
}
Scripting
94
GameServices:PlayMakerActions
ThePlayMakeractionsoftheGameServicesmodulearegroupinthecategoryEasyMobile-GameServicesinthe
PlayMaker'sActionBrowser.
PleaserefertotheGameServicesDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowtheseactionscanbeused.
PlayMakerActions
95
SavedGames
ThePlayMakeractionsoftheSavedGamesfeaturearegroupinthecategoryEasyMobile-SavedGamesinthe
PlayMaker'sActionBrowser.
PleaserefertotheGameServicesDemo_SavedGames_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowtheseactionscanbeused.
PlayMakerActions
96
PlayMakerActions
97
GIF:Introduction
GIFmoduleisavailableonEasyMobileProonly.
TheGIFmoduleprovidesyouconvenienttoolstorecordscreenactivitiesintoashortclip,playtherecordedclipand
exportitintoaGIFimage.YoucanthenuploadtheGIFfiletohostingsiteslikeGiphyandfinallyshareitsURLto
socialnetworks.Inshort,thismodulehelpsyoueasilyaddtheGIFsharingfeaturetoyourgame,whichallowsthe
usertoshareanimatedGIFimagesofthegameplay,insteadofstillscreenshots,tosocialnetworksincluding
FacebookandTwitter.Thefollowingpictureillustratesatypicalworkflowofsuchfeature.
Here'resomehighlightsofthismodule:
Highperformance,mobile-friendlyGIFgenerator
Lowoverheadscreen/camerarecorder
GIFgenerationisdoneinnativecode(iOSandAndroid)onaseparatethreadtoallowfastexportingwhile
minimizingimpacttothemainthread.Exportcallbacksarestillcalledfrommainthreadthough,soyoucan
safelyaccessUnityAPIinthecallbackhandlers
Flexible,fullycontrollableprocess
Youhavefullcontrolonthesizes,length,framerate,loopmodeandqualityoftheexportedGIF
Youcanalsosetthepriorityoftheexportingthreadtobestsuityourneeds
HighqualityGIF
ExportedGIFemploysGIF89aformatanduses256-colorlocalpalettes(onepaletteperframe)
FrameimagedataisLWZcompressed
WorksinUnityeditor
GIFexportingalsoworksintheeditor,mostlyfortestingpurpose.Onmobiles,theexportingisdoneinnative
code,whileineditoritisdoneinmanagedcodeusinganadaptedversionoftheMomentsplugin(see
Acknowledgement)
EasyGIFsharing
ThismodulealsoprovidesGiphyAPIforuploadingGIFimagestoGiphy,sothattheycanbesharedand
playedonmajorsocialnetworksincludingFacebookandTwitterusingtheGiphyhostedURLs
Acknowledgement
TherecorderusedinthismoduleisadaptedfromtherecorderoftheMomentspluginbyChman(ThomasHourdel).
Also,inUnityeditor,GIFgenerationisdoneusinganadaptedversionofthisplugin.
Introduction
98
Introduction
99
GIF:Setup
Thissectionexplainsthevariouscomponents,objectsandconceptsinvolvedincliprecording,clipplayingandGIF
exporting.Italsoprovidesaguideoncreatingandconfiguringrelevantobjectsandcomponents.
RecorderComponent
TheRecordercomponentrecordsthecontentrenderedbyacameraandreturnstherecordedclip.Tostartrecording,
simplyaddaRecordercomponenttothecamerathatrendersthecontentyou'reinterestedinrecording(normallythis
willbetheMainCamera).Toaddthecomponenttoacamera,selectthatcameraintheHierarchy,thenclickAdd
Component>EasyMobile>Recorder.
OncetheRecordercomponentisaddedtothecamera,youcanstartconfiguringitintheinspectortodeterminehow
therecordedclip(andasaresult,theexportedGIF)willbelike.
AutoHeight:whethertheclipheightshouldbecomputedautomaticallyfromthespecifiedwidthandthecamera's
aspectratio,whichisusefultomakesuretheexportedGIFhasacorrectaspectratio
Width:thewidthoftherecordedclipinpixels
Height:theheightoftherecordedclipinpixels
FramesPerSecond:theframerateoftheclip
Length:thecliplengthinseconds;therecorderautomaticallydiscardsoldcontenttopreservethislength,e.g.if
yousetthisvalueto3seconds,onlylast3secondsoftherecordingwillbestoredintheresultedclip,therestwill
Setup
100
bediscarded
EstimatedVRamUsage:theestimatedmemoryusedforrecording,calculatedbasedontheabovesettings
CurrentState:thecurrentstatusoftherecorder,whichiseitherStoppedorRecording
Nowthattherecorderisconfigured,youcanstartandstopitsrecordingactivityfromscript(seetheScripting
section).Oncetherecordingisstopped,therecordedclipwillbereturnedforplaybackofGIFexporting.
RecordingtheUI
TorecordtheUI(Canvascontent),youneedtosettheCanvasRenderModetoWorldSpaceorScreenSpace
-Camera,andsettheRenderCameratotheonecontainingtheRecordercomponentinthelattercase.
Recordingmultiplecompositedcameras
Ifyourscenecontainsmultiplecamerasbeingcomposited(usingCamera.depthandClearflags),youcanadd
theRecordercomponenttothetop-mostcamera,soitcaptureswhatevercontentbeingcompositedandshown
bythatcamera.
AnimatedClipClass
RecordedclipsarerepresentedbytheAnimatedClipclass,whichhasfollowingproperties:
Width:thewidthoftheclipinpixels
Height:theheightoftheclipinpixels
FramePerSecond:theframerateoftheclip
Length:thelengthoftheclipinseconds
Frames:anarrayofframes,eachframeisaRenderTextureobject
Playback
EasyMobileprovidestwobuilt-inobjectsdedicatedforplayingrecordedclips:theClipPlayerandClipPlayerUI
objects.Youcancreatethemfromthecontextmenu(asyouwouldwithotherUnitybuilt-inobjects),configurethemin
theinspector,andstartorstoptheirplayingactivityfromscript(seetheScriptingsection).
ClipPlayer
TheClipPlayerisanon-UIobject,whichisbasicallyaQuadobjectequippedwithaClipPlayercomponent.Itismeant
tobeusedinsidethegameworld.TocreateaClipPlayerobject,right-clickintheHierarchywindowtoopenthe
contextmenu,thenselectEasyMobile>ClipPlayer.
Setup
101
EachClipPlayerobjectcontainsaClipPlayercomponent.
TheonlyparameterofthiscomponentistheScaleMode,whichcantakeoneof3values:
None:don'tadjusttheobjectsizes
AutoHeight:keepsthecurrentheightoftheobject(theYcomponentofitslocalScale),andadjustthewidth(the
XcomponentofitslocalScale)tomatchtheaspectratiooftheclipbeingplayed
AutoWidth:keepsthecurrentwidthoftheobject(theXcomponentofitslocalScale),andadjusttheheight(theY
componentofitslocalScale)tomatchtheaspectratiooftheclipbeingplayed
ClipPlayerUI
TheClipPlayerUI,asitnameimplies,isaUIobjectlivinginsideaCanvas.Itistheobjecttousewhenyouwantto
playaclipinsidetheUI.ItisbasicallyaRawImageobjectequippedwithaClipPlayerUIcomponent.TocreateaClip
PlayerUIobject,right-clickintheHierarchywindowtoopenthecontextmenu,thenselectEasyMobile>ClipPlayer
(UI).
Setup
102
EachClipPlayerUIobjectcontainsaClipPlayerUIcomponent.
TheonlyparameterofthiscomponentistheScaleMode,whichcantakeoneof3values:
None:don'tadjusttheobjectsizes
AutoHeight:keepsthecurrentheightoftheobject(theHeightvalueinitsRectTransform),andadjustthewidth
(theWidthvalueinitsRectTransform)tomatchtheaspectratiooftheclipbeingplayed
AutoWidth:keepsthecurrentwidthoftheobject(theWidthvalueinitsRectTransform),andadjusttheheight
(theHeightvalueinitsRectTransform)tomatchtheaspectratiooftheclipbeingplayed
CustomClipPlayer
Besidethetwobuilt-inclipplayersprovidedbyEasyMobile,youcanconstructyourownplayertoserveyourspecific
needs.Tomakeitconsistentwithotherplayers,andcompatiblewithEasyMobileAPI,thisplayershouldcontaina
scriptimplementingtheIClipPlayerinterface,whichisresponsibleforapplyingtheframes(RenderTextures)ofthe
clip,attherequiredframerate,towhatevertexture-displayingcomponentitisequippedwith.
Setup
103
GIF:Scripting
ThissectionprovidesaguidetoworkwiththeGIFmodulescriptingAPI.Atthisstage,it'sassumedthatyouhave
setuparecorderforthecamerayouwanttorecord,andcreatedanappropriateclipplayertoplaytherecordedclip.If
you'renotfamiliarwiththeseconcepts,pleasereviewtheSetupsection.
YoucanaccesstheGIFmoduleAPIviatheGifclassundertheEasyMobilenamespace.AsforGiphyAPI,use
theGiphyclass.
Recording
Tostartrecordingonthecreatedrecorder,usetheStartRecordingmethod.Youcandothisassoonasthegame
starts;therecorderonlystoresafewlastseconds(specifiedbytheLengthparameterintheRecorderinspector)of
therecording,andautomaticallydiscardstherest.
//Putthisontopofthescript
usingEasyMobile;
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
Tostoprecording,simplycalltheStopRecordingmethod,passingtherelevantrecorder.Themethodreturnsan
AnimatedClipobject,whichcanbeplayedorexportedintoaGIFimageafterward.Tocontinuethepreviousexample:
//Putthisontopofthescript
usingEasyMobile;
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
//Asuitabletimetostoprecordingmaybewhenthegameends(theplayerdies)
Scripting
104
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//Dootherstuff...
}
Playback
Toplayarecordedclipusingapre-createdclipplayer,usethePlayClipmethod.Thismethodreceivesasargument
anIClipPlayerinterface,whichisimplementedbybothClipPlayerandClipPlayerUIclasses,thereforeitworkswith
bothClipPlayerandClipPlayerUIobject.ThesecondargumentisanAnimatedClipobject.Otherargumentsinclude
anoptionaldelaytimebeforetheplayingstarts,andtheloopingmode.Youcanpause,resumeandstoptheplayer
usingthePausePlayer,ResumePlayerandStopPlayermethods,respectively.
Tocontinuethepreviousexample:
//Putthisontopofthescript
usingEasyMobile;
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Supposeyou'vecreatedaClipPlayerUIobject(ClipPlayerwillalsowork)
//Dragthepre-createdclipplayertothisfieldintheinspector
publicClipPlayerUIclipPlayer;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
}
//Asuitabletimetostoprecordingmaybewhenthegameends(theplayerdies)
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//Playtherecordedclip!
PlayMyClip();
}
//Thismethodplaystherecordedcliponthecreatedplayer,
//withnodelaybeforeplaying,andloopindefinitely.
voidPlayMyClip()
{
Gif.PlayClip(clipPlayer,myClip);
}
//Thismethodplaystherecordedcliponthecreatedplayer,
//withadelayof1secondsbeforeplaying,andloopindefinitely,
Scripting
105
//(youcansetloop=falsetoplaythecliponlyonce)
voidPlayMyClipWithDelay()
{
Gif.PlayClip(clipPlayer,myClip,1f,true);
}
//Thismethodpausestheplayer.
voidPausePlayer()
{
Gif.PausePlayer(clipPlayer);
}
//Thismethodun-pausestheplayer.
voidUnPausePlayer()
{
Gif.ResumePlayer(clipPlayer);
}
//Thismethodstopstheplayer.
voidStopPlayer()
{
Gif.StopPlayer(clipPlayer);
}
ExportingGIF
ToexporttherecordedclipintoaGIFimage,usetheExportGifmethod.Intheeditor,theexportedGIFfilewillbe
storedrightundertheAssetsfolder;onmobiledevices,thestoragelocationisApplication.persistentDataPath.You
canspecifythefilenameandthequalityoftheGIFimageaswellasthepriorityoftheexportingthread.Thequality
settingacceptsvaluesfrom1to100(inputswillbeclampedtothisrange).Biggervalueswillresultinbetterlooking
GIFs,butwilltakeslightlylongerprocessingtime;80isgenerallyagoodvalueintermsoftime-qualitybalance.This
methodhastwocallbacks:oneiscalledrepeatedlyduringtheprocessandreceivestheprogressvalue(0to1),the
otheriscalledwhentheexportcompletesandreceivesthefilepathofthegeneratedimage.ThoughtheGIF
generationprocessisdoneinaseparatethread,thesecallbacksareguaranteedtobecalledfromthemainthread,so
youcansafelyaccessallUnityAPIfromwithinthem.
IntherarecasethatyouwanttocontroltheloopingmodeoftheexportedGIF(thedefaultisloopindefinitely),usethe
variantofExportGifthathasaloopparameter(notethatsomeGIFplayersmayignorethissetting):
loop<0:disablelooping(playonce)
loop=0:loopindefinitely
loop>0:loopanumberoftimes
Inthefollowingexample,we'llexportaGIFimagefromtherecordedclipreturnedaftertherecordinghasstopped.
//Putthisontopofthescript
usingEasyMobile;
//DragthecamerawiththeRecordercomponenttothisfieldintheinspector
publicRecorderrecorder;
//Therecordedclip
AnimatedClipmyClip;
//Youcanstartrecordingassoonasyourgamestarts
//(supposeyouhaveamethodnamedStartGame,whichiscalledwhenthegamestarts)
voidStartGame()
{
//Startrecording!
Gif.StartRecording(recorder);
//Dootherstuff...
Scripting
106
}
//Asuitabletimetostoprecordingcanbewhenthegameends(theplayerdies)
//(supposeyouhaveamethodnamedGameOver,calledwhenthegameends)
voidGameOver()
{
//Stoprecording
myClip=Gif.StopRecording(recorder);
//ExportGIFimagefromtheresultedclip
ExportMyGif();
}
//ThismethodexportsaGIFimagefromtherecordedclip.
voidExportMyGif()
{
//Parametersetup
stringfilename="myGif";//filename,noneedthe".gif"extension
intloop=0;//-1:noloop,0:loopindefinitely,>0:loopasetnumberoftimes
intquality=80;//80isagoodvalueintermsoftime-qualitybalance
System.Threading.ThreadPrioritytPriority=System.Threading.ThreadPriority.Normal;//exportingthreadpri
ority
Gif.ExportGif(myClip,
filename,
loop,
quality,
tPriority,
OnGifExportProgress,
OnGifExportCompleted);
}
//ThiscallbackiscalledrepeatedlyduringtheGIFexportingprocess.
//Itreceivesareferencetooriginalclipandaprogressvaluerangingfrom0to1.
voidOnGifExportProgress(AnimatedClipclip,floatprogress)
{
Debug.Log(string.Format("Exportprogress:{0:P0}",progress));
}
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimage.
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
{
Debug.Log("AGIFimagehasbeencreatedat"+path);
}
DisposingofAnimatedClip
Internally,eachAnimatedClipobjectconsistsofanarrayofRenderTexture,a"nativeengineobject"type,whichisnot
garbagecollectedasnormalmanagedtypes.Thatmeanstheserendertextureswon'tbe"destroyed"automatically
whentheircontainingclipisgarbagecollected(theclipobjectdoesgetcollected,buttherendertexturesitreferences
don't,thuscreatingmemoryleaks).Totakecareofthisissue,wehavetheAnimatedClipimplementtheIDisposable
interfaceandprovidetheDisposemethodtoreleasetherendertextures,asUnityadvised.It'sstronglyrecommended
thatyoucallthisDisposemethod,preferablyassoonasyou'redonewithusingaclip(e.g.afterplayingorexporting
GIF),tomakesuretherendertexturesareproperlyreleasedandnotcausememoryissues.
We'llextendtheOnGifExportCompletedcallbackhandlerofthepreviousexampletodisposetherecordedclipas
soonaswe'vegeneratedaGIFimagefromit.
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimage.
Scripting
107
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
{
Debug.Log("AGIFimagehasbeencreatedat"+path);
//We'vedoneusingtheclip,disposeittosavememory
if(clip==myClip)
{
myClip.Dispose();
myClip=null;
}
}
Sinceversion2.1.0,we'veupdatedAnimatedClipsuchthatitautomaticallyreleasestheRenderTextureobject
onceitiscollected,thusavoidingmemoryleaksevenifyouforgettocallDispose.However,itcantakealong
timebeforeaclipgetscollectedbythegarbagecollector,soit'sstillagoodpracticetodisposeaclipassoonas
you'redoneusingittoavoidwastingmemory.
SharingGIF
NowthataGIFimagehasbeencreated,youmaywanttoshareit(becauseit'snotfunotherwise,isit?).Acommon
approachistofirstuploadtheimagetoGiphy,apopularGIFhostingsite,andthensharethereturnedURLtoother
socialnetworkslikeFacebookandTwitter,usingEasyMobile'sNativeSharingfeature(seetheNativeSharing>
Scriptingsection,inparticulartheShareURLmethod).
AccordingtoGiphyAPIdocumentation,hostedGiphyURLsaresupportedandplayoneverymajorsocial
network.
UploadtoGiphy
TouploadaGIFimagetoGiphy,usetheUploadmethodoftheGiphyclass.Youcanuploadalocalimageonyour
device,oranimagehostedonline,providedthatyouhaveitsURL.Beforedoingso,you'llneedtopreparetheupload
contentbycreatingaGiphyUploadParamsstruct.Inthisstructyou'llspecifyeitherthefilepathofthelocalimage,or
theURLoftheonlineimagetoupload.Notethatifbothparametersareprovided,thelocalfilepathwillbeusedover
theURL.Withinthisstructyoucanalsospecifyotheroptionalparameterssuchasimagetags,thesourceofthe
image(e.g.yourwebsite),ormarktheimageasprivate(onlyvisiblebyyouonGiphy).TheUploadmethodhasthree
callbacks:thefirstoneiscalledrepeatedlyduringtheuploadprocess,receivingaprogressvalue(0to1);thesecond
oneiscalledoncetheuploadhascompleted,receivingtheURLoftheuploadedimage;andthelastonewillbecalled
iftheuploadhasfailed,receivingtheerrormessage.Allcallbacksarecalledfromthemainthread.
GiphyBetaandProductionKey
TheUploadmethodhastwovariants:oneusingGiphy'spublicbetakey,andtheotherusingyourownchannel
usernameandproductionAPIkey.Thepublicbetakeyismeanttobeusedindevelopmentonly.Accordingto
GiphyUploadAPIdocumentation,itis"subjecttoratelimitconstraints",andthey"donotencouragelive
productiondeploymentstousethepublickey".IfyouhavecreatedaGiphychannelandwanttouploadGIF
imagesdirectlytothatchannel,you'llneedtorequestanUploadProductionKey,thenprovidethatkeyandyour
channelusernametotheUploadmethod.
We'llextendtheaboveexample,andmodifytheOnGifExportCompletedcallbackhandlertouploadtheGIFimageto
Giphyonceitiscreated.We'lldemonstratetwocases:uploadusingthepublicbetakeyanduploadusingyourown
productionkey.
//ThiscallbackiscalledoncetheGIFexportinghascompleted.
//Itreceivesareferencetotheoriginalclipandthefilepathofthegeneratedimage.
voidOnGifExportCompleted(AnimatedClipclip,stringpath)
{
Debug.Log("AGIFimagehasbeencreatedat"+path);
Scripting
108
//We'vedoneusingtheclip,disposeittosavememory
if(clip==myClip)
{
myClip.Dispose();
myClip=null;
}
//TheGIFimagehasbeencreated,nowwe'lluploadittoGiphy
//Firstpreparetheuploadcontent
varcontent=newGiphyUploadParams();
content.localImagePath=path;//thefilepathofthegeneratedGIFimage
content.tags="easymobile,sglibgames,unity";//optionalimagetags,comma-delimited
content.sourcePostUrl="YOUR_WEBSITE_ADDRESS";//optionalimagesource,e.g.yourwebsite
content.isHidden=false;//optionalhiddenflag,settotruetomarktheimageasprivate
//UploadtheimagetoGiphyusingthepublicbetakey
UploadToGiphyWithBetaKey(content);
}
//ThismethoduploadsaGIFimagetoGiphyusingthepublicbetakey,
//noneedtospecifyanyusernameorAPIkeyhere.
voidUploadToGiphyWithBetaKey(GiphyUploadParamscontent)
{
Giphy.Upload(content,OnGiphyUploadProgress,OnGiphyUploadCompleted,OnGiphyUploadFailed);
}
//ThismethoduploadsaGIFimagetoyourownGiphychannel,
//usingyourchannelusernameandproductionkey.
voidUploadToGiphyWithProductionKey(GiphyUploadParamscontent)
{
Giphy.Upload("YOUR_CHANNEL_USERNAME","YOUR_PRODUCTION_KEY",
content,
OnGiphyUploadProgress,
OnGiphyUploadCompleted,
OnGiphyUploadFailed);
}
//Thiscallbackiscalledrepeatedlyduringtheuploadingprocess.
//Itreceivesaprogressvaluerangingfrom0to1.
voidOnGiphyUploadProgress(floatprogress)
{
Debug.Log(string.Format("Uploadprogress:{0:P0}",progress));
}
//Thiscallbackiscalledoncetheuploadinghascompleted.
//ItreceivestheURLoftheuploadedimage.
voidOnGiphyUploadCompleted(stringurl)
{
Debug.Log("TheGIFimagehasbeenuploadedsuccessfullytoGiphyat"+url);
}
//Thiscallbackiscallediftheuploadhasfailed.
//Itreceivestheerrormessage.
voidOnGiphyUploadFailed(stringerror)
{
Debug.Log("UploadingtoGiphyhasfailedwitherror:"+error);
}
DisplaytheGiphyAttributionMarks
TorequestaProductionKey,Giphyrequireyoutodisplaythe"PoweredbyGiphy"attributionmarkswhenevertheir
APIisutilizedinyourapp,andprovidescreenshotsofyourattributionplacementwhensubmittingforthekey.Totake
careofthis,weprovidethestaticIsUsingAPIbooleanpropertyinsidetheGiphyclass.Thispropertywillbetrueas
longasGiphyAPIisinuse,toletyouknowwhentoshowtheirattributionmarks.Youcandisplaytheattributionlogo
usinganImageoraSpriteobject,thenpollthispropertyinsidetheUpdate()function,andactivateordeactivatethe
objectaccordingly.
YoucandownloadGiphy'sofficialattributionmarkshere.
Scripting
109
//Dragtheobjectdisplayingtheattributionmarkstothisfieldintheinspector
publicGameObjectattribution;
voidUpdate()
{
attribution.SetActive(Giphy.IsUsingAPI);
}
ShareGiphyURLs
AfteruploadingyourGIFimagetoGiphyandobtainitsURL,youcansharethisURLusingtheShareURLmethodof
theMobileNativeShareclass.Intheexamplebelow,we'llmodifytheOnGiphyUploadCompletedcallbackhandlerof
thepreviousexampletostorethereturnedURLintoaglobalvariable,whichcanbeusedforlatersharing.
//GlobalvariabletoholdtheGiphyURLoftheuploadedGIF
stringgiphyURL;
//Thiscallbackiscalledoncetheuploadinghascompleted.
//ItreceivestheURLoftheuploadedimage.
voidOnGiphyUploadCompleted(stringurl)
{
Debug.Log("TheGIFimagehasbeenuploadedsuccessfullytoGiphyat"+url);
//StoretheURLintoourglobalvariable
giphyURL=url;
}
//ThismethodsharestheURLusingthenativesharingutilityoniOSandAndroid
publicvoidShareGiphyURL()
{
if(!string.IsNullOrEmpty(giphyURL))
{
MobileNativeShare.ShareURL(giphyURL);
}
}
Scripting
110
GIF:PlayMakerActions
ThePlayMakeractionsoftheGIFmodulearegroupinthecategoryEasyMobile-GifinthePlayMaker'sAction
Browser.
PleaserefertotheGifDemo_PlayMakersceneinfolderAssets/EasyMobile/Demo/PlayMakerDemo/Modulesfor
anexampleonhowtheseactionscanbeused.
PlayMakerActions
111
PlayMakerActions
112
In-AppPurchasing:Introduction
TheIn-AppPurchasingmodulehelpsyouquicklysetupandselldigitalproductsinyourgame.Here'resomehighlights
ofthismodules:
LeveragesUnityIn-AppPurchasingservice
ThismoduleisbuiltontopofUnityIAPservice,apowerfulservicethatsupportsmostappstoresincluding
iOSAppStore,GooglePlay,AmazonApps,SamsungGALAXYAppsandTizenStore
UnityIAPistightlyintegratedwiththeUnityengine,socompatibilityandreliabilitycanbeexpected
Easymanagementofproductcatalog
EasyMobile'scustomeditorfeaturesafriendlyinterfacethathelpsyoueasilyadd,editorremoveproducts
Receiptvalidation
Localreceiptvalidationthatoffersextrasecurity
Introduction
113
In-AppPurchasing:Settings
TousetheIn-AppPurchasingmoduleyoumustfirstenableit.GotoWindow>EasyMobile>Settings,selecttheIn-
AppPurchasingtab,thenclicktheright-handsidetoggletoenableandstartconfiguringthemodule.
EnablingUnityIAP
TheIn-AppPurchasingmodulerequiresUnityIAPservicetobeenabled.Itwillautomaticallycheckfortheservice's
availabilityandpromptyoutoenableitifneeded.BelowisthemodulesettingsinterfacewhenUnityIAPisdisabled.
Settings
114
Intheopenedconfigurationwindow,clickthetoggleattheright-handsideortheEnablebuttontoenableUnityIAP
service.
AdialogwindowwillappearaskingafewquestionsaboutyourgameinordertoensureCOPPAcompliance.
NextclicktheImportbuttontoimporttheUnityIAPpackagetoyourproject.
Settings
116
Afterimporting,thereshouldbeaUnityPurchasingfolderaddedunderAssets/Pluginsfolder.
EnablingUnityIAPservicewillautomaticallyenabletheUnityAnalyticsservice(ifit'snotenabledbefore),thisisa
requirementtouseUnityIAP.GobacktotheServicespanelandmakesurethatbothIn-AppPurchasingand
Analyticsservicesarenowenabled.
Settings
117
AfterenablingUnityIAPservice,thesettingsinterfaceoftheIn-AppPurchasingmodulewillbeupdatedandreadyfor
youtostartconfiguring.
Settings
118
SinceiOS8.0,AppleintroducesanewparentalcontrolfeaturecalledAskToBuy.Basically,AskToBuypurchases
willdeferforparentalapproval.Whenthisoccurs,theIn-AppPurchasingmodulewillnotifyyourappbyraisingthe
PurchaseDeferredevent,whichyoucansubscribetoperformnecessaryactions,e.g.updatingyourUItoreflectthe
deferredstateofthepurchases.
IntheAPPLEASK-TO-BUYsectionyoucanchecktheSimulateAsk-To-Buyoptiontoenablethesimulationofthis
featureinthesandboxenvironment,whichisusefulfortestingduringdevelopment.
Thissettinghasnoeffectonnon-Appleplatforms.
ApplePromotionalPurchases
Appleallowsyoutopromotein-apppurchasesthroughyourapp’sproductpage.Unlikeconventionalin-app
purchases,ApplepromotionalpurchasesinitiatedirectlyfromtheAppStoreoniOSandtvOS.TheAppStorethen
launchesyourapptocompletethetransaction,orpromptstheusertodownloadtheappifitisn’tinstalled.
YoucaninstructtheIn-AppPurchasingmoduletointerceptthesepromotionalpurchasesbycheckingtheIntercept
PromotionalPurchasesoptionintheAPPLEPROMOTIONALPURCHASESsection.Onceapromotionalpurchaseis
intercepted,thePromotionalPurchaseInterceptedeventwillbefired.Youcansubscribetothiseventandinitshandler
performnecessaryactionssuchaspresentingparentalgates,sendinganalyticsevents,etc.beforesendingthe
purchasebacktoApplebycallingtheContinueApplePromotionalPurchasesmethod,whichwillinitiateanyqueued-up
payments.Ifyoudonotenablethisoption,thepromotionalpurchaseswillgothroughimmediatelywiththe
PurchaseCompletedorPurchaseFailedeventbeingfiredaccordingtothepurchaseresult,andthe
PromotionalPurchaseInterceptedwillneverberaised.
TheInterceptPromotionalPurchasessettinghasnoeffectonnon-Appleplatforms.
ItisvitaltocalltheContinueApplePromotionalPurchasesmethodinthehandlerofthe
PromotionalPurchaseInterceptedeventforthepurchasestobeprocessedproperlyafterbeingintercepted.
ReceiptValidation
Thereceiptvalidationfeatureprovidesextrasecurityandhelpspreventpreventfraudulentusersfromaccessing
contenttheyhavenotpurchased.ThisfeatureemploysUnityIAP'slocalreceiptvalidation,whichmeansthevalidation
takesplaceonthetargetdevice,withouttheneedtoconnecttoaremoteserver.
ReceiptvalidationisavailableforApplestoresandGooglePlaystore.PleasefindmoreinformationaboutUnity
IAP'sreceiptvalidationhere.
ObfuscatingEncryptionKeys
Toenablereceiptvalidation,youmustfirstcreateobfuscatedencryptionkeys.Thepurposeofthisobfuscating
processistopreventafraudulentuserfromaccessingtheactualkeys,whichareusedforthevalidationprocess.To
obfuscateyourencryptionkeys,gotoWindow>UnityIAP>ReceiptValidationObfuscator.
Settings
120
IntheopenedIAPObfuscatorwindow,pasteinyourGooglePlaypublickeyandhittheObfuscatesecretsbutton.
AccordingtoUnitydocumentation,thiswillobfuscatebothApple'srootcertificate(bundlewithUnityIAP)andthe
providedGooglePlaypublickeyandcreatetwoC#filesAppleTangleandGooglePlayTangleat
Assets/Plugins/UnityPurchasing/generated.Thesefilesarerequiredforthereceiptvalidationprocess.
ToobtaintheGooglePlaypublickeyforyourapp,logintoyourGooglePlayDeveloperConsole,selectyour
app,thennavigatetotheServices&APIssectionandfindyourkeyunderthesectionlabeledYOUR
LICENSEKEYFORTHISAPPLICATION.
Notethatyoudon'tneedtoprovideaGooglePlaypublickeyifyou'reonlytargetingApplestores.
EnablingReceiptValidation
Aftercreatingtheobfuscatedencryptionkeys,youcannowenablereceiptvalidationforyourgame.OpentheIn-App
Purchasingmodulesettings,thenintheRECEIPTVALIDATIONsectioncheckthecorrespondingoptionsforyour
targetedstores.
ProductManagement
InthePRODUCTSsectionyoucaneasilyadd,editorremoveyourIAPproducts.
AddingaNewProduct
Toaddanewproduct,clicktheAddNewProductbutton.
Anewemptyproductwillbeadded.
Settings
121
Fillintherequiredinformationforyournewproduct:
Name:theproductname,canbeusedwhenmakingpurchases
Type:theproducttype,canbeConsumable,Non-ConsumableorSubscription
Id:theunifiedproductidentifier,youshouldusethisIDwhendeclaringtheproductonyourtargetedstores;
otherwise,ifyouneedtohaveadifferentIDforthisproductonacertainstore,addittotheStore-SpecificIds
array(seebelow)
ClickMoreifyouneedtoenterstore-specificIDsorfillinoptionalinformationforyourproduct.
Price:theproductpricestringfordisplayingpurpose
Description:theproductdescriptionfordisplayingpurpose
Store-SpecificIds:ifyouneedtouseadifferentproductID(thantheunifiedIDprovidedabove)onacertain
store,youcanaddithere
AddingStore-SpecificID
ToaddanewIDtotheStore-SpecificIdsarray,increasethearraysizebyadjustingthenumberintheright-
handsidebox.Anewrecordwillbeaddedwhereyoucanselectthetargetedstoreandenterthecorresponding
productIDforthatstore.
Belowisasampleproductwithalltheinformationenteredincludingthetwostore-specificIDs.
Settings
122
RemovingaProduct
Toremoveaproduct,simplyclickthe[-]buttonattherighthandside.
ArrangingProductList
Youcanusethetwoarrow-upandarrow-downbuttonstomoveaproductupwardordownwardwithintheproductlist.
SetupProductsforTargetedStores
BesidecreatingtheproductlistinUnity,youalsoneedtodeclaresimilarproductsforyourtargetedstores,e.g.if
you'retargetingiOSAppStoreyouneedtocreatetheproductsiniTunesConnect.Ifyou'renotfamiliarwiththe
process,youcanfollowUnity'sinstructionsonconfiguringIAPforvariousstores,whichalsoincludeusefulinformation
aboutIAPtesting.
OnGooglePlaystore,bothconsumableandnon-consumableproductsaredefinedasManagedproduct.Ifa
productissettoConsumabletypeinUnity,themodulewillautomaticallyhandletheconsumptionoftheproduct
onceitisboughtandmakeitavailableforpurchaseagain.
ConstantsGeneration
ConstantsgenerationisafeatureoftheIn-AppPurchasingmodule.Itreadsalltheproductnamesandgeneratesa
staticclassnamedEM_IAPConstantsthatcontainstheconstantsofthesenames.Later,youcanusetheseconstants
whenmakingpurchasesinscriptinsteadoftypingtheproductnamesdirectly,thushelppreventruntimeerrorsdueto
typosandthelikes.
Settings
123
Togeneratetheconstantsclass(youshoulddothisafterfinishingwithproductediting),clicktheGenerateConstants
ClassbuttonwithintheCONSTANTSCLASSGENERATIONsection.
Whentheprocesscompletes,afilenamedEM_IAPConstantswillbecreatedatAssets/EasyMobile/Generated.
Settings
124
In-AppPurchasing:Scripting
ThissectionprovidesaguidetoworkwiththeIn-AppPurchasingmodulescriptingAPI.
YoucanaccesstheIn-AppPurchasingmoduleAPIviatheInAppPurchasingclassundertheEasyMobile
namespace.
Initialization
ThemodulewillautomaticallyinitializeUnityIAPatstartwithoutyouhavingtodoanything.AllfurtherAPIcallscan
onlybemadeaftertheinitializationhasfinished.YoucancheckifUnityIAPhasbeeninitialized:
//CheckifUnityIAPhasbeeninitialized
boolisInitialized=InAppPurchasing.IsInitialized();
ObtainingProductList
Youcanobtainthearrayofallproductscreatedinthemodulesettingsinterface:
//GetthearrayofallproductscreatedintheIn-AppPurchasingmodulesettings
//IAPProductistheclassrepresentingaproductasdeclaredinthemodulesettings
IAPProduct[]products=InAppPurchasing.GetAllIAPProducts();
//Printallproductnames
foreach(IAPProductprodinproducts)
{
Debug.Log("Productname:"+prod.Name);
}
MakingPurchases
Youcanpurchaseaproductusingitsname.
ItisstronglyrecommendedthatyouusetheconstantsofproductnamesinthegeneratedEM_IAPConstants
class(seeIAPConstantsGenerationsection)insteadoftypingthenamesdirectlyinordertopreventruntime
errorsduetotyposandthelikes.
//Purchaseaproductusingitsname
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct"
InAppPurchasing.Purchase(EM_IAPConstants.Sample_Product);
APurchaseCompletedeventwillbefiredifthepurchaseissuccessful,otherwise,aPurchaseFailedeventwillbefired
instead.Youcanlistentotheseeventsandtakeappropriateactions,e.g.granttheuserdigitalgoodsifthepurchase
hassucceeded.
//SubscribetoIAPpurchaseevents
voidOnEnable()
{
InAppPurchasing.PurchaseCompleted+=PurchaseCompletedHandler;
InAppPurchasing.PurchaseFailed+=PurchaseFailedHandler;
}
//Unsubscribewhenthegameobjectisdisabled
Scripting
125
voidOnDisable()
{
InAppPurchasing.PurchaseCompleted-=PurchaseCompletedHandler;
InAppPurchasing.PurchaseFailed-=PurchaseFailedHandler;
}
//Purchasethesampleproduct
publicvoidPurchaseSampleProduct()
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct"
InAppPurchasing.Purchase(EM_IAPConstants.Sample_Product);
}
//Successfulpurchasehandler
voidPurchaseCompletedHandler(IAPProductproduct)
{
//Compareproductnametothegeneratednameconstantstodeterminewhichproductwasbought
switch(product.Name)
{
caseEM_IAPConstants.Sample_Product:
Debug.Log("Sample_Productwaspurchased.Theusershouldbegranteditnow.");
break;
caseEM_IAPConstants.Another_Sample_Product:
Debug.Log("Another_Sample_Productwaspurchased.Theusershouldbegranteditnow.");
break;
//Moreproductshere...
}
}
//Failedpurchasehandler
voidPurchaseFailedHandler(IAPProductproduct)
{
Debug.Log("Thepurchaseofproduct"+product.Name+"hasfailed.");
}
CheckingOwnership
Youcancheckifaproductisownedbyspecifyingitsname.Aproductisconsidered"owned"ifitsreceiptexistsand
passesthereceiptvalidation(ifenabled).
//Checkiftheproductisownedbytheuser
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct"
boolisOwned=InAppPurchasing.IsProductOwned(EM_IAPConstants.Sample_Product);
Consumableproducts'receiptsarenotpersistedbetweenapprestarts,thereforethismethodonlyreturnstrue
forthoseproductsinthesessionthey'repurchased.
Inthecaseofsubscriptionproducts,thismethodsimplychecksifaproducthasbeenbought(subscribed)
beforeandhasareceipt.Itdoesn'tcheckifthesubscriptionisexpiredornot.
RestoringPurchases
Non-consumableandsubscriptionproductsarerestorable.Appstoresmaintainapermanentrecordofeachuser's
non-consumableandsubscriptionproducts,sothatheorshecanbegrantedtheseproductsagainwhenreinstalling
yourgame.
ApplenormallyrequiresaRestorePurchasesbuttontoexistinyourgame,sothattheuserscanexplicitlyinitiatethe
purchaserestorationprocess.Onotherplatforms,e.g.GooglePlay,therestorationisdoneautomaticallyduringthe
firstinitializationafterreinstallation.
Scripting
126
Duringtherestorationprocess,aPurchaseCompletedeventwillbefiredforeachownedproduct,asiftheuser
hasjustpurchasedthemagain.Thereforeyoucanreusethesamehandlertogranttheusertheirproductsas
normalpurchases.
OniOS,youcaninitiateapurchaserestorationasbelow.
//Restorepurchases.ThismethodonlyhaseffectoniOS.
InAppPurchasing.RestorePurchases();
ARestoreCompletedeventwillbefirediftherestorationissuccessful,otherwise,aRestoreFailedeventwillbefired
instead.Notethattheseeventsonlymeanthesuccessorfailureoftherestorationitself,whilethe
PurchaseCompletedeventwillbefiredforeachrestoredproduct,asnotedearlier.Youcanlistentotheseeventsand
takeappropriateactions,e.g.informtheusertherestorationresult.
TheRestoreCompletedandRestoreFailedeventsareonlyraisedoniOS.
//SubscribetoIAPrestoreevents,theseeventsarefiredoniOSonly.
voidOnEnable()
{
InAppPurchasing.RestoreCompleted+=RestoreCompletedHandler;
InAppPurchasing.RestoreFailed+=RestoreFailedHandler;
}
//Successfulrestorationhandler
voidRestoreCompletedHandler()
{
Debug.Log("Allpurchaseshavebeenrestoredsuccessfully.");
}
//Failedrestorationhandler
voidRestoreFailedHandler()
{
Debug.Log("Thepurchaserestorationhasfailed.");
}
//Unsubscribe
voidOnDisable()
{
InAppPurchasing.RestoreCompleted-=RestoreCompletedHandler;
InAppPurchasing.RestoreFailed-=RestoreFailedHandler;
}
AppleAsk-To-Buy
OniOS8.0ornewer,AskToBuypurchaseswilldeferforparentalapproval.Youcansubscribetothe
PurchaseDeferredeventtoacknowledgewhenthisoccurs,andperformrelevantactionssuchasupdatingUItoreflect
thedeferredstateofthepurchase.Whenthepurchaseisapprovedorrejected,thenormalPurchaseCompletedor
PurchaseFailedeventswillbefired.
//SubscribetoAskToBuypurchasesdeferredevent,thiseventisfiredoniOSonly.
voidOnEnable()
{
InAppPurchasing.PurchaseDeferred+=PurchaseDeferredHandler;
}
//Unsubscribe.
voidOnDisable()
{
InAppPurchasing.PurchaseDeferred-=PurchaseDeferredHandler;
}
//ThishandlerisinvokedonceanAskToBuypurchaseisdeferredforparentalapproval.
voidPurchaseDeferredHandler(IAPProductproduct)
Scripting
127
{
Debug.Log("Purchaseofproduct"+product.Name+"hasbeendeferred.");
//Performnecessaryactions,e.g.updatingUItoinformuserthat
//thepurchasehasbeendeferred...
}
YoucansimulateAskToBuyfeatureinthesandboxappstorefortestingduringdevelopment,see
Settings/AppleAsk-To-Buy.
ApplePromotionalPurchases
IfyouhaveenabledtheInterceptPromotionalPurchasesoptionintheIn-AppPurchasingmodulesettings,the
PromotionalPurchaseInterceptedeventwillbefiredeverytimeapromotionalpurchaseisintercepted.Inthehandlerof
thiseventyoucanperformrelevantactionssuchaspresentingparentalgates,sendinganalyticsevents,etc.After
thatyoumustcalltheContinueApplePromotionalPurchasesmethodtocontinuethenormalprocessingofthe
purchase.Thiswillinitiateanyqueued-uppayments.Oncethetransactionisdone,thenormalPurchaseCompletedor
PurchaseFailedeventwillbefiredaccordingtothepurchaseresult.
IftheInterceptPromotionalPurchasesoptionisdisabled,thePromotionalPurchaseInterceptedeventwillnever
occur.
//Subscribetopromotionalpurchaseinterceptedevent,thiseventisfiredoniOSonly.
voidOnEnable()
{
InAppPurchasing.PromotionalPurchaseIntercepted+=PromotionalPurchaseInterceptedHandler;
}
//Unsubscribe.
voidOnDisable()
{
InAppPurchasing.PromotionalPurchaseIntercepted-=PromotionalPurchaseInterceptedHandler;
}
//Thishandlerisinvokedonceapromotionalpurchaseisintercepted.
voidPromotionalPurchaseInterceptedHandler(IAPProductproduct)
{
Debug.Log("Promotionalpurchaseofproduct"+product.Name+"hasbeenintercepted.");
//Hereyoucanperformnecessaryactions,e.g.presentingparentalgates,
//sendinganalyticsevents,etc.
//Finally,youmustcalltheContinueApplePromotionalPurchasesmethod
//tocontinuethenormalprocessingofthepurchase!
InAppPurchasing.ContinueApplePromotionalPurchases();
}
YoucanalsousetheSetAppleStorePromotionVisibilityandSetAppleStorePromotionOrdermethodstorespectively
setthevisibilityofapromotionalproductandtheorderofvisiblepromotionalproductsontheAppleappstoreofthe
currentdevice.
Scripting
128
In-AppPurchasing:AdvancedScripting
Thissectiondescribesthemethodstoaccomplishtasksbeyondthebasiconessuchasmakingorrestoring
purchases.Thesetasksincluderetrievingproductlocalizeddata,readingproductreceipts,refreshingreceipts,etc.
MostofthemethodsdescribedinthissectionareonlyavailableonceEasyMobile'sIAPmoduleandUnityIAP
serviceareenabled,whichisindicatedbythedefinitionofthesymbolEM_UIAP.Therefore,youshouldalways
wraptheuseofthesemethodsinsideacheckfortheexistingofthissymbol.
Also,thetypesexposedinthesemethodsareonlyavailablewhentheUnityIAPpackageisimported,andyou
shouldincludetheUnityEngine.PurchasingandUnityEngine.Purchasing.Securitynamespacesatthetopof
yourscriptforthesetypestoberecognized.
GettingUnityIAP'sProductObject
Thein-appproductsarerepresentedinUnityIAPbytheProductclass,whichisdifferentfromEasyMobile's
IAPProductclass,whosemainpurposeisforsettingsanddisplaying.ThisProductclassistheentrypointtoaccess
product-relateddataincludingitsmetadataandreceipt,whichispopulatedautomaticallybyUnityIAP.Toobtainthe
Productobjectofanin-appproduct,calltheGetProductmethodwiththeproductname.
#ifEM_UIAP
usingUnityEngine.Purchasing;
#endif
//ObtaintheProductobjectofthesampleproductandprintitsdata
publicvoidGetSampleProduct()
{
#ifEM_UIAP
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct"
ProductsampleProduct=InAppPurchasing.GetProduct(EM_IAPConstants.Sample_Product);
if(sampleProduct!=null)
{
Debug.Log("AvailableToPurchase:"+sampleProduct.availableToPurchase.ToString());
if(sampleProduct.hasReceipt)
{
Debug.Log("Receipt:"+sampleProduct.receipt);
}
}
#endif
}
GettingProductLocalizedData
Youcangetaproduct'smetadataretrievedfromtargetedappstores,e.g.localizedtitle,descriptionandprice.This
informationisparticularlyusefulwhenbuildingastorefrontinyourgamefordisplayingthein-appproducts.Togetthe
localizeddataofaproduct,calltheGetProductLocalizedDataandspecifytheproductname.Thefollowingexample
iteratesthroughtheproductlistandretrievethelocalizeddataofeachitem.
#ifEM_UIAP
usingUnityEngine.Purchasing;
#endif
AdvancedScripting
129
//Iteratethroughtheproductlistandgetthelocalizeddataretrievedfromthetargetedappstore.
//NotethecheckfortheEM_UIAPsymbol.
voidPrintProductsMetadata()
{
#ifEM_UIAP
//GetallproductscreatedintheIn-AppPurchasingmodulesettings
IAPProduct[]products=EM_Settings.InAppPurchasing.Products;
foreach(IAPProductprodinproducts)
{
//Getproductlocalizeddata.
ProductMetadatadata=InAppPurchasing.GetProductLocalizedData(prod.Name);
if(data!=null)
{
Debug.Log("Localizedtitle:"+data.localizedTitle);
Debug.Log("Localizeddescription:"+data.localizedDescription);
Debug.Log("Localizedpricestring:"+data.localizedPriceString);
}
}
#endif
}
GettingSubscriptionInfo
Youcangetasubscriptionproduct'sinformationsuchasexpiredateusingtheGetSubscriptionInfomethod.Internally,
thismethodusesUnityIAP'sSubscriptionManagerclasstoretrievethesubscriptiondata.Thefollowingexample
iteratesthroughtheproductlistandprintstheinformationofeachsubscriptionproduct.
#ifEM_UIAP
usingUnityEngine.Purchasing;
#endif
//Iteratesthroughallavailableproductsandprintsthedataofsubscriptions.
publicvoidPrintSubscriptionInfo()
{
#ifEM_UIAP
//GetallproductscreatedintheIn-AppPurchasingmodulesettings.
IAPProduct[]products=EM_Settings.InAppPurchasing.Products;
foreach(IAPProductpinproducts)
{
//Ifthisisasubscriptionproduct.
if(p.Type==IAPProductType.Subscription)
{
//Getthesubscriptioninformationofthecurrentproduct,
//notethatthismethodtakestheproductnameasinput.
SubscriptionInfoinfo=InAppPurchasing.GetSubscriptionInfo(p.Name);
if(info==null)
{
Debug.Log("Thesubscriptioninformationofthisproductcouldnotberetrieved.");
continue;
}
//Printssubscriptioninfo.
Debug.Log("ProductID:"+info.getProductId());
Debug.Log("PurchaseDate:"+info.getPurchaseDate());
Debug.Log("IsSubscribed:"+info.isSubscribed());
Debug.Log("IsExpired:"+info.isExpired());
Debug.Log("IsCancelled:"+info.isCancelled());
Debug.Log("IsFreeTrial:"+info.isFreeTrial());
Debug.Log("IsAutoRenewing:"+info.isAutoRenewing());
Debug.Log("RemainingTime:"+info.getRemainingTime().ToString());
Debug.Log("IsIntroductoryPricePeriod:"+info.isIntroductoryPricePeriod());
Debug.Log("IntroductoryPricePeriod:"+info.getIntroductoryPricePeriod().ToString());
Debug.Log("IntroductoryPricePeriodCycles:"+info.getIntroductoryPricePeriodCycles());
Debug.Log("IntroductoryPrice:"+info.getIntroductoryPrice());
AdvancedScripting
130
Debug.Log("ExpireDate:"+info.getExpireDate());
}
}
#endif
}
WorkingwithReceipts
Thissectionsdescribesmethodstoworkwithreceipts.Currently,UnityIAPonlysupportsparsingreceiptsfromApple
storesandGooglePlaystore.
Notethatforthereceiptreadingmethodstowork,youneedtoenablereceiptvalidationfeature(seethe
ReceiptValidationsection).
AppleAppReceipt
OniOS,youcangettheparsedAppleAppReceiptforyourappusingtheGetAppleAppReceiptmethod.
#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
//ReadtheAppReceiptoniOS.Receiptvalidationisrequired.
voidReadAppleAppReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
AppleReceiptappReceipt=InAppPurchasing.GetAppleAppReceipt();
//Printthereceiptcontent.
if(appReceipt!=null)
{
Debug.Log("AppVersion:"+appReceipt.appVersion);
Debug.Log("BundleID:"+appReceipt.bundleID);
Debug.Log("Numberofpurchasedproducts:"+appReceipt.inAppPurchaseReceipts.Length);
}
}
#endif
}
AppleInAppPurchaseReceipt
OniOS,youcangettheparsedAppleInAppPurchasereceiptforaparticularproduct,usingtheGetAppleIAPReceipt
methodwiththenameoftheproduct.
#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
//ReadtheInAppPurchasereceiptofthesampleproductoniOS.
//Receiptvalidationisrequired.
voidReadAppleInAppPurchaseReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct".
AdvancedScripting
131
AppleInAppPurchaseReceiptreceipt=InAppPurchasing.GetAppleIAPReceipt(EM_IAPConstants.Sample_Product);
//Printthereceiptcontent.
if(receipt!=null)
{
Debug.Log("ProductID:"+receipt.productID);
Debug.Log("OriginalPurchaseDate:"+receipt.originalPurchaseDate.ToShortDateString());
Debug.Log("OriginalTransactionID:"+receipt.originalTransactionIdentifier);
Debug.Log("PurchaseDate:"+receipt.purchaseDate.ToShortDateString());
Debug.Log("TransactionID:"+receipt.transactionID);
Debug.Log("Quantity:"+receipt.quantity);
Debug.Log("CancellationDate:"+receipt.cancellationDate.ToShortDateString());
Debug.Log("SubscriptionExpirationDate:"+receipt.subscriptionExpirationDate.ToShortDateString())
;
}
}
#endif
}
GooglePlayReceipt
OnAndroid,youcangettheparseGooglePlayreceiptforaparticularproduct,usingtheGetGooglePlayReceipt
methodwiththenameoftheproduct.
#ifEM_UIAP
usingUnityEngine.Purchasing;
usingUnityEngine.Purchasing.Security;
#endif
//ReadtheGooglePlayreceiptofthesampleproductonAndroid.
//Receiptvalidationisrequired.
voidReadGooglePlayReceipt()
{
#ifEM_UIAP
if(Application.platform==RuntimePlatform.Android)
{
//EM_IAPConstants.Sample_Productisthegeneratednameconstantofaproductnamed"SampleProduct".
GooglePlayReceiptreceipt=InAppPurchasing.GetGooglePlayReceipt(EM_IAPConstants.Sample_Product);
if(receipt!=null)
{
Debug.Log("PackageName:"+receipt.packageName);
Debug.Log("ProductID:"+receipt.productID);
Debug.Log("PurchaseDate:"+receipt.purchaseDate.ToShortDateString());
Debug.Log("PurchaseState:"+receipt.purchaseState.ToString());
Debug.Log("TransactionID:"+receipt.transactionID);
Debug.Log("PurchaseToken:"+receipt.purchaseToken);
}
}
#endif
}
RefreshingAppleAppReceipt
AppleprovidesamechanismtofetchanewAppReceiptfromtheirservers,typicallyusedwhennoreceiptiscurrently
cachedinlocalstorageSKReceiptRefreshRequest.YoucanrefreshtheAppReceiptoniOSusingthe
RefreshAppleAppReceiptmethod.Notethatthiswillprompttheuserfortheirpassword.
//FetchanewAppleAppReceiptoniOS.Thiswillprompttheuserfortheirpassword.
voidRefreshAppleAppReceipt()
{
if(Application.platform==RuntimePlatform.IPhonePlayer)
{
InAppPurchasing.RefreshAppleAppReceipt(SuccessCallback,ErrorCallback);
AdvancedScripting
132
}
}
voidSuccessCallback(stringreceipt)
{
Debug.Log("AppReceiptrefreshedsuccessfully.Newreceipt:"+receipt);
}
voidErrorCallback()
{
Debug.Log("AppReceiptrefreshingfailed.");
}
AdvancedScripting
133
In-AppPurchasing:PlayMakerActions
ThePlayMakeractionsoftheIn-AppPurchasingmodulearegroupinthecategoryEasyMobile-In-AppPurchasing
inthePlayMaker'sActionBrowser.
PleaserefertotheInAppPurchasingDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowtheseactionscanbeused.
PlayMakerActions
134
PlayMakerActions
135
NativeAPIs|NativeUI:Introduction
TheNativeUImoduleallowsyoutoaccessnativemobileUIelementssuchasalertsanddialogs.Thismodule
requiresnoconfigurationandalltaskscanbedonefromscript.
Alerts
Alertsareusefulinprovidingtheuserscontextualinformation,askingforconfirmationorpromptingthemtomakea
selectionoutofseveraloptions.Analertcanhaveone,twoorthreebuttonswithit.
BelowarethethreetypesofalertoniOS.
AndbelowarethethreetypesofalertonAndroid.
Toasts
Toastsareshortmessagesdisplayedatthebottomofthescreen.Theyautomaticallydisappearafteratimeout.
ToastsareavailableonAndroidonly.Belowisasampletoastmessage.
NativeUI
137
NativeUI
138
NativeAPIs|NativeUI:Scripting
ThissectionprovidesaguidetoworkwithNativeUIscriptingAPI.
YoucanaccesstheNativeUImoduleAPIviatheNativeUIclassundertheEasyMobilenamespace.
Alerts
AlertsareavailableonbothiOSandAndroidplatformandcanhaveuptothreebuttons.
Simple(one-button)alertsareusefulingivingtheusercontextualinformation.Toshowasimplealertwiththedefault
OKbutton,youonlyneedtoprovideatitleandamessageforthealert:
//ShowasimplealertwithOKbutton
NativeUI.AlertPopupalert=NativeUI.Alert("SampleAlert","ThisisasamplealertwithanOKbutton.");
Youcanalsoshowaone-buttonalertwithacustombuttonlabel.
//Showanalertwithabuttonlabeledas"Gotit"
NativeUI.AlertPopupalert=NativeUI.Alert(
"SampleAlert",
"Thisisasamplealertwithacustombutton.",
"Gotit"
);
Two-buttonalertscanbeusefulwhenneedingtoaskforuserconfirmation.Toshowatwo-buttonalert,youneedto
specifythelabelsofthesetwobuttons.
//Showatwo-buttonalertwiththebuttonslabeledas"Button1"&"Button2"
NativeUI.AlertPopupalert=NativeUI.ShowTwoButtonAlert(
"SampleAlert",
"Thisisatwo-buttonalert.",
"Button1",
"Button2"
);
Three-buttonalertscanbeusedtopresenttheuserwithseveraloptions,atypicalusageofitistoimplementtheRate
Uspopup.Toshowathree-buttonalert,youneedtospecifythelabelsofthethreebuttons.
//Showathree-buttonalertwiththebuttonslabeledas"Button1","Button2"&"Button3"
NativeUI.AlertPopupalert=NativeUI.ShowThreeButtonAlert(
"SampleAlert",
"Thisisathree-buttonalert.",
"Button1",
"Button2",
"Button3"
);
Wheneveranalertisshown,aNativeUI.AlertPopupobjectisreturned,whenthealertisclosed,thisobjectwillfirean
OnCompleteeventandthendestroyitself.Theargumentofthiseventistheindexoftheclickedbutton.Youshould
listentothiseventandtakeappropriateactiondependingonthebuttonselected.
//ShowathreebuttonalertandhandleitsOnCompleteevent
NativeUI.AlertPopupalert=NativeUI.ShowThreeButtonAlert(
"SampleAlert",
"Thisisathree-buttonalert.",
Scripting
139
"Button1",
"Button2",
"Button3"
);
//Subscribetotheevent
if(alert!=null)
{
alert.OnComplete+=OnAlertCompleteHandler;
}
//Theeventhandler
voidOnAlertCompleteHandler(intbuttonIndex)
{
switch(buttonIndex)
{
case0:
//Button1wasclicked
break;
case1:
//Button2wasclicked
break;
case2:
//Button3wasclicked
break;
default:
break;
}
}
Onlyonealertpopupcanbeshownatatime.Anycalltoshowanalertwhileanotheroneisbeingdisplayedwillbe
ignored.YoucancheckifanalertisbeingshownusingtheIsShowingAlertmethod.
//Checkifanalertisbeingdisplayed.
boolisShowingAlert=NativeUI.IsShowingAlert();
Toasts
Toastisashortmessagedisplayedatthebottomofthescreenandautomaticallydisappearsafteratimeout.Toasts
areavailableonlyonAndroidplatform.Toshowatoastmessage:
//Showasampletoastmessage
NativeUI.ShowToast("ThisisasampleAndroidtoast");
Scripting
140
NativeAPIs:PlayMakerActions
ThePlayMakeractionsoftheNativeAPIsmodulearegroupinthecategoryEasyMobile-NativeAPIsinthe
PlayMaker'sActionBrowser.
PleaserefertotheNativeUIDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/ModulesforanexampleonhowNativeUIactionscanbeused.
PlayMakerActions
141
PlayMakerActions
142
Notifications:Introduction
TheNotificationsmodulehelpsyouquicklyimplementnotificationsfeatureinyourapp.Here'resomehighlightsofthis
module:
Remote(push)notification
ThemoduleiscurrentlycompatiblewithOneSignalandFirebaseCloudMessaging(FCM).Botharefreeand
popularcross-platformpushnotificationdeliveryservices.
Localnotification
One-timeandrepeatnotifications.
Fully-customizablenotifications:sounds,icons,badge,light,vibration,lock-screenvisibility,etc.(feature
availabilityvariesbetweeniOSandAndroid)
Supportsnotificationcustomactionbuttons.
NotificationCategory
UnifiesAndroidnotificationcategory(channel)andiOSnotificationcategory,makesiteasytocustomizeand
organizenotificationsinyourapp.
FullysupportsnotificationchannelsintroducedandrequiredsinceAndroidO,whilemaintainingbackward-
compatibilitywitholderAndroidversions,wherenotificationchannelsdon'texist.
FullysupportsnotificationchannelgroupsintroducedsinceAndroidO.
Friendlyeditor
Makesiteasytosetupremotenotificationservice,managecategories,addingresources,etc.
EasyMobile'snotificationAPIworksoniOS10ornewer(morethan95%ofiOSdevices)andAndroidAPI14or
newer(morethan99%ofAndroiddevices).
UnderstandingNotifications
Anotificationisbasicallyamessagethatisdisplayedbythesystemoutsideofyourapp'sUItoprovidetheuserwith
sometimelyinformationaboutyourapp.Theusercanopenanotificationtobringyourapptoforegroundandtake
furtheractionsiftheywish.
Ifyourappisinforegroundatthemomentthenotificationisdelivered,thenthenotificationwon'tbeposted(in
otherwords,itwillbesilenced).Instead,itsdatawillbesenttotheappdirectlyinformofanevent.
Notificationsappeartousersindifferentlocationsandformats,plustheirappearancevariesslightlybetweeniOSand
Android,andamongdifferentversionsoftheseplatforms.Dependingonthecurrentstateofyourdevice,notifications
canappearinthestatusbar(Android),thenotificationcenter(iOS),orthelock-screen.Belowarethetypical
anatomiesofiOSandAndroidnotifications.
iOSNotificationAnatomy
Here'satypicaliOSnotificationwithbasicdetails.
Introduction
143
1. Smallicon:providedbythesystem
2. Appname:providedbythesystem
3. Title:thenotificationtitleprovidedbyyou
4. Subtitle:theoptionalnotificationsubtitleprovidedbyyou
5. Body:themainnotificationmessageprovidedbyyou
AndroidNotificationAnatomy
Here'satypicalAndroidnotificationwithbasicdetails.
1. Smallicon:required,providedbyyou(ifnovalidiconspecified,EasyMobileautomaticallyusesthefallbackicon,
whichisabell)
2. Appname:providedbythesystem
3. Timestamp:providedbythesystem
4. Largeicon:optional,providedbyyou(thisisusuallyusedonlyforcontactphotos;donotuseitforyourappicon)
5. Title:thenotificationtitleprovidedbyyou
6. Body:themainnotificationmessageprovidedbyyou
YoucanlearnmoreaboutiOSnotificationshereandAndroidnotificationshere.
NotificationCustomActions
Besidebasiccontent,youcanoptionallyattachadditionalactionbuttonstothenotificationforspecifictasks.Using
uniqueactionIDs,yourappcanacknowledgetheactionselectedbytheuserandresponseaccordingly,e.g.opena
particularUIfortheusertoperformfurtherinteraction.
AnotificationoniOScanhaveupto4actionbuttons.However,thenumberofactionsactuallydisplayeddependson
howandwherethenotificationisdisplayed.Forexample,bannersdisplaynomorethantwoactions.Here'san
exampleofaniOSnotificationwith2actionbuttons,ActionAandActionB.
Introduction
144
AnotificationonAndroidcanhaveupto3actionbuttons.Here'satypicalAndroidnotificationwith2actionbuttons,
REPLYandARCHIVE.
OnAndroid,EasyMobilesupportsnotificationactionsonAPI23ornewer.
LocalVs.RemoteNotifications
Localnotificationsarenotificationsscheduledbyyourapplocally.Yourappconfiguresthenotificationcontent,
specifiesatriggercondition,andpassesthesedetailstothesystem,whichthenhandlesthedeliveryofthenotification
whenthetriggerconditionismet.
One-timevs.Repeatlocalnotifications
Aonetimenotificationisdeliveredonlyonce.Arepeatnotificationisdeliveredonceeverytimetherepeat
intervalpassed.Bothnotificationtypessurvivedevicereboots.Ifthedeliverytimeispastatthemomentthe
devicehasfinishedreboot,thenotificationwillbedeliveredimmediately.
Remotenotificationsaresent(pushed)touserdevicesfromaremoteserver(beityourownserverora3rdparty
serverlikeOneSignal's)viatheApplePushNotificationservice(APNs)oniOS,ortheGoogleCloudMessaging
service(GCM)onAndroid.
NotificationCategories
EasyMobile'scross-platformnotificationcategoryunifiesAndroidnotificationchannel/categoryandiOSnotification
category,providingasimpleandconvenientwaytocustomizeandorganizenotificationsinyourapp.Youcanuse
categorytocontrolmultipleaspectsofnotificationsincludingimportance,light,vibration,soundandactionbuttons
(configurationvariesbetweenplatforms,e.g.importanceandlightareAndroid-only).Allnotificationspostedtothe
samecategorysharethesamecustomization.Everytimeyouscheduleanotification,simplysetthecategoryit
belongstoandallthesettingsofthatcategorywillbeappliedtothenotificationautomatically.Usingcategoriesis
thereforeamoreintuitiveandefficientwaytocustomizeandorganizenotifications.Youcanhavemultiplecategories
inyourapp,eachcontrolsadifferenttypeofnotifications.Forexample,youcanhaveacategorydedicatedforgame
eventnotifications,andanothercategoryforuser(chat)messagenotifications.
NotificationCategoriesoniOS
Introduction
145
OniOS,cross-platformcategoriesautomaticallytranslatetonativenotificationcategories,takingonlytheinformation
oncustomactionsbecauseiOSnotificationcategoriesaremostlyresponsibleforconfiguringcustomactions.About
theothersettingsofthecross-platformcategory,thoseareapplicableoniOS(e.g.sound)willautomaticallybe
appliedwhenanotificationofthiscategoryisscheduled.
iOSnotificationcategoriesarenotvisibletousers.Youcanlearnmoreaboutthemhere.
NotificationCategoriesonAndroid
OnAndroid8.0Oreo(APIlevel26),anewfeatureisintroducedwhichisnotificationchannels/categories.Startingin
thisversion,allnotificationsmustbeassignedtoachanneloritwillnotappear.Notificationchannelsoffertheability
togroupnotifications,andallowuserstocontrolthevisualandauditoryoptionsofeachchannelfromtheAndroid
systemsettings.
TheAndroiduserinterfacereferstochannelsas"categories".
OnAndroid8.0andnewer,EasyMobile'scross-platformnotificationcategoriesautomaticallytranslatetonative
notificationchannels.MostsettingsareapplicabletoAndroidchannels,exceptthecustomactions(Android
notificationchannelsarenotresponsibleforcustomactions).Whenalocalnotificationisscheduled,itwillbeassigned
tothenativenotificationchannel,andthecustomactions(ifany)foundinitscross-platformcategoryareautomatically
addedwhenconstructingthenotification.
OnAndroid7.1(APIlevel25)andlower,there'renonotificationchannels.Whenalocalnotificationisscheduled,
individualsettingswillbereadfromitscross-platformcategoryandappliedautomaticallywhenconstructingthe
notification.
Introduction
146
YoucanlearnmoreaboutAndroidnotificationchannels/categorieshere.
DefaultNotificationCategory
FornotificationstoworkconsistentlyacrossiOSandAndroidplatform,yourapprequiresatleastonenotification
category.EasyMobilehasabuilt-indefaultcategory.Ifyou'reschedulingalocalnotificationandnotspecifyingany
category,thenthedefaultonewillbeused.Thisdefaultcategorycanbecustomizedfromthemodulesettings,butit
cannotberemoved.
NotificationCategoryGroups
Youcanorganizeyournotificationcategoriesintocategorygroups.OnAndroidcategorygroupsdirectlytranslateto
nativechannelgroups.Belowisanexampleofhownotificationchannelsareorganizedintogroupsinthesystem
settingsonAndroid.
Notificationcategorygroupsdon'thaveanyeffectoniOS.
GDPRCompliance
WerecommendyoutoreadthePrivacychapterfirsttogainacomprehensiveunderstandingofthetoolsand
resourcesofferedbyEasyMobiletohelpyourappgetcompliantwithGDPR,includingtheconsentdialogand
theconsentmanagementsystem.
RemotenotificationisoneofthoseservicesaffectedbytheGDPR,becausemostproviders(includingOneSignaland
Firebase)needtocollectadevice'suniqueidentifierofonekindoranothertodelivermessagestothatdevice.
Thereforeitmaybeadvisabletogettheuserconsent(explicitopt-in)beforeinitializingtheservice,especiallyinEEA
Introduction
147
region.EasyMobileprovidesanative,multi-purposedialogwhichyoucanusetocollectuserconsentfortheremote
notificationserviceaswellasotherrelevantservicesinyourapp.
Allowingtheusertoprovideandmanageconsentforallservicesviaasingleinterface(dialog)isadvisablein
termsofuserexperience,becausetheusermayfinditirritatingbeingpresentedmultipledialogsasking
consentforvariousthings.
Oncethepushnotificationconsentiscollected,youcancommunicateittotheNotificationsmoduleusingthe
GrantDataPrivacyConsentorRevokeDataPrivacyConsentmethods(seeScripting/WorkingwithConsent).The
consentwillthenbeautomaticallyforwardedtotheselectedremotenotificationserviceduringitsinitialization.
Thereforeitisimportanttocollecttheconsentbeforeinitializingtheremotenotificationservice.Practically,thismeans
collectingconsentbeforeinitializingEasyMobileruntime,whichwillautomaticallyinitializetheremotenotification
service(providedthattheAutoInitializationfeatureoftheNotificationsmoduleisenabled).Thenextsectionsdetail
howconsentisappliedtoeachremotenotificationservicewhenitisusedinyourapp.
Currentlythere'sonlymodule-levelconsentavailabletotheNotificationsmodule.Becauseconsentisonly
applicabletoremotenotificationservice,andonlyoneservicecanbeselectedatatime,providingvendor-level
consentwouldbeirrelevant.Instead,themodule-levelconsent(ifany)willbeusedforwhicheverservicebeing
selected.
OneSignal
IfOneSignalisselectedastheremotenotificationproviderinyourapp,theNotificationsmodulewillperform
appropriateactionsaccordingtothespecifiedconsentbeforeinitializingOneSignalservice.Thetablebelow
summarizestheactionstakenineachcase.
Consent
Status ActionsTaken
Granted
CallingOneSignal.SetRequiresUserPrivacyConsent(true);and
OneSignal.UserDidProvideConsent(true);toinformOneSignalthattheconsentwasgrantedand
theninitializeit.
Revoked
CallingOneSignal.SetRequiresUserPrivacyConsent(true);and
OneSignal.UserDidProvideConsent(false);toinformOneSignalthattheconsentwasnot
granted/revoked.
Unknown Donothingregardingconsent.TheinitializationofOneSignaliscarriedoutasnormal(the"pre-
GDPR"behavior).
Reference:https://documentation.onesignal.com/docs/unity-sdk#section--setrequiresuserprivacyconsent-
FirebaseCloudMessaging
OncetheFCMSDKisimportedintoyourproject,itwillautomaticallyperforminitializationinruntime,duringwhichit
generatesanInstanceID,whichisusedasaregistrationtokenwithinFCM.WhenanInstanceIDisgeneratedthe
librarywilluploadtheidentifierandconfigurationdatatoFirebase.Thereforeifyouwanttogetanexplicitopt-inbefore
usingFCM,youmustdisableitsautoinitialization.PleasefollowtheinstructionsinthePreventautoinitialization
chapterintheFCMdocumentationforthatpurpose.
IftheconsentcommunicatedtotheNotificationsmoduleisGranted,itwillautomaticallyre-enableFCMduringthe
initializationprocessbycallingthefollowingcommand:
Firebase.Messaging.FirebaseMessaging.TokenRegistrationOnInitEnabled=true;
Summary
Introduction
148
Ifyouwanttogetanexplicitopt-inbeforeusingFCM,youmustdisableitsautoinitialization.Iftheconsent
providedtotheNotificationsmoduleisGranted,FCMwillbere-enabledautomaticallyduringinitialization.
IfyoudisabletheFCMautoinitializationandtheconsentprovidedtotheNotificationsmoduleisRevoked
orUnknown,theFCMservicewon'tbere-enabledanditwon'tfunction.
Ifyoudon'twanttogetanexplicitopt-inbeforeusingFCM,youcanignorealldescribedactions.TheFCM
servicewillbeinitializedautomaticallyasnormal.
Introduction
149
Notifications:Settings
TousetheNotificationsmoduleyoumustfirstenableit.GotoWindow>EasyMobile>Settings,selectthe
Notificationstab,thenclicktheright-handsidetoggletoenableandstartconfiguringthemodule.
AutoInitialization
AutoinitializationisafeatureoftheNotificationsmodulethatinitializestheserviceautomaticallywhenthemodule
starts.YoucanconfigurethisfeatureintheAUTO-INITCONFIGsection.
OniOS,apopupwillappearduringthefirstinitializationfollowingtheappinstallationtoaskfortheuser's
permissiontoenablenotificationsforyourgame.
Settings
150
AutoInit:uncheckthisoptiontodisabletheautoinitializationfeature,youcanstarttheinitializationmanuallyfrom
script(seeScripting)
AutoInitDelay:howlongafterthemodulestartthattheinitializationshouldtakeplace
"Modulestart"referstothemomenttheStartmethodofthemodule'sassociatedMonoBehavior(attachedtothe
EasyMobileprefab)runs.IfyouaddtheEasyMobileprefabinstancetothefirstsceneofyourgamethenthis
momentismostlyidenticaltothelaunchtimeoftheapp.
RemoteNotificationSetup
EnableRemoteNotifications
Toenableremote/pushnotificationsforyourapp,selectavalidserviceproviderfromthePushNotificationService
dropdowninREMOTENOTIFICATIONSETUPsection.CurrentlyOneSignalandFirebaseCloudMessagingservices
aresupported.
SetupOneSignal
BeforeYouBegin
BeforesettingupOneSignalinUnity,youmustfirstgenerateappropriatecredentialsforyourtargeted
platforms.Ifyou'renotfamiliarwiththeprocess,pleasefollowtheguideslistedhere.Youshouldalsofollowthe
instructionsincludedinthatdocumentonperformingnecessarysetupwhenbuildingforeachplatform.
ImportingOneSignalPlugin
UsingOneSignalservicerequirestheOneSignalpluginforUnity.EasyMobilewillautomaticallycheckforthe
availabilityofthepluginandpromptyoutoimportitifneeded.YoucanclicktheDownloadOneSignalPluginbuttonto
openthedownloadpagefortheplugin.OnceitisimportedintoUnitythesettingsinterfacewillbeupdatedandready
foryoutostartconfiguring.
InfactallyouneedtodoisenteryourOneSignalAppId.
Settings
151
CustomizingNotificationSoundsandIcons
Here'retheguidesoncustomizationOneSignalnotificationsoundsandAndroidnotificationicons:
CustomizeOneSignalAndroidNotificationIcons
CustomizeOneSignalNotificationSounds
SetupFirebaseCloudMessaging
BeforeYouBegin
BeforesettingupFirebaseCloudMessaginginUnity,youwillneedtocreateaprojectinFirebaseconsole.If
you'renotfamiliarwiththeprocess,pleasefollowthisguide.
NotethatforiOSyouneedtoassociateyourprojectwithanAPNscertificate:
InsideyourprojectintheFirebaseconsoleopenProjectSettingsthenselecttheCloudMessagingtab
SelecttheUploadCertificatebuttontouploadyourdevelopmentorproductioncertificate,orboth.Foreach
certificate,selectthe.p12fileandprovidepasswordifany.MakesurethebundleIDforthiscertificate
matchesthebundleIDofyourapp
Save
Aftersettinguptheproject,downloadtheGoogleService-Info.plistfile(iOS)orGoogle-Services.jsonfile
(Android)fromtheconsoleanddragitintoyourUnityproject(youcanplacethesefilesanywhereunderthe
Assetsfolderofyourproject).
ImportingFirebaseMessagingplugin
UsingFirebaseservicerequirestheFirebasepluginforUnity.EasyMobilewillautomaticallycheckfortheavailability
ofthepluginandpromptyoutoimportitifneeded.YoucanclicktheDownloadFirebasePluginbuttontodownload
theFirebaseUnitySDK.Afterthedownloadingfinishes,unzipthedownloadedfileandimporttheFirebaseMessaging
packageintoyourproject.OnceitisimportedintoUnitythesettingsinterfacewillbeupdatedandreadyforyouto
startconfiguring.
RegisteringDefaultNotificationTopics
FirebaseCloudMessaginghasaconceptoftopicmessaging,whichallowsyoutosendamessagetomultiple
devicesthathaveoptedintoaparticulartopic.Ifyouwanttohavesomedefaulttopicsthateveryinstanceofyourapp
subscribestowhenit'sinstalled,youcanregistertheminthesettingsinterface.AttheFirebaseTopicsfieldclickthe
"+"buttonandenterallthedefaulttopicsthatyouwant.
YoucanlearnmoreaboutFirebasetopicmessaginghere.
Settings
152
SendingNotificationstoYourApp
Firebaseprovides3waystosendnotificationstoyourapp:
1. UsingthenotificationcomposerintheFirebaseConsole-learnmorehere
2. UsingtheAdminSDK
3. UsingHTTPandXMPPProtocols
CustomizingNotificationSoundsandIcons
CurrentlytheFirebasenotificationcomposerdoesn'tallowcustomizingthenotificationsoundandicon(youcanonly
togglesoundonoroff).OniOS,theappiconwillbeusedasthenotificationicon.OnAndroid,youcansetupadefault
notificationiconforFirebasebyaddingthefollowinglinesbetweenthe<application>elementofthe
AndroidManifest.xmlfileattheAssets/notifications/Plugins/Androidfolder.
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/firebase_notification_icon"/>
Thenyouneedtocreatenotificationiconsatappropriateresolutions(youcanusetheAndroidAssetStudio),name
themfirebase_notification_icon,placethemintoaresfolderandimportthefolderintoyourUnityprojectusingthe
ImportResFolderbuttonundertheANDROIDNOTIFICATIONRESOURCESsectionofthesettingsinterface(see
chapterAddingNotificationResources).Youcanalsouseanothernamefortheiconsaslongasthenameinthe
AndroidManifest.xmlandthenameoftheiconfilesaresame.
Ifyou'reusingothermethodstosendnotificationstoyourapp,youcanspecifythenotificationiconsand
soundsinthenotificationcontent.Justmakesurethatthespecifiediconsandsoundsareavailableinyourapp.
iOSBuildNotes
AfterexportingtoXcode,performthefollowingsteps:
IntheGeneraltab,addtheUserNotificationsframeworkintoyourprojectifithasn'tbeenaddedyet.
IntheCapabilitiestab,turnonPushNotificationsandBackgroundModes,thenchecktheRemoteNotifications
boxunderBackgroundModes.
AddingNotificationResources
AndroidNotificationResources
Settings
153
Androidnotificationresourcesincludenotificationiconsandcustomnotificationsounds.
Customnotificationsounds:ifyoudon'twanttousethesystem'sdefaultnotificationsound,youcanprovide
customsoundstobeplayedwhenyournotificationsaredelivered.
Smallnotificationicons:smalliconsorstatusbariconsarerequiredandwillbeusedtorepresentnotifications
fromyourappinthestatusbar.StartingwithAndroid5,thesystemforcessmallnotificationiconstobeallwhite
whenyourapptargetsAndroidAPI21+.Ifyoudon'tmakeacorrecticon,itwillmostlikelybedisplayedasthe
fallbackicon(bell)orsolidwhiteiconinthestatusbar.
Largenotificationicons:largenotificationsiconsareoptionalandwillshowupatdifferentpositionsonthe
notificationdependingontheAndroidversion(seebelowscreenshot).Ifyoudonotspecifyalargeicon,thesmall
iconwillbeused.
PreparingAndroidNotificationIcons
It'sadvisabletousetheAndroidAssetStudiotoquicklyandeasilygeneratesmalliconswiththecorrectsettings.Note
thatthedefaultsmalliconsmustbenamedic_stat_em_defaultsothatEasyMobilecanrecognizeit.Belowisan
exampleofaresfoldercontainingthedefaultsmalliconsgeneratedbytheAndroidAssetStudio.Laterwewillimport
thisfoldertoUnitysothattheiconscanbeusedinyourproject.
Settings
154
Ifyouwanttohaveadefaultlargeicon,createa256x256icon,nameitic_large_em_defaultandplaceitinthe
drawable-xxxhdpifolderofthesameresfoldergeneratedpreviously.
Non-DefaultNotificationIcons
Besidethedefaulticons,youcanaddmorecustomiconswhichcanbeusedwhenschedulingdifferenttypesof
notifications.JustusetheAndroidAssetStudiotogeneratesmallicons,andcreatelargeiconsatthecorrect
sizes(256x256).Thenmergethemintothesameresfolderthatcontainsthedefaulticons,makingsurethe
iconsgointotheircorrectsubfolders.Whenschedulinganotification,youcanselectthesecustomiconsusing
theirnames.
Settings
155
PreparingAndroidNotificationSounds
Toaddcustomnotificationsounds,createafoldernamedrawinsidetheresfolderthatcontainsthedefault
notificationiconsandplacethesoundfilesthere.Laterthesecustomsoundscanbespecifiedinyourprojectwiththeir
names.Belowisanexampleresfolderthatcontainsdefaulticons,customiconsandcustomsoundsforAndroid
notifications.
ImportingAndroidNotificationResources
Settings
156
Afterconstructingtheresfolderwithalltherequirediconsandsounds,thenextstepisimportthefolderintoyour
Unityprojectsotheresourcescanbeusedinyourapp.IntheANDROIDNOTIFICATIONRESOURCESsection,click
theImportResFolderbutton,thenselectthegeneratedresfoldertoimport.That'sit!
iOSNotificationResources
OniOS,notificationiconsareprovidedbythesystem,butyoucanstillhavecustomnotificationsounds.Toadda
customsoundtoyouriOSapp,simplyplacethesoundfileanywhereinyourUnityproject,buildyourprojectforiOS
platform,andthendragthesoundfiletotheXcodeprojectroot(remembertoselectCopyitemsifneededonthe
Xcodeimportdialog).
NotesonNotificationSounds
It'srecommendedtohavenotificationsoundsin.wavformatsothattheycanbeusedonbothAndroidandiOS.
MakesuresoundnamesareconsistentacrossiOSandAndroid.
Notificationsoundsmustbelessthan30secondstomakesuretheycanbeplayedonbothAndroidandiOS.
CategoryManagement
Settings
157
NotificationCategoryGroups
IntheCATEGORYGROUPSsectionyoucanadd,editorremovegroupsforthenotificationcategoriesinyourapp.
1. Groupcontent:whereyoufillingroupinformation
2. Addbutton:usethisbuttontoaddanewgroup
3. Moveupbutton:movethegroupup,forarrangementpurpose
4. Deletebutton:usethisbuttontoremovethecurrentgroup
5. Movedownbutton:movethegroupdown,forarrangementpurpose
Acategorygroupcontentincludesfollowingfields:
Name:thegroupname,mustnotbeempty
Id:thegroupID,mustnotbeempty;acategoryspecifiesthegroupitbelongstousingthisID
NotificationCategories
YoucanmanagethenotificationcategoriesinyourappwithintheCATEGORIESsection.
Acategorycontentincludesfollowingfields:
Name:categoryname,onlyvisibleonAndroiddevices,thisisrequired
Settings
158
Id:categoryID,thisisrequired;anotificationspecifiesthecategoryitbelongstousingthisID
Description:optionalcategorydescription,onlyvisibleonAndroiddevices
GroupId:theidentifierofthegroupthiscategorybelongsto
EnableBadge:[Androidonly]whetherthenotificationsofthiscategorycanappearasbadgesinaLauncher
application
Importance:[Androidonly]howmuchinterruptive-visuallyandaudibly-thenotificationsofthiscategoryshould
be
Lights:[Androidonly]determineshowthenotificationlightshouldbedisplayed,ondevicesthatsupportthis
feature
LightColor:[Androidonly]thecolorofthenotificationlight,onlyconfigurablewhenLightsissettoCustom
Vibration:[Androidonly]determineshowthedeviceshouldvibratewhenanotificationarrives
VibrationPattern:[Android]thevibrationpatternofthedevice,onlyconfigurablewhenVibrationissettoCustom
Lock-screenVisibility:[Androidonly]determineshownotificationsshouldbedisplayedonlock-screen
Sound:determinewhetherthedefaultsound,oracustomsound,ornosoundatallshouldbeplayedwhena
notificationarrives
SoundName:thefilename(withextension)ofthecustomnotificationsound,onlyconfigurableifSoundissetto
Custom
Actionbuttons:thecustomactionbuttonstobeattachedtothenotificationsofthiscategory,eachactionbutton
requiresthefollowingfields:
Id:actionID,usedtodistinguishbetweenactions
Title:actiontitle,usedtodisplaytheactionbuttononthenotification
DefaultCategory
Yourappmusthaveatleastonenotificationcategory,andEasyMobileprovidesabuilt-indefaultcategory.Youcan
customizeitintheDefaultCategorysub-sectionoftheCATEGORIESsection.Youcan'tremovethedefault
category.
UserCategories
Besidethedefaultcategory,youcanaddasmanymorecategoriesasyouwish.Wecalltheseusercategories,and
theycanbemanagedintheUserCategoriessub-sectionoftheCATEGORIESsection.
Settings
159
1. Categorycontent:whereyoufillincategoryinformation
2. Addbutton:usethisbuttontoaddanewusercategory
3. Moveupbutton:movethecategoryup,forarrangementpurpose
4. Deletebutton:usethisbuttontoremovethecurrentusercategory
5. Movedownbutton:movethecategorydown,forarrangementpurpose
ConstantsGeneration
ConstantsgenerationisafeatureoftheNotificationsmodule.ItreadstheIDsofallusercategoriesaddedand
generatesastaticclassnamedEM_NotificationsConstantsthatcontainstheconstantsoftheseIDs.Later,youcan
usetheseconstantswhenschedulingalocalnotificationinscriptinsteadoftypingtheIDsdirectly,thushelpprevent
runtimeerrorsduetotyposandthelikes.
Togeneratetheconstantsclass(youshoulddothisafteraddingallrequiredusercategories),clicktheGenerate
ConstantsClassbuttonwithintheCONSTANTSCLASSGENERATIONsection.
Whentheprocesscompletes,afilenamedEM_NotificationsConstantswillbecreatedat
Assets/notifications/EasyMobile/Generated.
Settings
160
Notifications:Scripting
ThissectionprovidesaguidetoworkwiththeNotificationsmodulescriptingAPI.
YoucanaccesstheNotificationsmoduleAPIviatheNotificationsclassundertheEasyMobilenamespace.
WorkingwithConsent
Thefollowingsnippetshowshowyougrant,revokeandreadthemodule-levelconsentoftheNotificationsmodule.
//Grantsthemodule-levelconsentfortheNotificationsmodule.
Notifications.GrantDataPrivacyConsent();
//Revokesthemodule-levelconsentoftheNotificationsmodule.
Notifications.RevokeDataPrivacyConsent();
//Readsthecurrentmodule-levelconsentoftheNotificationsmodule.
ConsentStatusmoduleConsent=Notifications.DataPrivacyConsent;
Initialization
Beforenotificationscanbeused,themoduleneedstobeinitialized.Thisinitializationshouldonlybedoneoncewhen
theappisloaded,andbeforeanyothercallstotheAPIaremade.IfyouhaveenabledtheAutoinitializationfeature
(seeModuleConfigurationsection),youdon'tneedtostarttheinitializationfromscript.Otherwise,ifyouchooseto
disablethatfeature,youcaninitializetheserviceusingtheInitmethod.
//Initializepushnotificationservice
Notifications.Init();
Notethattheinitializationshouldbedoneearlyandonlyonce,e.g.youcanputitintheStartmethodofa
MonoBehaviour,preferablyasingletonsothatitwon'trunagainwhenthescenereloads.
//InitializationintheStartmethodofaMonoBehaviourscript
voidStart()
{
//Initializepushnotificationservice
Notifications.Init();
//Dootherstuff...
}
YoucancheckifthemodulehasbeeninitializedatanypointusingtheIsInitializedmethod.
//Checkifinitializationhascompleted.
boolisInitialized=Notifications.IsInitialized();
WorkingwithLocalNotifications
ConstructingaNotificationContent
Beforeschedulinganotification,youmustfirstprepareitscontent.TodothissimplycreateanewNotificationContent
objectandfillitwithappropriateinformation.
Scripting
161
//Constructthecontentofanewnotificationforscheduling.
NotificationContentPrepareNotificationContent()
{
NotificationContentcontent=newNotificationContent();
//Providethenotificationtitle.
content.title="DemoNotification";
//Youcanoptionallyprovidethenotificationsubtitle,whichisvisibleoniOSonly.
content.subtitle="DemoSubtitle";
//Providethenotificationmessage.
content.body="Thisisademonotification.";
//Youcanoptionallyattachcustomuserinformationtothenotification
//informofakey-valuedictionary.
content.userInfo=newDictionary<string,object>();
content.userInfo.Add("string","OK");
content.userInfo.Add("number",3);
content.userInfo.Add("bool",true);
//YoucanoptionallyassignthisnotificationtoacategoryusingthecategoryID.
//Ifyoudon'tspecifyanycategory,thedefaultonewillbeused.
//Notethatit'srecommendedtousethecategoryIDconstantsfromtheEM_NotificationsConstantsclass
//ifithasbeengeneratedbefore.Inthisexample,UserCategory_notification_category_testisthe
//generatedconstantofthecategoryID"notification.category.test".
content.categoryId=EM_NotificationsConstants.UserCategory_notification_category_test;
//Ifyouwanttousedefaultsmalliconandlargeicon(onAndroid),
//don'tsetthesmallIconandlargeIconfieldsofthecontent.
//Ifyouwanttousecustomiconsinstead,simplyspecifytheirnameshere(withoutfileextensions).
content.smallIcon="YOUR_CUSTOM_SMALL_ICON";
content.largeIcon="YOUR_CUSTOM_LARGE_ICON";
returncontent;
}
SchedulingLocalNotifications
Toschedulealocalnotification,preparethenotificationcontentandfeedittotheScheduleLocalNotificationmethod.
One-TimeLocalNotifications
Youcanscheduleanotificationtobedeliveredataspecifictime(inthefuture).Ifthespecifiedtimeisinthepast,the
notificationwillbedeliveredimmediately.
usingSystem;//touseDateTime
//Scheduleanotificationtobedeliveredby08:08AM,08August2018.
voidScheduleLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//Setthedeliverytime.
DateTimetriggerDate=newDateTime(2018,08,08,08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(triggerDate,content);
}
Insteadofatriggerdate,youcanalsoscheduleaone-timenotificationtobedeliveredaftersomedelaytime.
usingSystem;
Scripting
162
//Scheduleanotificationtobedeliveredafter08hours,08minutesand08seconds.
voidScheduleLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//SetthedelaytimeasaTimeSpan.
TimeSpandelay=newTimeSpan(08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(delay,content);
}
RepeatLocalNotifications
Ifyouwantthenotificationtorepeatautomatically,scheduleittobedeliveredaftersomedelaytime,andthenspecify
afixedrepeatinterval.Repeatintervalcanbeoneofthefollowingvalues:
None:norepeat
EveryMinute:notificationrepeatsonceeveryminute
EveryHour:notificationrepeatsonceeveryhour
EveryDay:notificationrepeatsonceeveryday
EveryWeek:notificationrepeatsonceeveryweek
usingSystem;
//Scheduleanotificationtobedeliveredafter08hours,08minutesand08seconds,
//thenrepeatonceeveryday.
voidScheduleRepeatLocalNotification()
{
//Preparethenotificationcontent(seetheabovesection).
NotificationContentcontent=PrepareNotificationContent();
//SetthedelaytimeasaTimeSpan.
TimeSpandelay=newTimeSpan(08,08,08);
//Schedulethenotification.
Notifications.ScheduleLocalNotification(delay,content,NotificationRepeat.EveryDay);
}
ManagingPendingLocalNotifications
GetPendingLocalNotifications
Togetallpending(scheduledbutnotyetdelivered)localnotifications,usetheGetPendingLocalNotificationsmethod.
ThependingnotificationswillbereturnedasanarrayofNotificationRequestobjectsviaacallback.Eachpending
notificationrequestisidentifiedbyitsrequestID.
//Getsallpendinglocalnotificationrequests.
voidGetPendingLocalNotifications()
{
Notifications.GetPendingLocalNotifications(GetPendingLocalNotificationsCallback);
}
//Callback.
voidGetPendingLocalNotificationsCallback(NotificationRequest[]pendingRequests)
{
foreach(varrequestinpendingRequests)
{
NotificationContentcontent=request.content;//notificationcontent
Scripting
163
Debug.Log("NotificationrequestID:"+request.id);//notificationrequestID
Debug.Log("Notificationtitle:"+content.title);
Debug.Log("Notificationbody:"+content.body);
}
}
CancelaPendingLocalNotification
Tocancelaparticularpendinglocalnotification,calltheCancelPendingLocalNotificationmethodwiththerequestIDof
thenotificationtobecanceled.Cancelednotificationswillnolongerbedelivered.
//CancelsapendinglocalnotificationwithknownrequestID.
Notifications.CancelPendingLocalNotification("REQUEST_ID_TO_CANCEL");
CancelAllPendingLocalNotifications
Tocancelallpendinglocalnotifications,simplycalltheCancelAllPendingLocalNotificationsmethod.
//Cancelsallpendinglocalnotifications.
Notifications.CancelAllPendingLocalNotifications();
WorkingwithRemoteNotifications
Remotenotificationsaresentfromaremoteserverandaretypicallyscheduledfromawebsite,e.g.OneSignal's
dashboard.Withinyourapp,youonlyneedtowritecodetohandlewhenaremotenotificationisdeliveredand
opened.
HandlingOpenedNotifications
Wheneveralocalorremotenotificationisopened-eitherbytheusertappingonit(defaultopenaction)orselecting
anactionbutton-yourappwillbebroughttoforegroundandthenaLocalNotificationOpened_ora
RemoteNotificationOpenedeventwillberaised.Youcansubscribetotheseeventsandtakeappropriateactions
accordingtoyourappdesign.It'srecommendedtosubscribetotheeventsassoonasyourappisloaded,e.g.inthe
_OnEnablemethodofaMonoBehaviourscriptinyourfirstscene.Otherwiseyouriskmissingthem.
Ifyourappisinforegroundwhenanotificationisdelivered-beitlocalorremote-thenthenotificationisnot
posted(notdisplayed)andtheLocalNotificationOpenedorRemoteNotificationOpenedeventwillberaised
immediatelyasifthenotificationwasopenedwiththedefaultaction.
//Subscribestonotificationevents.
voidOnEnable()
{
Notifications.LocalNotificationOpened+=OnLocalNotificationOpened;
Notifications.RemoteNotificationOpened+=OnRemoteNotificationOpened;
}
//Unsubscribesnotificationevents.
voidOnDisable()
{
Notifications.LocalNotificationOpened-=OnLocalNotificationOpened;
Notifications.RemoteNotificationOpened-=OnRemoteNotificationOpened;
}
//Thishandlerwillbecalledwhenalocalnotificationisopened.
voidOnLocalNotificationOpened(LocalNotificationdelivered)
{
Scripting
164
//TheactionIdwillbeemptyifthenotificationwasopenedwiththedefaultaction.
//OtherwiseitcontainstheIDoftheselectedactionbutton.
if(!string.IsNullOrEmpty(delivered.actionId))
{
Debug.Log("ActionID:"+delivered.actionId);
}
//Whetherthenotificationisdeliveredwhentheappisinforeground.
Debug.Log("Isappinforeground:"+delivered.isAppInForeground.ToString());
//Getsthenotificationcontent.
NotificationContentcontent=delivered.content;
//Takefurtheractionsifneeded...
}
//Thishandlerwillbecalledwhenaremotenotificationisopened.
voidOnRemoteNotificationOpened(RemoteNotificationdelivered)
{
//TheactionIdwillbeemptyifthenotificationwasopenedwiththedefaultaction.
//OtherwiseitcontainstheIDoftheselectedactionbutton.
if(!string.IsNullOrEmpty(delivered.actionId))
{
Debug.Log("ActionID:"+delivered.actionId);
}
//Whetherthenotificationisdeliveredwhentheappisinforeground.
Debug.Log("Isappinforeground:"+delivered.isAppInForeground.ToString());
//Getsthenotificationcontent.
NotificationContentcontent=delivered.content;
//IfOneSignalserviceisinuseyoucanaccesstheoriginalOneSignalpayloadlikebelow.
//IfOneSignalisnotinusethiswillbenull.
OneSignalNotificationPayloadosPayload=delivered.oneSignalPayload;
//IfFirebaseMessagingserviceisinuseyoucanaccesstheoriginalFirebase
//payloadlikebelow.IfFirebaseisnotinusethiswillbenull.
FirebaseMessagefcmPayload=delivered.firebasePayload;
//Takefurtheractionsifneeded...
}
HandlingFirebaseNotificationsonAndroid
OnAndroid,ifaFirebasenotificationarriveswhenyourappisnotrunningorisinbackground,onlythe"data
payload"willbesenttotheapp,whilethe"notificationcomponent"won'tbecauseitisintendedtobedisplayed
totheuseronly.Effectively,theNotificationContentassociatedwiththeRemoteNotificationobjectreturnedby
theRemoteNotificationOpenedeventwillcontainemptyfields(title,subtile,body,etc.)withtheexceptionofthe
'userInfo'fieldwhichrepresentsthedatapayload.Iftheappisinforegroundthenboththedatapayloadandthe
notificationcomponentwillbeforwardedtotheapp.
Youcanlearnmoreaboutthishereandhere.
RemovingDeliveredNotifications
Normallydeliverednotificationsareclearedautomaticallywhenthey'reopened.Youcanmanuallyclearallpreviously
shownnotificationsofyourappfromthenotificationcenterorstatusbarwiththeClearAllDeliveredNotifications
method.
//Clearalldeliverednotifications(localandremote).
Notifications.ClearAllDeliveredNotifications();
Scripting
165
[iOS]SettingApplicationIconBadgeNumber
iOSallowsustogetandsettheapplicationiconbadgenumberdirectly.EasyMobileprovidesthefollowingmethods
soyoucanmanagethebadgenumberwhenworkingwithnotificationsoniOS.Notethattheydon'thaveanyeffecton
Android.
//GetiOSapplicationiconbadgenumber.
intbadgeNumber=Notifications.GetAppIconBadgeNumber();
//SetiOSapplicationiconbadgenumber.
Notifications.SetAppIconBadgeNumber(badgeNumber+1);//increasethebadgenumberby1
Scripting
166
Notifications:PlayMakerActions
ThePlayMakeractionsoftheNotificationsmodulearegroupinthecategoryEasyMobile-Notificationsinthe
PlayMaker'sActionBrowser.
PleaserefertotheNotificationsDemo_PlayMakersceneinfolder
Assets/EasyMobile/Demo/PlayMakerDemo/Modulesforanexampleonhowtheseactionscanbeused.
PlayMakerActions
167
PlayMakerActions
168
Privacy:Introduction
ThePrivacymoduleprovidesyouconvenienttoolsandresourcestostreamlinetheprocessofgettingcompliantwith
userprivacydirectiveandregulationssuchastheGeneralDataPrivacyRegulation(GDPR).Here'resomehighlights
ofthismodules:
Comprehensive,flexibleconsentmanagementsystem
Multi-level,prioritizedconsentsystemforflexiblemanagementofvariousconsent-requirableservicesinyour
app
Consentisautomaticallystoredtobepersistentacrossappsessions
Consentisautomaticallyapplied/communicatedtorelevantservicesinruntime
Multi-purposebuilt-innativeconsentdialog
Easytousedialogthatcanbeusedasthecommoninterfaceforcollectinguserconsentforallrelevant
services(toavoidannoyingtheuserwithmultiplepopupsaskingconsentforvariousthings)
Thedialogcontentandlayoutcanbeflexiblyadaptedtoyourconsentmanagementneeds
Thedialogcanbecreatedfromscriptorbyusingthebuilt-ingraphicalcomposer
Nativelookmakessurethedialogcanvisuallyfitintoappsofanygraphicstyle
Locationdetectiontool
Built-inAPItocheckwhetherthecurrentdeviceisintheEuropeanEconomicArea(EEA)region,whichis
regulatedbytheGDPR
ConsentManagementSystem
ConsentStates
Aconsentcantakeoneof3values:Unknown,GrantedorRevoke.
State Description
Unknown Thedefaultstateofconsent,whichmeanstheuserneitheracceptsnordeniestherequesteduse
oftheirdata.
Granted Theuserallowspermissiontotherequesteduseoftheirdata
Revoked Theuserdenies(orrevokesapreviouslygranted)permissiontotherequesteduseoftheirdata
Aconsentisconsidered"defined"ifithasastateotherthanUnknown.
ConsentLevelsandPriorities
Theprimarydesigntargetoftheconsentsystemisflexibility,thatitallowssettingaglobalconsentorgranularconsent
forindividualmodulesorservices.Differentconsent-levelshavedifferentprioritiessuchthatthehigherprioritized
consentoverridesthelowerone.Inotherwords,morespecificconsentisprioritizedoverless-specificone.Thetable
belowsummarizesthetypesofconsentprovidedbyEasyMobile.
Type Level Priority Description
Vendor/Provider
Consent
Lowest(most
specific) Highest Consentgiventoaspecificvendorofacertainservice,
e.g.AdMob'sconsent
ModuleConsent Medium Medium Consentgiventoacertainmodule,e.g.Advertising
module'sconsent
Introduction
169
GlobalConsent Highest(least
specific) Lowest Thecommonconsentgiventothewholeapp
Wheneveraserviceseeksforanapplicableconsent,itsearchesfromthelowestleveltothehighestlevelandgetthe
definedconsentwithhighestpriority.Thefollowingpseudo-codeillustratestheprocess:
ifthere’sadefinedvendor-consent
Usevendor-consent
elseifthere’sadefinedmodule-consent
Usemodule-consent
elseifthere’sadefineglobal-consent
Useglobal-consent
else
Fallbacktoservice'sdefault-behaviour(the“pre-GDPR”behaviour)
ConsentManagerClass
Eachconsenttypeismanagedbyacorrespondingconsentmanager.Specifically,
Thevendorconsentismanagedbytheclientofthecorrespondingvendor,e.g.AdMob'sconsentismanagedby
theAdvertising.AdMobClientobject
Themoduleconsentismanagedbythecorrespondingconsentmanagerofthatmodule,e.g.theAdvertising
module'sconsentismanagedbytheAdvertisingConsentManager.
TheglobalconsentismanagedbytheGlobalConsentManager.
EachofthesemanagerextendstheConsentManagerclass,whichisanabstractclassthatimplementsthe
IConsentRequirableinterface.ItprovidesanAPIforfollowingtasks:
Queryingthecurrentstateofthemanagedconsent
Grantingorrevokingconsent
Observingconsentchangesusingevent
TheConsentManagerstoresthespecifiedconsentstateinPlayerPrefssothatitpersistsacrossapplaunches.
YoucancreateclassesextendingtheConsentManagerclasstomanageconsentforotherservicesinyourapp
thatarenotgovernedbyEasyMobile.
ConsentCommunication
Specifiedconsent(GrantedorRevoked)willbeautomaticallycommunicated(applied)tocorrespondingservices
duringtheirinitialization,whichmostofthetimeisdoneautomaticallyduringtheinitializationofEasyMobile(see
UsingEasyMobile>Initializing),unlessyouturnedofftheautoinitializationfeatureoftheseservicesintheSettings
interface.Iftheconsentischangedafterthecorrespondingservicehasbeeninitialized,thatchangewillbeappliedin
thenextinitialization.Insuchcaseyoushouldinformtheuseraccordingly.
DetailsonhowconsentisappliedforeachindividualservicecanbefoundinthechapteronGDPRofthe
correspondingmodule.Currently,twomodulesbeingaffectedaretheAdvertisingandNotificationsmodule.
ConsentDialog
Collectinguserconsentmaysoundtrivial,butifnotdonerightcouldnegativelyaffecttheuserexperience.Atypical
appcouldincorporatemultipleconsent-requirableservicessuchasads,analyticsandremotenotifications.Each
servicemayprovideitsowninterfacetocollectuserconsent,e.g.AdMobprovidestheConsentSDKandtheMoPub
Introduction
170
SDKoffersabuilt-indialogforthesamepurpose.However,itmaynotbeidealtousethesevendor-specificinterfaces
becauseitmeanstheuserwouldbepresentedwithmultipledialogsaskingconsentfordifferentthings,whichwould
notbeagreatexperience,tosaytheleast.
Tosolvethisproblem,EasyMobileprovidesamulti-purposeconsentdialogthatcanserveasacommoninterfacefor
collectinguserconsentforallrelevantservicesinyourapp.ThedialogisbuiltfromnativeUIelementstogiveita
mobile-nativelookthateasilyfitsintoyourappregardlessofitsgraphicstyle.
ConsentDialogAnatomy
ThefollowingimagedepictshowtheconsentdialoglooksonAndroidandiOS(lefttoright).
Aconsentdialogconsistsoffollowingparts:
Titlebar:ontopofthedialogisatitlebarcontaining:
Title(1):thetitleforyourdialog
Dismissbutton(2):optionalbuttonthatallowstheusertocancelthedialogwithoutupdatingtheconsent,
whenthisbuttonisclickedthedialogclosesandraisesitsDismissedevent.
Maincontent:thebodyofthedialogiscomprisedofthefollowingelement:
Texts(3)(5):youcanusecommonHTMLtagssuchas<b>,<i>tostylethetext,aswellasusingthe<a>tag
toincludehyperlinks,whichisusefulforprovidinglinkstoprivacypolicies.
Toggles(4):youcanoptionallyincludetogglesinyourdialogaskingtheusertoprovidegranularconsentfor
differentservices.Togglescanbeturnedoffbydefault,makingthemagoodmeansforuserstoprovide
explicitconsent(theyhavetoturnthemonexplicitly).Eachtoggleconsistsofatitle,aswitchanda
descriptiontext,whichiscollapsible.Similartothebodytext,youcanusecommonHTMLtagssuchas<b>,
<i>and<a>inthisdescriptiontext.Also,youcan,forexample,makeatoggleonbydefaultandmakeit
unclickableforaservicethatisvitalfortheoperationofyourapptoindicatetheuserthatconsentforsuch
serviceisrequiredforcontinueduseoftheapp.
Buttons(6):wheneverabuttoninthedialogisclicked,thedialogclosesandraisesitsCompletedeventwith
theIDoftheclickedbuttonandthecurrentvaluesofthetogglesifany.
Youcanconstructconsentdialogsdirectlyinscriptorbyusingthebuilt-inconsentdialogcomposer(see
Settings).
Youcanflexiblyinterleavetext,togglesandbuttonsinthebodyofyourconsentdialogtoachievethe
desiredlayout.
Introduction
171
Youcanhavezeroormoretoggles,butthedialogshouldalwayshaveatleastonebuttonotherwisethe
Completedeventwillneverfireandthedialogresults(clickedbuttonID,togglevalues)won'tbereturned.
ConsentDialogLocalization
OneoftherequirementoftheGDPRisthetherequestforconsentmustbeintelligible,soitmaybedesirableto
localizetheconsentdialogintomultiplelanguages.EasyMobile'sconsentdialogwasdesignedwiththatinmind.All
elementsoftheconsentdialogcanbeaccessedandalteredatruntime,makingiteasytolocalizethem.Atypical
approachwouldbeusingknownstring-patternsasplaceholdersforthetextsinthedialog(e.gtitle,bodytexts,toggle
description,buttonlabel)andreplacethemwithappropriatelocalizedtextsinruntimebeforeshowingthedialog.
Youmaywanttouseadedicatedlocalizationasset,whichcanbeeasilyfoundontheUnityAssetStore,forthe
purposeofmanagingandselectingcorrecttranslationfortheplaceholdertexts.
EEARegionChecking
SincetheGDPRappliesspecificallytotheEEAregion,itmaybedesirabletohavedifferentbehaviorsforyourapp
betweenEEAandnon-EEAregions,especiallyonprivacy-sensitivetasks.Toservethatpurpose,EasyMobile
providesabuilt-invalidatortodetectwhetherthecurrentdeviceisinEEAregionornot.Thetoolemploysseveral
differentmethodsforthetestwhicharesummarizedinthetablebelow.
Method Description
Google
Service
ValidatingusingtheserviceprovidedbyGoogleat
https://adservice.google.com/getconfig/pubvendors,requiresaninternetconection
Telephony Validatingusingthecountrycodeobtainedfromthedevice'smobilecarrierinformation(SIMcard
information),noconnectionrequiredbutwon'tworkwithoutaSIMcard
Timezone Validatingusingthedevice'stimezonesetting.Noconnectionrequired;notethatthissettingcan
bechangedbytheuser
Locale Validatingusingthedevice'slocalesetting.Noconnectionrequired;notethatthissettingcanbe
changedbytheuser
TheEEAregionvalidatorreturnsoneofthefollowingresults:InEEA,NotInEEAorUnknown.Itallowsyoutouseall
availablevalidatingmethodsorasubsetoftheminapredefinedorderofpriorities.Ifahigherprioritizedmethodfails,
thevalidatorwillusethenextmethoduntilanexplicitresult(eitheranInEEAorNotInEEAvalue)isdetermined.Inthe
rarecasethatallmethodsfail,theresultwillbereturnedasUnknown.ForexamplescriptsonusingtheEEAvalidator
seetheScriptingchapter.
ThecodesofthosecountriescurrentlyincludedintheEEAregionarestoredintheEEACountriesenum.
publicenumEEACountries
{
None=0,
AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IE,IT,LV,LT,LU,MT,NL,PL,PT,RO,SK,SI,ES
,SE,GB,//28memberstates.
GF,PF,TF,//FrenchterritoriesFrenchGuiana,Polynesia,SouthernTerritories.
EL,UK,//AlternativeEUnamesforGRandGB.
IS,LI,NO,//NotEUbutinEAA.
CH,//NotinEUorEAAbutinsinglemarket.
AL,BA,MK,XK,ME,RS,TR//Candidatecountries.
}
AProposedWorkflowforWorkingwithConsent
Introduction
172
It'suptoyoutodecidehowyourappshouldreacttotheGDPR,includingwhetheruserconsentshouldbecollected
forthoseservicesintheappthatmaybesubjecttotheregulation.Thefollowingproposedworkflowcanbeoptionally
employedinthecaseyoudecidetocollectuserconsentandconfigurerelevantservicewiththatconsent.
SgLibGamesoffertoolsandinformationasaresourcetoassistyouingettingcompliantwiththeGDPR,butwe
don'tofferlegaladvice.WerecommendyoucontactyourlegalcounseltofindouthowGDPRaffectsyou.
WhenbuildingaconsentawareappusingEasyMobile,followingconsiderationscanbemade:
Sincetheconsentisappliedduringserviceinitialization,it'sadvisabletocollectuserconsentandforwarditto
relevantmodulesorservicesbeforeinitializingtheEasyMobileruntime.
Aconsentdialogshouldbepresentedthefirsttimetheapplaunchestocollectuserconsentforallrelevant
services;thisdialogshouldnotbedismissible,aswewanttheusertospecifyexplicitconsentoncebeforethe
normaloperationruns.
Theconsentdialogshouldbeaccessibleagain(e.g.viaamenubutton)sothattheusercanchangetheir
consent;thisdialogshouldreflectthecurrentconsentstatesforeachindividualservicesandshouldbe
dismissible,sotheusercanleaveitwithoutupdatingtheirconsentincasethedialogwasopenedunintentionally.
Iftheuserdoeschangetheirconsent,theyshouldbeinformedthatthechangeswilltakeplaceduringthenext
applaunch(thenextinitialization).
ItmaybeadvisabletoonlycollectandapplyconsentfordevicesintheEEAregion,whilehavinga"pre-GDPR"
behaviorinotherregion(notsettinganyconsent,aswhat'snormallydonebeforetheGDPRcameout).
ItmaybedesirabletousealocalizationtooltolocalizetheconsentdialogtolanguagesintheEEAregion.
Sinceit'scommontohavemultipleconsent-requirableservicesinyourapp,someofthosemaybegovernedbyEasy
Mobilewhileothersmaybenot,werecommendhavingacustom"appconsent"classthatrepresentsthecollectionof
allconsentgiventoeachindividualserviceintheapp.Thisclassshouldhaveseveralfunctionalities:
Representsthecollectionofconsentgiventovariousservices,specificallyadvertising,analyticsandnotifications
asintheexamplescript
ForwardsthecollectedconsenttocorrespondingservicesusingappropriateAPIprovidedbyEasyMobile(and
otherrelevant3rdpartySDKsifany)
AllowssavingandretrievingthecollectedconsenttoandfromPlayerPrefsinformofaJSONstring(thusitis
attributedasSerializable)sothatwecanrestoretheconsentvaluesandreflectthemontheconsentdialogwhen
itisshownagain
Forthepurposeofexplainingthisproposedworkflow,wereusetheDemoAppConsentclassfoundinourPrivacy
demosceneasanexampleoftheaforementionedclass.
usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
usingSystem;
namespaceEasyMobile.Demo
{
///<summary>
///Thisclassrepresentsacollectionofindividualconsentsfor
///all3rd-partyservicesthatrequireconsentinourapp.
///Thisclasscanbeusedtomanageconsentforany
///3rd-partyservice,notjustthosemanagedbyEasyMobile.
///Notethatthisclassisserializablesowecanserializeit
///toaJSONstringandstoreinPlayerPrefs.
///</summary>
[Serializable]
publicclassDemoAppConsent
{
publicconststringDemoStorageKey="EM_Demo_AppConsent";
#region3rd-partyServicesConsent
//TheconsentforthewholeAdvertisingmodule.
Introduction
173
//(wecouldhavehaddifferentconsentsforindividualadnetworks,butfor
//thesakeofsimplicityinthisdemo,we'llasktheuserasingleconsent
//forthewholemoduleanduseitforalladnetworks).
publicConsentStatusadvertisingConsent=ConsentStatus.Unknown;
//TheconsentforthewholeNotificationsmodule.
//Notethatdataconsentisonlyapplicabletopushnotifications,
//localnotificationsdon'trequireanyconsent.
publicConsentStatusnotificationConsent=ConsentStatus.Unknown;
//SincethisdemoappalsohasIn-AppPurchase,whichforcestheuseof
//UnityAnalytics,wecouldhavehadtoaskaconsentforthattoo.However,
//accordingtoUnityit'ssufficienttoprovidetheuserwithanURL
//sotheycanopt-outonUnitywebsite.SowewillincludethatURLinour
//consentdialogandnotneedtoaskandstoreanyexplicitconsentlocally.
//Hereyoucanaddconsentvariablesforother3rdpartyservicesifneeded,
//includingthosenotmanagedbyEasyMobile...
#endregion
///<summary>
///ToJSONstring.
///</summary>
///<returns>A<seecref="System.String"/>thatrepresentsthecurrent<seecref="EasyMobile.Demo.AppCo
nsent"/>.</returns>
publicoverridestringToString()
{
returnJsonUtility.ToJson(this);
}
///<summary>
///ConvertsthisobjecttoJSONandstoresinPlayerPrefswiththeprovidedkey.
///</summary>
///<paramname="key">Key.</param>
publicvoidSave(stringkey)
{
PlayerPrefs.SetString(key,ToString());
}
///<summary>
///ForwardstheconsenttorelevantmodulesofEM.
///</summary>
///<paramname="consent">Consent.</param>
///<remarks>
///Inareal-worldapp,you'dwanttowritesimilarmethod
///toforwardtheobtainedconsentnotonlytorelevantEMmodules
///andservices,butalsotootherrelevant3rd-partySDKsinyourapp.
publicstaticvoidApplyDemoAppConsent(DemoAppConsentconsent)
{
//ForwardtheconsenttotheAdvertisingmodule.
if(consent.advertisingConsent==ConsentStatus.Granted)
Advertising.GrantDataPrivacyConsent();
elseif(consent.advertisingConsent==ConsentStatus.Revoked)
Advertising.RevokeDataPrivacyConsent();
//ForwardtheconsenttotheNotificationsmodule.
if(consent.notificationConsent==ConsentStatus.Granted)
Notifications.GrantDataPrivacyConsent();
elseif(consent.notificationConsent==ConsentStatus.Revoked)
Notifications.RevokeDataPrivacyConsent();
//Hereyoucanforwardtheconsenttootherrelevant3rd-partyservicesifneeded...
}
///<summary>
///SavesthegiveappconsenttoPlayerPrefsasJSONusingthedemostoragekey.
///</summary>
///<paramname="consent">Consent.</param>
publicstaticvoidSaveDemoAppConsent(DemoAppConsentconsent)
{
if(consent!=null)
consent.Save(DemoStorageKey);
Introduction
174
}
///<summary>
///LoadsthedemoappconsentfromPlayerPrefs,returnsnullifnothingstoredpreviously.
///</summary>
///<returns>Thedemoappconsent.</returns>
publicstaticDemoAppConsentLoadDemoAppConsent()
{
stringjson=PlayerPrefs.GetString(DemoStorageKey,null);
if(!string.IsNullOrEmpty(json))
returnJsonUtility.FromJson<DemoAppConsent>(json);
else
returnnull;
}
}
}
Thefollowingpseudo-codeillustratestheprocessthatshouldbedoneateveryapplaunchtocollectandforward
consent(ifneeded)andtheninitializeEasyMobileinaconsent-awareapp.
ifNOTinEEAregion//notaffectedbyGDPR
{
InitializeEasyMobile//noconsentneeded,goaheadwithinitialization
}
else//isinEEAregion,affectedbyGDPR
{
ifNOTfirstapplaunch//consentshouldbegivenalreadyinthe1stlaunch
{
InitializeEasyMobile//goaheadwithinitialization
}
else//thisisfirstapplaunch,shouldaskforconsent
{
Getthedefaultconsentdialog//orcreateanewone
Localizetheconsentdialog//ifneeded,preferablyusingalocalizationtool
Shownon-dismissibleconsentdialog//usercannotskipit
Constructnew"appconsent"object//consentcollected,constructtheobjectoffit
Forwardconsenttorelevantservices//usingthe"appconsent"object
Saveconsentvalues//usingthe"appconsent"object
InitializeEasyMobile//everythingisset,cannowstartworking!
}
}
Asmentionedpreviously,apartfromcollectingconsentatthefirstapplaunchwealsoneedtoprovideawayforthe
usertoaccesstheconsentdialogagaintoupdatetheirconsentifneeded.Thesimplestwayishavingamenubutton
thatcanopentheconsentdialogattheuserdiscretion.Thisdialogshouldreflectthecurrentconsentstatusandis
dismissible.Thefollowingpseudo-codeillustratetheprocesstohandlesuchconsentdialogopeningbutton.
ifbuttonclicked
{
Getthedefaultconsentdialog//orcreateanewone
Localizetheconsentdialog//ifneeded,preferablyusingalocalizationtool
ifhavingastoredconsent//checkusingthe"appconsent"class
{
Retrievethestoredconsent//usingthe"appconsent"class
Updatedialogwithstoredconsent//reflectthecurrentconsentstatus
}
Showdismissibleconsentdialog//usercanskipitifopenedunintentionally
ifconsentisupdated//dialogisclose&consenthaschanged
{
Constructnew"appconsent"object//consentcollected,constructtheobjectoffit
Forwardconsenttorelevantservices//usingthe"appconsent"object
Saveconsentvalues//usingthe"appconsent"object
Showalert//informuserchangeswillbeappliedsincenextapplaunch
}
}
Introduction
175