https://luckygg.tistory.com/181

 

[Design Pattern] 옵저버 패턴(Observer Pattern) 이야기 #1 (예제 포함)

옵저버 패턴(Observer Pattern) 옵저버 패턴은 관찰자 패턴이라고도 합니다. 일대다 관계를 이루고 있으며, 상태가 업데이트되면 모든 옵저버들에게 정보를 갱신할 수 있도록 하는 것을 의미합니다.

luckygg.tistory.com

해당 블로그 글을 보며 정리하였습니다.

 

Observer Pattern(옵저버패턴)

  • 디자인패턴 중 하나
  •  한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 정보가 자동으로 갱신되는 방식으로, 일대다(one-to-many) 의존성을 정의합니다
  • Subject(관찰대상,주제자)업데이트 되면 Observer(관찰자)들에게 정보를 갱신 할 수 있도록 함
  • ex)Subject(유튜브채널,신문사) / Observer(구독자들)

Observer(관찰자) 특징

  • 언제든지 새로 추가 되거나 제거 될수 있어야 함
  • 추가 / 삭제가 되더라도 Subject에 영향을 주지 않음 :: 해당 관계를 느슨한 결합(Loose coupling)이라고 함
  • C++ 에서 구현시 Observer는 특정 interface를 구현해야만 함
  • 해당 interface에서는 Observer는 Update() 기능을 구현함

Subject(주제자) 특징

  • Observer에 대한 정보가 없음 : Observer가 무슨 동작을 하는지 모름
  • C++ 에서 구현시 Subject특정 interface를 구현해야만 함
  • 해당 interface에서는 상속받는 Subject Class의 등록,해제,갱신을 구현 및 기타 함수를 구현함.

C++ 구현시의 UML로 표현한 Observer 패턴 구조

 


C#에서는 따로 옵저버 패턴을 구현할 필요가 없다.

이 기능을 Deligate 와 Event 함수가 대신하기 떄문이다.

 

해당 출처를 참고하면 더욱 도움이 된다.

https://unity.com/kr/how-to/create-modular-and-maintainable-code-observer-pattern

 

옵저버 패턴으로 유지 관리가 가능한 모듈식 코드 만들기

이 페이지에서는 관찰자 패턴과 이 패턴이 서로 상호작용하는 객체 간의 느슨한 결합 원리를 지원하는 데 어떻게 도움이 되는지 설명합니다.

unity.com

 

 

 

유니티 5주차 - 하이퍼캐주얼 게임 개발에서 터치패드를 구현할떄 사용 하였다.
해당 게임에서 구현한 옵저버 패턴

나는 옵저버패턴을 이런식으로 구현해서 사용했지만 , 실제로는 이렇게 구현하면 안된다.

일대다관계로 구성이 되야하지만 현재 일대일 관계로 2개를 구현하였다.

다음에는 구현 시 주의하면 만들어봐야겠다.

 


C# - Delegate Vs Envet ?

C++ 의 함수포인터C#으로 변환한게 Delegate이다. 

Delegate멀티캐스팅 기능으로 하나의 델리게이트로 여러 인스턴스(객체)가 여러 메서드(함수)를 호출 할수 있도록 구성이 가능하다.

 

하지만 이렇게되면 객체지향의 4대원칙 중 보안성이 지양되기 때문에 Delegate가 아닌 Event 함수가 주로 사용됩니다.

Event함수는 Delegate기반으로 되있지만 엑세스 제한으로 외부에서 이벤트를 추가(+=)/ 제거(-=)만 가능하며 이벤트 핸들러에 등록된 함수가 호출되지 않는다.

 

즉, Event함수의 함수호출은 내부클래스에서만 호출이 된다.

 

특징 Delegate Event
역할 함수포인터로 사용 델리게이트에 대한 접근을 제한하고 이벤트 모델 제공
엑세스 제어 어디서든 호출 가능 선언된 클래스 외부에서는 호출불가
사용 목적 일반적인 콜백이나 함수 참조 관리 안전한 이벤트 발생 및 처리
기본 엑세스 public 특별히 제한된 엑세스
(add/Remove만 허용)
멀티캐스트 지원 가능 가능

 


<이 부분은 개인적으로 생각한 것을 적은 부분입니다.  틀린 내용이 있으시면 댓글 부탁드립니다 >

 

왜 하나의 클래스에 Delegate와 Event를 동시에 쓰나요?

 

Delegate함수 포인터라는 자료형의 변수를 선언하는 것과 유사하다.

그리고 Event는 Delegate에서 선언한 변수를 자료형으로 삼고 사용한다.

  public delegate void TouchPadMoveHandler(); // delegate 키워드를 사용하여 함수포인터 변수 선언
  public static event TouchPadMoveHandler OnTouchLeftPad; //선언한 변수를 자료형으로 event 함수 선언

'내일배움캠프_Unity_6기 > TIL(Today I Learend)' 카테고리의 다른 글

TIL : 2024-09-04(수)  (0) 2024.09.04
TIL : 2024-09-03(화)  (0) 2024.09.03
TIL : 2024-08-29(목)  (0) 2024.08.29
TIL : 2024-08-28(수)  (0) 2024.08.28
TIL : 2024-08-27(화)  (0) 2024.08.27

인기게임 역기획 하기

 

역기획 대상 게임 : 로스트아크 

더보기
  1. 이 게임의 매력 포인트는 무엇인가요?
  • 다양한 플레이어블 캐릭터 직업군
  • 로스트아크는 26개의 직업군'직업 각인'이라는 시스템으로 한개의 직업군으로 두가지의 전투 스타일을 경험할 수 있습니다.
  • 보스 레이드의 패턴 파훼 및 클리어 성취감이를 파훼하고 클리어하는 과정에서 높은 성취감을 제공합니다. 특히, 플레이어들은 팀원들과 협력하여
  • 다양한 기믹(협동 카운터,보스 전멸 기믹 넘기기 등등)을 파훼할 수 있고 보스의 공격 패턴을 분석(저스트가드 및 카운터하여 딜타임 확보)하며 숙련될 수록 더욱 재미를 느끼게 하였습니다.
  • 보스는 고유의 공격 패턴난이도를 가지고 있으며,
  • 수평 방향의 내실 및 다양한 수집 콘텐츠
  • 전투 및 레이드만 아닌 섬 탐험, 항해, 생활 콘텐츠(낚시, 채광 등), 모험의 서, 카드 수집 등의 컨텐츠로 다양한 볼거리 와 재미를 제공합니다.

2. 이 게임을 사람들이 좋아하는 이유

  • 풍부한 콘텐츠와 스토리의 몰입도
  • 이 게임은 전투/수집 콘텐츠에서도 다양하고 깊이 있는 재미를 유저들이 느낄 수 있으며 그 뿐만 아니라 위의 컨텐츠를 즐기면서 메인스토리 및 서브스토리 전부 유저들이 몰입하여 감상 할 수 있는 점을 사람들이 좋아하는 이유라고 생각합니다.

3.게임 개요

3-1. 이름 : 로스트아크

3-2. 장르 : MMORPG

3-3. 제작사 : 스마일게이트RPG,트라이포드 스튜디오

3-4. 출시일 : 2018년 11월 7일(정식 출시 시즌1 2019년 12월)

3-5. 엔진 : 언리얼 엔진3

4.시스템 개요

4-1. 보스레이드 : 군단장 레이드 및 카제로스 레이드의 지원 시스템

4-2. 지원 시스템(에스더스킬,연합군스킬)은 8인 이상 레이드 컨텐츠에서 공대장이 사용하는 지원 스킬로 전투 시에 다양하게 이로운 점을 줘서 선택했습니다.

기능은 스토리를 진행하면서 만났던 NPC들을 호출하여 전투를 유리하게 이끌고 가는 시스템 입니다.

전투 중 게이지를 모아서 원하는 타이밍에 공대장이 해당 버튼을 누르면 NPC가 등장하여 전투를 유리하게 해주고 퇴장합니다.

사용 예시)

1.보스를 무력화 하여 딜타임을 만

2.전멸 기믹을 막아서 더욱 보스레이드를 수월하게 진

3. 특수한 타이밍에 사용해서 새로운 연출(보스와 NPC의 상호작용)로 유리한 전투를 극대화 함

 

해당 이벤트를 통해 아무리 인기 게임이여도 사람마다 생각하는 점이 달라 재미있었고 다르게 생각 할수 있었던것 같다.

5주차 숙제 게임개발

 

Run & Jump - 프로토타입 설명

1.마우스를 누르면 캐릭터 머리 위에 파워 게이지가 표시됨

2.마우스를 놓으면 놓은 방향으로 캐릭터가 빙글빙글 돌아가며 발판들을 넘어감

3.이미 한번 착지한 발판은 점수 x , 발판을 처음 밟을때만 점수 증가

 

오늘 구현 한것

1. 블럭 사이에 있을때 점프로 가시 통과할 수 있게 구현

2. 블럭 끼리 겹치지 않게 구현 - BlockSpanwer에서 생성 로직 변경 필요

3. LV에 따른 스테이지 난이도 변경

4. 블럭에 빠지는 함정 만들기(함정 특징: 땅바닥인척 하지만 갑자기 내려감)

4-1. Player 점프 로직 변경(떨어지는 블럭 위에 있을때 점프 가능하게)

 

구현 목표

1.옆으로 흐르는 백그라운드 만들기

2.Title Scene 만들기(마스크 트랜지션 사용해서 화면 옮겨보기)

3.파티클 및 연출 효과 넣어 시각적으로 좋게 만들기

4.사운드 추가하기


1. 블럭 사이에 있을때 점프로 가시 통과할 수 있게 구현

 

각 블럭의 높이를 줄여 공간을 확보 하였고 , 가시의 갯수가 많아 게임이 너무 어려워 1개로 듬성듬성 배치하기로 함

 

2. 블럭 끼리 겹치지 않게 구현 - BlockSpanwer에서 생성 로직 변경 필요

코드를 따로 변경하진 않았고, 블럭의 속도를 전부 똑같이 통일 하였다.

전부 똑같이 변경한 이유는 게임이 [점프킹] 같은 점프 시스템을 가져왔다보니

블럭들의 속도가 종류에 상관없이 일정해야  게임의 난이도가 좀 나아질것 같다.

 

3. LV에 따른 스테이지 난이도 변경

BlockSpanwer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum defineNum
{
    Blokc_End_posX = -17, //해당 x좌표에 닿으면 pivot이 닿으면 사라지는 좌표 
}

public class BlockSpawner : MonoBehaviour
{
    [SerializeField] List<Block> block_list;
    [SerializeField] float blockSummonCoolTime = 2.0f;

    [SerializeField] float startPosX = 17.0f; //생성 시작되는 위치 x좌표
    [SerializeField] float startPosY_Min = -5.0f;
    [SerializeField] float startPosY_Max = 2.0f;

    Coroutine coroutineBlockSpanw;
    [SerializeField] List<float> blockSpawnPosY;

    [SerializeField] bool isstop = false;

    int isSpawnBlockIndex = -1;

    public void BlockSpawnerON()
    {
        if (!isstop)
        {
            if (coroutineBlockSpanw == null)
            {
                coroutineBlockSpanw = StartCoroutine(BlockSpanw());
            }
            else
            {
                StopCoroutine(coroutineBlockSpanw);
                coroutineBlockSpanw = StartCoroutine(BlockSpanw());
            }
        }
    }

    public void BlockSpawnerOFF()
    {
        if (coroutineBlockSpanw != null)
        {
            StopCoroutine(coroutineBlockSpanw);
        }
    }

    IEnumerator BlockSpanw()
    {
        float LevelSuummonCoolTime;

        while (true)
        {
            MakeBlock();

            //현재 레벨의 10% 만큼 블럭 생성주기 증가 -> 블럭이 늦게 등장함
            LevelSuummonCoolTime = blockSummonCoolTime + ( (GameManager.Instance.CurLevel - 1) *  0.1f);
            
            yield return  new WaitForSeconds(LevelSuummonCoolTime);
        }
    }

   private void MakeBlock()
    {
        //블록 생성확률이 100% 에서 75%까지 떨어짐
        //생성확률은 6레벨 이하까지만 적용 그 이후로는 50% 동일
        int curStageLv = GameManager.Instance.CurLevel;
        int SuumonPercent = curStageLv >= 5 ? 5 : (curStageLv - 1);

        if (SuumonPercent <= Random.Range(0, 20))
        {

            int type = Random.Range(0, block_list.Count);

            Block block = Instantiate(block_list[type]);
            block.gameObject.SetActive(false);

            float x = startPosX;
            //y 범위 : -4.5 ,-1.5,1.5, 4.5
        
            //한번 생겼던 위치에 다시 생기지 않는 함수
            int y;
            do
            {
                y = Random.Range(0, blockSpawnPosY.Count);
            }
            while (isSpawnBlockIndex == y);
            isSpawnBlockIndex = y;


            block.Block_pos = new Vector3(x, blockSpawnPosY[y], 0);
            //레벨 수치의 10% 만큼 모든 블럭의 speed 증가
            if (curStageLv > 1) block.Speed += (curStageLv * 0.1f);
            block.gameObject.SetActive(true);
        }
    }
}

블럭 생성시 게임매니저에서 현재 스테이지 레벨을 호출 하여 블록 생성 확률,블록 생성 주기,블록 이동 속도 수치를 조절하게 함.

4. 블럭에 빠지는 함정 만들기(함정 특징: 땅바닥인척 하지만 갑자기 내려감)

 

1.좌우로 이동하는 블록 추가

 

BlockGroup.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockGroup : Block
{
    [SerializeField] protected List<GameObject> blocks;

    private void Update()
    {
        MoveBlock();
    }
}

MovePattern.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovePattern : MonoBehaviour
{
    [SerializeField] float avoidSpeed = 2.0f;

    int dir = 1;
    private Vector3 originPos;
    private float moveRange = 1.0f;

    private void Start()
    {
    	//처음 위치 좌표 저장
        originPos = transform.localPosition;
    }

    private void Update()
    {
    	//이동거리(MoveRange) 이상 이동한경우 방향값(dir) 변경
        if(Mathf.Abs(transform.localPosition.x - originPos.x) >= moveRange) dir *= -1;
         
        transform.localPosition += dir * ((Vector3.right  * avoidSpeed)) * Time.deltaTime;
    }
}

좌,우로 왔다 갔다 하는 패턴을 만들때 왼쪽/오른쪽 좌표로 설정해도 되지만

조건문을 이동거리(MoveRange)로 사용하면 좌표 값을 건드리지 않고 이동거리로 제어가 가능해져서 유용하다.

2.밟으면 떨어지는 블록 추가

DropPattern.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DropPattern : MonoBehaviour
{
    [SerializeField] float DropSpeed = 0.5f;

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.CompareTag("Player"))
        {
            StartCoroutine(Drop());
        }
    }

    IEnumerator Drop()
    {
        while(transform.position.y >= -12f)
        {
            transform.position += Vector3.down * DropSpeed * Time.deltaTime;
            yield return null;
        }
    }

}

 

위에 블럭들의 콜리더 분리가 필요해서 부모 오브젝트를 만들고 자식으로 3개의 블럭을 추가하였다.

각 블럭마다 콜리더를 추가하였고 기믹이 필요한곳에만 스크립트(MovePattern.cs , DropPattern.cs)를 넣어서 사용 하였고 3개의 블럭은 동시에 같은속도로 같이 이동해야 되기때문에

새 스크립트인 BlockGroup 스크립트를 만들고 Block을 부모로 상속하여 이동하는 함수를 사용하였다.

 

4-1. Player 점프 로직 변경(떨어지는 블럭 위에 있을때 점프 가능하게)

떨어지는 블럭에 캐릭터가 서있으면 OnColliderEnter() 메서드가 자꾸 호출하여서 점프가 안되는 버그가 있었다.

Collider 함수 안에 점프상태를 초기화하는 내용이 있음

대처방법으로 콜리더가 호출 되더라도 점프 상태인 경우에만 점프상태를 초기화 할 수 있게 로직을 변경하였음 

 

떨어지지만 게이지는 초기화 되지 않기 때문에 점프가 가능해짐

 

Player.cs

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Block"))
        {
            // 캐릭터가 점프 + 점프중 이라는 상태를 만족해야만 멈춤
            if (isJump && isJumping)
            {
                PlayerStop();
                anim.SetBool("isJump", false);
            }
        }

        if (collision.gameObject.CompareTag("Spike"))
        {
            Debug.Log("Player 가시 닿음");
            isdead = true;
            anim.SetBool("isDead", true);
            PlayerStop();
            GameManager.Instance.PlayerDie();
        }
    }

 

5주차 숙제 게임개발

 

 

Run & Jump - 프로토타입 설명

1.마우스를 누르면 캐릭터 머리 위에 파워 게이지가 표시됨

2.마우스를 놓으면 놓은 방향으로 캐릭터가 빙글빙글 돌아가며 발판들을 넘어감

3.이미 한번 착지한 발판은 점수 x , 발판을 처음 밟을때만 점수 증가

 

오늘 구현 한것

1. 플레이어 캐릭터가 카메라 경계선으로 이동시 카메라 Zoom In/Out 기능 추가

2. 블럭 스폰 위치 규격화 하기

2-1. 4방향에서 랜덤하게 나오게 구현

 

구현 목표

1.블럭 사이에 있을때 점프로 가시 통과할 수 있게 구현

이런 상황이 되었을때 점프를 해서 지나 갈 수 있게 해야함

1-2. 블럭 끼리 겹치지 않게 구현 - BlockSpanwer에서 생성 로직 변경 필요

현재 블럭이 2개가 겹쳐서 생성됨

2.블럭에 빠지는 함정 만들기(함정 특징: 땅바닥인척 하지만 갑자기 내려감)

3.LV 수치에 따른 스테이지 변화 다르게 하기

4.옆으로 흐르는 백그라운드 만들기

5.Title Scene 만들기(마스크 트랜지션 사용해서 화면 옮겨보기)

 


1.카메라 Zoom 기능 추가

이유 : 게임을 플레이 해보는 도중 경계선에 있으면 캐릭터가 화면 밖에 있을때 조작이 잘 되고있는지 못하는 불편함을 느껴 추가하기로 하였다.

 

하지만 카메라가 오브젝트를 추적 할 수있는 메서드를 몰라 구글링 및 ChatGpt의 도움을 받아 추천 받았다.

카메라 안에 있는 특정 오브젝트를 찾는 방법은 2가지를 알았다.

 

1-1.첫번째는 Camera.WorldToViewportPoint' 메서드를 사용

해당 메서드는 월드 좌표를 뷰 포트 좌표로 변환해주는 역할을 한다.

ViewPort란 좌측하단(0,0) 에서 우측상단(1,1)을 가르키는 좌표계이다. 최소(-1,-1) 에서 최대 (2,2)까지 값을 가진다.

 

즉,카메라가 보는 영역인 ViewPort 좌표계에 Player 월드좌표를 변환 시켜서 사용 하는것이다.

    public float originSize = 6.87f;
    public float targetSize = 15f;  // 목표로 하는 Orthographic Size
    public float zoomSpeed = 5f;   // 줌 인/아웃 속도

    void ObjINCamera() //해당 메서드를 Update()에 포함 시킴
    {
        // 오브젝트의 월드 좌표를 뷰포트 좌표로 변환
        Vector3 viewportPos = Camera.main.WorldToViewportPoint(transform.position);

        // 뷰포트 좌표가 카메라의 시야에 있는지 확인
        if (!(viewportPos.x >= 0 && viewportPos.x <= 1 && viewportPos.y >= 0 && viewportPos.y <= 1 && viewportPos.z > 0))
        {
        	Debug.Log("오브젝트는 카메라의 뷰 밖에 있습니다.");
            Camera.main.orthographicSize = Mathf.Lerp(Camera.main.orthographicSize, targetSize, Time.deltaTime * zoomSpeed);

        }
        else
        {
        	Debug.Log("오브젝트는 카메라의 뷰 안에 있습니다.");
            Camera.main.orthographicSize = Mathf.Lerp(Camera.main.orthographicSize, originSize, Time.deltaTime * zoomSpeed);
        }
    }

이런식으로 하면 Lerp 함수를 통하여 자연스럽게 화면이 Zoom기능이 구현된다.

 

1-2.GeometryUtility.TestPlanesAABB 메서드를 사용한 방법

GeometryUtility.CalculateFrustumPlanesGeometryUtility.TestPlanesAABB 두 메서드를 사용하는 방법이다.

카메라의 View frustum(절두체)에 오브젝트의 바운딩 박스가 포함되는지 확인 하는 방법이다.

 

절두체는 카메라가 실질적으로 비추는 3D 공간이라 생각하면된다.

즉, 절두체에 오브젝트가 존재하는지 체크하여 Zoom 기능이 동작하는 코드이다. 

using UnityEngine;

public class ObjectInCameraView : MonoBehaviour
{
    public Camera mainCamera; // 사용할 카메라

    void Update()
    {
        // 카메라의 뷰 프러스텀을 계산
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);

        // 오브젝트의 바운딩 박스를 얻음
        Bounds objectBounds = GetComponent<Renderer>().bounds;

        // 오브젝트의 바운딩 박스가 카메라의 뷰 프러스텀 안에 있는지 확인
        if (GeometryUtility.TestPlanesAABB(planes, objectBounds))
        {
            Debug.Log("오브젝트는 카메라의 뷰 안에 있습니다.");
        }
        else
        {
            Debug.Log("오브젝트는 카메라의 뷰 밖에 있습니다.");
        }
    }
}

 

절두체는 컬링기법(그래픽 렌더링을 줄여 최적화하는 기법중 하나)을 사용할떄 주로 나타나는 이론이다.

https://01149.tistory.com/category/%EC%BB%B4%ED%93%A8%ED%84%B0%20%EA%B7%B8%EB%9E%98%ED%94%BD%EC%8A%A4

 

'컴퓨터 그래픽스' 카테고리의 글 목록

개발 공부하면서 자주 사용하던것, 알아야하는것을 올리는 곳입니다.

01149.tistory.com

잘 모르면 해당 글을 참고하자 

 

우선, 나는 첫번째 방법으로 구현을 하였으나 문제가 있었다.

플레이어가 뷰포트 경계에 서있는경우 카메라 Zoom 기능이 계속 동작하여 불쾌함을 주었다.

 

해당 코드를 조건문을 View 포트 안/밖 여부만 확인하고 바로 동작하게 되는 로직을 짜서 해당 현상이 일어났던 것이다.

 

비동기로 Zoom 기능이 동작을 해야만 자연스럽게 움직일것같아 코드를 수정하였다.

    public float originSize = 6.87f;
    public float targetSize = 15f;  // 목표로 하는 Orthographic Size
    public float zoomSpeed = 5f;   // 줌 인/아웃 속도
    bool isZooming = false;

    void ObjINCamera() //Update()함수안에 포함
    {
        // 오브젝트의 월드 좌표를 뷰포트 좌표로 변환
        Vector3 viewportPos = Camera.main.WorldToViewportPoint(transform.position);

        // 뷰포트 좌표가 카메라의 시야에 있는지 확인
        if (!(viewportPos.x >= 0 && viewportPos.x <= 1 && viewportPos.y >= 0 && viewportPos.y <= 1 && viewportPos.z > 0))
        {
            //Camera.main.orthographicSize = Mathf.Lerp(Camera.main.orthographicSize, targetSize, Time.deltaTime * zoomSpeed);
            if (!isZooming)
            {
                StartCoroutine(ZoomCamera(targetSize));
            }
        }
        else
        {
            //Camera.main.orthographicSize = Mathf.Lerp(Camera.main.orthographicSize, originSize, Time.deltaTime * zoomSpeed);
            if (!isZooming)
            {
                StartCoroutine(ZoomCamera(originSize));
            }
        }
    }

    // 코루틴을 사용하여 카메라의 줌을 부드럽게 변경
    IEnumerator ZoomCamera(float targetZoom)
    {
        isZooming = true;
        while (Mathf.Abs(Camera.main.orthographicSize - targetZoom) > 0.01f)
        {
            Camera.main.orthographicSize = Mathf.Lerp(Camera.main.orthographicSize, targetZoom, Time.deltaTime * zoomSpeed);
            yield return null;
        }
        Camera.main.orthographicSize = targetZoom;
        isZooming = false;
    }

isZooming 이라는 bool 함수를 넣어서 Zoom 기능 사용중인지 체크 여부를 했다.

코루틴에서 isZooming을 true로 바꿔 ObjINCamera()의 if문이 참이여도 동작을 안하게 막아놓았다.

코루틴은 비동기로 해당 Camera Size 만큼 Zoom 동작을 하고 코루틴을 탈출하게 설계를 하였다. 

 

해당 코드로 수정하니 카메라 Zoom 기능이 잘 동작 하였다.

 

2. 블럭 스폰 위치 규격화 하기

블럭 위치를 4방향에서 랜덤하게 나오게 구현하였다.

 

BlockSpanwer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum defineNum
{
    Blokc_End_posX = -17, //해당 x좌표에 닿으면 pivot이 닿으면 사라지는 좌표 
}

public class BlockSpawner : MonoBehaviour
{
    [SerializeField] List<Block> block_list;
    [SerializeField] float blockSummonCoolTime = 2.0f;

    [SerializeField] float startPosX = 17.0f; //생성 시작되는 위치 x좌표
    [SerializeField] float startPosY_Min = -5.0f;
    [SerializeField] float startPosY_Max = 2.0f;

    Coroutine coroutineBlockSpanw;
    [SerializeField] List<float> blockSpawnPosY;

    int isSpawnBlockIndex = -1;

    public void BlockSpawnerON()
    {
       
        if (coroutineBlockSpanw == null)
        {
            coroutineBlockSpanw = StartCoroutine(BlockSpanw());
        }
        else
        {
            StopCoroutine(coroutineBlockSpanw);
            coroutineBlockSpanw = StartCoroutine(BlockSpanw());
        }
    }

    public void BlockSpawnerOFF()
    {
        if (coroutineBlockSpanw != null)
        {
            StopCoroutine(coroutineBlockSpanw);
        }
    }

    IEnumerator BlockSpanw()
    {
        while(true)
        {
            MakeBlock();

            yield return  new WaitForSeconds(blockSummonCoolTime);
        }
    }

   private void MakeBlock()
    {
        int type = Random.Range(0, block_list.Count);

        Block block = Instantiate(block_list[type]);
        block.gameObject.SetActive(false);

        float x = startPosX;
        //y 범위 : -4.5 ,-1.5,1.5, 4.5

        //한번 생겼던 위치에 다시 생기지 않는 함수
        int y;
        do
        {
            y = Random.Range(0, blockSpawnPosY.Count);
        }
        while (isSpawnBlockIndex == y);
        isSpawnBlockIndex = y;

        block.block_pos = new Vector3(x, blockSpawnPosY[y], 0);




        block.gameObject.SetActive(true);
    }
}

블럭 생성하는것을 코루틴으로 작성하여 비동기로 동작하게 하였으며 스테이지 LV 변화에 따라 생성속도를 빠르게 하기위해 blockSummonCoolTime 변수로 조절할 수 있게 했다.

 

또한 한번 생성한 자리에는 다시 생성하지 않게 로직을 만들어 변칙성을 주었다.

 

SerializeField(직렬화)를 통하여 Inspector에서 제어 할 수 있게 했다.

 

'내일배움캠프_Unity_6기 > TIL(Today I Learend)' 카테고리의 다른 글

TIL : 2024-08-29(목)  (0) 2024.08.29
TIL : 2024-08-28(수)  (0) 2024.08.28
TIL : 2024-08-26(월)  (0) 2024.08.26
TIL : 2024-08-22(금)  (0) 2024.08.23
TIL :: 2024-08-22(목)  (0) 2024.08.22

유니티퀘스트 8.야구게임

using Microsoft.VisualBasic;

Random rand = new Random(); //랜덤클래스 초기화
int Number = rand.Next(100, 1000); //100이상 1000미만 랜덤 숫자 초기화

Console.WriteLine("랜덤 정답 : " + Number);

char[] targetNumber = new char[3];
bool guessedCorrectly = false;
int attempts = 0;
for(int i = 0; i < 3 ; i++)
{
    targetNumber[i] = Number.ToString()[i];
}

while(!guessedCorrectly)
{
    attempts++;

    Console.Write("Enter your guess (3 digits) : ");
    string userGuess = Console.ReadLine();

    //예외처리
    //1.유저가 숫자를 입력 안한경우
    //총합 입력값이 (100 <= input <= 999) 이면 검사 시작



    int strikes = 0;
    int balls = 0;

    for (int i = 0; i < 3; i++) // userGuess
    {
        for(int j = 0; j < 3; j++) // targetNumber
        {
            if(targetNumber[j]  == userGuess[i]) //숫자가 같은지 비교
            {

                switch(i == j) //같은 자리인지 비교
                {
                    case true: //같은자리 (스트라이크)
                        strikes++;
                        break;

                    case false: //다른자리 (볼)
                        balls++;
                        break;
                }

            }
        }
    }
    Console.WriteLine(strikes + "Strike(s), " + balls + "Ball(s)");

    if (strikes >= 3) guessedCorrectly = true;
}

Console.WriteLine("Congratulations! You've guessed the number in" + attempts + " attempts.");

 

5주차 숙제 게임개발

 

Run & Jump - 프로토타입 설명

1.마우스를 누르면 캐릭터 머리 위에 파워 게이지가 표시됨

2.마우스를 놓으면 놓은 방향으로 캐릭터가 빙글빙글 돌아가며 발판들을 넘어감

3.이미 한번 착지한 발판은 점수 x , 발판을 처음 밟을때만 점수 증가

 

오늘 구현 한것

1.각종 플레이어의 플레이를 방해할 장애물 및 아이템 추가

    ㄴ점수를 주는 아이템인 동전 추가 및 플레이어를 방해하는 가시 추가 

2.날라오는 블럭들이 랜덤 범위, 규격화 필요

     ㄴ위치는 규격화 하지 못헀지만, 블럭들 4종류로 고정시켜 동전과 가시를 배치시킴

3. 게임 오버시 팝업창 및 제작

     ㄴ 플레이어가 떨어지거나 가시에 찔리면 팝업차이 뜨며 현재 점수 및 최고점수가 표시되게 설정 Retry버튼 누르맨

          Scene로드하기

 

구현 해야될것

1.날라오는 블럭 위치 규격화하기 

 


 

1.Scene 재로드 하는경우 Find()로 오브젝트를 못 찾는 이유 

이유 : GameObject가 비활성화 상태

비활성화 된 객체를 활성화시키려면, 활성화된 부모를 찾아서 자식을 찾는 형식으로 접근해야 합니다. 

빈 오브젝트를 하나 만들고 자식으로 넣으면 해결

게임이 끝나면 뜨는 팝업창에 빈 오브젝트를 부모오브젝트로 설정

GameManager.cs

 private void OnEndPanel()
 {
     Transform EndObj = endPanel.transform.GetChild(0);
     EndObj.gameObject.SetActive(true);
 }

 private void DisEndPanel()
 {
     Transform EndObj = endPanel.transform.GetChild(0);
     EndObj.gameObject.SetActive(false);
 }

게임 매니저에 해당 코드를 추가하여 EndPanel 오브젝트를 껏다 켰다 할수 있게 설계

 

2.자식오브젝트의 Scale 값을 부모오브젝트와 Scale 값을 덜 영향받게 하기

이유 : 자식으로 오브젝트를 넣으며 해당 오브젝트는 계층 구조를 가지기 떄문에
위치,크기,각도 값이 같이 변경 된다.

위의 두 Block 오브젝트는 Scale 값이 다른걸 볼 수있다. 이걸 바로 자식 오브젝트로 넣으면 Coin의 Scale을 조절 해야됨으로 나중에 보수적인 면에서도 좋지 않다. 부모(Block) - 자식1(빈오브젝트) - 자식2(Coin)으로 설계를 하면 

빈 오브젝트를 조절하여 Coin 은 제어하지 않고도 조절이 가능하다. 

'내일배움캠프_Unity_6기 > TIL(Today I Learend)' 카테고리의 다른 글

TIL : 2024-08-28(수)  (0) 2024.08.28
TIL : 2024-08-27(화)  (0) 2024.08.27
TIL : 2024-08-22(금)  (0) 2024.08.23
TIL :: 2024-08-22(목)  (0) 2024.08.22
TIL : 2024-08-21(수)  (0) 2024.08.21

5주차 숙제 게임개발 

 

Run & Jump - 프로토타입 설명

1.마우스를 누르면 캐릭터 머리 위에 파워 게이지가 표시됨

2.마우스를 놓으면 놓은 방향으로 캐릭터가 빙글빙글 돌아가며 발판들을 넘어감

3.이미 한번 착지한 발판은 점수 x , 발판을 처음 밟을때만 점수 증가

 

구현 해야될것

1.각종 플레이어의 플레이를 방해할 장애물 추가 필요(화면을 날라다니는 오브젝트,발판 위의 가시 등등)

2.날라오는 블럭들이 지금 완전 랜덤 범위라서, 규격화가 필요함 ( 블럭이 겹쳐서 나오면 캐릭터가 피할 곳이 없음)

 

오늘 구현한것

1.게임에서 Player들이 획득하는 아이템(Coin)추가

2.Player의 게임플레이를 방해할 Spikes(가시) 추가 , 가시에 닿게되면 캐릭터가 죽음(Dead) 처리 됨

3.밑에 버그 수정


 

버그 수정

1. 자기 발 밑에 바닥이 있는 상태로 아래를 향해 발사하면 캐릭터
착지하지 못한 판정이 되서 조작이 불가함

 

해당 블럭 위에서 아래로 캐릭터를 이동시키면 제자리에서 계속 구르는 버그가 있었다.

 

Player.cs

  private void PlayerStop()
  {

      isJump = false;
      isJumping = false;
      rigid.velocity = Vector2.zero;
  }

  private void OnCollisionEnter2D(Collision2D collision)
  {
      if (collision.gameObject.CompareTag("Block"))
      {
          PlayerStop();
          anim.SetBool("isJump", false);
      }

      if (collision.gameObject.CompareTag("Spike"))
      {
          Debug.Log("Player 가시 닿음");
          isdead = true;
          anim.SetBool("isDead", true);
          PlayerStop();

      }
  }

Player.cs 있는 CollisonEnter2D 메서드 내의 "Block"태그 의 PlayerStop() 메서드가 동작해야되는데

Block 위에서만 발사 하면  CollisonEnter2D가 동작을 하지 않는것이다. 그렇다고 Stay함수를 쓰면 로직이 꼬이기 시작하였다. 

 

Block 위에서 아랫방향으로 굴러도 버그 발생 x

Player.cs

 void Update()
 {
     if (!isdead)
     {
         anim.SetBool("isRun", false);

         if (!isJump && !isJumping)
         {
             if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
             {
                 transform.position += Vector3.right * speed * Time.deltaTime;
                 renderer.flipX = true;
                 anim.SetBool("isRun", true);
             }

             if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
             {
                 transform.position += Vector3.left * speed * Time.deltaTime;
                 renderer.flipX = false;
                 anim.SetBool("isRun", true);
             }

             if (Input.GetMouseButton(0) || Input.GetKeyDown(KeyCode.Space))
             {
                 isJump = true;
                 JumpArrow.JumpArrowPrint(transform.position);
                 JumpPower.JumpPowerPrint(transform.position);
                 anim.SetBool("isRun", false);

             }

         }

         if (isJump && !isJumping)
         {
             if (Input.GetMouseButtonUp(0) || Input.GetKeyUp(KeyCode.Space))
             {
                 isJumping = true;

                 anim.SetBool("isJump", true);
                 Vector2 PlayerPos = transform.position;
                 Vector2 dir = JumpArrow.GetJumpingDir() - PlayerPos;
                 dir.Normalize();

                 float power = JumpPower.GetJumpingPower();

                 Debug.Log("발사 방향" + dir);
                 Debug.Log("발사 파워" + power);

                 rigid.AddForce(dir * power, ForceMode2D.Impulse);
                 JumpArrow.JumpArrowOff();
                 JumpPower.JumpPowerOff();

                 //제자리 구르기 or 밑에 방향으로 발사 시에
                 //블럭이 있으면 멈추지 않는 현상을
                 //콜리더를 껏다가 바로 켜는형식으로 변경
                 collider.enabled = false;
                 Invoke("ColliderON", 0.05f);
             }
         }
     }
 }

Player 오브젝트 객체가 발사하여 날라갈때 , Player의 Collider를 껏다 켜는걸로 해결하였다.

발사해서 공중에 날라갈떄 Collider를 껏다가 0.05f 후에 Collider를 다시 켜서 충돌처리를 하게 하였다.

Collider를 껏다 다시 켜니 위에 있던 CollisonEnter2D가 동작이 되어서 정상적으로 처리 되었다.

 

 

2.재시작(Scene 재로드)를 하면 GameManager의 오브젝트들이 Mssing되어버림

GameManager.cs

   [SerializeField] GameObject block;
   [SerializeField] Text scoreTxt;
   [SerializeField] Text levelTxt;
   [SerializeField] float blockSummonCoolTime = 2.0f;
   [SerializeField] bool isstop = false;

   int totalscore = 0;
   int curLevel = 0;
   
   
    private void UI_Load()
 {
     // 씬 전환 시 매번 호출하여 최신 오브젝트 참조를 설정합니다.
     if (scoreTxt == null)
     {
         GameObject scoreObj = GameObject.Find("UI_Score");
         if (scoreObj != null)
         {
             scoreTxt = scoreObj.GetComponent<Text>();
         }
     }

     if (levelTxt == null)
     {
         GameObject levelObj = GameObject.Find("UI_Level");
         if (levelObj != null)
         {
             levelTxt = levelObj.GetComponent<Text>();
         }
     }

 }
 private void OnEnable()
 {
     UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
 }

 private void OnDisable()
 {
     UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
 }

 private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
 {
     // 씬이 로드된 후 UI 로드
     UI_Load();
 }

GameManager를 싱글톤으로 구성하고  DontDestroyOnLoad()를 이용하여 Scene 파괴를 막았는데 오브젝트가 Missing이 되었다. 

Scene이 Load 될때마다 Text 오브젝트들이 Missing 되었다.

원인 :
1.Scene은 Load씨에 하이어라키에 있는 오브젝트들은 전부 제거된다.

2.DontDestroyOnLoad() 실행 시,  GameManager는 파괴되지 않더라도 다른 오브젝트들이 터졌다가 다시 생성되는것이기에 Missing이 됨

3.Inspector에서 참조한 경우 자동으로 참조하지 못하기에 직접 스크립트에서 참조 해줄수 있게 해줘야함 

 private void UI_Load()
 {
     // 씬 전환 시 매번 호출하여 최신 오브젝트 참조를 설정합니다.
     if (scoreTxt == null)
     {
         GameObject scoreObj = GameObject.Find("UI_Score");
         if (scoreObj != null)
         {
             scoreTxt = scoreObj.GetComponent<Text>();
         }
     }

     if (levelTxt == null)
     {
         GameObject levelObj = GameObject.Find("UI_Level");
         if (levelObj != null)
         {
             levelTxt = levelObj.GetComponent<Text>();
         }
     }

 }

해당 코드를 통해서 null 인경우 새 GameObject를 만들고 하이어라키에 존재하는 오브젝트를 찾아 초기화 해준뒤

Text 컴포넌트를 추가해주었다.

 

하지만 위에 코드만으로는 Scene 로드 할떄마다 오브젝트를 원하는 타이밍에 넣어 줄 수가 없었다.

 

** Awake vs OnEnable vs Start **


게임 오브젝트가 활성화된 상태로 씬에 처음 로드되면:

  1. Awake called: 오브젝트가 씬에 로드되면서 호출됨.
  2. OnEnable called: 오브젝트가 활성화되면서 호출됨.
  3. Start called: Awake가 끝나고 오브젝트가 활성화된 상태이므로 Start가 호출됨.

만약 오브젝트를 비활성화했다가 다시 활성화하면:

  1. OnDisable called: 오브젝트가 비활성화될 때 호출됨.
  2. OnEnable called: 오브젝트가 다시 활성화될 때 호출됨.

따라서 Awake는 오브젝트의 초기 설정을 위한 함수로, 활성화 여부에 관계없이 한 번만 호출되는 반면, OnEnable은 활성화될 때마다 호출됩니다.

 

새로운 씬이 로드되어도 이 오브젝트는 파괴되지 않으므로, AwakeStart 함수가 다시 호출되지 않습니다.


이 내용대로 동작 하는 경우면 GameManager는 계속 살아있기에 Awake()와 Start()는 호출이 되지 않기에 

OnEnable(),OnDisable() 함수를 활용해야한다. 

 

OnEnable(),OnDisable()을 활용하여 게임매니저가 활성화/비활성화 될때마다

Scene을 Load할때 동작하는 Event 함수를 만들어서 동작하였다.

Event를 추가하기 위해서 자료형 Scene,LoadSceneMode를 매개변수로 필요로 함
매개변수를 사용하진 않더라도 해당 메서드를 작성

 

 

유니티 퀘스트 달리기반 7번 행맨

string secretWord = "hangMan".ToUpper();
//C#에서는 string이 읽기 속성이기에 char[] 로 문자열을 관리해야된다.
//C#에서의 char[]과 string에 관련하여 TIL 작성하기
char[] guessWord = Enumerable.Repeat('_',secretWord.Length).ToArray(); // char[]을 '_'로 정답 글자수만큼 초기화하는 코드 
int attepts = secretWord.Length;
bool wordGuessed = false;

string copySecretWord = secretWord;

for (int i = 0; i < attepts; i++)
{
    //char[] -> string 변환 코드
    string str = string.Concat(guessWord);
    //'_'가 포함되어있으면 false , 포함되지 않으면 true
    wordGuessed = !str.Contains('_');

    //게임 클리어
    if (wordGuessed)
    {
        Console.WriteLine("");
        Console.WriteLine("축하합니다!! 단어 " + copySecretWord + " 를(을) 완성하셨습니다!! ");
        Console.WriteLine("도전 횟수 " + i + "회에 통과하셨습니다!");
        break;
    }

    //게임 안내
    Console.WriteLine("도전 횟수 : " + (i+1) + "/" + attepts);
    Console.WriteLine("[ " + str + " ]");
    Console.Write("추측할 단어를 입력해 주세요 : ");

    //글자 입력
    string input = Console.ReadLine();

    //글자 입력 체크 - 한개의 글자 체크 
    if(input.Length >= 2)
    {
        Console.WriteLine("");
        Console.WriteLine("두 글자 이상이 입력되었습니다. 다시 입력해 주십시오.");
        i--;
        continue;
    }
    //글자 입력 체크 - 글자 영문 체크
    else if ( !((input[0] >= 'A' && input[0] <= 'Z') || (input[0] >= 'a' && input[0] <= 'z')))
    {
        Console.WriteLine("");
        Console.WriteLine("영어가 아닙니다. 다시 입력해 주십시오.");
        i--;
        continue;
    }

    //글자 소문자 고정
    input = input.ToUpper();
    //입력된 글자가 어디의 자리인지 체크
    int result = secretWord.IndexOf(input);

    do
    {
        if (result == -1)//없는 경우
        {
            Console.WriteLine("");
            Console.WriteLine("해당 문자는 없습니다.");
            break;
        }
        else
        {
            guessWord[result] = char.Parse(input);
            char[] word = secretWord.ToCharArray(); //문자열을 수정 할 수 있게 char[] 로 변경

            word[result] = '_'; //맞춘 글자를 '_' 처리함
            secretWord  = string.Concat(word); //char[]를 문자열로 다시 수정함 

        }

        result = secretWord.IndexOf(input);
    }
    while (result != -1);

 
    
}

if (!wordGuessed)
{
    Console.WriteLine("");
    Console.WriteLine("실패하셨습니다!! 정답은 " + copySecretWord + " 입니다!! 다음기회에...");
}

 

5주차 숙제 게임개발 

Run & Jump - 프로토타입 설명

1.마우스를 누르면 캐릭터 머리 위에 파워 게이지가 표시됨

2.마우스를 놓으면 놓은 방향으로 캐릭터가 빙글빙글 돌아가며 발판들을 넘어감

3.이미 한번 착지한 발판은 점수 x , 발판을 처음 밟을때만 점수 증가

 

구현 해야될것

1.각종 플레이어의 플레이를 방해할 장애물 추가 필요(화면을 날라다니는 오브젝트,발판 위의 가시 등등)

2.날라오는 블럭들이 지금 완전 랜덤 범위라서, 규격화가 필요함 ( 블럭이 겹쳐서 나오면 캐릭터가 피할 곳이 없음

 

버그

1.자기 발 밑에 바닥이 있는 상태로 아래를 향해 발사하면 캐릭터 착지하지 못한 판정이 되서 조작이 불가함

2.재시작(Scene 재로드)를 하면 GameManager에 있는 오브젝트들이 Mssing되어버림

싱글 톤 상태에서 Don'tDestroty를 했는데 Text 파일들이 Missing 됨

 

*Collider2D의 한 면만 충돌판정을 받게 하고 싶을때

//Blcok Class

private void OnCollisionEnter2D(Collision2D collision)
 {
 	 //플레이어한테 점수 부여가 안되있고 , 콜리더가 Player 태그일때
     if (!isScore && collision.gameObject.CompareTag("Player"))
     {
     	 
         foreach (ContactPoint2D contact in collision.contacts)
         {
             if (contact.point.y > transform.position.y + 0.3f)
             {
                 GameManager.Instance.AddScore(score);
                 isScore = true;
             }
         }
     }
  
 }

 

  • ContactPoint2D: 충돌 지점에 대한 정보를 나타내는 구조체로, 충돌한 두 물체의 접점 좌표, 충돌 방향(normal), 충돌 지점의 상대 속도 등을 포함합니다.
  • collision.contacts: 충돌이 발생했을 때, 해당 충돌에 관련된 모든 접점을 포함하는 배열입니다. 이 배열은 충돌이 발생한 각 접점에 대해 ContactPoint2D 객체를 담고 있습니다.
  • 이 조건은 충돌 지점(Block의 윗면 = transform.positon.y +0.3f)의 y 좌표가 현재 객체(Player)y 위치보다 0.3 단위 이상 위에 있을 때만 참이 됩니다. 이 코드는 플레이어가 적을 위에서 밟았을 때 점수를 주는 식으로 사용할 수 있다.

 

5주차 하이퍼 캐주얼 게임 제작

 

 

1.2D 에서  기준 벡터(pivot_dir)와 각도를 알고 있을때  날라가는 벡터값 구하기

Vector2 pivot_dir = new Vector2(1, 0);
var quaternion = Quaternion.Euler(0, 0, tr.localEulerAngles.z);
Vector3 dir = quaternion * pivot_dir;
return dir;

pivot_dir로 오른쪽 진행이 기준 , 유니티는 각도값을 쿼터니언을 사용하기에 오일러(degree)를 쿼터니언으로 변경

최종적으로 각도 * 방향 벡터 = 원하는 방향 벡터를 구하여서 사용 하였음 

+ Recent posts