Implement extension-admob for mobile ads

forgor that

videos and fixed banner size

make this a define

keep thes demo units when using TESTING_ADS only

always enable it on debug builds (?)

Update AdMobUtil.hx

admob features

Update PlayState.hx

Update AdMobUtil.hx

Update AdMobUtil.hx

Show privacy options form if required.

Update Main.hx

Update Main.hx

Update PlayState.hx

Update AdMobUtil.hx

Update AdMobUtil.hx

Update AdMobUtil.hx

Co-Authored-By: Mihai Alexandru <77043862+majigsaw77@users.noreply.github.com>
Co-Authored-By: sector-a <82838084+sector-a@users.noreply.github.com>
This commit is contained in:
KarimAkra
2025-04-15 21:36:05 +03:00
co-authored by Mihai Alexandru sector-a
parent 1b2f75395f
commit 7babcfaf6b
7 changed files with 273 additions and 4 deletions
+7
View File
@@ -208,6 +208,13 @@
"dir": null,
"ref": "bdb191fe7cf745c02a980749906dbf22719e200b",
"url": "https://github.com/fponticelli/thx.semver"
},
{
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": null,
"url": "https://github.com/mcagabe19-stuff/extension-admob"
}
]
}
+46 -1
View File
@@ -280,12 +280,24 @@ class Project extends HXProject {
*/
static final FEATURE_GHOST_TAPPING:FeatureFlag = "FEATURE_GHOST_TAPPING";
/**
/**
* `-DEXCLUDE_ARMV7`
* If this flag is enabled, armv7 won't be compiled when building android.
*/
static final EXCLUDE_ARMV7:FeatureFlag = "EXCLUDE_ARMV7";
/**
* `-DDISABLE_ADMOB_ADS`
* If this flag is enabled, ADs from AdMob will be disabled.
*/
static final DISABLE_ADMOB_ADS:FeatureFlag = "DISABLE_ADMOB_ADS";
/**
* `-DTESTING_ADS`
* If this flag is enabled, ADs will use testing mode.
*/
static final TESTING_ADS:FeatureFlag = "TESTING_ADS";
//
// CONFIGURATION FUNCTIONS
//
@@ -306,6 +318,11 @@ class Project extends HXProject {
configureHaxelibs();
configureAssets();
configureIcons();
if (DISABLE_ADMOB_ADS.isDisabled(this))
{
configureAdMobKeys();
}
}
/**
@@ -510,6 +527,8 @@ class Project extends HXProject {
// TODO: Thanks to new changes to FlxTouch and such, this became useless. Find a way to make it work again -Zack.
MOUSE_TO_TOUCH.apply(this, false);
EXCLUDE_ARMV7.apply(this, false);
DISABLE_ADMOB_ADS.apply(this, !isMobile());
TESTING_ADS.apply(this, isDebug());
// Should be true unless explicitly requested.
HARDCODED_CREDITS.apply(this, true);
@@ -741,6 +760,10 @@ class Project extends HXProject {
if (FEATURE_PARTIAL_SOUNDS.isEnabled(this)) {
addHaxelib('FlxPartialSound'); // Partial sound
}
if (DISABLE_ADMOB_ADS.isDisabled(this)) {
addHaxelib('extension-admob'); // Ads Extension
}
}
function configureAssets() {
@@ -813,6 +836,22 @@ class Project extends HXProject {
addIcon("art/iconOG.png");
}
/**
* Configure the platforms' Ad Mob application keys.
*/
function configureAdMobKeys() {
if (isAndroid())
{
setEnv("ADMOB_APPID", "");
}
if (isIOS())
{
// This ID is not from us, it's a test ID provided by AdMob. not really sure if it works.
setEnv("ADMOB_APPID", "");
}
}
//
// HELPER FUNCTIONS
// Easy functions to make the code more readable.
@@ -916,6 +955,12 @@ class Project extends HXProject {
return this.defines.exists(name);
}
public function setEnv(name:String, value:String = '') {
this.defines.set(name, value);
this.environment.set(name, value);
this.setenv(name, value);
}
/**
* Add a library to the list of dependencies for the project.
* @param name The name of the library to add.
+4
View File
@@ -128,6 +128,10 @@ class Main extends Sprite
@:privateAccess
game._customSoundTray = funkin.ui.options.FunkinSoundTray;
#if NO_DISABLE_ADMOB_ADS
funkin.mobile.util.AdMobUtil.init();
#end
addChild(game);
#if FEATURE_DEBUG_FUNCTIONS
+154
View File
@@ -0,0 +1,154 @@
package funkin.mobile.util;
import extension.admob.Admob;
import extension.admob.AdmobEvent;
/**
* Utility class for managing AdMob advertisements in a mobile application.
* This class provides functions to initialize AdMob, manage ad events,
* and control the display of different ad types, including banners,
* interstitial ads, and rewarded ads.
*/
class AdMobUtil
{
/**
* Production ad unit IDs (to be set for actual ad campaigns).
*/
#if !TESTING_ADS
private static final BANNER_AD_UNIT_ID:String = "";
private static final INTERSTITIAL_AD_UNIT_ID:String = "";
private static final INTERSTITIAL_VIDEO_AD_UNIT_ID:String = "";
private static final REWARDED_AD_UNIT_ID:String = "";
#else
/**
* Test ad unit ID for banner ads, used during development.
*/
private static final BANNER_AD_UNIT_ID:String = #if android "ca-app-pub-3940256099942544/6300978111" #elseif ios "ca-app-pub-3940256099942544/2934735716" #else "" #end;
/**
* Test ad unit ID for interstitial ads, used during development.
*/
private static final INTERSTITIAL_AD_UNIT_ID:String = #if android "ca-app-pub-3940256099942544/1033173712" #elseif ios "ca-app-pub-3940256099942544/4411468910" #else "" #end;
/**
* Test ad unit ID for interstitial video ads, used during development.
*/
private static final INTERSTITIAL_VIDEO_AD_UNIT_ID:String = #if android "ca-app-pub-3940256099942544/8691691433" #elseif ios "ca-app-pub-3940256099942544/5135589807" #else "" #end;
/**
* Test ad unit ID for rewarded ads, used during development.
*/
private static final REWARDED_AD_UNIT_ID:String = #if android "ca-app-pub-3940256099942544/5224354917" #elseif ios "ca-app-pub-3940256099942544/1712485313" #else "" #end;
#end
/**
* Initializes the AdMob SDK and sets up event listeners for interstitial and rewarded ads.
* The listeners display ads automatically when they are loaded.
*/
public static inline function init():Void
{
Admob.status.addEventListener(AdmobEvent.INTERSTITIAL_LOADED, (ae:AdmobEvent) -> Admob.showInterstitial());
Admob.status.addEventListener(AdmobEvent.REWARDED_LOADED, (ar:AdmobEvent) -> Admob.showRewarded());
Admob.init(#if TESTING_ADS true #else false #end);
}
/**
* Loads an interstitial ad. Depending on the forceVideo parameter or random selection,
* it loads either a video or a standard interstitial ad.
* @param forceVideo If true, forces loading an interstitial video ad. Otherwise, it uses a 50% chance.
*/
public static inline function loadInterstitial(forceVideo:Bool = false):Void
{
Admob.loadInterstitial((forceVideo || FlxG.random.bool(50)) ? AdMobUtil.INTERSTITIAL_VIDEO_AD_UNIT_ID : AdMobUtil.INTERSTITIAL_AD_UNIT_ID);
}
/**
* Adds a banner ad at the specified size and alignment.
* @param size The size of the banner ad, defaulting to the standard banner size.
* @param align The alignment of the banner ad, defaulting to the bottom of the screen.
*/
public static inline function addBanner(size:Int = Admob.BANNER_SIZE_BANNER, align:Int = Admob.BANNER_ALIGN_BOTTOM):Void
{
Admob.showBanner(AdMobUtil.BANNER_AD_UNIT_ID, size, align);
}
/**
* Removes the currently displayed banner ad, if any.
*/
public static inline function removeBanner():Void
{
Admob.hideBanner();
}
/**
* Loads a rewarded ad, preparing it to be displayed once loaded.
* Rewarded ads typically grant rewards upon user completion, like game items or bonuses.
*/
public static inline function loadRewarded():Void
{
Admob.loadRewarded(AdMobUtil.REWARDED_AD_UNIT_ID);
}
/**
* Sets the volume level for ads with sound, allowing control over ad audio playback.
* @param volume A Float representing the desired volume (0 = mute, 1 = full volume).
*/
public static inline function setVolume(volume:Float):Void
{
Admob.setVolume(volume);
}
/**
* Checks whether consent for a specific advertising purpose has been granted.
* @param purpose The purpose for which consent is required.
* @return An Int indicating consent status (-1 for no consent, 1 for granted).
*/
public static inline function hasConsentForPurpose(purpose:Int):Int
{
return Admob.hasConsentForPurpose(purpose);
}
/**
* Checks if the user has given consent for all ad purposes.
* This is typically required for GDPR compliance, where each purpose (0-9) needs to be individually consented.
* @return Bool indicating whether the user has consented to all purposes.
*/
public static function hasFullConsent():Bool
{
for (purpose in 0...10)
{
if (Admob.hasConsentForPurpose(purpose) != 1) return false;
}
return true;
}
/**
* Retrieves the current user's consent status as a string.
* Useful for GDPR compliance to understand if ads can be personalized.
* @return A String with the consent status.
*/
public static inline function getConsent():String
{
return Admob.getConsent();
}
/**
* Determines if showing a privacy options form is required based on regional laws.
* @return A Bool indicating if a privacy options form is required (true if required, false if not required).
*/
public static inline function isPrivacyOptionsRequired():Bool
{
return Admob.isPrivacyOptionsRequired() == 1;
}
/**
* Displays the privacy options form to the user, allowing them to adjust consent.
* Useful for GDPR and other privacy regulations compliance.
*/
public static inline function showPrivacyOptionsForm():Void
{
Admob.showPrivacyOptionsForm();
}
}
+38
View File
@@ -58,6 +58,9 @@ import haxe.Int64;
#if mobile
import funkin.mobile.util.TouchUtil;
import funkin.mobile.ui.FunkinHitbox;
#if NO_DISABLE_ADMOB_ADS
import funkin.mobile.util.AdMobUtil;
#end
#end
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;
@@ -987,6 +990,11 @@ class PlayState extends MusicBeatSubState
// Attempt to pause the game.
if ((controls.PAUSE || androidPause || pauseButtonCheck) && isInCountdown && mayPauseGame && !justUnpaused)
{
// Add banner ad when game is paused.
#if NO_DISABLE_ADMOB_ADS
AdMobUtil.addBanner(extension.admob.Admob.BANNER_SIZE_BANNER, extension.admob.Admob.BANNER_ALIGN_TOP);
#end
#if mobile
pauseButton.alpha = 0;
#end
@@ -1173,6 +1181,12 @@ class PlayState extends MusicBeatSubState
function moveToGameOver():Void
{
// Shows a video ad on mobile devices each 3 blueballs
#if NO_DISABLE_ADMOB_ADS
Constants.GLOBAL_BLUEBALL_COUNTER++;
if (Constants.GLOBAL_BLUEBALL_COUNTER > 0 && Constants.GLOBAL_BLUEBALL_COUNTER % 3 == 0) AdMobUtil.loadInterstitial(true);
#end
// Reset and update a bunch of values in advance for the transition back from the game over substate.
playerStrumline.clean();
opponentStrumline.clean();
@@ -1323,6 +1337,11 @@ class PlayState extends MusicBeatSubState
*/
public override function closeSubState():Void
{
// Remove banner ad when game is unpaused.
#if NO_DISABLE_ADMOB_ADS
AdMobUtil.removeBanner();
#end
if (Std.isOfType(subState, PauseSubState))
{
var event:ScriptEvent = new ScriptEvent(RESUME, true);
@@ -1605,6 +1624,11 @@ class PlayState extends MusicBeatSubState
{
performCleanup();
// Remove banner ad when playstate is destroyed.
#if NO_DISABLE_ADMOB_ADS
AdMobUtil.removeBanner();
#end
super.destroy();
}
@@ -3417,6 +3441,20 @@ class PlayState extends MusicBeatSubState
var talliesToUse:Tallies = PlayStatePlaylist.isStoryMode ? Highscore.talliesLevel : Highscore.tallies;
// Shows a video ad on mobile devices each 3 blueballs
#if NO_DISABLE_ADMOB_ADS
if (PlayStatePlaylist.isStoryMode)
{
AdMobUtil.loadInterstitial(true);
}
else
{
Constants.GLOBAL_FREEPLAY_VICTORY_COUNTER++;
if ((Constants.GLOBAL_FREEPLAY_VICTORY_COUNTER > 0
&& Constants.GLOBAL_FREEPLAY_VICTORY_COUNTER % 3 == 0)) AdMobUtil.loadInterstitial(true);
}
#end
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
+14
View File
@@ -587,4 +587,18 @@ class Constants
* 0.04 = 4% of distance per frame.
*/
public static final DEFAULT_CAMERA_FOLLOW_RATE:Float = 0.04;
#if NO_DISABLE_ADMOB_ADS
/**
* A Counter that is increased by 1 each blueball.
* Only used for video ads each 3 blueballs on mobile.
*/
public static var GLOBAL_BLUEBALL_COUNTER:Int = 0;
/**
* A Counter that is increased by 1 each victory only in freep;ay songs.
* Only used for video ads each 3 victories on mobile.
*/
public static var GLOBAL_FREEPLAY_VICTORY_COUNTER:Int = 0;
#end
}
+10 -3
View File
@@ -51,9 +51,16 @@ class CrashHandler
{
errorSignal.dispatch(generateErrorMessage(error));
#if sys
logError(error);
#end
try
{
#if sys
logError(error);
#end
}
catch (e:Dynamic)
{
trace('Error while logging error: ' + e);
}
displayError(error);
}