Space Ad Mobile 9.5.1
Space Ad Mobile makes ads easy to add into your mobile game: one unified workflow for LevelPlay, AdMob, Yandex.Ads across banners, interstitials and rewarded.
Loading...
Searching...
No Matches
BannerCanvasOffsetHelper.cs
1using System.Collections;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5
7{
13 [AddComponentMenu("SCG/SpaceAdMobile/" + nameof(BannerCanvasOffsetHelper))]
14 public class BannerCanvasOffsetHelper : MonoBehaviour
15 {
16 #region Constants
17
18 private const string SubRootName = "sub-root";
19 private const int MinOffsetApplyAttempts = 1;
20 private const int MaxOffsetApplyAttempts = 10;
21 private const float MinApplyOffsetDelaySeconds = 0f;
22 private const float MaxApplyOffsetDelaySeconds = 1f;
23
24#if UNITY_6000_4_OR_NEWER
25 private static readonly Dictionary<EntityId, Dictionary<BannerAnchor, EntityId>> sideLocksByContainer = new();
26#else
27 private static readonly Dictionary<int, Dictionary<BannerAnchor, int>> sideLocksByContainer = new();
28#endif
29
30 #endregion
31
32 #region Serialized fields
33
35 [Tooltip("Canvas that hosts UI content. If null, it is searched on this GameObject.")]
36 [SerializeField] private Canvas canvas;
37
39 [Tooltip("Banner ad component implementing IBannerAd.")]
40 [SerializeField] private AdUnitBehaviourBase bannerAdComponent;
41
43 [Tooltip("Optional banner ad controller that keeps a Banner reference available, including when the Banner persists between scenes.")]
44 [SerializeField] private AdControllerBase bannerAdController;
45
47 [Tooltip("Optional container to offset. If null, a default sub-root is created and used.")]
48 [SerializeField] private RectTransform explicitContainer;
49
51 [Tooltip("Delay in seconds between repeated offset applications. Zero waits until the end of the frame.")]
52 [Range(MinApplyOffsetDelaySeconds, MaxApplyOffsetDelaySeconds)]
53 [SerializeField] private float applyOffsetDelaySeconds = MinApplyOffsetDelaySeconds;
54
56 [Tooltip("Number of offset applications performed after a banner becomes visible.")]
57 [Range(MinOffsetApplyAttempts, MaxOffsetApplyAttempts)]
58 [SerializeField] private int offsetApplyAttempts = 1;
59
61 [Tooltip("Extra canvas-unit margin applied when the banner is anchored to the top.")]
62 [SerializeField] private float marginTop;
63
65 [Tooltip("Extra canvas-unit margin applied when the banner is anchored to the bottom.")]
66 [SerializeField] private float marginBottom;
67
69 [Tooltip("Extra canvas-unit margin applied when the banner is anchored to the left.")]
70 [SerializeField] private float marginLeft;
71
73 [Tooltip("Extra canvas-unit margin applied when the banner is anchored to the right.")]
74 [SerializeField] private float marginRight;
75
77 [Tooltip("When true, only the banner side is inset. When false, full shift is allowed if the opposite side is free.")]
78 [SerializeField] private bool shiftOnlyBannerSide = true;
79
81 [Tooltip("When true, safe-area inset on the banner side is subtracted from the applied banner offset.")]
82 [SerializeField] private bool accountForSafeArea = true;
83
84 #endregion
85
86 #region Runtime state
87
88 // This flag is read by callbacks that may be invoked from an ad SDK thread.
89 private volatile bool isComponentActive;
90
91 private IBannerAd bannerAd;
92 private RectTransform targetContainer;
93 private BannerAnchor appliedAnchor;
94 private (float top, float bottom, float left, float right) appliedInsets;
95 private bool hasAppliedInsets;
96 private Vector2 appliedShiftOffset;
97 private bool hasAppliedShiftOffset;
98 private bool hasInitialState;
99 private bool isSubscribed;
100 private Coroutine offsetRefreshCoroutine;
101 private int offsetRefreshVersion;
102
103 #endregion
104
105 #region Unity lifecycle
106
107#if UNITY_EDITOR
113 protected virtual void OnValidate()
114 {
115 if (Application.isPlaying) return;
116
117 if (canvas == null)
118 TryGetComponent(out canvas);
119
120 bannerAdComponent = bannerAdComponent ? bannerAdComponent : FindBannerAdCandidate();
121
122 if (bannerAdComponent == null || bannerAdComponent is IBannerAd) return;
123
124 Debug.LogWarning(
125 $"{nameof(BannerCanvasOffsetHelper)}: Assigned banner component must implement {nameof(IBannerAd)}.",
126 this);
127 bannerAdComponent = null;
128 }
129
136 private AdUnitBehaviourBase FindBannerAdCandidate()
137 {
138 if (bannerAdController != null && bannerAdController.Ad is IBannerAd)
139 return bannerAdController.Ad;
140
141#if UNITY_6000_3_OR_NEWER
142 var behaviours = FindObjectsByType<AdUnitBehaviourBase>(FindObjectsInactive.Exclude);
143#elif UNITY_2023_1_OR_NEWER
144 var behaviours = FindObjectsByType<AdUnitBehaviourBase>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
145#else
146 var behaviours = FindObjectsOfType<AdUnitBehaviourBase>();
147#endif
148 return behaviours.OfType<IBannerAd>().FirstOrDefault() as AdUnitBehaviourBase;
149 }
150
156 protected virtual void Reset()
157 {
158 if (Application.isPlaying) return;
159
160 if (canvas == null)
161 TryGetComponent(out canvas);
162
163 if (canvas != null)
164 return;
165
166#if UNITY_6000_3_OR_NEWER
167 canvas = FindAnyObjectByType<Canvas>();
168#elif UNITY_2023_1_OR_NEWER
169 canvas = FindFirstObjectByType<Canvas>();
170#else
171 canvas = FindObjectOfType<Canvas>();
172#endif
173 }
174#endif
175
179 protected virtual void Awake() => isComponentActive = isActiveAndEnabled;
180
186 protected virtual void OnEnable()
187 {
188 isComponentActive = true;
189 CancelOffsetRefresh();
190 ClearAppliedOffsetState();
191
192 if (!ResolveRuntimeReferences())
193 return;
194
195 Subscribe();
196 }
197
203 protected virtual void Start() => RefreshOffset();
204
210 protected virtual void OnDisable()
211 {
212 isComponentActive = false;
213 CancelOffsetRefresh();
214 Unsubscribe();
215 ResetOffset();
216 ClearRuntimeReferences();
217 }
218
219 #endregion
220
221 #region Public API
222
228 public void RefreshOffset()
229 {
230 if (!isComponentActive)
231 return;
232
233 if (!EnsureRuntimeReferences())
234 return;
235
236 switch (bannerAd)
237 {
238 case { IsVisible: true }:
239 ApplyOffset();
240 ScheduleOffsetRefresh();
241 return;
242 case { IsVisible: false }:
243 CancelOffsetRefresh();
244 ResetOffset();
245 break;
246 }
247 }
248
249 #endregion
250
251 #region Event handlers
252
258 private void OnBannerShown() => RefreshOffset();
259
265 private void OnBannerHidden() => RefreshOffset();
266
267 #endregion
268
269 #region Resolution
270
277 private bool EnsureRuntimeReferences()
278 {
279 if (!hasInitialState || bannerAd == null || targetContainer == null)
280 {
281 if (!ResolveRuntimeReferences())
282 return false;
283 }
284
285 Subscribe();
286 return true;
287 }
288
295 private bool ResolveRuntimeReferences()
296 {
297 if (canvas == null)
298 TryGetComponent(out canvas);
299
300 if (canvas == null)
301 {
302 Debug.LogWarning($"{nameof(BannerCanvasOffsetHelper)}: Canvas not found.", this);
303 return false;
304 }
305
306 bannerAd = ResolveBannerAd();
307 if (bannerAd == null)
308 {
309 Debug.LogWarning(
310 $"{nameof(BannerCanvasOffsetHelper)}: Banner component implementing {nameof(IBannerAd)} not found.",
311 this);
312 return false;
313 }
314
315 targetContainer = ResolveTargetContainer();
316 if (targetContainer == null)
317 return false;
318
319 WarnIfContainerIsNotStretched(targetContainer);
320 hasInitialState = true;
321 return true;
322 }
323
330 private IBannerAd ResolveBannerAd()
331 {
332 if (bannerAdComponent != null)
333 {
334 if (bannerAdComponent is IBannerAd directBanner)
335 return directBanner;
336
337 Debug.LogWarning(
338 $"{nameof(BannerCanvasOffsetHelper)}: Assigned banner component must implement {nameof(IBannerAd)}.",
339 this);
340 return null;
341 }
342
343 if (bannerAdController != null && bannerAdController.Ad is IBannerAd controllerBanner)
344 {
345 bannerAdComponent = bannerAdController.Ad;
346 return controllerBanner;
347 }
348
349 return null;
350 }
351
358 private RectTransform ResolveTargetContainer()
359 {
360 var root = canvas.transform as RectTransform;
361 if (root == null)
362 {
363 Debug.LogWarning($"{nameof(BannerCanvasOffsetHelper)}: Canvas root RectTransform not found.", this);
364 return null;
365 }
366
367 if (explicitContainer == null)
368 return EnsureSubRoot(root);
369
370 if (explicitContainer.transform.IsChildOf(root))
371 return explicitContainer;
372
373 Debug.LogWarning(
374 $"{nameof(BannerCanvasOffsetHelper)}: Explicit container is outside the Canvas hierarchy. " +
375 $"Falling back to '{SubRootName}'.",
376 this);
377
378 return EnsureSubRoot(root);
379 }
380
388 private static RectTransform EnsureSubRoot(RectTransform root)
389 {
390 var subRoot = root.Find(SubRootName) as RectTransform;
391 if (subRoot == null)
392 {
393 var subRootObject = new GameObject(SubRootName, typeof(RectTransform));
394 subRoot = subRootObject.GetComponent<RectTransform>();
395 subRoot.SetParent(root, false);
396 ConfigureSubRoot(subRoot);
397 }
398
399 var children = new List<Transform>(root.childCount);
400 for (var index = 0; index < root.childCount; index++)
401 {
402 var child = root.GetChild(index);
403 if (child == subRoot)
404 continue;
405
406 children.Add(child);
407 }
408
409 foreach (var child in children)
410 {
411 child.SetParent(subRoot, false);
412 }
413
414 return subRoot;
415 }
416
423 private static void ConfigureSubRoot(RectTransform subRoot)
424 {
425 subRoot.anchorMin = Vector2.zero;
426 subRoot.anchorMax = Vector2.one;
427 subRoot.offsetMin = Vector2.zero;
428 subRoot.offsetMax = Vector2.zero;
429 subRoot.anchoredPosition = Vector2.zero;
430 subRoot.localScale = Vector3.one;
431 }
432
439 protected virtual void WarnIfContainerIsNotStretched(RectTransform container)
440 {
441 if (!shiftOnlyBannerSide || container == null)
442 return;
443
444 if (IsStretched(container))
445 return;
446
447 Debug.LogWarning(
448 $"{nameof(BannerCanvasOffsetHelper)}: Target container is not stretched. " +
449 "Single-side inset mode modifies RectTransform offsets and is intended for stretched containers.",
450 this);
451 }
452
460 private static bool IsStretched(RectTransform rectTransform) =>
461 rectTransform.anchorMin == Vector2.zero &&
462 rectTransform.anchorMax == Vector2.one;
463
469 private void ClearRuntimeReferences()
470 {
471 bannerAd = null;
472 targetContainer = null;
473 ClearAppliedOffsetState();
474 hasInitialState = false;
475 }
476
477 #endregion
478
479 #region Event subscription
480
486 private void Subscribe()
487 {
488 if (isSubscribed || bannerAd == null)
489 return;
490
491 bannerAd.OnShown += OnBannerShown;
492 bannerAd.OnHide += OnBannerHidden;
493 isSubscribed = true;
494 }
495
501 private void Unsubscribe()
502 {
503 if (!isSubscribed)
504 return;
505
506 if (bannerAd != null)
507 {
508 bannerAd.OnShown -= OnBannerShown;
509 bannerAd.OnHide -= OnBannerHidden;
510 }
511
512 isSubscribed = false;
513 }
514
515 #endregion
516
517 #region Offset logic
518
524 private void ApplyOffset()
525 {
526 if (!hasInitialState || targetContainer == null || bannerAd == null)
527 return;
528
529 if (shiftOnlyBannerSide)
530 {
531 ApplySingleSideInset();
532 return;
533 }
534
535 ApplyFullShiftOffset();
536 }
537
541 private void ScheduleOffsetRefresh()
542 {
543 CancelOffsetRefresh();
544
545 var attempts = Mathf.Clamp(offsetApplyAttempts, MinOffsetApplyAttempts, MaxOffsetApplyAttempts);
546 if (attempts <= 1)
547 return;
548
549 var requestVersion = offsetRefreshVersion;
550 offsetRefreshCoroutine = StartCoroutine(RefreshOffsetAfterFrames(requestVersion, attempts - 1));
551 }
552
558 private IEnumerator RefreshOffsetAfterFrames(int requestVersion, int remainingAttempts)
559 {
560 while (remainingAttempts > 0)
561 {
562 var delaySeconds = Mathf.Clamp(
563 applyOffsetDelaySeconds,
564 MinApplyOffsetDelaySeconds,
565 MaxApplyOffsetDelaySeconds);
566 if (delaySeconds <= MinApplyOffsetDelaySeconds)
567 yield return new WaitForEndOfFrame();
568 else
569 yield return new WaitForSeconds(delaySeconds);
570
571 if (requestVersion != offsetRefreshVersion || !isComponentActive || bannerAd is not { IsVisible: true })
572 yield break;
573
574 ApplyOffset();
575 remainingAttempts--;
576 }
577
578 offsetRefreshCoroutine = null;
579 }
580
584 private void CancelOffsetRefresh()
585 {
586 offsetRefreshVersion++;
587
588 if (offsetRefreshCoroutine == null)
589 return;
590
591 StopCoroutine(offsetRefreshCoroutine);
592 offsetRefreshCoroutine = null;
593 }
594
600 private void ResetOffset()
601 {
602 if (!hasInitialState || targetContainer == null)
603 return;
604
605 RemoveAppliedInsets();
606 RemoveAppliedShiftOffset();
607 ReleaseSideLock(appliedAnchor);
608 ClearAppliedOffsetState();
609 }
610
616 private void ApplySingleSideInset()
617 {
618 RemoveAppliedInsets();
619 RemoveAppliedShiftOffset();
620 ReleaseSideLock(appliedAnchor);
621
622 var insets = GetInsetValues();
623 if (IsZeroInsets(insets))
624 {
625 ClearAppliedOffsetState();
626 return;
627 }
628
629 var side = GetInsetSide(insets);
630 if (!TryAcquireSideLock(side))
631 {
632 ClearAppliedOffsetState();
633 return;
634 }
635
636 var offsetMin = targetContainer.offsetMin;
637 var offsetMax = targetContainer.offsetMax;
638 AddInsetsToOffsets(ref offsetMin, ref offsetMax, insets);
639 targetContainer.offsetMin = offsetMin;
640 targetContainer.offsetMax = offsetMax;
641
642 appliedAnchor = side;
643 appliedInsets = insets;
644 hasAppliedInsets = true;
645 appliedShiftOffset = Vector2.zero;
646 hasAppliedShiftOffset = false;
647 }
648
654 private void ApplyFullShiftOffset()
655 {
656 RemoveAppliedInsets();
657 RemoveAppliedShiftOffset();
658 ReleaseSideLock(appliedAnchor);
659
660 var offset = GetShiftOffsetVector();
661 if (IsZeroVector(offset))
662 {
663 ClearAppliedOffsetState();
664 return;
665 }
666
667 var side = GetShiftLockSide();
668 if (!TryAcquireSideLock(side))
669 {
670 ClearAppliedOffsetState();
671 return;
672 }
673
674 targetContainer.anchoredPosition += offset;
675
676 appliedAnchor = side;
677 appliedInsets = (0f, 0f, 0f, 0f);
678 hasAppliedInsets = false;
679 appliedShiftOffset = offset;
680 hasAppliedShiftOffset = true;
681 }
682
688 private void RemoveAppliedInsets()
689 {
690 if (!hasAppliedInsets || targetContainer == null)
691 return;
692
693 var offsetMin = targetContainer.offsetMin;
694 var offsetMax = targetContainer.offsetMax;
695 RemoveInsetsFromOffsets(ref offsetMin, ref offsetMax, appliedInsets);
696 targetContainer.offsetMin = offsetMin;
697 targetContainer.offsetMax = offsetMax;
698
699 appliedInsets = (0f, 0f, 0f, 0f);
700 hasAppliedInsets = false;
701 }
702
708 private void RemoveAppliedShiftOffset()
709 {
710 if (!hasAppliedShiftOffset || targetContainer == null)
711 return;
712
713 targetContainer.anchoredPosition -= appliedShiftOffset;
714
715 appliedShiftOffset = Vector2.zero;
716 hasAppliedShiftOffset = false;
717 }
718
725 private (float top, float bottom, float left, float right) GetInsetValues()
726 {
727 if (bannerAd == null)
728 return (0f, 0f, 0f, 0f);
729
730 var anchor = bannerAd.GetBannerAnchor();
731 var effectiveExtent = GetEffectiveBannerExtent(anchor);
732
733 return anchor switch
734 {
735 BannerAnchor.Top => (effectiveExtent, 0f, 0f, 0f),
736 BannerAnchor.Bottom => (0f, effectiveExtent, 0f, 0f),
737 BannerAnchor.Left => (0f, 0f, effectiveExtent, 0f),
738 BannerAnchor.Right => (0f, 0f, 0f, effectiveExtent),
739 _ => (0f, 0f, 0f, 0f)
740 };
741 }
742
749 private Vector2 GetShiftOffsetVector()
750 {
751 if (bannerAd == null)
752 return Vector2.zero;
753
754 var anchor = bannerAd.GetBannerAnchor();
755 var effectiveExtent = GetEffectiveBannerExtent(anchor);
756
757 return anchor switch
758 {
759 BannerAnchor.Top => new Vector2(0f, -effectiveExtent),
760 BannerAnchor.Bottom => new Vector2(0f, effectiveExtent),
761 BannerAnchor.Left => new Vector2(effectiveExtent, 0f),
762 BannerAnchor.Right => new Vector2(-effectiveExtent, 0f),
763 _ => Vector2.zero
764 };
765 }
766
774 private float GetEffectiveBannerExtent(BannerAnchor anchor)
775 {
776 var resolvedBannerExtentPixels = GetResolvedBannerExtent(anchor);
777 var safeAreaInsetPixels = accountForSafeArea ? GetSafeAreaInsetForAnchor(anchor) : 0f;
778 var effectiveExtentPixels = Mathf.Max(0f, resolvedBannerExtentPixels - safeAreaInsetPixels);
779
780 return ConvertScreenPixelsToCanvasUnits(effectiveExtentPixels, anchor) + GetMarginForAnchor(anchor);
781 }
782
790 private float GetResolvedBannerExtent(BannerAnchor anchor)
791 {
792 if (bannerAd == null)
793 return 0f;
794
795 var resolvedHeight = Mathf.Max(0f, bannerAd.GetBannerHeightPixels());
796 var resolvedWidth = Mathf.Max(0f, bannerAd.GetBannerWidthPixels());
797 if (resolvedWidth <= 0f)
798 resolvedWidth = Screen.width;
799
800 return anchor switch
801 {
802 BannerAnchor.Top or BannerAnchor.Bottom => resolvedHeight,
803 BannerAnchor.Left or BannerAnchor.Right => resolvedWidth,
804 _ => 0f
805 };
806 }
807
816 private float ConvertScreenPixelsToCanvasUnits(float screenPixels, BannerAnchor anchor)
817 {
818 if (Mathf.Approximately(screenPixels, 0f))
819 return 0f;
820
821 var root = canvas != null ? canvas.transform as RectTransform : null;
822 if (root == null)
823 return screenPixels;
824
825 var isHorizontalAxis = anchor is BannerAnchor.Left or BannerAnchor.Right;
826 var screenExtent = isHorizontalAxis ? Screen.width : Screen.height;
827 var canvasExtent = isHorizontalAxis ? root.rect.width : root.rect.height;
828
829 if (screenExtent > 0f && canvasExtent > 0f)
830 return screenPixels * canvasExtent / screenExtent;
831
832 var scaleFactor = canvas != null ? Mathf.Max(1f, canvas.scaleFactor) : 1f;
833 return screenPixels / scaleFactor;
834 }
835
843 private float GetMarginForAnchor(BannerAnchor anchor) =>
844 anchor switch
845 {
846 BannerAnchor.Top => marginTop,
847 BannerAnchor.Bottom => marginBottom,
848 BannerAnchor.Left => marginLeft,
849 BannerAnchor.Right => marginRight,
850 _ => 0f
851 };
852
860 private static float GetSafeAreaInsetForAnchor(BannerAnchor anchor)
861 {
862 var (top, bottom, left, right) = GetSafeAreaInsets();
863 return anchor switch
864 {
865 BannerAnchor.Top => top,
866 BannerAnchor.Bottom => bottom,
867 BannerAnchor.Left => left,
868 BannerAnchor.Right => right,
869 _ => 0f
870 };
871 }
872
879 private static (float top, float bottom, float left, float right) GetSafeAreaInsets()
880 {
881 var safeArea = Screen.safeArea;
882 return (
883 top: Mathf.Max(0f, Screen.height - (safeArea.y + safeArea.height)),
884 bottom: Mathf.Max(0f, safeArea.y),
885 left: Mathf.Max(0f, safeArea.x),
886 right: Mathf.Max(0f, Screen.width - (safeArea.x + safeArea.width)));
887 }
888
895 private BannerAnchor GetShiftLockSide() =>
896 bannerAd == null
897 ? BannerAnchor.Custom
898 : bannerAd.GetBannerAnchor() switch
899 {
900 BannerAnchor.Top => BannerAnchor.Bottom,
901 BannerAnchor.Bottom => BannerAnchor.Top,
902 BannerAnchor.Left => BannerAnchor.Right,
903 BannerAnchor.Right => BannerAnchor.Left,
904 _ => BannerAnchor.Custom
905 };
906
915 private static void AddInsetsToOffsets(
916 ref Vector2 offsetMin,
917 ref Vector2 offsetMax,
918 (float top, float bottom, float left, float right) insets)
919 {
920 offsetMin = new Vector2(offsetMin.x + insets.left, offsetMin.y + insets.bottom);
921 offsetMax = new Vector2(offsetMax.x - insets.right, offsetMax.y - insets.top);
922 }
923
932 private static void RemoveInsetsFromOffsets(
933 ref Vector2 offsetMin,
934 ref Vector2 offsetMax,
935 (float top, float bottom, float left, float right) insets)
936 {
937 offsetMin = new Vector2(offsetMin.x - insets.left, offsetMin.y - insets.bottom);
938 offsetMax = new Vector2(offsetMax.x + insets.right, offsetMax.y + insets.top);
939 }
940
948 private static bool IsZeroInsets((float top, float bottom, float left, float right) insets) =>
949 Mathf.Approximately(insets.top, 0f) &&
950 Mathf.Approximately(insets.bottom, 0f) &&
951 Mathf.Approximately(insets.left, 0f) &&
952 Mathf.Approximately(insets.right, 0f);
953
961 private static bool IsZeroVector(Vector2 shiftOffset) =>
962 Mathf.Approximately(shiftOffset.x, 0f) &&
963 Mathf.Approximately(shiftOffset.y, 0f);
964
970 private void ClearAppliedOffsetState()
971 {
972 appliedAnchor = BannerAnchor.Custom;
973 appliedInsets = (0f, 0f, 0f, 0f);
974 hasAppliedInsets = false;
975 appliedShiftOffset = Vector2.zero;
976 hasAppliedShiftOffset = false;
977 }
978
986 private static BannerAnchor GetInsetSide((float top, float bottom, float left, float right) insets) =>
987 !Mathf.Approximately(insets.top, 0f) ? BannerAnchor.Top :
988 !Mathf.Approximately(insets.bottom, 0f) ? BannerAnchor.Bottom :
989 !Mathf.Approximately(insets.left, 0f) ? BannerAnchor.Left :
990 !Mathf.Approximately(insets.right, 0f) ? BannerAnchor.Right : BannerAnchor.Custom;
991
999 private bool TryAcquireSideLock(BannerAnchor side)
1000 {
1001 side = NormalizeLockAnchor(side);
1002 if (side == BannerAnchor.Custom || targetContainer == null)
1003 return true;
1004
1005#if UNITY_6000_4_OR_NEWER
1006 var containerId = targetContainer.GetEntityId();
1007#else
1008 var containerId = targetContainer.GetInstanceID();
1009#endif
1010
1011 if (!sideLocksByContainer.TryGetValue(containerId, out var sideLocks))
1012 {
1013#if UNITY_6000_4_OR_NEWER
1014 sideLocks = new Dictionary<BannerAnchor, EntityId>();
1015#else
1016 sideLocks = new Dictionary<BannerAnchor, int>();
1017#endif
1018 sideLocksByContainer[containerId] = sideLocks;
1019 }
1020
1021#if UNITY_6000_4_OR_NEWER
1022 var ownerId = GetEntityId();
1023#else
1024 var ownerId = GetInstanceID();
1025#endif
1026
1027 if (sideLocks.TryGetValue(side, out var lockedBy) && lockedBy != ownerId)
1028 return false;
1029
1030 sideLocks[side] = ownerId;
1031 return true;
1032 }
1033
1040 private void ReleaseSideLock(BannerAnchor side)
1041 {
1042 side = NormalizeLockAnchor(side);
1043 if (side == BannerAnchor.Custom || targetContainer == null)
1044 return;
1045
1046#if UNITY_6000_4_OR_NEWER
1047 var containerId = targetContainer.GetEntityId();
1048#else
1049 var containerId = targetContainer.GetInstanceID();
1050#endif
1051
1052 if (!sideLocksByContainer.TryGetValue(containerId, out var sideLocks))
1053 return;
1054
1055#if UNITY_6000_4_OR_NEWER
1056 var ownerId = GetEntityId();
1057#else
1058 var ownerId = GetInstanceID();
1059#endif
1060
1061 if (!sideLocks.TryGetValue(side, out var lockedBy) || lockedBy != ownerId)
1062 return;
1063
1064 sideLocks.Remove(side);
1065 if (sideLocks.Count == 0)
1066 sideLocksByContainer.Remove(containerId);
1067 }
1068
1076 private static BannerAnchor NormalizeLockAnchor(BannerAnchor anchor) =>
1077 anchor is BannerAnchor.Center or BannerAnchor.Custom
1078 ? BannerAnchor.Custom
1079 : anchor;
1080
1081 #endregion
1082 }
1083}
Base class for controlling ad units within Unity, providing the foundation for specific ad functional...
Base MonoBehaviour class for ad units. This class defines the abstract and virtual properties and met...
virtual void WarnIfContainerIsNotStretched(RectTransform container)
Logs a warning when side-inset mode is used on a non-stretched RectTransform. OffsetMin and offsetMax...
virtual void Awake()
Captures the initial active state on Unity's main thread.
void RefreshOffset()
Refreshes the target container offset according to the current banner visibility. Visible banners app...
virtual void Start()
Refreshes offset according to the current banner visibility. This covers banners that were shown befo...
virtual void OnEnable()
Resolves dependencies and subscribes to banner events. Existing helper-applied state is cleared befor...
virtual void OnDisable()
Restores original state and unsubscribes from events. The helper removes only offsets that it has app...
Offsets a Canvas UI container to avoid overlap with an active banner ad. The helper can use an explic...
Provides banner-specific layout data needed by UI offset helpers. Implementations expose current visi...
BannerAnchor
Describes screen anchors used by banner ads. The value defines from which edge of the screen UI conte...