오늘의 학습 키워드

Fog

 

공부한 내용

Fog

포스트 프로세싱을 이용한 Fog 효과가 아닌 간단한 Material을 이용해 Fog를 사용하는 것성능상 유리하다고 하여 적용해보기로 했다.

1. Material을 만들고 Shader를 Particles/Standard Unlit으로 만들어준 뒤

2. Rendering Mode를 Transparent 또는 Fade로 적용한다.

3. Soft Particles를 체크하고 Near fade와 Far fade 값을 적절히 조정해준다.

4. Albedo값을 원하는 색과 알파 값을 정해주면 간단한 Fog Material이 만들어진다.

* 만약 Soft Particles가 적용이 안 된다면 (한 톤으로만 적용된다면) Quality의 Soft Particles를 체크해주면 된다.

이후에 Plane에 Fog Material을 적용해주면

이렇게 간단한 Fog 효과를 만들 수 있다.

 

 

오늘의 회고

 오늘은 Particle Shader를 이용하여 간단한 Fog 효과를 만들어봤다. 정말 간단하고 쉽지만 성능도 포스트 프로세싱보다 쓸만하고 보기에도 괜찮은 게 모바일에서 유용하게 사용할 수 있을 것 같다. 오늘 고정 오브젝트 배치 등 전체적인 틀을 만들어놨으니 내일부터 본격적으로 플레이어 충돌이나 오브젝트 생성 등 핵심 로직을 구현할 생각이다.

 오늘 시간이 없어서 코드 카타 문제를 풀지 못했는데 내일 그 문제를 격파해야겠다. 내일도 파이팅!

와이어 프레임

이번 팀 프로젝트는 미니 게임을 모아서 하나의 게임을 만드는 프로젝트이다.

여러가지 자신이 하고싶은 종류의 미니 게임을 나열한 뒤 그 중에서 제일 하고 싶은 게임을 선택해서 만드는 방식이다.

컨셉은 전역을 하기 위해서 미니게임 수행도가 일정 기준치를 넘어야 클리어가 되는 컨셉이다.

입대 이미지는 달리2를 이용하여 가져왔다.

 

 

오늘의 회고

 오늘은 새 팀으로 프로젝트를 기획했다. 운 좋게 Synty 스튜디오에서 에셋을 무료로 할인하고 있어서 팀원이 모두 구매를 한 뒤 사용하기로 했다. 튜터님이 커뮤니티에 올려주셔서 알게 된 내용이었다. Dalle-2 이미지는 살짝 깨져있거나 어색한 부분이 존재하는데 우리나라 군부대의 정보가 얼마 없어서 그렇지 않나 싶다.

 다음주는 미니게임을 만들 예정이다. 나는 장애물 피하기를 만들건데 경사진 면에 장애물이 떨어지고 플레이어는 이 장애물들을 피해서 올라가는 게임이다. 다음주도 잘해보자 파이팅!

공부한 내용

플레이어 공격과 몬스터 스폰

저번에 배웠던 상태머신을 기반으로 플레이어가 자동으로 공격할 수 있게끔 구현했다.

또한 플레인의 랜덤 위치에서 몬스터가 스폰되도록 구현하였다.

 

 

오늘의 회고

 오늘은 저번에 배웠던 상태 머신을 적용해보는 시간을 가졌다. 캐릭터에 있는 스킬 구현 쪽에서 어려움을 겪었지만 스킬 클래스를 따로 하나 파고 애니메이션 이벤트로 받아오는 방법을 사용해 다른 코드와 의존성을 줄이도록 했다.

 내일은 최종 프로젝트 전 마지막 팀 프로젝트 발제가 있는 날이다. 마지막 프로젝트 전에 공부하고 싶었던 것 적용하면서 팀 프로젝트도 재미있게 했으면 좋겠다. 내일도 파이팅!

오늘의 학습 키워드

TextMeshPro 움직이기

 

공부한 내용

TextMeshPro 움직이기

TextMeshPro로 만들어진 문자를 움직이려면 TMP_TextInfo라는 것에 접근을 해서 각 문자의 버텍스 위치를 변경해주면 된다. 나는 Sin파의 절댓값을 사용하여 글자가 움직이게 만들었다.

문제 : Index관련 오류가 떴는데 TextMesh의 버텍스를 불러오지 못하는 문제였다.

원인 : 버텍스는 TMP_Text의 메쉬로부터 불러오는데 메쉬부터가 null이니 버텍스는 당연히 불러오지 못하게 되는 것이다.

해결 : TMP_Text의 ForceMeshUpdate 함수 호출 이후에 text의 mesh를 초기화줌으로써 버텍스를 추적할 수 있게끔 하여 해결하였다.

private void JumpLoadingText()
{
    loadingText.ForceMeshUpdate();
    _loadingTextMesh = loadingText.mesh;
    _verts = _loadingTextMesh.vertices;

    for (int i = 0; i < loadingText.textInfo.characterCount; i++)
    {
        TMP_CharacterInfo c = loadingText.textInfo.characterInfo[i];

        int index = c.vertexIndex;

        float offset = Mathf.Sin(Time.time + i) * textJumpingHeight;

        for (int j = 0; j < 4; j++)
        {
            _verts[index + j].y += offset;
        }
    }

    _loadingTextMesh.vertices = _verts;
    loadingText.canvasRenderer.SetMesh(_loadingTextMesh);
}

 

 

오늘의 회고

 오늘은 TMP_Text의 글자를 움직이는 법을 배웠다. 일반 텍스트랑은 다르게 버텍스를 조작하여 움직일 수도 있고 컬러도 조정이 가능하다는 점이 쓸만한 것 같다. 유튜브의 여러 컨텐츠에는 다양한 효과들도 있던데 이런 것 하나하나가 게임의 비주얼적인 요소에서 꽤나 좋아보이게 하는 영향을 준다고 생각한다. 다음에는 상자에서 글자를 뽑아내는 것과 같은 효과도 구현해볼 생각이다.

 내일은 개인 과제를 제출하는 날이다. 내일까지 열심히 해서 계획한 만큼 결과물을 뽑아내도록 하자. 내일도 파이팅!

오늘의 학습 키워드

ManiFest 오류

 

공부한 내용

UnExpected Token Manifest 오류

Git에 업로드를 하던 중 UnExpected Token이라면서 Manifest 충돌에서 오류가 생겼다.

 

원인이라고 생각했던 것 : mainfest 파일에서 충돌이 일어났으니 manifest 파일을 고치면 된다고 생각했다.

결과 : 반은 맞았다. manifest 파일 쪽에서 충돌이 일어난 것이 맞았고 수정을 하게 되었지만 아직도 문제가 완전히 해결되지는 않았다.

 

다른 원인 :  Packages 폴더에는 manifest 파일 말고 packages.lock.json이라는 파일도 있는데 이 부분에서도 충돌이 일어났던 상황이었다.

 

결과 : 잘 작동하게 되었다.

 

오늘의 회고

 오늘은 DoTween을 이용해서 타이틀 씬과 로딩 씬을 구현했다. 처음 써보는 기능인데 내가 직접 Coroutine으로 효과를 구현하는 것보다 쉽고 다양한 것들이 존재했다. 현업에서도 쓰인다고 하니 앞으로도 계속 애용해야겠다.

 내일은 상태머신을 이용해 캐릭터를 움직이게끔 만들려고 한다. 저번에 배웠던 것을 실제로 적용해보는 시간을 가질 것이다. 내일도 파이팅!

오늘의 학습 키워드

Excel에서 데이터 읽어오기

 

공부한 내용

Excel에서 데이터 읽어오기

엑셀에서 첫 줄에 key 값을 적고 그 아래로 value들을 적는다.

저장할 때 Sheet 파일 이름이 중요한데 이 이름이 스크립터블 오브젝트의 리스트의 변수명이 됩니다.

Serialize된 클래스의 변수명은 위의 key 이름과 동일해야한다.

using System;

[Serializable]
public class LunchDialogEntity
{
    public int branch;
    public string name;
    public string dialog;
}

스크립터블 오브젝트에 [ExcelAsset] 속성을 부여하고 ScriptableObject의 리스트 변수명엑셀 시트의 이름과 동일하게 리스트를 선언해준다. * 만약 ExcelAsset에서 namespace 오류가 뜬다면 아래의 코드를 Packages/manifest.json 안의 "dependency"의 괄호 안의 부분에 넣어준다.

"net.mikinya.unity-excel-importer": "https://github.com/mikito/unity-excel-importer.git?path=Assets/ExcelImporter#v0.1.1/upm",
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExcelAsset]
public class LunchDialog : ScriptableObject
{
    // 변수명과 엑셀 시트의 이름이 동일해야 함
    public List<LunchDialogEntity> Entities;
}

이후에 엑셀 파일을 Reimport 하게되면 

스크립터블 오브젝트 파일이 하나 생기게 된다.

이 데이터에 접근하여 text를 불러올 수 있다.

 

 

오늘의 회고

 오늘은 Excel 파일을 유니티에서 읽어오는 방법을 배웠다. 과정이 귀찮지만 데이터 테이블을 엑셀로 관리해서 불러오는 것은 유용할 것 같다. 맨날 json에 데이터를 적고 불러오는 방식을 사용했는데 다음부턴 엑셀로 불러오도록 해야겠다.

 내일부터는 개인 과제 개발에 들어가는데 기획한대로 잘 구현했으면 좋겠다. 내일도 파이팅!

 

오늘의 학습 키워드

Finite State Machine

 

공부한 내용

Finite State Machine

한 번에 한 개의 상태만 가질 수 있고 다른 상태로 전이가 가능한 모델을 FSM(유한 상태 머신)이라고 부른다.

게임에서는 애니메이션 쪽에서 자주 쓰이게 된다. (Idle <-> Walk <-> Run <-> Attack)

 

이번에 배웠던 예제에서는 신기한 구조를 가지고 있었는데 각 상태들이 new 생성자를 통하여 상태 머신의 인스턴스를 받아온다는 것이었다.

그리고 각 상태는 IState 인터페이스를 상속받으며 ChangeState로 상태머신의 IState변수 인스턴스를 교체해준다. (상태 전이)

public class PlayerBaseState : IState
{
    protected PlayerStateMachine stateMachine;
    protected readonly PlayerGroundData groundData;

    public PlayerBaseState(PlayerStateMachine playerStateMachine)
    {
        stateMachine = playerStateMachine;
        groundData = stateMachine.Player.Data.GroundData;
    }
}
public class PlayerStateMachine : StateMachine
{
    // States
    public PlayerIdleState IdleState { get; }
    public PlayerWalkState WalkState { get; }
    public PlayerRunState RunState { get; }
    public PlayerJumpState JumpState { get; }
    public PlayerFallState FallState { get; }
    public PlayerComboAttackState ComboAttackState { get; }

    public PlayerStateMachine(Player player)
    {
        IdleState = new PlayerIdleState(this);
        WalkState = new PlayerWalkState(this);
        RunState = new PlayerRunState(this);

        JumpState = new PlayerJumpState(this);
        FallState = new PlayerFallState(this);

        ComboAttackState = new PlayerComboAttackState(this);
    }
}
public interface IState
{
    void Enter();
    void Exit();
    void handleInput();
    void Update();
    void PhysicsUpdate();
}
public abstract class StateMachine
{
    protected IState currentState;

    public void ChangeState(IState newState)
    {
        currentState?.Exit();

        currentState = newState;

        currentState?.Enter();
    }
}

 

 

오늘의 회고

 오늘은 상태 머신에 대해서 배웠다. 어렵지만 그림으로 정리하니 이해하기 좀 더 쉬워진 것 같다. 저 구조 이후에는 BaseState를 상속받은 각 상태에서 조건만 추가하여 다음 상태로 넘어가게 구현하면 된다. 처음 구조짤 때는 어렵지만 그냥 한 곳에서 짜는 것보다 유지 보수하기 쉬워보인다.

 내일은 개인 과제로 무엇을 만들지 기획을 좀 해봐야겠다. 내일도 파이팅!

오늘의 학습 키워드

static 클래스

 

공부한 내용

MonoBehaviour를 상속받은 static 클래스 vs static 클래스

문제 : SceneManager.LoadScene으로 게임을 재시작했을 때 static 클래스인 WeaponManager의 다음 판에도 데이터가 계속 남아 있어 문제가 되었다.

public class WeaponManager
{
    private static WeaponManager _instance;
    public static WeaponManager Instance { get => GetInstance(); }

    public List<UpgradeItem> WeaponPrefabs { get; private set; }
    public List<WeaponData> WeaponDatas { get; private set; } = new List<WeaponData>();

    [Header("Resource Path")]
    private const string ITEM_UI_PATH = "UI/Weapons/";

    public event Action<WeaponData> WeaponUpgradeEvent;

    private static WeaponManager GetInstance()
    {
        if (_instance == null)
            _instance = new WeaponManager();

        return _instance;
    }
}

원인으로 생각한 것 : SceneManager.LoadScene으로 게임을 다시 시작했을 때 MonoBehaviour를 상속 받은 GameManager 싱글턴의 데이터는 초기화 되는 것으로 보아 싱글턴을 여러 곳에서 관리 하는 것에서 문제가 생긴다고 생각했다.

해결 방안 : WeaponManager의 싱글턴을 제거한 후 GameManager 싱글턴이 WeaponManager까지 관리를 해서 게임을 재시작 할 때 GameManager 싱글턴이 파괴되어 WeaponManager까지 파괴될 수 있도록 했습니다.

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    private WeaponManager _weapon;
    public WeaponManager Weapon { get => GetInstance(ref _weapon); }

    private void Awake()
    {
        instance = this;
    }

    public T GetInstance<T>(ref T t) where T : new()
    {
        if (t == null)
            t = new T();

        return t;
    }
}

 

 

오늘의 회고

 오늘은 저번 주부터 진행한 팀 프로젝트를 마무리하고 발표하는 날이었다. 추석 연휴가 껴있어서 일정 맞추기도 어렵고 짬짬이 구현을 해야했지만 기한이 다가올수록 팀원들이 많이 도와주어서 급한 순서대로 작업을 잘 쳐낼 수 있었다. 다음엔 리팩토링도 신경써서 잘 해봐야겠다.

 내일은 새로운 것을 공부할텐데 기대가 된다. 열심히 잘 해봐야겠다. 내일도 파이팅!

오늘의 학습 키워드

Sin, Cos

 

공부한 내용

쇠스랑 무기 구현

쇠스랑 무기는

1. 마우스의 위치로 바라보는 방향이 정해지며

2. 그 방향대로 직선으로 갔다가 돌아오는 움직임으로 구현하도록 했다.

마우스 위치에서 내 위치에를 뺀 targetRot을 구해 transform.up = targetRot에서 바라보는 방향을 정해주고

첫 번째 while에서 마우스 위치로 가는 것, 두 번째 while에서 offset위치로 돌아오도록 만들었다.

protected override IEnumerator Move()
    {
        Vector2 targetPos = _mainCam.ScreenToWorldPoint(Input.mousePosition);
        float harfDuration = movingDuration * 0.5f;
        float elapsedTime = 0f;
        float normalizedRatio = 0f;
        Vector2 targetRot = (targetPos - (Vector2)transform.position).normalized;

        transform.up = targetRot;

        while (elapsedTime < harfDuration)
        {
            elapsedTime += Time.deltaTime;
            normalizedRatio = elapsedTime / harfDuration;
            transform.position = Vector2.Lerp(transform.position, targetPos, normalizedRatio);
            yield return null;
        }

        elapsedTime = 0f;

        while (elapsedTime < harfDuration)
        {
            elapsedTime += Time.deltaTime;
            normalizedRatio = elapsedTime / harfDuration;
            transform.position = Vector2.Lerp(transform.position, offsetTransform.position, normalizedRatio);
            yield return null;
        }

        StartCoroutine(Move());
    }

 

 

낫 무기 구현

낫 무기는

1. 캐릭터를 중심으로 원형으로 돌며

2. 자신을 중심으로 원형으로 회전하도록 구현하도록 했다.

transform.rotation에 Quaternion.Euler로 자기 자신의 회전을 정해주고

단위원에서의 삼각함수를 이용해 x,y의 위치를 정해주었다.

protected override IEnumerator Move()
    {
        transform.rotation = Quaternion.Euler(0, 0, -currentAngle);

        if (currentAngle < 360f)
        {
            float xPos = Mathf.Sin(currentAngle * Mathf.Deg2Rad);
            float yPos = Mathf.Cos(currentAngle * Mathf.Deg2Rad);

            transform.position = new Vector2(xPos, yPos) * radius;
            currentAngle += Time.deltaTime * moveSpeed;

            yield return null;
        }
        else
            currentAngle = 0f;

        StartCoroutine(Move());
    }

 

결과

 

 

오늘의 회고

 오늘은 무기 공격 방식과 업그레이드 부분을 구현했다. 다음에는 실제 몬스터에게 데미지를 입힐 수 있도록 Collider 충돌 로직을 구현해야겠다. 연휴라도 짬짬이 시간을 내서 씬을 합치는 날인 월요일까지 맡은 부분을 완성할 수 있도록 해야겠다. 월요일까지 화이팅!

공부한 내용

원형으로 발사되는 투사체 구현

플레이어를 중심으로 특정 갯수만큼 원형으로 발사되는 투사체를 구현하고 싶었다.

그리고 너무 단순하게 한 방향으로만 나가는 것이 재미 없다고 생각해서 두 방향으로 나가게 했다.

360도를 개수만큼 나누어 투사체가 뻗어가게 하였고 다른 방향은 나눈 기준의 절반을 더해줘 두 가지 패턴이 나오게 구현했다.

protected override IEnumerator Shoot()
{
    while (shovels.Count < Data.count)
    {
        Shovel shovel = CreateNewWeapon() as Shovel;
        shovels.Add(shovel);
    }

    currentElapsedTime += Time.deltaTime;

    float angleUnit = ANGLE / Data.count;

    float angle = 0;
    if (isEven)
    {
        float angleMod = angleUnit * 0.5f;
        for (int i = 0; i < Data.count; i++)
        {
            angle = angleUnit * i + angleMod;
            if (angle >= ANGLE)
                angle -= ANGLE;

            SetShovelState(angle, i);
        }
    }
    else
    {
        for (int i = 0; i < Data.count; i++)
        {
            angle = angleUnit * i;
            SetShovelState(angle, i);
        }
    }

    isEven = !isEven;


    yield return CoroutineRef.GetWaitForSeconds(timePerAttack);
    StartCoroutine(Shoot());
}

 

 

오늘의 회고

 오늘은 무기를 발사하는 Shooter와 발사체를 구현하였다. 최대한 관리 쪽은 Shooter에서 처리하고 발사체의 이동은 따로 Weapon 클래스에서 구현하도록 했다. 구현하면서 구조에 대해서 많이 생각하게 되는데 기본적으로 관리하는 객체와 관리되는 객체의 행동을 구분하는 게 중요하다는 생각이 들었다.

 내일부터는 추석 연휴이다. 짬짬이 구현해서 다음주 회의 전까지 맡은 역할을 다 구현하고 합칠 수 있도록 해야겠다. 추석 연휴 잘 보내자!

 

+ Recent posts