와챠의 우당탕탕 코딩 일기장

[유니티] 스파르타 코딩 : 게임 개발 - 5주차(마지막) 본문

코딩 일기장/Unity

[유니티] 스파르타 코딩 : 게임 개발 - 5주차(마지막)

minWachya 2021. 6. 23. 20:32
반응형

오늘 한 것

  • 기존 키 입력에서 터치 입력으로 변경(조이스틱)
    • 모바일 환경에 맞게 설정하기
  • 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(조이스틱 막대기 부분)

 

  • 조이스틱 움직이기
    • 조이스틱에 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
      using 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;
      
          }
      }
      ​
      CharacterJimp.cs
      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
    • ▲위의 값이 추가되어있어야 함. 없다면 유니티 허브에서 설치
      • 허브 > 점 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 체크 : 가로 화면을 유지한 상태로 스마트폰을 어떤 방향으로 두는지에 따라 위 아래가 바뀜
    • ▼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);
              }
          }
      }​

 

 

 


...플레이 스토어에서 개발자 등록 했는데

등록할만한 사진이...........................뭐가있지

 

 

검토중....^^

 

수료증까지 주네?ㅋㅋㅋ 재밌었다.

다른 책으로도 유니티 기초를 배우고 있는데,

스코에서는 가벼운 기초 + 살짝 깊이있는 내용까지 가볍게 다뤄줘서 좋았음

 

근데 중간에 멘토님이 바뀌시는데 그때 같이 했던 플젝도 조금 달라져서 따라잡느라 고생했음.

 

게임 레벨링이나... 맵같은 걸 더 공부해서 더 심화된 프로젝트도 만들어보고 싶다!!!

지금 다른 친구랑 스터디중인데 더 사람을 모아서 큰 플젝 해보려고 한다.

팟팅!

반응형
Comments