오늘 배운 것
1. 새로운 방식으로 Stage Panel 제작
2. 광고
01. 새로운 방식으로 Stage Panel 제작
사실 어제 한 방식(한 칸 한 칸씩 위치를 정해주는 방법)도 있지만, 한 셀에 여러개를 넣는 방법도 있다.
(오히려 이쪽이 더욱 쉬울 수도 있다)
한 줄은 구조체가 될 수도, Cell 클래스가 될 수도, 배열이 될 수도 있다.
item는 전체 아이템(데이터)를 갖고 있는 리스트고,
또 다른 리스트인 visibleCells은 Cell 3개를 한 줄로 묶은 배열의 리스트이다.
튜플로 선언해서, 각 인덱스와 셀 버튼을 넣어줄 것이다.
* 참고 : 어떤 자료구조를 쓸 지를 선택하는 방법
보통 많이 쓰는 배열, 리스트, 딕셔너리, 스택 , 큐, 링크드 리스트 중에 어울릴 것 같은 자료구조를 생각해본다.
우리가 한 방식이 위 아래 (first, last)를 조작하는 방식이기때문에 index를 사용할 수 있는 것이 좋다.
우리는 코드로 설정해줄 것이기 때문에 Layout Group을 꺼줘야한다.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(PopupPanelController))]
public class StagePopupPanelController : MonoBehaviour
{
[SerializeField] private GameObject stageCellPrefab;
[SerializeField] private Transform contentTransform;
[SerializeField] private GameObject scrollView;
[SerializeField] private int cellColumnCount;
[SerializeField] private float cellWidth;
[SerializeField] private float cellHeight;
[SerializeField] private Vector2 spacing;
private ScrollRect _scrollViewScrollRect;
private RectTransform _scrollViewRectTransform;
private List<(int index, StageCellButton[] stageCellButtons)> _visibleCells; // 화면에 나타나는 셀 영역을 담고있는 리스트
private float _previousScrollRectYValue = 1f;
private int _maxStageCount = 100;
private void Awake()
{
_scrollViewScrollRect = scrollView.GetComponent<ScrollRect>();
_scrollViewRectTransform = scrollView.GetComponent<RectTransform>();
}
private void Start()
{
// 타이틀 지정
GetComponent<PopupPanelController>().SetTitleText("STAGE");
// 데이터 로드
ReloadData();
}
private (int start, int count) GetVisibleIndexRange()
{
var visibleRect = new Rect(
_scrollViewScrollRect.content.anchoredPosition.x,
_scrollViewScrollRect.content.anchoredPosition.y,
_scrollViewRectTransform.rect.width,
_scrollViewRectTransform.rect.height);
var start = Mathf.FloorToInt(visibleRect.y / (cellHeight + spacing.y));
var visibleCount = Mathf.CeilToInt(visibleRect.height / (cellHeight + spacing.y));
// 버퍼 추가
// Count 값 설정
var count = Mathf.CeilToInt(_maxStageCount / cellColumnCount);
count = Mathf.Min(count, start + visibleCount);
return (start, count);
}
/// <summary>
/// 특정 인덱스가 화면에 나와야 할 인덱스인지 확인
/// </summary>
/// <param name="index">인덱스</param>
/// <returns>true: 나와야 함, false: 안 나와도 됨</returns>
private bool IsVisibleIndex(int index)
{
var (start, end) = GetVisibleIndexRange();
end = Mathf.Min(end, _maxStageCount - 1);
return start <= index && index <= end;
}
private StageCellButton CreateStageCellButton(int index, int row, int col)
{
var stageCellButtonObject = ObjectPool.Instance.GetObject();
var stageCellButton = stageCellButtonObject.GetComponent<StageCellButton>();
stageCellButton.SetStageCell(index, StageCellButton.StageCellType.Normal);
// StageCellButton 위치 지정
float centerIndex = 0;
if (cellColumnCount % 2 == 0)
{
centerIndex = cellColumnCount / 2 - 0.5f;
}
else
{
centerIndex = cellColumnCount / 2;
}
var offset = col - centerIndex;
var x = cellWidth * offset + spacing.x * offset;
var y = -(cellHeight + spacing.y) * row;
stageCellButton.RectTransform.anchoredPosition = new Vector2(x, y);
return stageCellButton;
}
private void ReloadData()
{
// Scroll View의 Content 사이즈 조절
_scrollViewScrollRect.content.sizeDelta =
new Vector2(0, Mathf.CeilToInt(_maxStageCount / cellColumnCount) * (cellHeight + spacing.y));
// 화면에 보이는 셀을 담고있는 _visibleCell 리스트 초기화
_visibleCells = new List<(int index, StageCellButton[] stageCellButtons)>();
////////////////////////////////////////
// 셀 생성
// 만들어야 하는 셀 index 찾기
var (start, count) = GetVisibleIndexRange();
for (int i = start; i < count; i++) // 셀 영역 하나씩 생성
{
List<StageCellButton> stageCellButtons = new List<StageCellButton>();
for (int j = 0; j < cellColumnCount; j++) // 한 줄의 셀 영역 생성
{
var index = i * cellColumnCount + j;
if (index < _maxStageCount)
{
// 셀 생성
var stageCellButton = CreateStageCellButton(index, i, j);
stageCellButtons.Add(stageCellButton);
}
}
_visibleCells.Add((i, stageCellButtons.ToArray()));
}
}
/// <summary>
/// 스크롤 뷰가 스크롤 될 때 호출되는 메서드
/// </summary>
/// <param name="value">스크롤 정도</param>
public void OnValueChanged(Vector2 value)
{
if (_previousScrollRectYValue < value.y)
{
////////////////////////////////////////
// 위로 스크롤
// 상단에 새로운 셀을 만들 필요가 있으면 만들기
var firstRow = _visibleCells.First();
var newFirstIndex = firstRow.index - 1;
if (IsVisibleIndex(newFirstIndex))
{
List<StageCellButton> stageCellButtons = new List<StageCellButton>();
for (int j = 0; j < cellColumnCount; j++)
{
var index = newFirstIndex * cellColumnCount + j;
if (index < _maxStageCount)
{
var stageCellButton = CreateStageCellButton(index, newFirstIndex, j);
stageCellButtons.Add(stageCellButton);
}
}
_visibleCells.Insert(0, (newFirstIndex, stageCellButtons.ToArray()));
}
// 하단에 더이상 보이지 않는 셀이 있으면 제거하기
var lastRow = _visibleCells.Last();
if (!IsVisibleIndex(lastRow.index))
{
var stageCellButtons = lastRow.stageCellButtons;
foreach (var stageCellButton in stageCellButtons)
{
ObjectPool.Instance.ReturnObject(stageCellButton.gameObject);
}
_visibleCells.RemoveAt(_visibleCells.Count - 1);
}
}
else
{
////////////////////////////////////////
// 아래로 스크롤
// 하단에 새로운 셀을 만들 필요가 있으면 만들기
var lastRow = _visibleCells.Last();
var newLastIndex = lastRow.index + 1;
if (IsVisibleIndex(newLastIndex))
{
List<StageCellButton> stageCellButtons = new List<StageCellButton>();
for (int j = 0; j < cellColumnCount; j++)
{
var index = newLastIndex * cellColumnCount + j;
if (index < _maxStageCount)
{
var stageCellButton = CreateStageCellButton(index, newLastIndex, j);
stageCellButtons.Add(stageCellButton);
}
}
_visibleCells.Add((newLastIndex, stageCellButtons.ToArray()));
}
// 상단에 더이상 보이지 않는 셀이 있으면 제거하기
var firstRow = _visibleCells.First();
if (!IsVisibleIndex(firstRow.index))
{
var stageCellButtons = firstRow.stageCellButtons;
foreach (var stageCellButton in stageCellButtons)
{
ObjectPool.Instance.ReturnObject(stageCellButton.gameObject);
}
_visibleCells.RemoveAt(0);
}
}
_previousScrollRectYValue = value.y;
}
}
// Scroll View의 Content 사이즈 조절
_scrollViewScrollRect.content.sizeDelta =
new Vector2(0, Mathf.CeilToInt(_maxStageCount / cellColumnCount) * (cellHeight + spacing.y));
그렇지 않으면 자동으로 int타입으로 바뀌어서 짤리게 된다.
내 코드와 달라서 한 번 더 유심히 봐야 하는 부분은
private StageCellButton CreateStageCellButton(int index, int row, int col)
{
var stageCellButtonObject = ObjectPool.Instance.GetObject();
var stageCellButton = stageCellButtonObject.GetComponent<StageCellButton>();
stageCellButton.SetStageCell(index, StageCellButton.StageCellType.Normal);
// StageCellButton 위치 지정
float centerIndex = 0;
if (cellColumnCount % 2 == 0)
{
centerIndex = cellColumnCount / 2 - 0.5f;
}
else
{
centerIndex = cellColumnCount / 2;
}
var offset = col - centerIndex;
var x = cellWidth * offset + spacing.x * offset;
var y = -(cellHeight + spacing.y) * row;
stageCellButton.RectTransform.anchoredPosition = new Vector2(x, y);
return stageCellButton;
}
우선 이 부분이다. 새로운 스테이지 셀 버튼을 오브젝트 풀에서 가져와서 위치를 설정하는 부분이다.
홀수와 짝수를 나누어서 해 주어야 한다. 왜냐하면 짝수, 예를 들어 4의 절반은 2인데 이는 중간을 정확히 설명하는 위치가 아니다. 여기서 0.5를 빼 주어서 1.5를 만들어줘야 한다. (왜 2.5가 아닐까 ? 우리는 항상 0부터 생각해야 한다. 0 1 2 3의 절반은 1.5다) 홀수의 경우 그냥 써 주면된다.
col은 세로줄이다, 그러니 col - centerIndex로 가운데에서 얼만큼 떨어져 있는지를 구한다.
(Ex, col 이 0 이면 맨 처음이니 중심으로부터 centerIndex만큼 떨어져 있다)
그렇게 구한 offset으로 X좌표의 위치를 구해주고, y도 구해주어 좌표값을 반환한다.
나머지는 크게 다른 부분은 없다. ! 현재 위로 가면 - 값이 생성되는 문제가 있는데 쉽게 고칠 수 있다.
start를 0으로 제한해 두면 된다.
02. 유니티 광고
모바일 게임에서 수익을 만들기 위해서 보통 광고를 많이 활용한다.
광고를 쉽게 게재할 수 있게 도와주는 다양한 SDK 들이 있다.
광고 SDK
- Unity Ads : 적용하기는 제일 쉬우나 , 광고가 살짝 부족한 문제가 있다.
* 이 문제를 해결하기 위해 Iron Source를 인수해 노력중 - Google AdMob - 유니티 적용에 편하지는 않으나, 광고가 많고 광고 단가도 괜찮은 편이다.
광고 플랫폼에서는 최대한 같은 광고를 틀지 않으려고 하고 , 사용자 상황에 맞는 광고를 틀려고 한다.
그렇기 때문에 우리가 원하는 시점에 광고가 나오지 않을 수도 있다. 광고의 전체적인 양은 부족하지 않겠지만,
'우리 게임에 어울리는, 광고주들이 게재하고 싶을만한 광고'가 부족할 수 있다.
그래서 일단은 Google AdMob으로 적용해보자.
그래서 필요한 시점에 광고가 나오지 않는 문제가 있음.
미디에이션 형태로 광고를 적용하는 것도 좋은 방법이다.
* 미디에이션 : 여러 광고 네트워크를 하나의 플랫폼으로 통합하고 중앙화
https://www.is.com/ko/community/blog/what-is-mobile-ad-mediation/
미디에이션 파헤치기: ‘모바일 광고 미디에이션'이란 무엇인가? 앱 개발자에게 주는 이점은? | U
모바일 광고 미디에이션이 앱에서 광고 수익을 극대화하기 위해 왜 중요한지, 어떻게 작동하는지, 하나의 SDK로 어떻게 여러 광고 네트워크를 관리할 수 있는지 등에 대해 자세히 알아보세요!
unity.com
자 , 이제 구글 애드몹에 들어가보자.
앱을 안드로이드용과, IOS용 따로 만들어 주어야 한다.
광고 단위
- 배너 : 앱 레이아웃 일부를 차지하고 ,계속해서 노출되는 광고
- 전면 : 레벨 완료 등 자연스러운 중단 시점에 게재되는 전체 페이지형 광고
- 리워드 : 광고를 시청한 사용자에게 리워드를 제공하는 전체 페이지 광고 형식
- 보상형 전면 광고 : 리워드와 비슷하지만 선택할 수 없다.
차단 관리
- 일반 카테고리 : 특정 카테고리의 광고 차단 가능
- 민감한 카테고리 : 종교, 정치, 성인용품 등 민감한 내용의 광고 차단 가능
- 광고 형식 : 배너, 전면 광고 등 특정한 형식의 광고 차단 가능
- 광고 네트워크 : 특정 광고 네트워크의 광고 차단 가능
- 광고주 : 특정 광고주의 광고 차단 가능
차단관리는 앱의 품질을 유지하는데 중요한 역할을 한다
- 부적절한 광고로부터 앱 보호 : 어린이용 앱에 성인 용품 광고가 나오면 안된다.
- 사용자 경험 저해 방지 : 일부 광고는 성능을 저하시키거나, 너무 많은 광고가 나오지 않게 한다.
- 법적 책임 회피 : 개발자는 앱에 게재되는 광고 내용에 대해 일정부분 책임을 져야 한다.
기기 테스트
테스트 기기를 등록하고 , 등록한 테스트 기기로 광고를 실험하지 않으면 AdMob의 무효 트래픽 정책을 위반할 수 있다.
( 테스트 기기를 등록하지 않고 계속해서 테스트를 하면, 목적을 오해해서 차단당할 수도 있다)
https://support.google.com/admob/answer/9691433#example
테스트 기기 설정 - Google AdMob 고객센터
도움이 되었나요? 어떻게 하면 개선할 수 있을까요? 예아니요
support.google.com
만든 앱에 광고를 생성하면 앱 ID와 광고 단위 ID가 나오고 이를 이용해 코드를 작성할 수 있다.
기본적으로 다음 가이드를 참고하면 된다.
블로그나, 혹은 유튜브 등 다양한 플랫폼에서 정보를 얻을 수 있겠지만 , 계속해서 방법이 바뀌기 때문에
공식 문서를 참고하는 것이 가장 좋은 방법이다.
https://developers.google.com/admob/unity/quick-start?hl=ko
시작하기 | Unity | Google for Developers
A mobile ads SDK for AdMob publishers who are building apps on Unity.
developers.google.com
현재 버전을 간략히 살펴보자면,
우선 Unity 2019.4 버전 이상을 사용해야 하며
iOS의 경우
- XCode 16.0 이상
- iOS 12.0 이상 타겟팅
- CocoaPods( 유니티의 패키지 매니저 같이 여러 오픈소스들을 쉽게 설치해 주는 것,
빌드 할 때 Xcode로 하게 되는데 이때 필요로 하는 오픈소스를 설치해야 하기때문)
* ios는 앱을 올릴떄 항상 최신 버전을 사용해야함. (상술인가.. ? )
Andoroid의 경우
- 최소 Android API 수준 21 이상
- Android API 수준 34 이상 타겟팅
안드로이드로 테스트해보자. 우선 Unity용 모바일 광고 플러그인을 다운받고 임포트해준다.
이런 SDK들은 따로 두는 것이 좋다. Native 코드와 연동되다 보니 오류가 생길 수 있기 때문이다.
그래서 항상 SDK를 임포트 하기 전에 커밋을 따로 해 두거나, 설치되는 것들의 이름을 적어놔서 되돌릴 수 있게 해야 한다.
Plugins나 ExternalDependencyManger와 같은 폴더명들은 다른 앱이나 SDK도 사용할 수 있기 때문에
더더욱 조심해야 한다.
* 팁 : 임포트하고 진득하게 기다려야 한다. ( 오른쪽 아래가 완료되었더라도)
* 팁2 : 애초에 자주 사용하는 SDK들은 빈 프로젝트에 다 넣어서 오류가 뜨는지 보고, 그 위에 게임 코드를 올리는 것도 방 법이 될 수 있다.
다음과 같은 경우 IOS를 설치 안해놔서 뜨는 오류다. 쓰지 않더라도 ios를 설치해 두어야 한다.
설정들
설명에서 봤던 API 레벨 설정
*운영체제 버전은 가능한 최소한으로 낮추는 것이 좋다. 최대한 많은 사람이 즐기게 하기 위해서
그러나, 고퀄리티 게임은, 상위 운영체제 버전을 기본값으로 사용(사양문제)
* 갤럭시 같은 경우, 운영체제 업데이트를 많이 해줌에도 불구하고 , 최신 운영체제는 최신 핸드폰에만 가능
하지만 아이폰의 경우 오래된 아이폰도 최신 OS를 사용할 수 있다.
기본적으로 64비트 빌드가 기본이기 때문에 Mono가 아닌 IL2CPP로 설정해야 한다.
(Mono는 32비트 환경에서만 작동)
* 참고 : Scripting Backend란 코드를 실행 가능한 형태로 변환하는 방식
ARM 64로 체크
* ARM64는CPU 아키텍쳐를 의미한다. CPU 아키텍쳐는 CPU가 데이터를 처리하고 프로그램을 실행하는 방식을 정의하는 설계 방식이고, ARM64는 ARM 아키텍쳐의 64비트 버전이다. ARmv7의 경우 32비트 버전이니 우리는 64로 해야한다.
가로 세로형 게임에 따른 세팅도 필요하다.
(눕혀서도 가능하게 만들 수도 있지만 굉장히 복잡해진다, 아래 두 개를 설정 해제하면 된다)
가이드 설명대로 하는 중이다.
간단하게 알아봤는데 External Dependency Manager에서 Resolve는 외부 라이브러리를 다운로드하고, 통합하는 과정을 의미한다.
그 후 Google Mobile Ads 세팅에 들어가서 아이디를 설정해준다. ( 지금은 테스트 아이디로 )
이제, 다음과 같은 위치에 있는 스크립트를 조정해보자.
AdmobAdsManager 스크립팅
광고는 단계가 있다. SDK 초기화 - 광고 로드 - 광고 표시로 이루어지는 순서다.
이 중, 광고 표시가 끝나게 되면 다시 광고 로드로 돌아가 실행되게 된다.
처음에 딱 앱을 켰을 때 SDK 초기화 작업이 필요하다.
using문으로 네임스페이스를 가져와야 사용할 수 있다.
람다식 안에는 초기화 후에 해야할 일을 넣어주면 된다.
(예를 들어 초기화하자마자 배너광고가 뜬다던가 하는 일)
public class AdmobAdsManager : Singleton<AdmobAdsManager>
{
#if UNITY_ANDROID
private string _bannerADUnitId = "ca-app-pub-3940256099942544/6300978111";
#elif UNITY_IOS
private string _bannerADUnitID = "ca-app-pub-9027792654397252/3458274561";
#endif
private BannerView _bannerView;
private void Start()
{
MobileAds.Initialize(initStatus =>
{
//여기다가 광고 제거했으면 안뜨게 함.
LoadBannerAd();
});
}
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
}
#region Banner Ads
public void CreateBannerView()
{
Debug.Log("CreateBannerView");
if (_bannerView != null)
{
//TODO: 배너 뷰 소멸
_bannerView.Destroy();
_bannerView = null;
}
_bannerView = new BannerView(_bannerADUnitId, AdSize.Banner, AdPosition.Bottom);
}
public void LoadBannerAd()
{
if (_bannerView == null)
{
CreateBannerView();
}
var adRequest = new AdRequest();
_bannerView.LoadAd(adRequest);
}
코드 전문은 다음과 같다. 새로운 클래스들이 마구 나와서 아직 잘 모르겠지만 일단 따로 포스팅하면서 공부해야지.
* 첫 게임을 만들 때에 개발자 커뮤니티가 아닌 일반 커뮤니티에서 활동하는 것도 좋다(광고)
레딧 같은 커뮤니티도 좋다.
*참고
true로 해 두면 꺼져있는 (SetActive = false 인 것도 다 찾는다)
오늘의 목표
1. 코딩 테스트 2~ 3문제 풀기
2. 광고 관련 클래스 간단 정리
'TIL' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임 개발 2.28일 : 출시 설정 (0) | 2025.02.28 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL회고] 02.27 : 보상형, 전면 광고 (0) | 2025.02.28 |
[멋쟁이사자처럼 부트캠프 TIL 회고] (0) | 2025.02.25 |
[멋쟁이 사자처럼 부트캠프 TIL회고] (0) | 2025.02.21 |
[멋쟁이사자처럼 부트캠프 TIL회고] (0) | 2025.02.20 |