Initial Void Bastion 0.20 prototype

This commit is contained in:
Emporex committed 2026-10-03 21:28:56 +02:00
commit 7362ca0f02
251 files changed
+29688

No files matched your search

+350
View File
@@ -0,0 +1,350 @@
#
# © 2024-present https://github.com/cengiz-pz
#
@icon("icon.png")
class_name InlineAdaptiveBanner
extends Control
const MINIMUM_VALID_WIDTH: float = 50.0
const MINIMUM_CLIP_THRESHOLD: float = 0.05
const MAXIMUM_CLIP_THRESHOLD: float = 1.0
const MINIMUM_RESIZE_THRESHOLD: float = 10.0
const MAXIMUM_RESIZE_THRESHOLD: float = 500.0
## Maximum pixel height of the requested ad. If set to -1, height will be automatically determined.
@export var max_ad_height: int = -1
## Minimum portion of the banner’s area (as a percentage) that must be clipped before the banner is considered hidden.
@export_range(MINIMUM_CLIP_THRESHOLD, MAXIMUM_CLIP_THRESHOLD, 0.05) var clip_threshold: float = 0.1:
set(a_value):
clip_threshold = clampf(a_value, MINIMUM_CLIP_THRESHOLD, MAXIMUM_CLIP_THRESHOLD)
## Width-change threshold (in pixels) that triggers a banner reload when the ad container is resized by this amount
## or more.
@export_range(MINIMUM_RESIZE_THRESHOLD, MAXIMUM_RESIZE_THRESHOLD, 5.0) var resize_threshold: float = 50.0:
set(a_value):
resize_threshold = clampf(a_value, MINIMUM_RESIZE_THRESHOLD, MAXIMUM_RESIZE_THRESHOLD)
## Path to Admob node.
@export_node_path("Admob") var admob_path: NodePath
## Whether the inline-adaptive banner ad should be automatically loaded.
@export var auto_load: bool = false
@export_category("Ad Unit Identifier")
## Use ad unit identifier value defined in the Admob node.
@export var inherit_from_admob_node: bool = false
@export_group("Android", "ad_unit_id_android")
## Ad unit ID of the inline adaptive banner ad to be loaded on the Android platform.
@export var ad_unit_id_android_debug: String = "ca-app-pub-3940256099942544/2014213617"
## Ad unit ID of the inline adaptive banner ad to be loaded on the Android platform.
@export var ad_unit_id_android_real: String = ""
@export_group("iOS", "ad_unit_id_ios")
## Ad unit ID of the inline adaptive banner ad to be loaded on the iOS platform.
@export var ad_unit_id_ios_debug: String = "ca-app-pub-3940256099942544/2934735716"
## Ad unit ID of the inline adaptive banner ad to be loaded on the iOS platform.
@export var ad_unit_id_ios_real: String = ""
var admob: Admob
var _ad_unit_id: String
var ad_id: String = ""
var last_width: float
var pending_load: bool = false
var is_position_dirty: bool = false
var is_banner_visible: bool = false
var do_ignore_resize: bool = false
func _enter_tree() -> void:
var __admob: Admob
if admob_path:
__admob = get_node(admob_path)
if __admob:
initialize(__admob)
else:
push_warning("Admob node not found")
func _exit_tree() -> void:
if ad_id != "":
remove_ad()
func _ready() -> void:
if Engine.is_editor_hint():
return
size_flags_vertical = Control.SIZE_SHRINK_CENTER
last_width = size.x
resized.connect(_on_resized)
func initialize(a_admob_node: Admob) -> void:
admob = a_admob_node
visibility_changed.connect(_on_visibility_changed)
admob.banner_ad_loaded.connect(_on_banner_ad_loaded)
admob.banner_ad_failed_to_load.connect(_on_banner_ad_failed_to_load)
admob.banner_ad_refreshed.connect(_on_banner_ad_refreshed)
admob.banner_ad_size_measured.connect(_on_banner_ad_size_measured)
if admob.is_initialization_completed:
if auto_load:
call_deferred("_deferred_load_ad")
else:
admob.initialization_completed.connect(_on_Admob_initialization_completed)
if inherit_from_admob_node:
_ad_unit_id = admob._banner_id
else:
if OS.has_feature("ios"):
if admob.is_real:
_ad_unit_id = ad_unit_id_ios_real
else:
_ad_unit_id = ad_unit_id_ios_debug
else:
if admob.is_real:
_ad_unit_id = ad_unit_id_android_real
else:
_ad_unit_id = ad_unit_id_android_debug
func _deferred_load_ad() -> void:
load_ad()
_mark_position_dirty()
# --------------------------------------------------------------
# AD MANIPULATION
# --------------------------------------------------------------
func load_ad() -> void:
if _ad_unit_id == "":
return
var req := LoadAdRequest.new()
req.set_ad_unit_id(_ad_unit_id)
req.set_ad_size(LoadAdRequest.RequestedAdSize.INLINE_ADAPTIVE)
if custom_minimum_size.x < MINIMUM_VALID_WIDTH:
GmpLogger.log_warn("Can't load ad. Invalid inline adaptive banner width: %.1f" % custom_minimum_size.x)
return
req.set_adaptive_width(round(custom_minimum_size.x))
if max_ad_height != -1:
req.set_adaptive_max_height(max_ad_height)
req.set_ad_position(LoadAdRequest.AdPosition.CUSTOM)
pending_load = true
admob.load_banner_ad(req)
func show_ad() -> void:
if not is_banner_visible:
admob.show_banner_ad(ad_id)
is_banner_visible = true
func hide_ad() -> void:
if is_banner_visible:
admob.hide_banner_ad(ad_id)
is_banner_visible = false
func move_ad(a_physical_position: Vector2) -> void:
admob.move_banner_ad(ad_id, a_physical_position.x, a_physical_position.y)
func remove_ad() -> void:
admob.remove_banner_ad(ad_id)
is_banner_visible = false
ad_id = ""
# --------------------------------------------------------------
# POSITIONING
# --------------------------------------------------------------
func _process(_delta: float) -> void:
if is_position_dirty:
_deferred_update_position()
is_position_dirty = false
else:
update_banner_position()
func _mark_position_dirty() -> void:
is_position_dirty = true
call_deferred("_deferred_update_position")
func _deferred_update_position() -> void:
update_banner_position()
func update_banner_position() -> void:
if ad_id == "":
return
# Check basic tree visibility (Tabs, hidden parents)
if not is_visible_in_tree():
hide_ad()
return
# Smart clipping (visibility logic)
var is_clipped_out: bool = false
if size.x > 1.0 and size.y > 1.0:
var virtual_rect := get_global_rect()
var original_area := virtual_rect.get_area()
var visible_region := virtual_rect
var parent = get_parent()
# Walk up the tree to find ScrollContainers or other clippers
while parent is Control:
if parent.clip_contents:
var parent_rect: Rect2 = parent.get_global_rect()
var intersection := visible_region.intersection(parent_rect)
# No intersection -> fully clipped (100%)
if intersection.get_area() <= 0:
is_clipped_out = true
break
visible_region = intersection
parent = parent.get_parent()
# Final check
if not get_viewport_rect().intersects(virtual_rect):
is_clipped_out = true
else:
# Calculate the percentage clipped
var visible_area := visible_region.get_area()
var clipped_ratio := 1.0 - (visible_area / original_area)
if clipped_ratio >= clip_threshold:
is_clipped_out = true
if is_clipped_out:
if is_banner_visible:
hide_ad()
else:
if not is_banner_visible:
show_ad()
move_ad(_godot_to_physical(global_position))
# --------------------------------------------------------------
# SIGNAL HANDLERS
# --------------------------------------------------------------
func _on_Admob_initialization_completed(_status: InitializationStatus) -> void:
if auto_load:
call_deferred("_deferred_load_ad")
func _on_resized() -> void:
if ad_id == "":
return
if do_ignore_resize:
do_ignore_resize = false
return
if abs(size.x - last_width) < resize_threshold:
return
last_width = size.x
remove_ad()
call_deferred("_deferred_load_ad")
_mark_position_dirty()
func _on_visibility_changed() -> void:
if ad_id != "":
if is_visible_in_tree():
show_ad()
else:
hide_ad()
func _on_banner_ad_loaded(loaded_ad_info: AdInfo, _response: ResponseInfo) -> void:
if !pending_load:
return
if loaded_ad_info.get_load_ad_request().get_ad_size() != LoadAdRequest.RequestedAdSize.INLINE_ADAPTIVE:
return
if loaded_ad_info.get_load_ad_request().get_ad_unit_id().casecmp_to(_ad_unit_id) != 0:
return
pending_load = false
ad_id = loaded_ad_info.get_ad_id()
_apply_loaded_size(Vector2(loaded_ad_info.get_measured_width(), loaded_ad_info.get_measured_height()))
_mark_position_dirty()
func _on_banner_ad_failed_to_load(failed_ad_info: AdInfo, _err: LoadAdError) -> void:
if failed_ad_info.get_load_ad_request().get_ad_size() != LoadAdRequest.RequestedAdSize.INLINE_ADAPTIVE:
return
if failed_ad_info.get_load_ad_request().get_ad_unit_id().casecmp_to(_ad_unit_id) != 0:
return
if pending_load:
pending_load = false
func _on_banner_ad_size_measured(ad_info: AdInfo) -> void:
if ad_info.get_ad_id().casecmp_to(ad_id) == 0:
_apply_loaded_size(Vector2(ad_info.get_measured_width(), ad_info.get_measured_height()))
func _on_banner_ad_refreshed(refreshed_ad_info: AdInfo, _response: ResponseInfo) -> void:
if refreshed_ad_info.get_ad_id().casecmp_to(ad_id) == 0:
hide_ad() # Prevent ad from briefly displaying out of position
_apply_loaded_size(Vector2(refreshed_ad_info.get_measured_width(), refreshed_ad_info.get_measured_height()))
_mark_position_dirty()
# --------------------------------------------------------------
# UTILITY
# --------------------------------------------------------------
func _apply_loaded_size(a_size: Vector2) -> void:
do_ignore_resize = true
custom_minimum_size = a_size
size = a_size
func _calculate_scale_factor() -> Vector2:
var screen_size: Vector2 = DisplayServer.window_get_size() as Vector2
var virtual_size: Vector2 = get_viewport().get_visible_rect().size
return screen_size / virtual_size
func _physical_to_godot(a_physical_size: Vector2) -> Vector2:
return a_physical_size / _calculate_scale_factor()
func _godot_to_physical(a_godot_size: Vector2) -> Vector2:
return a_godot_size * _calculate_scale_factor()