와챠의 우당탕탕 코딩 일기장
[유니티] 스파르타 코딩 : 게임 개발 - 5주차(마지막) 본문
반응형
오늘 한 것
- 기존 키 입력에서 터치 입력으로 변경(조이스틱)
- 모바일 환경에 맞게 설정하기
- Unity Ads로 광고 붙이기
- 플레이어가 죽으면 광고 재생 > 다 보면 부활
- 앱 빌드, 마켓 출시
오늘 배운 것 정리
- 모바일 전용 Input Manager 만들기
- 더보기
MonoSingleton.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MonoSingleton<T> : MonoBehaviour where T : Component { protected static T _instance; public static T Instance { get { // 없으면 만들고 if (_instance == null) { _instance = FindObjectOfType<T>(); if (_instance == null) { GameObject obj = new GameObject(); _instance = obj.AddComponent<T>(); } } // 있으면 바로 가져오기 return _instance; } } protected virtual void Awake() { if (!Application.isPlaying) { return; } _instance = this as T; } }
InputManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class InputManager : MonoSingleton<InputManager> // 어디서든지 접근 가능해짐 { protected Vector2 _movement = Vector2.zero; // 방향 정보 protected bool _isJump = false; // 점프 정보 public Vector2 Movement { get { return _movement; } } public bool IsJump { get { return _isJump; } } public virtual void SetMovement(Vector2 movement) { _movement.x = movement.x; _movement.y = movement.y; } // 점프하기 public void StartJump() { if (_isJump) { return; } _isJump = true; } // 무한 점프 방지 public void ClearJump() { if (!_isJump) { return; } _isJump = false; } }
- 조이스틱 만들기
- 하이어라이키창 > 우클릭 > Camera
- Camera Component 세팅하기
- 메인 카메라에서는 UI 체크 해제, UI카메라에는 UI만 체크하기
- Culling Mask : 여기에 들어있는 값과 동일한 layer만 찍겠다
- Camera 게임 오브젝트의 구조
- 카메라
- 캔버스
- 이미지1(백그라운드)
- 이미지2(조이스틱 막대기 부분)
- 이미지1(백그라운드)
- 캔버스
- 카메라
- 조이스틱 움직이기
- 조이스틱에 Joystick 컴포넌트 추가
-
더보기
using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; public class Joystick : MonoBehaviour, IDragHandler, IEndDragHandler // IDragHandler, IEndDragHandler : 드래그 위한 인터페이스 { [Header("Camera")] public Camera TargetCamera; [Header("Axis")] public float MaxRange = 1f; [Header("Binding")] public UnityEvent<Vector2> JoystickValue; // protected Vector2 _neutralPosition; protected Vector2 _joystickValue; protected Vector2 _newTargetPosition; protected Vector3 _newJoystickPosition; protected virtual void Start() { Initialize(); } protected virtual void OnEnable() { Initialize(); } public virtual void Initialize() { _neutralPosition = GetComponent<RectTransform>().transform.position; } protected virtual void Update() { if (JoystickValue == null) { return; } // 입력값 가공 _joystickValue.x = EvaluateInputValue(_newTargetPosition.x); _joystickValue.y = EvaluateInputValue(_newTargetPosition.y); JoystickValue.Invoke(_joystickValue); } // IDragHandler 상속 시 반드시 구현 // 드래그 하는 동안 계속 호출 public virtual void OnDrag(PointerEventData eventData) { // 조이스틱 위치 계산 _newTargetPosition = TargetCamera.ScreenToWorldPoint(eventData.position); // 영역 밖 벗어나지 않도록 _newTargetPosition = Vector2.ClampMagnitude(_newTargetPosition - _neutralPosition, MaxRange); _newJoystickPosition = _neutralPosition + _newTargetPosition; // 조이스틱 위치 수정 transform.position = _newJoystickPosition; var localPos = transform.localPosition; transform.localPosition = new Vector3(localPos.x, localPos.y, 0f); } // IEndDragHandler 상속 시 반드시 구현 // 드래그 끝날 때 조이스틱 원래 위치로 돌려놓기 public virtual void OnEndDrag(PointerEventData eventData) { // 초기값으로 설정 _newJoystickPosition = _neutralPosition; transform.position = _newJoystickPosition; var localPos = transform.localPosition; transform.localPosition = new Vector3(localPos.x, localPos.y, 0f); _newTargetPosition = Vector2.zero; _joystickValue.x = 0f; _joystickValue.y = 0f; } // 드래그 시작 때 호출되는 함수는 IBeginDragHandler 상속 받은 후 사용하는 OnBeginDrag 함수임. protected virtual float EvaluateInputValue(float vectorPosition) { return Mathf.InverseLerp(0, MaxRange, Mathf.Abs(vectorPosition)) * Mathf.Sign(vectorPosition); } }
- UICamera에 InputManager 추가
- 꼭 여기에 안 해도 됨. InpupManager용 오브젝트에 추가해도 ㅇㅋ
- 이미지2(조이스틱 막대기 부분)에 Joystick.cs 컴포넌트 추가
- 드래그되는 대상이기 때문에 여기다가 추가하는 것임
- TargetCamera 변수에는 UICamera 드래그 앤 드롭
- UICamera 기준으로 드래그된 위치 계산 위해 카메라 분리한 것!!
- JoystickValue 변수에는 InputManager가 붙은 오브젝트 드래그 앤 드롭
- Binding에 UICamera의 InputManager > SetMovement 함수 연결
- 점프 버튼 추가
- Button 추가 > InputManager 연결 > OnClick에 StartJump 연결
- ChracterMove, CharacterJimp.cs 수정
-
더보기ChracterMove.cs
CharacterJimp.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class CharacterMove : MonoBehaviour { public Transform trans; public Rigidbody2D rigid; public SpriteRenderer render; public float moveSpeed; // Update is called once per frame void Update() { // x좌표 (normalized : 균일한 크기로) 읽어오기 Vector2 direction = new Vector2(InputManager.Instance.Movement.x, 0).normalized; // 기본 방향은 0 입니다. // Vector2.zero 는 Vector2(0f, 0f) 와 같습니다 //Vector2 direction = Vector2.zero; // x좌표가 음수면 = 왼쪽이면 if (direction.x < 0f) // 왼쪽 키 누름 if (Input.GetKey(KeyCode.LeftArrow)) { // 왼쪽 키를 누른 경우엔 왼쪽 즉 Vector2.left 로 이동합니다 // Vector2.left 는 Vector2(-1f, 0f) 와 같습니다 direction = Vector2.left * moveSpeed; render.flipX = true; // 좌우반전 함 Debug.Log("dd"); } // x좌표가 양수면 = 오른쪽 else if (direction.x > 0f) // 오른 쪽 키 누름 else if (Input.GetKey(KeyCode.RightArrow)) { direction = Vector2.right * moveSpeed; render.flipX = false; // 좌우반전 안 함 } direction.y = rigid.velocity.y; // y값은 원래 Rigidbody2D가 갖고 있던 값 그대로 rigid.velocity = direction; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CharacterJump : MonoBehaviour { public Rigidbody2D rigid; public float jumpHeight; public AudioSource audioSource; public AudioClip clip; //bool isGround; // true : 딸에 닿음(점프 가능), false : 땅에 안 닿음(점프 1번만 가능) int jumpCount = 0; // 점프 횟수 int limitJumpCount = 2; // 최대 점프 가능 횟수 private void Start() { // 게임 시작 시 볼륨 설정(그 뒤로 바뀌지 않음) audioSource.volume = SoundManager.I.Volume; } // Update is called once per frame void Update() { if (InputManager.Instance == null) // 위쪽 화살표 눌렀을 때 if (Input.GetKeyDown(KeyCode.UpArrow)) // GetKey : 계속 누르면 true 반환, GetKeyDown : 계속 눌러도 true 한 번 반환 { return; } if (InputManager.Instance.IsJump) { // 점프 횟수 < 최대 점프 횟수 if (jumpCount < limitJumpCount) { jumpCount = jumpCount + 1; // 점프 횟수 증가 // AddForce : 어떠한 방향으로 움직이기, ForceMode2D.Impulse : 강제로 올리기 rigid.AddForce(Vector2.up * jumpHeight, ForceMode2D.Impulse); } } // 2-6 : 왜 동작을 안 하느냐? // -> CharacterMove에서 Vector2(-1f, 0f)로 움직여서 y값이 0이 됨... } // 유니티 기본 제공 함수, 트리거 설정한 박스 콜라이더에 다른 박스 콜라이더가 감지됐을 때(충돌 발생) 호출 private void OnTriggerEnter2D(Collider2D collision) { // 그라운드 태그면 땅에 닿았다.(true) if (collision.gameObject.tag == "ground") { //isGround = true; // 땅에 닿았다고 체크 jumpCount = 0; // 점프 횟수 초기화 InputManager.Instance.ClearJump(); // _isJump를 false로 } // 적 태그면 공격 성공 else if (collision.gameObject.tag == "enemy") { // 게임 오브젝트 내(진도 사우르스)의 EnemySaurus 컴포넌트를 가져오기 EnemySaurus enemy = collision.gameObject.GetComponent<EnemySaurus>(); enemy.OnDamage(); // 공격받기 // 적 공격 시 한 번 더 점프 // AddForce : 어떠한 방향으로 움직이기, ForceMode2D.Impulse : 강제로 올리기 rigid.AddForce(Vector2.up * jumpHeight, ForceMode2D.Impulse); // 소리 재생 audioSource.clip = clip; audioSource.Play(); // 볼륨 설정(게임 도중에도 바꿀 수 있음) audioSource.volume = SoundManager.I.Volume; } Debug.Log("Enter"); } }
- 앱 빌드하기 - Android
- 설정하기
- Edit > Preference > External Tools
- Edit > Preference > External Tools
- ▲위의 값이 추가되어있어야 함. 없다면 유니티 허브에서 설치
- 허브 > 점 3개 > 모듈 추가 > Android Build Support 설치
- ▼Player Settings에서 Company Name, Product Name, Version 입력
- ▼Icon 지정
- 설정 안 하면 유니티 로고
- Adaptive Icon은 배경과 로고가 분리되어 동적인 움직임 연출 가능
- ▼Resolution and Presentation
- 이 겜은 가로로 플레이되는 게임이라 Orientation 수정 필요
- Detault Orientation = Auto Rotation
- Allowed Orientations for Auto Rotation
- Landscape 체크 : 가로 화면을 유지한 상태로 스마트폰을 어떤 방향으로 두는지에 따라 위 아래가 바뀜
- 이 겜은 가로로 플레이되는 게임이라 Orientation 수정 필요
- ▼Other Settings
- 64bit 지원 해야해서 Scripting Backend = IL2CPP로 변경
- Target Architectures의 ARM64 체크
- ▼Publishing Settings
- Keystore없으면 Keystore Manager로 새로 생성
- 빌드~~
- File > Build Settings... > Android 선택 > Switch Platform > Build
- 설정하기
- Unity Ads로 광고 넣기
- Window > General > Services
- Project ID 설정
- Ads 클릭
- Monetize your games 클릭해서 활성화
- 플레이어 죽으면 광고 뜬 후 부활하게 설정하기
-
더보기PlayerHealth.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerHealth : MonoBehaviour { public int health = 1; [Header("Colliders")] public BoxCollider2D col1; public BoxCollider2D col2; public SpriteRenderer render; // private Transform transform; private Vector3 initialPos; private int revivalCount = 1; public void Awake() { // 현재 위치 저장 this.transform = GetComponent<Transform>(); initialPos = this.transform.position; } // 공격받음(공룡에 닿음) private void OnDamage() { // 체력 깎기 health--; // 체력 값이 양수면 그냥 넘어가기 if (health > 0) { return; } // 체력값이 양수가 아니면 죽기 OnDead(); } // 죽기 private void OnDead() { // 부활 가능 횟수가 남아있으면 부활하기 if (revivalCount > 0) { ShowRevival(); // 광고 보기 revivalCount--; // 부활 횟수--; return; } // 부활 가능 횟수가 없으면 죽기 col1.enabled = false; col2.enabled = false; render.color = new Color(1, 1, 1, 0.5f); } // 광고 보기 private void ShowRevival() { // 광고 표기 UnityAdsManager.Instance.ShowAd(); } // 부활시 출력될 함수 public void OnRevival() { col1.enabled = true; col2.enabled = true; render.color = new Color(1, 1, 1, 1f); health = 1; this.transform.position = initialPos; } private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "enemy") { OnDamage(); } } }
-
더보기GameManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; // 씬 사용 public class GameManager : MonoBehaviour { public static GameManager I; // static : 게임 전체에 단 하나만 존재, ㅣ = 인스턴스 public int Score; // 점수 public PlayerHealth player; // Start와 동일한데 한 단계 앞서서 호출 // Awake -> Start -> Update -> Update... private void Awake() { I = this; // 자기 자신을 넣어주기 } // 코인 먹는 함수 public void AddScore(int score) { Score += score; Debug.Log(Score.ToString()); } // 2초 뒤에 게임 오버 public void GameOver() { Invoke("GameOver_", 2); // GameOver_를 2초 뒤에 호출 } // 게임 이김(게임 다시 시작) public void WinGame() { GameOver_(); } // 게임 오버 void GameOver_() { SceneManager.LoadScene("GameScene"); // 게임 씬 부르기 } // 플레이어 부활 public void RevivePlayer() { player.OnRevival(); } }
-
더보기UnityAdsManager.cs
using System.Collections; using UnityEngine; using UnityEngine.Advertisements; public class UnityAdsManager : MonoSingleton<UnityAdsManager>, IUnityAdsListener { // window > General > Service 에서 확인 private const string android_game_id = "4182769"; private const string ios_game_id = "4182769"; private const string myPlacementId = "rewardedVideo"; // 보상형 광고 public void Start() { Initialize(); //초기화 } // 초기화 private void Initialize() { // 게임 아이디 세팅 Advertisement.AddListener(this); #if UNITY_ANDROID Advertisement.Initialize(android_game_id); #elif UNITY_IOS Advertisement.Initialize(ios_game_id); #endif } private void OnDisable() { Advertisement.RemoveListener(this); } // 광고 보이기 public void ShowAd() { StartCoroutine(ShowAdWhenReady()); } IEnumerator ShowAdWhenReady() { while (!Advertisement.IsReady(myPlacementId)) { yield return null; } Advertisement.Show(myPlacementId); } // 광고 끝났을 때 public void OnUnityAdsDidFinish(string placementId, ShowResult showResult) { switch (showResult) { case ShowResult.Failed: Debug.LogError("보상 처리 실패"); break; case ShowResult.Skipped: Debug.Log("보상 처리 중도 이탈"); break; case ShowResult.Finished: Debug.Log("보상 처리 완료"); GameManager.I.RevivePlayer(); // 캐릭터 부활 break; } } public void OnUnityAdsReady(string placementId) { if (placementId == myPlacementId) { // Advertisement.Show(myPlacementId); } } public void OnUnityAdsDidError(string message) { Debug.LogError("OnUnityAdsDidError :" + message); } public void OnUnityAdsDidStart(string placementId) { if (placementId == myPlacementId) { // Advertisement.Show(myPlacementId); } } }
-
...플레이 스토어에서 개발자 등록 했는데
등록할만한 사진이...........................뭐가있지
검토중....^^
수료증까지 주네?ㅋㅋㅋ 재밌었다.
다른 책으로도 유니티 기초를 배우고 있는데,
스코에서는 가벼운 기초 + 살짝 깊이있는 내용까지 가볍게 다뤄줘서 좋았음
근데 중간에 멘토님이 바뀌시는데 그때 같이 했던 플젝도 조금 달라져서 따라잡느라 고생했음.
게임 레벨링이나... 맵같은 걸 더 공부해서 더 심화된 프로젝트도 만들어보고 싶다!!!
지금 다른 친구랑 스터디중인데 더 사람을 모아서 큰 플젝 해보려고 한다.
팟팅!
반응형
'코딩 일기장 > Unity' 카테고리의 다른 글
[Unity] 게임 규칙 만들기 (0) | 2021.10.31 |
---|---|
[Unity] 3D 장애물 런게임 만들기(c# 스트립트 없이) (0) | 2021.10.24 |
[유니티] 스파르타 코딩 : 게임 개발 - 4주차 (0) | 2021.06.21 |
[유니티] 스파르타 코딩 : 게임 개발 - 3주차 (0) | 2021.06.19 |
[유니티] 스파르타 코딩 : 게임 개발 - 2주차 (0) | 2021.06.08 |
Comments