← Volver al listado de tecnologías
Integración de Anuncios (AdMob, Unity Ads, etc)
Integración de Anuncios (AdMob, Unity Ads, etc)
La monetización a través de publicidad es fundamental para el éxito de los juegos móviles gratuitos. Esta guía te enseñará a integrar diferentes redes publicitarias en tu juego Defold.
Configuración Base para Publicidad
1. Extension Setup
Instalación de Extensiones
# Añadir extensiones al game.project
[dependencies]
https://github.com/defold/extension-admob/archive/main.zip
https://github.com/defold/extension-unityads/archive/main.zip
https://github.com/defold/extension-ironsource/archive/main.zip
Configuración game.project
[android]
# AdMob App ID
manifest = /bundles/android/AndroidManifest.xml
[ios]
# AdMob App ID
infoplist = /bundles/ios/Info.plist
[project]
dependencies = https://github.com/defold/extension-admob/archive/main.zip,https://github.com/defold/extension-unityads/archive/main.zip
2. Ads Manager Base
Sistema de Gestión de Anuncios
-- ads_manager.script
local M = {}
function init(self)
self.initialized = false
self.current_provider = nil
self.providers = {}
self.ad_queue = {}
-- Configuración de anuncios
self.config = {
enabled = true,
test_mode = sys.get_config("project.debug", "0") == "1",
show_delay = 2.0, -- Delay mínimo entre anuncios
last_ad_time = 0,
retry_delay = 30.0, -- Retry fallido después de 30s
max_retries = 3
}
-- Estadísticas
self.stats = {
ads_requested = 0,
ads_shown = 0,
ads_clicked = 0,
ads_failed = 0,
revenue = 0
}
-- Estados de anuncios
self.ad_states = {
banner = {loaded = false, showing = false},
interstitial = {loaded = false, showing = false},
rewarded = {loaded = false, showing = false}
}
print("Ads Manager initialized")
end
function M.initialize_ads(self, providers_config)
if self.initialized then return end
self.providers_config = providers_config
-- Inicializar proveedores en orden de prioridad
for _, provider_config in ipairs(providers_config) do
self:initialize_provider(provider_config)
end
self.initialized = true
print("Ads system initialized with " .. #providers_config .. " providers")
end
function M.initialize_provider(self, config)
local provider = {
name = config.name,
priority = config.priority,
enabled = true,
initialized = false,
error_count = 0
}
if config.name == "admob" then
self:initialize_admob(config, provider)
elseif config.name == "unity" then
self:initialize_unity_ads(config, provider)
elseif config.name == "ironsource" then
self:initialize_ironsource(config, provider)
end
table.insert(self.providers, provider)
-- Ordenar por prioridad
table.sort(self.providers, function(a, b)
return a.priority < b.priority
end)
end
function M.get_active_provider(self)
for _, provider in ipairs(self.providers) do
if provider.enabled and provider.initialized then
return provider
end
end
return nil
end
function M.show_ad(self, ad_type, placement, callback)
if not self:can_show_ad() then
if callback then callback(false, "Ad not ready or too soon") end
return false
end
local provider = self:get_active_provider()
if not provider then
if callback then callback(false, "No active provider") end
return false
end
self.stats.ads_requested = self.stats.ads_requested + 1
local ad_data = {
type = ad_type,
placement = placement,
callback = callback,
provider = provider.name,
timestamp = socket.gettime()
}
self:show_ad_with_provider(provider, ad_data)
return true
end
function M.can_show_ad(self)
if not self.config.enabled then return false end
local current_time = socket.gettime()
return current_time - self.config.last_ad_time >= self.config.show_delay
end
return M
Integración AdMob
1. Configuración AdMob
AndroidManifest.xml
<!-- AdMob configuration -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
<!-- Optional: AdMob optimization -->
<meta-data
android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
android:value="true"/>
<!-- Network security config -->
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
Info.plist (iOS)
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<!-- Más IDs de SKAdNetwork según necesidades -->
</array>
2. AdMob Implementation
AdMob Manager
-- admob_manager.lua
local M = {}
function M.initialize(config, provider)
if admob then
-- Configurar AdMob
local admob_config = {
app_id = config.app_id,
test_mode = config.test_mode or false
}
admob.set_callback(function(self, message_id, message)
M.handle_admob_callback(self, message_id, message, provider)
end)
admob.initialize(admob_config, function(self, result)
if result.success then
provider.initialized = true
print("AdMob initialized successfully")
-- Precargar anuncios
M.preload_ads(config)
else
provider.enabled = false
print("AdMob initialization failed: " .. (result.error or "Unknown error"))
end
end)
else
print("AdMob extension not available")
provider.enabled = false
end
end
function M.preload_ads(config)
-- Precargar banner
if config.banner_id then
admob.load_banner(config.banner_id, admob.SIZE_BANNER)
end
-- Precargar interstitial
if config.interstitial_id then
admob.load_interstitial(config.interstitial_id)
end
-- Precargar rewarded
if config.rewarded_id then
admob.load_rewarded_video(config.rewarded_id)
end
end
function M.handle_admob_callback(self, message_id, message, provider)
local ads_manager = require "main.ads_manager"
if message_id == admob.MSG_BANNER then
ads_manager:handle_banner_event(message, "admob")
elseif message_id == admob.MSG_INTERSTITIAL then
ads_manager:handle_interstitial_event(message, "admob")
elseif message_id == admob.MSG_REWARDED then
ads_manager:handle_rewarded_event(message, "admob")
elseif message_id == admob.MSG_INITIALIZATION then
if message.event == admob.EVENT_COMPLETED then
print("AdMob initialization completed")
elseif message.event == admob.EVENT_JSON_ERROR then
print("AdMob JSON error: " .. (message.error or "Unknown"))
provider.error_count = provider.error_count + 1
end
end
end
function M.show_banner(config, placement)
if not admob or not config.banner_id then return false end
local position = placement == "top" and admob.POS_TOP_CENTER or admob.POS_BOTTOM_CENTER
admob.show_banner(config.banner_id, position)
return true
end
function M.hide_banner()
if admob then
admob.hide_banner()
end
end
function M.show_interstitial(config)
if not admob or not config.interstitial_id then return false end
if admob.is_interstitial_loaded(config.interstitial_id) then
admob.show_interstitial(config.interstitial_id)
return true
else
-- Recargar si no está disponible
admob.load_interstitial(config.interstitial_id)
return false
end
end
function M.show_rewarded(config)
if not admob or not config.rewarded_id then return false end
if admob.is_rewarded_video_loaded(config.rewarded_id) then
admob.show_rewarded_video(config.rewarded_id)
return true
else
-- Recargar si no está disponible
admob.load_rewarded_video(config.rewarded_id)
return false
end
end
return M
Integración Unity Ads
1. Unity Ads Setup
Unity Ads Manager
-- unity_ads_manager.lua
local M = {}
function M.initialize(config, provider)
if unityads then
local unity_config = {
game_id = config.game_id,
test_mode = config.test_mode or false
}
unityads.set_callback(function(self, message_id, message)
M.handle_unity_callback(self, message_id, message, provider)
end)
unityads.initialize(unity_config, function(self, result)
if result.success then
provider.initialized = true
print("Unity Ads initialized successfully")
-- Configurar placements
M.setup_placements(config)
else
provider.enabled = false
print("Unity Ads initialization failed: " .. (result.error or "Unknown error"))
end
end)
else
print("Unity Ads extension not available")
provider.enabled = false
end
end
function M.setup_placements(config)
-- Unity Ads usa placements predefinidos
for placement_name, placement_config in pairs(config.placements or {}) do
print("Unity Ads placement configured: " .. placement_name)
end
end
function M.handle_unity_callback(self, message_id, message, provider)
local ads_manager = require "main.ads_manager"
if message_id == unityads.MSG_INIT then
if message.event == unityads.EVENT_COMPLETED then
print("Unity Ads initialization completed")
elseif message.event == unityads.EVENT_FAILED then
print("Unity Ads initialization failed: " .. (message.error or "Unknown"))
provider.error_count = provider.error_count + 1
end
elseif message_id == unityads.MSG_LOAD then
if message.event == unityads.EVENT_LOADED then
ads_manager:handle_ad_loaded(message.placement_id, "unity")
elseif message.event == unityads.EVENT_FAILED_TO_LOAD then
ads_manager:handle_ad_load_failed(message.placement_id, "unity", message.error)
end
elseif message_id == unityads.MSG_SHOW then
if message.event == unityads.EVENT_START then
ads_manager:handle_ad_started(message.placement_id, "unity")
elseif message.event == unityads.EVENT_COMPLETED then
ads_manager:handle_ad_completed(message.placement_id, "unity", message.rewarded)
elseif message.event == unityads.EVENT_SKIPPED then
ads_manager:handle_ad_skipped(message.placement_id, "unity")
elseif message.event == unityads.EVENT_FAILED_TO_SHOW then
ads_manager:handle_ad_show_failed(message.placement_id, "unity", message.error)
end
end
end
function M.show_ad(config, placement_id, ad_type)
if not unityads then return false end
if unityads.is_ready(placement_id) then
unityads.show(placement_id)
return true
else
-- Cargar anuncio
unityads.load(placement_id)
return false
end
end
function M.is_ready(placement_id)
return unityads and unityads.is_ready(placement_id)
end
return M
Sistema de Waterfall
1. Ad Mediation
Waterfall Manager
-- waterfall_manager.lua
local M = {}
function init(self)
self.waterfall_config = {}
self.current_attempt = 1
self.max_attempts = 3
self.fallback_providers = {}
end
function M.setup_waterfall(ad_type, providers)
self.waterfall_config[ad_type] = {
providers = providers,
current_index = 1,
attempts = 0
}
end
function M.show_ad_with_waterfall(self, ad_type, placement, callback)
local config = self.waterfall_config[ad_type]
if not config or #config.providers == 0 then
if callback then callback(false, "No providers configured") end
return false
end
self:attempt_next_provider(ad_type, placement, callback)
return true
end
function attempt_next_provider(self, ad_type, placement, callback)
local config = self.waterfall_config[ad_type]
if config.current_index > #config.providers then
-- Todos los proveedores fallaron
if callback then callback(false, "All providers failed") end
self:reset_waterfall(ad_type)
return
end
local provider = config.providers[config.current_index]
config.attempts = config.attempts + 1
print(string.format("Attempting ad with provider %s (attempt %d)",
provider.name, config.attempts))
-- Intentar mostrar anuncio con el proveedor actual
local success = self:try_provider(provider, ad_type, placement, function(success, error)
if success then
-- Éxito - resetear waterfall para próxima vez
self:reset_waterfall(ad_type)
if callback then callback(true) end
else
-- Falló - intentar siguiente proveedor
print(string.format("Provider %s failed: %s", provider.name, error or "Unknown"))
config.current_index = config.current_index + 1
-- Delay antes del siguiente intento
timer.delay(1.0, false, function()
self:attempt_next_provider(ad_type, placement, callback)
end)
end
end)
if not success then
-- Provider no disponible inmediatamente
config.current_index = config.current_index + 1
self:attempt_next_provider(ad_type, placement, callback)
end
end
function try_provider(self, provider, ad_type, placement, callback)
if provider.name == "admob" then
return self:try_admob(provider, ad_type, placement, callback)
elseif provider.name == "unity" then
return self:try_unity_ads(provider, ad_type, placement, callback)
elseif provider.name == "ironsource" then
return self:try_ironsource(provider, ad_type, placement, callback)
end
return false
end
function reset_waterfall(self, ad_type)
local config = self.waterfall_config[ad_type]
if config then
config.current_index = 1
config.attempts = 0
end
end
return M
Ad Placement Strategy
1. Intelligent Ad Timing
Ad Timing Manager
-- ad_timing_manager.lua
local M = {}
function init(self)
self.placement_rules = {}
self.user_behavior = {
session_start = socket.gettime(),
games_played = 0,
total_playtime = 0,
last_ad_shown = 0,
ad_fatigue_level = 0,
engagement_score = 1.0
}
self.timing_config = {
min_session_time = 30, -- 30s mínimo antes del primer ad
min_between_ads = 120, -- 2 minutos entre ads
max_ads_per_session = 5, -- Máximo 5 ads por sesión
fatigue_threshold = 0.7, -- Threshold de fatiga
engagement_bonus = 1.2 -- Bonus por alto engagement
}
end
function M.can_show_ad(self, placement)
local current_time = socket.gettime()
local session_time = current_time - self.user_behavior.session_start
-- Verificar tiempo mínimo de sesión
if session_time < self.timing_config.min_session_time then
return false, "Session too short"
end
-- Verificar tiempo entre anuncios
local time_since_last_ad = current_time - self.user_behavior.last_ad_shown
if time_since_last_ad < self.timing_config.min_between_ads then
return false, "Too soon since last ad"
end
-- Verificar límite de anuncios por sesión
if self.user_behavior.ads_shown_this_session >= self.timing_config.max_ads_per_session then
return false, "Session ad limit reached"
end
-- Verificar fatiga del usuario
if self.user_behavior.ad_fatigue_level > self.timing_config.fatigue_threshold then
return false, "User ad fatigue too high"
end
-- Verificar reglas específicas del placement
local rule = self.placement_rules[placement]
if rule and not self:check_placement_rule(rule) then
return false, "Placement rule not met"
end
return true
end
function M.add_placement_rule(self, placement, rule)
self.placement_rules[placement] = rule
end
function check_placement_rule(self, rule)
if rule.min_level and self.user_behavior.current_level < rule.min_level then
return false
end
if rule.min_games and self.user_behavior.games_played < rule.min_games then
return false
end
if rule.min_playtime and self.user_behavior.total_playtime < rule.min_playtime then
return false
end
if rule.only_after_death and not self.user_behavior.just_died then
return false
end
return true
end
function M.record_ad_shown(self, placement, success)
local current_time = socket.gettime()
if success then
self.user_behavior.last_ad_shown = current_time
self.user_behavior.ads_shown_this_session =
(self.user_behavior.ads_shown_this_session or 0) + 1
-- Incrementar fatiga
self.user_behavior.ad_fatigue_level =
math.min(1.0, self.user_behavior.ad_fatigue_level + 0.1)
print(string.format("Ad shown successfully. Fatigue level: %.2f",
self.user_behavior.ad_fatigue_level))
else
-- Ad fallido - reducir ligeramente la fatiga
self.user_behavior.ad_fatigue_level =
math.max(0.0, self.user_behavior.ad_fatigue_level - 0.05)
end
end
function M.update_engagement(self, engagement_delta)
self.user_behavior.engagement_score =
math.max(0.1, math.min(2.0, self.user_behavior.engagement_score + engagement_delta))
-- Reducir fatiga con alto engagement
if self.user_behavior.engagement_score > 1.0 then
self.user_behavior.ad_fatigue_level =
math.max(0.0, self.user_behavior.ad_fatigue_level - 0.02)
end
end
return M
2. Rewarded Video Strategy
Rewarded Ads Manager
-- rewarded_ads_manager.lua
local M = {}
function init(self)
self.rewards = {}
self.watch_tracking = {}
self.incentive_config = {
coins_multiplier = 2,
extra_lives = 1,
power_ups = true,
bonus_xp = 50
}
end
function M.offer_rewarded_ad(self, reward_type, base_reward, context)
local incentive = self:calculate_incentive(reward_type, base_reward, context)
-- Mostrar oferta al usuario
msg.post("main:/ui", "show_reward_offer", {
reward_type = reward_type,
base_reward = base_reward,
bonus_reward = incentive.bonus,
total_reward = incentive.total,
context = context
})
return incentive
end
function calculate_incentive(self, reward_type, base_reward, context)
local multiplier = 1.0
local bonus = 0
if reward_type == "coins" then
multiplier = self.incentive_config.coins_multiplier
bonus = base_reward * (multiplier - 1)
elseif reward_type == "lives" then
bonus = self.incentive_config.extra_lives
elseif reward_type == "xp" then
bonus = self.incentive_config.bonus_xp
elseif reward_type == "power_up" then
bonus = 1 -- Un power-up adicional
end
-- Ajustar según contexto
if context == "game_over" then
multiplier = multiplier * 1.5 -- Bonus extra en game over
elseif context == "level_complete" then
multiplier = multiplier * 1.2 -- Bonus menor en level complete
end
return {
base = base_reward,
bonus = math.floor(bonus * multiplier),
total = base_reward + math.floor(bonus * multiplier),
multiplier = multiplier
}
end
function M.show_rewarded_ad(self, reward_data, callback)
local ads_manager = require "main.ads_manager"
-- Trackear el intento
self.watch_tracking[reward_data.id] = {
start_time = socket.gettime(),
reward_data = reward_data,
callback = callback
}
ads_manager:show_ad("rewarded", "game_reward", function(success, error)
self:handle_rewarded_result(reward_data.id, success, error)
end)
end
function handle_rewarded_result(self, reward_id, success, error)
local tracking = self.watch_tracking[reward_id]
if not tracking then return end
local watch_time = socket.gettime() - tracking.start_time
if success then
-- Otorgar recompensa
self:grant_reward(tracking.reward_data)
-- Registrar analytics
self:track_rewarded_view(tracking.reward_data, watch_time, true)
if tracking.callback then
tracking.callback(true, tracking.reward_data)
end
print("Rewarded ad completed - reward granted")
else
print("Rewarded ad failed: " .. (error or "Unknown error"))
if tracking.callback then
tracking.callback(false, error)
end
end
self.watch_tracking[reward_id] = nil
end
function grant_reward(self, reward_data)
if reward_data.type == "coins" then
msg.post("main:/game_state", "add_coins", {amount = reward_data.total})
elseif reward_data.type == "lives" then
msg.post("main:/game_state", "add_lives", {amount = reward_data.total})
elseif reward_data.type == "power_up" then
msg.post("main:/game_state", "add_power_up", {
type = reward_data.power_up_type,
quantity = reward_data.total
})
elseif reward_data.type == "continue" then
msg.post("main:/game_state", "continue_game")
elseif reward_data.type == "xp" then
msg.post("main:/game_state", "add_xp", {amount = reward_data.total})
end
-- Mostrar feedback visual
msg.post("main:/ui", "show_reward_feedback", reward_data)
end
function track_rewarded_view(self, reward_data, watch_time, completed)
-- Enviar a analytics
msg.post("main:/analytics", "track_event", {
event = "rewarded_ad_viewed",
properties = {
reward_type = reward_data.type,
reward_amount = reward_data.total,
watch_time = watch_time,
completed = completed,
context = reward_data.context
}
})
end
return M
Ad Revenue Optimization
1. A/B Testing Framework
Ad Testing Manager
-- ad_testing_manager.lua
local M = {}
function init(self)
self.active_tests = {}
self.user_group = self:determine_user_group()
self.test_results = {}
end
function M.determine_user_group(self)
-- Usar ID del dispositivo para asignar grupo consistente
local device_id = sys.get_sys_info().device_ident or "unknown"
local hash = 0
for i = 1, #device_id do
hash = hash + string.byte(device_id, i)
end
-- Distribuir en grupos A/B/C
local group_number = hash % 3
local groups = {"A", "B", "C"}
return groups[group_number + 1]
end
function M.create_test(self, test_name, variants)
self.active_tests[test_name] = {
variants = variants,
user_variant = variants[self.user_group] or variants["A"],
start_time = socket.gettime(),
metrics = {
impressions = 0,
clicks = 0,
revenue = 0,
completion_rate = 0
}
}
print(string.format("A/B Test '%s' started. User in group %s",
test_name, self.user_group))
end
function M.get_variant(self, test_name)
local test = self.active_tests[test_name]
return test and test.user_variant or nil
end
function M.track_test_metric(self, test_name, metric, value)
local test = self.active_tests[test_name]
if test then
test.metrics[metric] = test.metrics[metric] + value
-- Enviar a analytics
msg.post("main:/analytics", "track_ab_test", {
test = test_name,
variant = test.user_variant.name,
metric = metric,
value = value
})
end
end
-- Ejemplo de test de posición de banner
function M.setup_banner_position_test(self)
self:create_test("banner_position", {
A = {name = "top", position = "top", multiplier = 1.0},
B = {name = "bottom", position = "bottom", multiplier = 1.1},
C = {name = "smart", position = "smart", multiplier = 0.9}
})
end
-- Ejemplo de test de frecuencia de interstitials
function M.setup_interstitial_frequency_test(self)
self:create_test("interstitial_frequency", {
A = {name = "low", interval = 180, multiplier = 1.0}, -- 3 minutos
B = {name = "medium", interval = 120, multiplier = 1.5}, -- 2 minutos
C = {name = "high", interval = 90, multiplier = 2.0} -- 1.5 minutos
})
end
return M
2. Revenue Tracking
Revenue Analytics
-- revenue_analytics.lua
local M = {}
function init(self)
self.revenue_data = {
daily_revenue = 0,
session_revenue = 0,
lifetime_revenue = 0,
ad_sources = {},
ecpm_tracking = {}
}
self.tracking_config = {
currency = "USD",
precision = 0.001, -- Track down to $0.001
session_id = self:generate_session_id()
}
end
function M.track_ad_revenue(self, provider, ad_type, revenue, currency)
currency = currency or self.tracking_config.currency
-- Convertir a USD si es necesario
local usd_revenue = self:convert_to_usd(revenue, currency)
-- Actualizar totales
self.revenue_data.session_revenue = self.revenue_data.session_revenue + usd_revenue
self.revenue_data.daily_revenue = self.revenue_data.daily_revenue + usd_revenue
self.revenue_data.lifetime_revenue = self.revenue_data.lifetime_revenue + usd_revenue
-- Trackear por fuente
if not self.revenue_data.ad_sources[provider] then
self.revenue_data.ad_sources[provider] = {
revenue = 0,
impressions = 0,
ecpm = 0
}
end
local source = self.revenue_data.ad_sources[provider]
source.revenue = source.revenue + usd_revenue
source.impressions = source.impressions + 1
source.ecpm = (source.revenue / source.impressions) * 1000
-- Enviar a analytics externos
self:send_revenue_event(provider, ad_type, usd_revenue)
print(string.format("Revenue tracked: $%.3f from %s %s (eCPM: $%.2f)",
usd_revenue, provider, ad_type, source.ecpm))
end
function send_revenue_event(self, provider, ad_type, revenue)
-- Enviar a múltiples plataformas de analytics
local event_data = {
event = "ad_revenue",
properties = {
provider = provider,
ad_type = ad_type,
revenue = revenue,
currency = "USD",
session_id = self.tracking_config.session_id,
timestamp = socket.gettime()
}
}
-- Enviar a Firebase Analytics
msg.post("main:/firebase", "track_revenue", event_data)
-- Enviar a Facebook Analytics
msg.post("main:/facebook", "track_purchase", {
amount = revenue,
currency = "USD",
content_type = "ad_revenue",
content_id = provider .. "_" .. ad_type
})
-- Enviar a AppsFlyer
msg.post("main:/appsflyer", "track_revenue", {
revenue = revenue,
currency = "USD",
receipt_id = self:generate_receipt_id(),
product_id = provider .. "_ad"
})
end
function generate_session_id(self)
return "session_" .. os.time() .. "_" .. math.random(1000, 9999)
end
function generate_receipt_id(self)
return "ad_" .. os.time() .. "_" .. math.random(100000, 999999)
end
function convert_to_usd(self, amount, currency)
-- Tasas de cambio simplificadas (en producción usar API real)
local exchange_rates = {
USD = 1.0,
EUR = 1.18,
GBP = 1.37,
JPY = 0.0091,
CAD = 0.79
}
return amount * (exchange_rates[currency] or 1.0)
end
return M
Debugging y Testing
1. Ad Debug Console
Debug Ad System
-- ad_debug.gui_script
function init(self)
self.debug_enabled = sys.get_config("project.debug", "0") == "1"
self.ad_log = {}
self.max_log_entries = 50
if self.debug_enabled then
self:create_debug_ui()
end
end
function create_debug_ui(self)
-- Crear panel de debug
self.debug_panel = gui.get_node("debug_panel")
self.log_text = gui.get_node("log_text")
self.test_buttons = {
banner = gui.get_node("test_banner"),
interstitial = gui.get_node("test_interstitial"),
rewarded = gui.get_node("test_rewarded")
}
-- Mostrar estadísticas iniciales
self:update_debug_display()
end
function on_message(self, message_id, message, sender)
if not self.debug_enabled then return end
if message_id == hash("ad_event") then
self:log_ad_event(message)
self:update_debug_display()
elseif message_id == hash("update_stats") then
self:update_debug_display()
end
end
function log_ad_event(self, event)
local log_entry = string.format("[%s] %s - %s: %s",
os.date("%H:%M:%S"),
event.provider or "unknown",
event.ad_type or "unknown",
event.event_type or "unknown"
)
table.insert(self.ad_log, log_entry)
-- Mantener solo las últimas entradas
if #self.ad_log > self.max_log_entries then
table.remove(self.ad_log, 1)
end
end
function update_debug_display(self)
local ads_manager = require "main.ads_manager"
local stats = ads_manager:get_stats()
local debug_text = string.format(
"=== AD DEBUG INFO ===\n" ..
"Requested: %d\n" ..
"Shown: %d\n" ..
"Failed: %d\n" ..
"Success Rate: %.1f%%\n" ..
"Revenue: $%.3f\n\n" ..
"=== RECENT EVENTS ===\n%s",
stats.ads_requested,
stats.ads_shown,
stats.ads_failed,
stats.ads_shown > 0 and (stats.ads_shown / stats.ads_requested * 100) or 0,
stats.revenue,
table.concat(self.ad_log, "\n")
)
gui.set_text(self.log_text, debug_text)
end
function on_input(self, action_id, action)
if not self.debug_enabled or not action.pressed then
return false
end
if action_id == hash("touch") then
-- Test banner
if gui.pick_node(self.test_buttons.banner, action.x, action.y) then
msg.post("main:/ads_manager", "test_ad", {type = "banner"})
return true
-- Test interstitial
elseif gui.pick_node(self.test_buttons.interstitial, action.x, action.y) then
msg.post("main:/ads_manager", "test_ad", {type = "interstitial"})
return true
-- Test rewarded
elseif gui.pick_node(self.test_buttons.rewarded, action.x, action.y) then
msg.post("main:/ads_manager", "test_ad", {type = "rewarded"})
return true
end
end
return false
end
Configuración de Producción
1. Ad Configuration File
ads_config.json
{
"providers": [
{
"name": "admob",
"priority": 1,
"enabled": true,
"config": {
"app_id": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy",
"banner_id": "ca-app-pub-xxxxxxxxxxxxxxxx/zzzzzzzzzz",
"interstitial_id": "ca-app-pub-xxxxxxxxxxxxxxxx/aaaaaaaaaa",
"rewarded_id": "ca-app-pub-xxxxxxxxxxxxxxxx/bbbbbbbbbb",
"test_mode": false
}
},
{
"name": "unity",
"priority": 2,
"enabled": true,
"config": {
"game_id": "1234567",
"placements": {
"banner": "banner_placement",
"interstitial": "video_placement",
"rewarded": "rewarded_placement"
},
"test_mode": false
}
}
],
"timing": {
"min_session_time": 30,
"min_between_ads": 120,
"max_ads_per_session": 5
},
"ab_tests": {
"banner_position": {
"enabled": true,
"variants": ["top", "bottom", "smart"]
}
}
}
Mejores Prácticas
1. User Experience
- No interrumpir gameplay con anuncios
- Ofrecer valor real con rewarded videos
- Respetar las decisiones del usuario
- Implementar frequency capping
2. Performance
- Precargar anuncios antes de mostrarlos
- Implementar timeouts apropiados
- Manejar errores gracefully
- Optimizar para diferentes conexiones
3. Revenue Optimization
- Implementar A/B testing
- Usar waterfall/mediation
- Trackear métricas detalladamente
- Optimizar placement timing
4. Compliance
- Seguir políticas de las ad networks
- Implementar GDPR/CCPA compliance
- Respetar regulaciones de menores
- Proveer opciones de opt-out
Esta implementación completa te permite monetizar tu juego efectivamente mientras mantienes una excelente experiencia de usuario.