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.02.07 내일배움캠프 32일차 TIL_Unity (숙련, 알고리즘) 본문

Unity

2024.02.07 내일배움캠프 32일차 TIL_Unity (숙련, 알고리즘)

mongle_0l 2024. 2. 7. 03:09

개인 과제인 ATM 시스템 만들기는 해설 영상을 보며 부족한 부분을 채워 넣었다.

UI에 관한 많은 것을 알 수 있는 해설 영상이었다!

 

유니티 숙련 과정에서 주어진 강의 영상으로 Survival 게임을 다 만들어 보았다.

3D라 그런지 확실히 재미도 있었고 평소에 하던 게임의 형태로 익숙한 부분이 많았다.

코드가 생각보다 많아 머리가 아프지만 천천히 다시 보면서 익숙해질 때까지 써봐야겠다!

 

필수요구사항

1. ATM 화면 구성 (완료)

2. 입금 기능 (완료)

3. 출금 기능 (완료)

 

선택요구사항

1. 통화 단위 적용 (난이도 - ★☆☆☆☆)

2. 금액 입력시 숫자만 입력 (난이도 - ★☆☆☆☆)

3. 로그인 기능 (난이도 - ★★☆☆☆)

4. 회원가입 (난이도 - ★★★★☆)

5. 송금 기능 (난이도 - ★★★★☆)


알고리즘 코드카타 18일차

두 정수 사이의 합

문제 설명
두 정수 a, b가 주어졌을 때 a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수, solution을 완성하세요.예를 들어 a = 3, b = 5인 경우, 3 + 4 + 5 = 12이므로 12를 리턴합니다.
public class Solution {
    public long solution(int a, int b) {
        long answer = 0;
        if(a > b)
        {
            int c = a;
            a = b;
            b = c;
        }
        
        for(int i = a; i <= b;i++)
        {
            answer += i;
        }
        
        return answer;
    }
}


3D 게임 기초 개발

적 생성과 로직
포스트 포로세싱 적용
발걸음 소리와 뮤직 존 생성

 

 

01. 핵심 내용

1-1) AI 네비게이션(AI Navigation)

AI 네비게이션(AI Navigation)은 인공지능이 게임이나 시뮬레이션 등 가상 환경에서 이동하는 방법을 결정하는 기술입니다. 주로 3D 게임에서 캐릭터나 NPC가 지능적으로 이동하도록 만들어집니다. 이를 위해 AI 네비게이션 시스템은 지형, 장애물, 목표 지점 등을 고려하여 적절한 경로를 생성하고 이동하는데 사용됩니다.

AI 네비게이션을 구현하는 주요 기술과 개념은 다음과 같습니다.

 

  1. Navigation Mesh (네비게이션 매쉬)
    • 3D 공간을 그리드로 나누어 이동 가능한 지역과 장애물이 있는 지역을 구분하는 매쉬입니다.
    • 캐릭터가 이동할 수 있는 영역과 이동할 수 없는 영역을 정의하고, 이를 기반으로 경로를 계산합니다.
  2. Pathfinding (경로 탐색)
    • 캐릭터의 현재 위치에서 목표 지점까지 가장 적절한 경로를 찾는 알고리즘입니다.
    • 주로 A* 알고리즘 등이 사용되며, 지정된 목표 위치까지 최단 경로를 탐색합니다.
  3. Steering Behavior (스티어링 동작)
    • 캐릭터나 NPC가 경로를 따라 이동할 때, 보다 자연스러운 동작을 구현하는데 사용됩니다.
    • 동적으로 캐릭터의 이동 방향과 속력을 조정하여 부드럽고 현실적인 이동을 시뮬레이션합니다.
  4. Obstacle Avoidance (장애물 피하기)
    • 캐릭터가 이동 중에 장애물과 충돌하지 않도록 하는 기술입니다.
    • 각종 센서나 알고리즘을 사용하여 장애물을 감지하고 피하는 동작을 수행합니다.
  5. Local Avoidance (근접 회피)
    • 여러 캐릭터나 NPC가 서로 충돌하지 않도록 하는 기술입니다.
    • 캐릭터들 사이의 거리를 유지하거나 회피 동작을 수행하여 서로 부딪히지 않도록 합니다.

02. 핵심 내용

2-1) 포스트 프로세싱

포스트 프로세싱(Post-Processing)은 Unity나 다른 게임 엔진에서 화면에 렌더링된 이미지에 추가적인 효과를 적용하는 기술입니다. 이 기술을 사용하여 게임 화면에 색상 보정, 블러 효과, 광학 효과 등을 적용하여 시각적으로 더 흥미로운 그래픽 효과를 구현할 수 있습니다.

포스트 프로세싱의 일부 기능과 사용법은 다음과 같습니다.

 

  1. 색상 보정 및 색감 필터링: 화면에 특정 색조, 채도, 밝기 등의 보정을 적용하여 화면의 색상을 조작할 수 있습니다.
  2. 블러 효과: 게임 화면에 블러 효과를 적용하여 광학적 흐림, 광학 흐림, 도형 흐림 등을 구현할 수 있습니다.
  3. 선 처리 및 톤 매핑: 경계선 처리, 윤곽선 강조, 톤 매핑 등을 적용하여 게임 화면을 더 생동감 있게 만들 수 있습니다.
  4. 광학 효과: 렌즈 반사, 빛 착란, 먼지 효과 등을 추가하여 게임 화면에 현실적인 광학 효과를 부여할 수 있습니다.
  5. 사용자 정의 효과: Unity의 포스트 프로세싱 스택을 사용하여 사용자가 직접 쉐이더 코드를 작성하여 원하는 효과를 만들어 적용할 수도 있습니다.
 

포스트 프로세싱 효과와 전체 화면 효과 - Unity 매뉴얼

Unity는 간단한 설정을 통해 애플리케이션의 모습을 크게 개선할 수 있는 다양한 포스트 프로세싱 효과와 전체 화면 효과를 제공합니다. 이러한 효과를 사용하여 물리적 카메라 및 필름 프로퍼티

docs.unity3d.com


중요한 기능들을 정리해서 알아보자!
적 NPC 구현 코드
더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public enum AIState
{
    Idle,
    Wandering,
    Attacking,
    Fleeing
}

public class NPC : MonoBehaviour, IDamagable
{
    [Header("Stats")]
    public int health;
    public float walkSpeed;
    public float runSpeed;
    public ItemData[] dropOnDeath;

    [Header("AI")]
    private AIState aiState;
    public float detectDistance;
    public float safeDistance;

    [Header("Wandering")]
    public float minWanderDistance;
    public float maxWanderDistance;
    public float minWanderWaitTime;
    public float maxWanderWaitTime;

    [Header("combat")]
    public int damage;
    public float attackRate;
    private float lastAttackTime;
    public float attackDistance;

    private float playerDistance;

    public float fieldofView = 120f;

    private NavMeshAgent agent;
    private Animator animator;
    private SkinnedMeshRenderer[] meshRenderers;

    private void Awake()
    {
        agent = GetComponent<NavMeshAgent>();
        animator = GetComponentInChildren<Animator>();
        meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
    }

    private void Start()
    {
        SetState(AIState.Wandering);
    }

    private void Update()
    {
        playerDistance = Vector3.Distance(transform.position, PlayerController.instance.transform.position);

        animator.SetBool("Moving", aiState != AIState.Idle);

        switch (aiState)
        {
            case AIState.Idle: PassiveUpdate(); break;
            case AIState.Wandering: PassiveUpdate(); break;
            case AIState.Attacking: AttackingUpdate(); break;
            case AIState.Fleeing: FleeingUpdate(); break;
        }
    }

    private void FleeingUpdate()
    {
        if(agent.remainingDistance < 0.1f)
        {
            agent.SetDestination(GetFleeLocation());
        }
        else
        {
            SetState(AIState.Wandering);
        }
    }

    private void AttackingUpdate()
    {
        if(playerDistance > attackDistance || !IsPlayerInFireIdOfView())
        {
            agent.isStopped = false;
            NavMeshPath path = new NavMeshPath();
            if(agent.CalculatePath(PlayerController.instance.transform.position, path))
            {
                agent.SetDestination(PlayerController.instance.transform.position); 
            }
            else
            {
                SetState(AIState.Fleeing);
            }
        }
        else
        {
            agent.isStopped = true;
            if (Time.time - lastAttackTime > attackRate)
            {
                lastAttackTime = Time.time;
                PlayerController.instance.GetComponent<IDamagable>().TakePhysicalDamage(damage);
                animator.speed = 1;
                animator.SetTrigger("Attack");
            }
        }
    }

    private void PassiveUpdate()
    {
        if(aiState == AIState.Wandering && agent.remainingDistance < 0.1f)
        {
            SetState(AIState.Idle);
            Invoke("WanderToNewLocation", Random.Range(minWanderWaitTime, maxWanderWaitTime));
        } 

        if(playerDistance < detectDistance)
        {
            SetState(AIState.Attacking);
        }
    }

    bool IsPlayerInFireIdOfView()
    {
        Vector3 directionToPlayer = PlayerController.instance.transform.position - transform.position;
        float angle = Vector3.Angle(transform.forward, directionToPlayer);
        return angle < fieldofView * 0.5f;
    }

    private void SetState(AIState newState)
    {
        aiState = newState;
        switch(aiState)
        {
            case AIState.Idle:
                {
                    agent.speed = walkSpeed;
                    agent.isStopped = true;
                }
                break;
            case AIState.Wandering:
                {
                    agent.speed = walkSpeed;
                    agent.isStopped = false;
                }
                break;
            case AIState.Attacking:
                {
                    agent.speed = runSpeed;
                    agent.isStopped = false;
                }
                break;
            case AIState.Fleeing:
                {
                    agent.speed = runSpeed;
                    agent.isStopped = false;
                }
                break;
        }

        animator.speed = agent.speed / walkSpeed;
    }

    void WanderToNewLocation()
    {
        if(aiState != AIState.Idle)
        {
            return;
        }
        SetState(AIState.Wandering);
        agent.SetDestination(GetWanderLocation());
    }

    Vector3 GetWanderLocation()
    {
        NavMeshHit hit;
        NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas);

        int i = 0;
        while(Vector3.Distance(transform.position, hit.position) < detectDistance)
        {
            NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas);
            i++;
            if(i == 30)
                break;
        }

        return hit.position;
    }

    Vector3 GetFleeLocation()
    {
        NavMeshHit hit;
        NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);

        int i = 0;
        while (GetDestinationAngle(hit.position) > 90 || playerDistance< safeDistance)
        {
            NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
            i++;
            if (i == 30)
                break;
        }

        return hit.position;
    }

    float GetDestinationAngle(Vector3 targetPos)
    {
        return Vector3.Angle(transform.position - PlayerController.instance.transform.position, transform.position + targetPos);
    }

    public void TakePhysicalDamage(int damageAmount)
    {
        health -= damageAmount;
        if (health < 0)
            Die();

        StartCoroutine(DamageFlash());
    }

    void Die()
    {
        for(int x = 0; x < dropOnDeath.Length; x++)
        {
            Instantiate(dropOnDeath[x].dropPrefab, transform.position + Vector3.up * 2, Quaternion.identity);
        }
        Destroy(gameObject);
    }

    IEnumerator DamageFlash()
    {
        for (int x = 0; x < meshRenderers.Length; x++)
            meshRenderers[x].material.color = new Color(1.0f, 0.6f, 0.6f);

        yield return new WaitForSeconds(0.1f);
        for (int x = 0; x < meshRenderers.Length; x++)
            meshRenderers[x].material.color = Color.white;
    }
}
적 NPC을 생성하여 움직이고 공격 할 수있게 해주는 코드이다.
IEnumerator DamageFlash()
{
    for (int x = 0; x < meshRenderers.Length; x++)
        meshRenderers[x].material.color = new Color(1.0f, 0.6f, 0.6f);

    yield return new WaitForSeconds(0.1f);
    for (int x = 0; x < meshRenderers.Length; x++)
        meshRenderers[x].material.color = Color.white;
}

 

이 코드는 일시적으로 오브젝트를 빨간색으로 변경한 다음, 일정 시간이 지난 후 다시 흰색으로 변경하는 코루틴을 정의한다. 게임에서 플레이어가 피해를 입었을 때 빨간색으로 깜빡이는 효과를 구현한 것이다.

 


 


개인 과제

Canvas

Screen Space - Overlay (나는 이것을 사용)

Canvas가 우선순위가 되어 게임 씬 맨 위에 덮어 쒸워준다.

Screen Space - Camera

Canvas가 지정된 카메라 앞에 지정된 거리만큼 배치된다.

World Space

월드 좌표에서 Canvas을 띄울 수 있다.

 

Canvas Scaler

Scale With Screen Size

각자가 쓰는 해상도가 다른데 다른 해상도로 바뀌여도 대응을 해주는 컴포넌트이다.

 

Rect Transform(★ )

Rect Transform 컴포넌트는 Transform 컴포넌트의 2D 레이아웃 버전이다. 트랜스폼은 포인트 하나를 나타내지만, Rect Transform은 UI 요소를 안에 넣을 수 있는 사각형을 나타낸다. 사각 트랜스폼의 상위 컴포넌트도 사각 트랜스폼인 경우, 하위 사각 트랜스폼이 상위 사각형에 대한 상대적인 포지션과 크기도 지정할 수 있다.

 

Anchors

트랜스폼 컴포넌트와는 다르게 렉트 트랜스폼 컴포넌트는 앵커(Anchor)라고 하는 기준점을 가진다

 

 

Rect Transform을 이용하면 부모의 해상도가 아무리 커지거나 작아져도 자식은 그 위치에 그대로 있게된다.

사각형을 위로 설정

아무리 흰색 화면을 늘려도 검정색 사각형은 위 좌표로 고정되어있는 모습

 

피벗(Pivot)

피벗은 UI의 중심을 나타낸다.

 

Anchors과 Pivot을 활용하면 다음과 같이 정확하게 이미지 위치을 설정할 수있다.

Shift을 누르면Anchors와 Pivot이 같이 바뀐다

 

사각 트랜스폼 - Unity 매뉴얼 (unity3d.com)

 

사각 트랜스폼 - Unity 매뉴얼

Rect Transform 컴포넌트는 Transform 컴포넌트의 2D 레이아웃 버전입니다. 트랜스폼은 포인트 하나를 나타내지만, Rect Transform은 UI 요소를 안에 넣을 수 있는 사각형을 나타냅니다. 사각 트랜스폼의 상

docs.unity3d.com

 

Content Size Firrer

Content 사이즈에 맞춰서 글자가 아무리 늘어나도 자동으로 맞춰서 늘어나진다.

노란색 부분이 글자에 맞춰 자동으로 늘어나는 모습

 

스크립트을 작성 하지않고 유니티에서 On Click() 함수 구현하기

입금을 누르면 MainButtons가 꺼져야되니 MainButtons을 가져온다.

SetcTive을 true로 하면 메인 버튼이 꺼진다.

체크를 하지 않으면 false로 숨기는 것이고 체크를 하면 ture로 화면을 보여주는 것이다.

이렇게 해서 스크립트을 작성하지 않고도 유니티 안에서 간단하게 입금 출금 버튼을 왔다 갔다 할 수 있는 기능을 만들 수 있다.