Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

동구의_C# & Unity_개발일지

2024.01.23 내일배움캠프 22일차 TIL_Unity (입문, 알고리즘) 본문

Unity

2024.01.23 내일배움캠프 22일차 TIL_Unity (입문, 알고리즘)

mongle_0l 2024. 1. 23. 13:21

오늘은 개인 과제 제출일이다!

필수요구사항을 거의 완료하였지만 좀 하자가 있는 부분이 있어서 아쉬웠다

처음 시작할 때는 선택 구현을 어디까지 하고 무엇을 하지 고민하지만,,

어림도 없다..

오류도 많고 시행착오도 많았지만 여러 튜터님들께서 도와주신 덕분에 마지막 제출은 잘 한 것 같다!

 

필수요구사항

1. 캐릭터 만들기 (완료)

2. 캐릭터 이동 (완료)

3. 방 만들기 (완료)

4. 카메라 따라가기 (완료)

5. 캐릭터 애니메이션 추가 (완료)

6. 이름 입력 시스템 (완료)

7. 캐릭터 선택 시스템 (중...완료?)


알고리즘 코드카타 9일차

평균 구하기

문제 설명
정수를 담고 있는 배열 arr의 평균값을 return하는 함수, solution을 완성해보세요.
public class Solution {
    public double solution(int[] arr) {
        double avg = 0;
        for(int i = 0; i < arr.Length; i++){
            avg += arr[i];
        }
        double answer = avg / arr.Length;
        return answer;
    }
}


2D 게임 심화 개발 / 2D 게임 로직 및 상태 관리

스텟 만들기
투사체 구현하기

 

 

01. 핵심 내용

1-1) 스크립터블 오브젝트(Scriptable Object)

  • 스크립터블 오브젝트는 Unity에서 데이터를 저장하고 관리하는 유연한 데이터 컨테이너입니다.
  • 게임에서 재사용 가능한 데이터 또는 설정을 저장하는 데 사용됩니다.
  • 코드와 데이터를 분리하여 코드를 더 깔끔하고 관리하기 쉽게 만듭니다.
  • 하나의 스크립터블 오브젝트를 여러 게임 오브젝트에서 참조하거나 재사용할 수 있습니다.
  • Unity 에디터와 통합되어 인스펙터 창에서 직접 수정하고 관리할 수 있습니다.

02. 핵심 내용

2-1) 레이어 비트 연산

  • 레이어 비트 연산은 Unity에서 게임오브젝트의 레이어를 빠르게 검사하고 조작하는 방법입니다.
  • 비트 연산은 AND, OR, XOR, NOT 등의 연산을 이용해 레이어 마스크를 조작합니다.
  • 이 방법은 물리적 충돌, 레이캐스팅, 카메라 렌더링 등을 제어하는 데 사용됩니다.
  • 각 게임 오브젝트는 고유한 레이어에 배치될 수 있으며, 이 레이어는 비트 필드로 표현됩니다.
  • 예를 들어, levelCollisionLayer.value == (levelCollisionLayer.value | (1 << other.gameObject.layer)) 코드에서는 게임 오브젝트의 레이어가 levelCollisionLayer에 있는지를 확인합니다. 여기서 **1 << other.gameObject.layer는 other.gameObject.layer에 해당하는 비트를 활성화시키는 것이고, levelCollisionLayer.value | (1 << other.gameObject.layer)는 그 비트가 levelCollisionLayer.value에 이미 설정되어 있는지 확인하는 것입니다

2-2) 쿼터니언과 벡터의 곱셈

  1. 먼저 벡터 v를 쿼터니언으로 변환합니다. 이때 v = (x, y, z)인 벡터는 (0, x, y, z)라는 쿼터니언으로 변환됩니다.
  2. 이렇게 변환된 벡터 쿼터니언을 원래의 쿼터니언 q와 그 켤레 쿼터니언 q와의 사이에 두고 곱셈을 수행합니다. 즉, 결과는 qvq 형태가 됩니다.
  3. 이 결과로 나온 쿼터니언의 x, y, z 요소는 원래 벡터 v가 쿼터니언 q에 의해 회전한 후의 좌표를 나타냅니다.
Unity에서는 Quaternion과 Vector3의 곱셈을 직접 지원하며, 이는 내부적으로 위에서 설명한 과정을 거쳐 수행됩니다.

 

2-3) 싱글턴 패턴

"싱글턴 패턴"은 소프트웨어 디자인 패턴 중 하나로, 특정 클래스의 인스턴스가 하나만 존재하도록 보장하고, 이를 전역적으로 접근할 수 있는 글로벌 접근점을 제공하는 패턴입니다.

 

  • 싱글턴 패턴은 클래스의 인스턴스가 하나만 존재하도록 보장하는 디자인 패턴입니다.
  • 싱글턴 객체는 전역적으로 접근 가능한 글로벌 접근점을 제공합니다.
  • 싱글턴 패턴은 전역 변수를 사용하지 않고도 객체 간 데이터를 공유하거나, 전역적인 상태를 관리하거나, 특정 서비스를 애플리케이션 전체에서 사용할 수 있게 해줍니다.
  • 싱글턴 패턴은 잘못 사용하면 코드의 결합도를 높이고 테스트와 유지 보수를 어렵게 할 수 있으므로 주의해서 사용해야 합니다.
  • 싱글턴 객체는 프로그램의 수명주기 동안 계속 존재하므로, 메모리 관리에 신경써야 합니다.
중요한 기능들을 정리해서 알아보자!
public enum StatsChangeType
{

}

데이터 단위밖에 쓰지 않을 꺼다 (객체화 시키지않을 꺼라 MonoBehaviour을 지워준다)

MonoBehaviour을 지웠기 때문에 Start와 Updata같은 유니티에서 사용할 수있는 메시지들을 아무것도 쓸수가 없다.

[SerializeField] private CharacterStats baseStats;

[SerializeField]을 걸어도 Stats가 안보이는 이유: CharacterStats가 Class라서

해결 방법

[Serializable]
public class CharacterStats
{
    public StatsChangeType statsChangeType;
    [Range(1, 100)] public int maxHealth;
    [Range(1, 20f)] public float speed;

    // 공격 데이터
    public AttackSO attackSO;
}

동기화을 해줄 꺼다라는 [Serializable] 이라고 하는 직열화가 가능한 상태로 만들어야 한다.

private void OnShoot(AttackSO attackSO)
{
    RangedAttackData rangedAttackData = attackSO as RangedAttackData;
    float projectilesAngleSpace = rangedAttackData.multipleProjectilesAngel;
    int numberOfProjectilesPerShot = rangedAttackData.numberofProjectilesPerShot;

    float minAngel = -(numberOfProjectilesPerShot / 2f) * projectilesAngleSpace + 0.5f * rangedAttackData.multipleProjectilesAngel; 

    for (int i = 0; i < numberOfProjectilesPerShot; i++)
    {
        float angle = minAngel + projectilesAngleSpace * i;
        float randomSpread = Random.Range(-rangedAttackData.spread, rangedAttackData.spread);
        angle += randomSpread;
        CreateProjectile(rangedAttackData, angle);
    }
}

오른쪽 부채꼴 모양과 같이 화살이 나갈수 있도록하는 코드이다

(feat.ChatGPT)
1. RangedAttackData에 AttackSO 시전:
이 메서드는 'AttackSO' 매개 변수를 사용하여 보다 구체적인 형식인 'RangedAttackData'로 캐스팅합니다. 이는 'RangedAttackData'가 'AttackSO'의 하위 클래스 또는 구현임을 시사합니다.
2. 데이터 추출:
'projectilesAngleSpace'(발사체 사이의 각도) 및 'numberOfProjectilesPerShot'(각 발사체의 발사체 수)과 같은 관련 데이터는 'RangedAttackData'에서 추출됩니다.
3. 확산 각도 계산:
'minAngel'은 발사체의 개수와 지정된 각도 공간을 기준으로 발사체를 고르게 분산시키는 시작 각도를 결정하기 위해 계산됩니다.
4. 발사체 생성 루프:
루프는 각 발사체를 반복처리합니다('numberOfProjectilesPerShot' 횟수).
각 반복에서 각도는 초기 각도와 각도 공간을 기준으로 계산됩니다. 이것은 발사체를 고르게 분배하는 데 도움이 됩니다.
변동성을 도입하기 위해 각도에 랜덤 스프레드가 추가됩니다('randomSpread').
'CreateProjectile' 메서드는 발사체를 인스턴스화하기 위해 'rangedAttackData'와 계산된 각도로 호출됩니다.

전반적으로, 이 코드는 'RangedAttackData' 오브젝트에 제공된 사양에 따라 발사체 확산을 생성하는 역할을 합니다. 무작위 확산은 발사체의 궤적에 예측 불가능한 요소를 추가합니다.

 

싱글톤 패턴의 핵심

정적 메모리(static)을 만들면 정적 메모리에 할당을 받는다 > 그것을 바라보게 만든다

오브젝트을 100개든 1000개을 만들어도 저 변수는 공간을 하나만 가지고 있다.

이 인스턴트을 정적 메모리에 들어있기 때문에 Class명으로도 접근이 가능하다.

Class명으로 인스턴트을 접근하면 만들어져있는 객체에 접근이 가능하다.(자기 자신의 참조니까!) 

 

여기서 문제점! (싱글톤 패턴의 단점)

오브젝트가 10개든 100개든 1000개든 되면 우리가 각각 접근할 수 있는 방법이 없다.

제일 마지막에 있는 애만 접근이 가능함!

 

단일객체을 중시! (여러개x ) 

 

쿼터니언과 벡터의 곱셈

private static Vector2 RotateVector2(Vector2 v, float degree)
{
    return Quaternion.Euler(0, 0, degree) * v;
}

위에서 설명한 쿼터니언과 벡터의 곱셈의 코드이다.

 

레이어 마스크(Layer Mask)

private void OnTriggerEnter2D(Collider2D collision)
{
    if(levelCollisionLayer.value == (levelCollisionLayer.value | (1 << collision.gameObject.layer)))
    {
        DestroyProjectile(collision.ClosestPoint(transform.position) - _direction * .2f, fxOnDestory);
    }
}

layer값은 이진 값을 가졌다고 생각!

(feat.ChatGPT)
이 코드는 Unity에서 2D 충돌이 발생했을 때 호출되는 `OnTriggerEnter2D` 함수를 포함한 스크립트 일부분으로 보입니다. 이 코드의 목적은 특정 레이어에 속한 객체와의 충돌을 감지하고, 조건을 만족하는 경우에 `DestroyProjectile` 함수를 호출하여 프로젝타일을 제거하는 것입니다.

1. `void OnTriggerEnter2D(Collider2D collision)`: Unity에서 제공하는 MonoBehaviour 함수 중 하나로, 2D 충돌이 발생했을 때 자동으로 호출되는 함수입니다. 충돌한 상대방의 Collider 정보는 `collision` 매개변수를 통해 전달됩니다.
2. `levelCollisionLayer.value == (levelCollisionLayer.value | (1 << collision.gameObject.layer)) `: 이 부분은 레이어 마스크를 사용하여 충돌한 객체의 레이어가 특정 레이어에 속하는지를 확인하는 것입니다.
`1 << collision.gameObject.layer`: 충돌한 객체의 레이어를 나타내는 비트 마스크를 생성합니다. `levelCollisionLayer.value | (1 << collision.gameObject.layer)`: 현재 객체의 레이어 마스크 값에 새로운 레이어를 추가합니다. 비트 OR 연산자 `|`를 사용하여 레이어를 추가합니다.
`levelCollisionLayer.value == (levelCollisionLayer.value | (1 << collision.gameObject.layer)) `: 최종적으로 현재 객체의 레이어 마스크 값이 새로운 레이어를 추가한 결과와 동일한지를 확인합니다. 이 부분은 특정 레이어에 대한 충돌 여부를 검사합니다.
3. `DestroyProjectile(collision. ClosestPoint(transform.position) - _direction * .2f, fxOnDestory)`: 만약 레이어가 일치한다면, `DestroyProjectile` 함수를 호출하여 프로젝타일을 제거합니다. `collision. ClosestPoint(transform.position) - _direction * .2f`는 충돌 지점에서 조금 떨어진 위치로 설정되어 프로젝타일이 파괴될 때 이펙트를 생성할 위치를 나타냅니다. `fxOnDestory`는 파괴 시 재생할 이펙트를 나타내는 것으로 보입니다.

총괄적으로 이 코드는 특정 레이어에 속한 객체와의 충돌을 감지하고, 해당 조건을 만족하면 프로젝타일을 제거하는 것으로 사용될 것입니다.

오늘의 오류

커밋을 할려고 하니 용량이 너무 크다고 한다...

이것 저것 다 해봤는데 안됨 ㅠ

그래서 튜터님의 말씀대로

사실 뭔가 꼬였다 하면
깃 리포지토리 지웠다가 다시 만들어서 하는게
젤 직빵이긴 합니다만
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
원래 초기화가 짱이죠..

 

하 ㅆ.....명언이였다... 바로됨 ㅠㅠ

근데..

ㅋㅋㅋ

이건 또 뭐지

락이 걸렸다는데 ... 또한 이것 저것 찾아서 해보다가....또 다시 튜터님에게...

 

해결방법

해당 디렉토리에에가서 숨김 파일 표시 옵션을 선택 후 .git 폴더가 생길건데

그 안에 있는 index.lock파일 삭제하고 다시 커밋!

깔끔하게 커밋이 되었다!

감사합니닷 ㄱㅈㄱ튜터님! ㅈㅇㅇ튜터님!