공부/Unity

[Unity] 도전 2주모작 오버쿡드(1) 스팀게임 애셋 뜯고 (Asset Studio), 플레이어 이동, 접시 들기 등

굴러다니다니 2023. 6. 22. 00:42
728x90

2023.06.19 (월) - 2023.06.30 (금) (2주) 개인 프로젝트

1. 게임 애셋 뜯기!

모작을 위해 스팀에서 Overcooked 2를 다운 + AssetStudio 다운

https://github.com/Perfare/AssetStudio/releases

 

Releases · Perfare/AssetStudio

AssetStudio is a tool for exploring, extracting and exporting assets and assetbundles. - Perfare/AssetStudio

github.com

저기서 제일 최근걸 받고 Steam > SteamApps > Common > Overcooked

파일 폴더 통째로 열었다

그다음 Filter에서 animation clip + animator + mesh + sprite + texture 모 이정도만 받아서 빼려고 했더니 너무 오래걸려서 이미 유리언니가 뜯은 asset을 받았다 (헤헤)


2. 하이어라키창도 따로 빼주자!

하이어라키창에서 level 1-1을 뜯고싶었는데 그 이름을 못찾아서

sushi 1-1인가 그걸로 뜯었다 (찾음!)


3. Unity URP 세팅 다 해주고 하이어라키창에서 빼준 Art를 바로 가져왔다

나는 저거 책상도 없는 상태였어서 mesh에서 다 찾아서 가져왔는데,, Art가 아니라 뜯은 하이어라키창의 Design에 통째로 있었다구 한다... 흑흑

쨌든 scale 적당히 조정해주고

저렇게 다들 허여멀건했는데 material 찾아서 연결해주고

나는 2주동안 짧게 기능위주로 구현할거라 디테일은 버리려고 저 npc들 다 꺼줬다 우하하


4. 다시 쓸 애들 프리팹으로 빼두면서 설정하기

이번 맵에 자주쓰는 저 친구들 CounterTop 친구들 부딪히게 mesh있는 쪽에 box collider 넣어주고 프리팹으로 빼준다음

위치 저렇게 잡아줬다.

쓰레기통도 Crate들도 도마들도 빼주기!

 

1) Material Tilling

타일 material이 이렇게 생겨서 냅다 넣으면 무늬 반복이 아니라 저게 쭉쭉 늘어나던데 어떻게하지 하다가

이렇게 이름에 14*4 처럼 이름 붙어있는거에서 힌트를 얻어서

material의 tilling을 손봐줬다

그대신 저 material들은 같은 값을 공유해서 다른 부분에 같은 이미지로 다른 tilling을 하고싶다면 그냥 또다른 material을 만들어야된다는 번거로움 우우

 

2) Crate 이미지

마찬가지로 크기에 맞게 tilling 하고 offset으로 위치 바꿔서 재료 골라준다!

 

그렇게 기본 맵 세팅을 해준다!

필요없는건 끄고 접시도 올려두고 뭐 그렇게

(하루 마무리할 때 찍은 영상으로 캡쳐했음)


5. 지나가는 근처 오브젝트 하얗게 하이라이트 처리

1. 충돌 범위 및 물체 script

위에 그림처럼 상호작용 가능한 친구들은 하이라이트가 되게 하고 싶었다

근데 나는 시간이 없으니 그냥 야매로 대충 떼웠다

이렇게 상호작용 가능한 애들은 부딪히는 collider 따로, 충돌 감지용 collider를 따로 만들어주었다 (isTrigger처리로 충돌은 안됨)

요 상태로 프리팹 ok 해주면 모든애들한테 이 콜라이더 다 적용!

그다음에 하이라이트가 되는게 가능한 모든 친구들한테 Object라는 script를 만들어 넣어줬다.

 

하이라이트 될때 색이 바뀌는걸 material의 색을 바뀌게 하려고 하니까 material은 모두 공유되는거라 다같이 하얘지길래 하얘지는 material을 따로 만들어줬다.

(기존 친구에서 emission 체크하고 검정이었던 Emission Map을 회색쪽으로 올려주면 더 밝아짐)

 

그래서 OnHighlight 함수가 실행되면 해당 MeshRenderer에 접근해 넣어준 밝은 material로 바꾼다

Off는 끄는거

(CounterTop은 바꿔줄 mateial이 한개, Crate는 두개라서 따로 나눠줬다)

public class Object : MonoBehaviour
{
    public bool canActive = false;
    [SerializeField] private Material[] materials;
    [HideInInspector] public enum ObjectType { CounterTop, Crate, Return, Station, Sink };
    public ObjectType type;

    public void OnHighlight()
    {
        if (type == ObjectType.CounterTop)
        {
                canActive = true;
                Renderer rd = transform.parent.GetComponent<MeshRenderer>();
                rd.material = materials[1];
        }
        else if (type == ObjectType.Crate)
        {
                canActive = true;
                Renderer rd = transform.parent.transform.parent.transform.parent.GetChild(0).GetComponent<MeshRenderer>();
                rd.material = materials[1];
                rd = transform.parent.transform.parent.transform.parent.GetChild(1).GetComponent<MeshRenderer>();
                rd.material = materials[1];
        }
    }

    public void OffHighlight()
    {
        if (type == ObjectType.CounterTop)
        {
                canActive = false;
                Renderer rd = transform.parent.GetComponent<MeshRenderer>();
                rd.material = materials[0];
        }
        else if (type == ObjectType.Crate)
        {
            Renderer rd = transform.parent.transform.parent.transform.parent.GetChild(0).GetComponent<MeshRenderer>();
            rd.material = materials[0];
            rd = transform.parent.transform.parent.transform.parent.GetChild(1).GetComponent<MeshRenderer>();
            rd.material = materials[0];
        }
    }
}

(*GetComponent<MeshRenderer>().material = materials[0] 하면 할당식이 반대라서 안된다 하니깐 앞의 친구들을 따로 변수로 빼주고 걔를 바꿔주자)

 

2. PlayerController 이동 및 충돌

이제 PlayerController Script를 만들어 플레이어의 이동과 충돌처리를 해주자

플레이어의 이동을 위한 코드 (구글링함)

물리적인 이동을 위해 FixedUpdate에서 진행했고, animtor를 만들어 걷는 중이면 애니메이션 연결을 해주기 위해 isMoving bool 함수를 만들어 연결했다.

void FixedUpdate()
    {
        anim.SetBool("isWalking", isMoving);
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");


        Vector3 dir = new Vector3(h, 0, v); 

        if (!(h == 0 && v == 0))
        {
            isMoving = true;
            //이동 회전 한번에 처리
            transform.position += dir * Speed * Time.deltaTime; 
            transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * rotateSpeed);
        }
        else
        {
            isMoving = false;
        }
    }

 

충돌처리를 위해 isTrigger해준 친구랑 비교하려구 onTrigger 친구들을 대량으로 사용했다 우하하

일단 현재 상호작용이 가능한 Object를 위해 GameObject activeObject 라고 변수 선언을 해뒀다. (+ nextActiveObject도)

canAcvie bool 변수는 상호작용이 가능한 object가 있는지를 위한 bool 변수,,, (이지만 나중에 까멈ㄱ고 안씀)

private void OnTriggerEnter(Collider other)
    {
        canActive = true;
        if (activeObject == null)
        {
            activeObject = other.gameObject;
            other.GetComponent<Object>().OnHighlight();
        }
        else
        {
            nextActiveObject = other.gameObject;
        }
    }

만일 상호작용 가능한 오브젝트(active object)가 없으면 충돌된 그 친구를 받아서 밝게 켜준다

만일 상호작용 가능한 오브젝트가 있는데 또 들어왔다? 이는 두가지 테이블 사이에 있는거라 다음에 active 해줄 친구로 넣어줫다.

 

 private void OnTriggerStay(Collider other)
    {
        canActive = true;
    }

만일 충돌된 상태가 계속되면 canActive를 true로,, (임시방편,,?)

 

만약 위에 상황처럼 두 콜라이더 모두 충돌되는 상황이라면 나는 어떻게 해야할까

정답은 기존의 켜진 오브젝트를 우선으로 처리한다! 이다

이를 위해 OnTriggerEnter에서 뭐가 들어와도 다음에 처리할 nextActiveObject로 넣어줬고, Exit에서 잘 처리해야한다.

   private void OnTriggerExit(Collider other)
    {
        if (activeObject != null && nextActiveObject == null)
        {
            canActive = false;
            if (activeObject.GetComponent<Object>() != null)
            {
                activeObject.GetComponent<Object>().OffHighlight();
            }
            activeObject = null;
        }
        else
        {
            if (other.gameObject == activeObject)
            {
                activeObject.GetComponent<Object>().OffHighlight();
                activeObject = nextActiveObject;
                activeObject.GetComponent<Object>().OnHighlight();
                nextActiveObject = null;
            }
            else
            {
                nextActiveObject = null;
            }
        }
    }
}

만일 현재 활성화된 친구가 있고 다음 활성화될 친구가 없는데 exit이면 기존의 활성화된 친구를 끄고 null로 바꾼다.

만일 exit된 other이 활성화된 애였다면 다음 활성화될 친구를 대상으로 바꾸고, exit된 other이 다음 활성화될 애였으면 그냥 얘를 next에서 빼주면 된다!

그렇게 하이라이트 완료


6. Plate 들고 내리기

아까 만들어둔 CounterTop 4개위에 plate를 이미 올려두었는데, 올리고 내리고 잡는 이런걸 다 setParent를 이용할거다.

그리고 CounterTop에 올라갔을때 위치를 특정짓기 위해 CounterTop 자식객체로 position을 설정해준다

이런식으로,, 큭큭

CounterTop에 자식객체로 Plate를 넣어줬고, 저렇게 Plate가 올라가있는 Object친구는 <Object>의 OnSomething bool변수를 켜고 시작해주자.


자 이제 playerController에서 이를 잡을 시간

Update문에서 처리해줄거다,,,

 

        if (Input.GetKeyDown(KeyCode.Space) && activeObject.CompareTag("CounterTop") && canActive && activeObject.GetComponent<Object>().onSomething && !isHolding) 
        {
            activeObject.GetComponent<Object>().onSomething = false;
            isHolding = true;
            anim.SetBool("isHolding", isHolding);
            GameObject handleThing = activeObject.transform.parent.GetChild(2).gameObject;
            handleThing.GetComponent<Handle>().PlayerHandle(transform);
        }
        else if (Input.GetKeyDown(KeyCode.Space) && activeObject.CompareTag("CounterTop") && canActive && !activeObject.GetComponent<Object>().onSomething && isHolding) 
        {
            activeObject.GetComponent<Object>().onSomething = true;
            isHolding = false;
            GameObject handleThing = transform.GetChild(1).gameObject;
            handleThing.GetComponent<Handle>().PlayerHandleOff(activeObject.transform.parent, activeObject.transform.parent.GetChild(1).localPosition);
            anim.SetBool("isHolding", isHolding);
        }

상호작용 대상이 CounterTop이고 뭔가가 위에있으며, 내가 아무것도 안들었으면

onSomething 꺼주고, isHolding 켜주고 애니메이션고 연결, 내가 지금 들 친구를 activeObject의 자식객체에서 찾아서 가져온다. <Handle> PlayerHandle 코드 사용

그 아래 else if문은 내가 들고있는데 CounterTop이 비어있다면 내려놓는 식이다

<Handle>의 PlayerHandleOff 사용

 

-> Handle 코드를 또 봐야겠지,,?

들릴 수 있는 Object들은 Handle코드를 가지고있다

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

public class Handle : MonoBehaviour
{
    public void PlayerHandle(Transform something)
    {
        transform.SetParent(something);
        transform.localPosition = new Vector3(-0.4f, 0.24f, 2.23f);
    }
    public void PlayerHandleOff(Transform parent, Vector3 target)
    {
        transform.SetParent(parent);
        transform.localPosition = target;
    }
}

PlayerHandle로 들리면 Player 자식객체로 plate를 옮기고, local Position으로 손 위치를 맞춰주고

Off가 되면 앞의 오브젝트의 자식객체로 넣어주고, localPosition을 아까 위에서 선언해둔 빈 오브젝트의 자리를 잡아서 localPosition을 맞춰준다.

 

들기와 놓기 성공!


7. Crate에서 spacebar 누르면 재료 만들기

PlayerController에서 Crate랑 상호작용하면 Crate의 Open함수로 연다. (헷갈려서 다 Craft라고 썼다;;)

만약 Crate위에도 뭐가 없고 내가 뭘 안들었으면 열면서 애니메이션 연결

위에 뭐는 없는데 내가 뭐라도 들고있으면 내꺼를 내려놓는다

그리고 걔 위에 뭐가 있는데 내가 안들고있으면 다시 내가 잡고 오키오키

if (Input.GetKeyDown(KeyCode.Space) && activeObject != null && activeObject.CompareTag("Crate"))
        {
            if (!activeObject.GetComponent<Object>().onSomething && !isHolding) 
            {
                activeObject.GetComponent<Craft>().OpenCraft(); 
                activeObject.GetComponent<Object>().onSomething = false;
                isHolding = true;
                anim.SetBool("isHolding", isHolding);
            }
            else if(!activeObject.GetComponent<Object>().onSomething && isHolding) 
            {
                activeObject.GetComponent<Object>().onSomething = true;
                transform.GetChild(1).gameObject.GetComponent<Handle>().PlayerHandleOff(activeObject.transform.parent, activeObject.transform.parent.GetChild(1).localPosition); 
                isHolding = false;
                anim.SetBool("isHolding", false);
            }
            else if (activeObject.GetComponent<Object>().onSomething && !isHolding) 
            {
                activeObject.GetComponent<Object>().onSomething = false;
                isHolding = true;
                anim.SetBool("isHolding", isHolding);
                GameObject handleThing = activeObject.transform.parent.GetChild(2).gameObject;
                handleThing.GetComponent<Handle>().PlayerHandle(transform);
            }
        }

<Craft> 스크립트를 보자

 [SerializeField] private Animator CraftAnim;
 public enum FoodType { Fish, Shrimp }
    public FoodType food;
    public GameObject foodPrefabs;
    public void OpenCraft()
    {
        CraftAnim.SetTrigger("Open");
        if (food == FoodType.Fish)
        {
            GameObject newFish = Instantiate(foodPrefabs, Vector3.zero, Quaternion.identity);
            FindObjectOfType<PlayerController>().isHolding = true;
            newFish.transform.GetChild(0).transform.GetChild(0).GetComponent<Handle>().IngredientHandle(FindObjectOfType<PlayerController>().transform);
        }
        else if (food == FoodType.Shrimp)
        {
            GameObject newShrimp = Instantiate(foodPrefabs, Vector3.zero, Quaternion.identity);
            FindObjectOfType<PlayerController>().isHolding = true;
            newShrimp.transform.GetChild(0).transform.GetChild(0).GetComponent<Handle>().IngredientHandle(FindObjectOfType<PlayerController>().transform);
        }
    }

불리면 Trigger로 애니메이션으로 Craft 열리게 되고, foodtype에 따라 나눠줬다..(코드 똑같은데 왜,,? 나중을 위해,,? 흐음)

foodPrefabs으로 넣어준 재료 프리팹을 생성해서 플레이어의 손에 들려주는 코드이당

큭큭

끝!

728x90