//총알을 180도로 아래 방향으로 총알의 방향벡터를 만드는 로직 
 void MakeCircle()
 {
     Vector3 targetPos = playerTransform.position;
     Vector3 myPos = new Vector3(0f, 1.4f, 0);
     Vector3 vectorToTarget = (targetPos - myPos).normalized;
     int n = 30;

     for(int i = 0; i < n; i ++)
     {
         GameObject MakeCircle = Instantiate(bullet, myPos, Quaternion.identity);
         Vector2 circleVec = new Vector2(Mathf.Cos((Mathf.PI) * i / n ), Mathf.Sin(-(Mathf.PI) * i / n ));
         MakeCircle.GetComponent<Rigidbody2D>().AddForce(circleVec * 30f);
     }
     
 }

삼각함수를 통해서 원하는 각도의 좌표방향을 알수있다.

 

Mathf.Cos()은 x축 방향을 의미, Mathf.Sin()은 y축 방향을 의미한다.

단위 원 일때 (반지름이 1인 원) Cos = 밑변 / 빚면인데 빚면은 반지름을 나타내고 결국 밑변은 x좌표값을 나타낸다.

즉 Cos(degree) = x 좌표를 의미한다. Sin도 이와 같은 의미를 취한다 .

 

Mathf.Cos()의 매개변수는 Radian값을 의미하므로 (Mathf.Pi / 180) * degree 라는 변환식을 사용하거나

DegtoRad 매크로 함수를 사용해셔 변환해서 사용하면된다. 

 

지금 위의 함수는 180도의 범위만 필요하기에  Mathf.Pi만 사용하였다. 

 


게임 개발에서 삼각함수는 뗄래야 땔수 없는 존재지만 오늘 개발중에 살짝 과부하가 와서

기억을 전혀 못했었다.. 공부를 다시 할 필요가 있다. 

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

[System.Serializable]
public struct Stage
{
    public bool isBoss;
    public int stageNum;
    public int boardWidth;
    public int boardHeight;
    public bool isClear;
}

Stage 관리를 하기위해 Stage 구조체를 만들었다. 

 

public class DataManager : MonoBehaviour
{
    [SerializeField] private List<Stage> stages;
    private int curStage;

    public List<Stage> Stages { get { return stages; } }
    public int CurStage { set { curStage = value - 1; } }


    public Stage GetCurStgae()
    {
        return stages[curStage];
    }

    public void SetCurStgaeIsClear()
    {
    	//해당 코드는 문법상 오류 발생
        //stages[curStage].isClear = false;

        //구조체 변수를 새로 선언해서 초기화 해줘야함
        Stage stage = stages[curStage];
        stage.isClear = true;
        stages[curStage] = stage;
    }

Class 내에 Stage 구조체를 이용해 List를 만들어서 List[index]로 변수에 접근했으나 Stage의 변수에 접근하지 못했다. 

분명 List로 만들어서 배열처럼 접근했으나 이런식으로 답변이 나왔다.

 

다른 Stage 구조체를 선언해서 다시 넣어줘서  해결하였다.

 


해당 문제는 기초가 모자라 그런것 같다.. 다시 한번 기초를 다듬어봐야할것 같다.

 

1.Class 내부 변수 이름으로 호출하기

 

Unity에서 대화상자를 구현하고 있는데 Excel을 이용하여 외부데이터를 불러와서 읽어오는 방식을 택했

다.

 

해당 Asset을 사용하여 Excel를 읽었다.

https://github.com/mikito/unity-excel-importer

 

GitHub - mikito/unity-excel-importer

Contribute to mikito/unity-excel-importer development by creating an account on GitHub.

github.com

 

DialogDB.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using UnityEngine;

[ExcelAsset]
public class DialogDB : ScriptableObject
{
	public List<DialogDBEntity> Stage1; // Replace 'EntityType' to an actual type that is serializable.
	public List<DialogDBEntity> Stage2; // Replace 'EntityType' to an actual type that is serializable.
	
    //외부에서 내부 변수를 이름으로 호출 할 수있는 함수
	public object PrintField(string name)
    {
    	// public변수가 아니면 GetField에서 null이 리턴된다
        var result = this.GetType().GetField(name).GetValue(this); 

        return result;
    }
}

 

DialogeSystem.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class DialogeSystem : MonoBehaviour
{
    [SerializeField] DialogDB dialogDB;

    List<DialogDBEntity> curStageDialoge;

    private void Start()
    {		
        //해당 오브젝트가 활성화시 Stage1의 DialogDB를 호출한다.
        DialogLoad("Stage1");
    }

    public void DialogLoad(string StageName)
    {
    	//매개변수의 StageName을 받아서 dialgoDB Class의 변수를 이름으로 호출한다.
        object curStage = dialogDB.PrintField(StageName);
        //object 자료형으로 호출하였기에 언박싱을 하여 원래의 자료형으로 돌려줘야한다.
        curStageDialoge = (List<DialogDBEntity>)curStage;
    }

}

 

DialogeSystem 에서 DialogeDB의 PrintField() 메서드를 통해서 해당 클래스의 변수를 박싱( List<DialogDBEntity  ->var)해서 받아오고, 받아온 변수를 언박싱(object -> List<DialogDBEntity>)하여 내가 쓸수있게 만들어준다. 

 

GitHub 기초 및 익히기 

GitHub : VCS ( Version Control System) 버전 관리 시스템

Local Repository (로컬 저장소) : 작업한것이 각 사용자의 컴퓨터에 저장됨

Remote Repository (원격 저장소) : 작업한 것이 서버에 저장됨

 

Commit : 작업한 것의 메모, 개인이 작한것이 아직 원격저장소에 push하지 않은 단계

History : Commit의 기록들 , 누가 작업했는지 기록을 알수있다.

 

Branch(가지) : 저장소의 단위 ex) 너 Branch 하나 파서 작업해라 -> 너의 개인 저장소 만들어서 기능구현 해라

Merge(병합) : 각각의 저장소를 상위 저장소로 합치는것 -> 각각 기능구현한 작업물을 하나로 합치는 과정

 

Fetch Origin : 현재 위치한 Branch의 최신 이력 정보를 확인하는 기능

Pull : 저장소에 올라온 작업물을 가져오는것 -> Pull할떄는 상관이없다. 어차피 원격저장소에 다 저장이 되어있기에

Push : 작업한것을 저장소로 보내는 과정 -> <매우 중요> Push 할때는 웬만하면 소통을 하고 ,신중히 Push 할것 

잘못 Push하면 상위 저장소가 날라가게될 수도 있기에 

 

Stash (스태시) : 변경사항을 커밋하기엔 아직 이르거나, 다른 브랜치로 체크아웃(다른 사람 브랜치 들갈때) 유지 하는것

Revert : 원하는 커밋한 내용을 삭제할때 사용되는 기능 , History에 남는다. 

 

 

<GitHub 작업시 중요한점 >


1. Merge 작업하기 전 무조건 확인 해야되는점 

 

2.GitHub 자주 충돌나는 원인 및 대처법

 

이런 경우 오류가 발생한다.

 

충돌이 일어난경우 부모 branch를 수정할것인지 자신의 branch를 수정할것인지 대안을 준다.

웬만하면 자기 branch를 수정하는것이 좋다... 부모것 함부로 수정후 Push하면 대참사가 날수도 있다.

내건 수정하거나 없애고 새로운 branch로 클론을 만들어 작업하는것이 훨씬좋다.

 

3.History에서 해당 시점으로 Branch 생성하기.

Histroy에서 해당 기능을 사용하면 해당 시점으로 Branch를 만들어 다시 작업 할 수있다.

 


추가로 공부해야 될 것

동적 생성 : Scene에 작업할것이 너무 많은경우 사용하는 방법

깃 플로우 전략

깃 컨벤션

유튜브 <골드메탈> 영상 시청 및 따라 만들기 - 3D 쿼터뷰 액션게임


추가 및 수정한 스크립트

Player.cs :: 물리 충돌시 각종 버그를 수정하기위헤 FixedUpdate를 추가

1.오브젝트와 충돌시 회전속력이 발생하는 버그를 없애기위해 FreezeRotation()함수 추가

2.벽을 관통하는 버그가 있어 StopToWall() 함수 추가

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

public class Player : MonoBehaviour
{
    bool isBorder = false; //현재 벽과 충돌했는지 체크 유무

    private void Move()
    {
        //정규화를 하는 이유? : 대각선 이동시 빨라지기 때문에 크기(속도)를 1로 만들어서 속도를 똑같이 만들어줘야한다.
        //쉽게 말하면 방향값이 1로 보정되는 벡터라고 생각하면 된다.
        //왜 빨라짐? : hAxis = 1 , vAxis = 1 인경우 대각성 이동시 피타고라스의 법칙으로 root(1^2+1^2) = root(2) = 1.141... 때문이다.
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;

        //무기 교체시 or 무기 공격시 or 재장전시 움직이지 못하게 설정
        if (isSwap || !isFireReady || isReload) moveVec = Vector3.zero;

        //회피 동작 중인경우 회피 방향벡터가 대입되어 방향전환을 못하게 막기 위한 코드
        if (isDodge) moveVec = dodgeVec;

        //transform 이동은 가끔 물리충돌을 무시하는 경우가 발생 함
        //Rigidbody 컴포넌트에서 CollisonDetection을 Continuos로 변경 
        //Cpu를 더 먹어 최적화는 떨어지지만 정확도는 올라가기 때문 

        //위의 조건과 같이 걸어버리면 회전할시에 moveVec에 영향을 받게 되서 캐릭터가 굳어버릴수 있다.
        if (!isBorder)
        { 
            if (!wDown) transform.position += moveVec * speed * Time.deltaTime;
            else transform.position += moveVec * speed * 0.3f * Time.deltaTime;
        }

        //SetXXXX("애니메이션 이름" , bool 변수) 를 넣어 조건문처럼 사용 할 수 있다.
        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    private void Turn()
    {
        //지정된 벡터를 향해서 회전시켜주는 함수 - 3D기능
        //현재 플레이어 위치에 방향벡터를 더해서 우리가 나아가는 쪽으로 오브젝트를 회전한다는 뜻
        //#1.키보드에 의한 회전
        transform.LookAt(transform.position + moveVec);

        //#2.마우스에 의한 회전
        if (fDown)
        {
            //스크린 -> 월드로 Ray를 쏘는 함수
            Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit rayHit;
            if (Physics.Raycast(ray, out rayHit, 100))
            {
                Vector3 nextVec = rayHit.point - transform.position;
                nextVec.y = 0; //마스크가 없기에 부피가 있는 콜라이더 물체를 클릭시 캐릭터가 빙빙 도는 현상을 막기위해 y =0 추가
                transform.LookAt(transform.position + nextVec);
            }
        }
    }

    void FreezeRotation()
    {
        //rigid.angularVelocity : 물리회전속도
        rigid.angularVelocity = Vector3.zero;
    }

    void StopToWall()
    {
        Debug.DrawRay(transform.position, transform.forward * 5, Color.green);
        //레이어마스크(Wall)과 5 거리 안에 충돌시 isBorder는 true가 될것이다.
        isBorder = Physics.Raycast(transform.position, transform.forward, 5, LayerMask.GetMask("Wall"));
    }

    private void FixedUpdate()
    {
        FreezeRotation();
        StopToWall();
    }


}

 

Item.cs :: 드랍 아이템들의 Player와의 물리충돌을 없애기 위해

1.Floor와 충돌시 Rigidbody를 Kinematic 처리 와 물리충돌을 담당하는 Collider을 비활성화

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Item : MonoBehaviour
{
    Rigidbody rigid;
    SphereCollider sphereCollider;

    //열거형 변수 enum
    public enum Type
    {
        Ammo, Coin, Grenade, Heart, Weapon
    };

    public Type type; //아이템 종류 변수
    public int value; //아이템 값을 저장할 변수

    private void Awake()
    {
        rigid = GetComponent<Rigidbody>();

        //Item 오브젝트에는 2개의 스피어콜라이더가 있음
        //GetComponent 호출시 Inspector 제일 위에있는 컴포넌트를 호출하게됨
        sphereCollider = GetComponent<SphereCollider>();
    }

    void Update()
    {
        //Rotate(오일러 각도) : 오브젝트를 해당 각도(방향벡터)로 회전시키는 함수 
       transform.Rotate(Vector3.up * 10 * Time.deltaTime);
    }

    //물리효과 변경을 스크립트로 수정
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.CompareTag("Floor"))
        {
            //바닥에 충돌시 키네마틱(물리충돌연산x)로 변경
            rigid.isKinematic = true;
            sphereCollider.enabled = false;

        }
    }
}

수정된 물리연산들 & 유니티 기능 정리

1.자동회전방지

탄피에도 RigidBody와 Collider가 있어 탄피가 쌓이고 Player가 위로 올라가면 자동으로 회전하는 버그가 있었다.

이 버그의 원인은 Player <> 총알탄피 의 물리충돌로 인하여 Player의 Rigidbody에 회전속력이 추가되기에 발생되는 것이다.

 

Player.cs

  void FreezeRotation()
    {
        //rigid.angularVelocity : 물리회전속도
        rigid.angularVelocity = Vector3.zero;
    }

해당 코드를 FixedUpdate()에서 호출하여 회전속도를 강제로 0으로 만들어 처리하였다.

 

2.충돌레이어 설정

오브젝트의 레이어를 설정하여 충돌을 원하는데로 컨트롤 할 수 있다.

위 그림과 같이 탄피(BulletCase)와 플레이어 총알(PlayerBullet)의 레이어를 설정하고 충돌 관계를 설정하여 

충돌을 방지했다. 

 

1,2 번으로 문제점을 해결한 결과

1.탄피가 겹치지 않게 설정 완료 , 2.Player가 탄피위에 올라가도 충돌 발생 X

 

3.벽 관통 방지

해당 프로젝트에서 캐릭터가 벽을 관통하는 버그가 있었다. 실제로 3D프로젝트를 진행하면 자주 겪는 버그다.

이 버그를 대처하기 위해서는 RayCast기능을 활용하였다.

Unity Editer에서 확인하기 편하게 해당 코드를 적어준다.

해당 코드는 인게임에서는 보이지 않고 에디터에서만 보이는 레이저다.

    void StopToWall()
    {
        Debug.DrawRay(transform.position, transform.forward * 5, Color.green);
        //레이어마스크(Wall)과 5 거리 안에 충돌시 isBorder는 true가 될것이다.
        isBorder = Physics.Raycast(transform.position, transform.forward, 5, LayerMask.GetMask("Wall"));
    }

isBorder Bool변수를 만들어 레이저가 벽에 닿은 경우 true가 되게 만든뒤 

private void Move()
    {

        //무기 교체시 or 무기 공격시 or 재장전시 움직이지 못하게 설정
        if (isSwap || !isFireReady || isReload) moveVec = Vector3.zero;

        //위의 조건과 같이 걸어버리면 회전할시에 moveVec에 영향을 받게 되서 캐릭터가 굳어버릴수 있다.
        if (!isBorder)
        { 
            if (!wDown) transform.position += moveVec * speed * Time.deltaTime;
            else transform.position += moveVec * speed * 0.3f * Time.deltaTime;
        }

        //SetXXXX("애니메이션 이름" , bool 변수) 를 넣어 조건문처럼 사용 할 수 있다.
        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

Move()함수에 추가하여 transform에 영향을 안주게 막아버렸다. 이러면 애니메이션은 계속 재생하기에

플레이어가 키를 눌렀다는것을 인지함과 동시에 벽 관통 버그를 원천 봉쇄해버렸다.

 

4.아이템 물리 총돌 제거

물리 충돌을 담당하는 콜라이더와 Player가 충돌시 문제가 발생한다.

충돌시 아이템의 회전속도가 지멋대로 바뀌며 Player는 해당 아이템에 올라 탈 수 있다.

Item.cs

  private void Awake()
    {
        rigid = GetComponent<Rigidbody>();

        //Item 오브젝트에는 2개의 스피어콜라이더가 있음
        //GetComponent 호출시 Inspector 제일 위에있는 컴포넌트를 호출하게됨
        sphereCollider = GetComponent<SphereCollider>();
    }
    
    
    //물리효과 변경을 스크립트로 수정
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.CompareTag("Floor"))
        {
            //바닥에 충돌시 키네마틱(물리충돌연산x)로 변경
            rigid.isKinematic = true;
            sphereCollider.enabled = false;

        }
    }


Floor와 충돌할때, Rigid를 키네마틱 , 물리충돌 담당하는 콜라이더를 비활성화 시켜 Player와의 충돌을 방지하였다.


 

 

 

오늘로 사전캠프가 종료되고 9월9일(월)부터 본 캠프가 시작된다.

유튜브 <골드메탈> 영상 시청 및 따라 만들기 - 3D 쿼터뷰 액션게임


추가 및 수정한 스크립트

Player.cs :: 총을 추가해서 해당 마우스 방향으로 총을 발사시 캐릭터가 돌아가게 하는 기능 추가

  private void Turn()
  {
      //지정된 벡터를 향해서 회전시켜주는 함수 - 3D기능
      //현재 플레이어 위치에 방향벡터를 더해서 우리가 나아가는 쪽으로 오브젝트를 회전한다는 뜻
      //#1.키보드에 의한 회전
      transform.LookAt(transform.position + moveVec);

      //#2.마우스에 의한 회전
      if (fDown)
      {
          //스크린 -> 월드로 Ray를 쏘는 함수
          Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
          RaycastHit rayHit;
          if (Physics.Raycast(ray, out rayHit, 100))
          {
              Vector3 nextVec = rayHit.point - transform.position;
              nextVec.y = 0; //마스크가 없기에 부피가 있는 콜라이더 물체를 클릭시 캐릭터가 빙빙 도는 현상을 막기위해 y =0 추가
              transform.LookAt(transform.position + nextVec);
          }
      }
  }

followCamera는 현재 화면을 비추는 mainCamera를 가지고있다.

스키린화면(우리가 보는 화면)에서 월드(3D 월드스페이스를 말함)로 레이저를 쏘는데 그방향이 '마우스 위치'로 레이저를 쏜다.

RaycastHit는 레이저가 발사해서 맞은 곳의 data를 가져온다. 

마우스의 위치가 저 상태면 바닥의 Floor 오브젝트의 정보를 가져온다.

즉, 캐릭터가 향하는 방향 nextVec를 벡터의 뺼셈을 이용하여 방향벡터를 구하고 

현재 캐릭터의 위치에 방향벡터를 더하여 방향을 수정해준다. 

 


https://www.youtube.com/watch?v=07q9RUTRq4M   

: 원거리 무기 로직 구현 및 총알 오브젝트 및 탄피 생성 방법 

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

TIL : 2024-09-09(월) :: GitHub 기초  (0) 2024.09.09
TIL : 2024-09-07(토)  (0) 2024.09.07
TIL : 2024-09-05(목)  (0) 2024.09.05
TIL : 2024-09-04(수)  (0) 2024.09.04
TIL : 2024-09-03(화)  (0) 2024.09.03

유튜브 <골드메탈> 영상 시청 및 따라 만들기 - 3D 쿼터뷰 액션게임


추가 및 수정한 스크립트

Player.cs ::

1."Item" 태그 오브젝트 충돌시  처리 추가

2."Waepon" 태그 오브젝트 충돌시 처리 추가

3.각종 아이템 갯수 변수 및 아이템 최대갯수 변수 추가

4.Player가 장착하는 무기 배열변수 및 활성화된 무기 bool 변수 추가

5.무기 Swap 기능 근접 무기 공격기능 추가

6.수류탄 공전 연출 추가

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

public class Player : MonoBehaviour
{
    [SerializeField] float speed; //캐릭터의 속도
    [SerializeField] float jumpPower; //캐릭터의 점프파워

    [SerializeField] GameObject[] weapons; // 플레이어가 무기를 들고있을때 활성화 해주는 오브젝트 배열
    [SerializeField] bool[] hasWeapons; // 플레이어가 해당 무기를 끼고있는것츨 체크하는 배열

    //플레이어의 아이팀 소지 갯수 변수
    public int ammo;
    public int coin;
    public int health;
    public int hasGrenades;
    [SerializeField] GameObject[] grenades; //수류탄들을 저장할 배열 변수, 공전 물체를 컨트롤하기 위해 배열로 생성

    //아이템 소지 갯수 최대 변수
    public int maxammo;
    public int maxcoin;
    public int maxhealth;
    public int maxhasGrenades;

    float hAxis; //수평선 축 value값
    float vAxis; //수직선 축 value값

    bool wDown = false; //Input Manager에서 추가한 Walk(Left Shift)의 키 유무 체크
    bool jDown = false; //점프 키 유무 체크
    bool iDown = false; //아이템,무기 획득 키 유무 체크 - 상호작용
    bool fDown = false; //공격 키 유무 체크

    bool isJump = false; //현재 점프 중인지 체크 유무
    bool isDodge = false; //현재 회피 중인지 체크 유무
    bool isSwap = false; //현재 무기 교체 중인지 체크유무 -> 교체할때 다른 액션이 간섭 못하게 하기위해
    bool isFireReady = true; //현재 공격이 가능한 상태를 체크 유무
                      
    //FPS 게임 시 1, 2,3 을누르면 무기 스왑하는 기능으로 설계
    bool sDown1 = false;
    bool sDown2 = false;
    bool sDown3 = false;

    Vector3 moveVec; // hAxis와 vAxis 값을 받아와 3차원 벡터값(방향벡터) 변수
    // 회피시 캐릭터 방향전환을 막기위해 생성
    Vector3 dodgeVec; //회피할때 적용되는 방향벡터
    Animator anim;
    Rigidbody rigid;

    GameObject nearGameObject; // 무기에 근접했을때, 무기 오브젝트를 저장할 변수 
    //GameObject equipWeapon; // 자료형 GameObject -> Weapon으로 변경
    Weapon equipWeapon;  // 현재 장착하고 있는 무기 오브젝트를 저장할 변수 

    //처음 초기값을 -1로 , 무기 첫데이터가 0번 Index이기 때문
    int equipWeaponIndex = -1; // 현재 장착하고 있는 무기의 Index 변수
    float fireDelay; //공격딜레이 변수

    private void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();
    }

    private void Update()
    {
        GetInput();
        Move();
        Turn();
        Jump();
        Attack();
        Dodge();
        Interation();
        Swap();
    }

    private void GetInput()
    {
        //해당 프로젝트 InputManager를 이용하여 만듬 

        //GetAxisisRaw() : Axis 값을 정수로 반환하는 함수
        //GetButton() : 꾹 누르고 있을때만 호출
        hAxis = Input.GetAxisRaw("Horizontal");
        vAxis = Input.GetAxisRaw("Vertical");
        wDown = Input.GetButton("Walk");
        jDown = Input.GetButtonDown("Jump");
        fDown = Input.GetButtonDown("Fire1");
        iDown = Input.GetButtonDown("Interation");
        sDown1 = Input.GetButtonDown("Swap1");
        sDown2 = Input.GetButtonDown("Swap2");
        sDown3 = Input.GetButtonDown("Swap3");
    }

    private void Move()
    {
        //정규화를 하는 이유? : 대각선 이동시 빨라지기 때문에 크기(속도)를 1로 만들어서 속도를 똑같이 만들어줘야한다.
        //쉽게 말하면 방향값이 1로 보정되는 벡터라고 생각하면 된다.
        //왜 빨라짐? : hAxis = 1 , vAxis = 1 인경우 대각성 이동시 피타고라스의 법칙으로 root(1^2+1^2) = root(2) = 1.141... 때문이다.
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;

        //무기 교체시 or 무기 공격시 움직이지 못하게 설정
        if (isSwap || !isFireReady) moveVec = Vector3.zero;

        //회피 동작 중인경우 회피 방향벡터가 대입되어 방향전환을 못하게 막기 위한 코드
        if (isDodge) moveVec = dodgeVec;

        //transform 이동은 가끔 물리충돌을 무시하는 경우가 발생 함
        //Rigidbody 컴포넌트에서 CollisonDetection을 Continuos로 변경 
        //Cpu를 더 먹어 최적화는 떨어지지만 정확도는 올라가기 때문 

        if (!wDown) transform.position += moveVec * speed * Time.deltaTime;
        else transform.position += moveVec * speed * 0.3f * Time.deltaTime;

        //SetXXXX("애니메이션 이름" , bool 변수) 를 넣어 조건문처럼 사용 할 수 있다.
        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    private void Turn()
    {
        //지정된 벡터를 향해서 회전시켜주는 함수 - 3D기능
        //현재 플레이어 위치에 방향벡터를 더해서 우리가 나아가는 쪽으로 오브젝트를 회전한다는 뜻
        transform.LookAt(transform.position + moveVec);
    }

    //점프키와 회피키가 같지만 이동키가 눌리고 있을때는 회피, 이동 안할때는 점프를 하게된다.
    private void Jump()
    {
        //방향벡터가 0 이기에 이동 하지 않으므로 점프
        if(jDown && moveVec == Vector3.zero && !isJump && !isDodge && !isSwap) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
        {
            //Addforce : rigidbody에 포함된 메서드이며 해당 오브젝트에 힘을 가해주는 함수
            rigid.AddForce(Vector3.up * jumpPower,ForceMode.Impulse); // Impulse : 힘을 주는 종류 중 하나로, 즉발로 힘을 준다는 의미
            anim.SetBool("isJump",true);
            anim.SetTrigger("doJump");
            isJump = true;
        }
    }

    private void Attack()
    {
        if (equipWeapon == null) //무기가 있을떄만 실행되도록 체크
        {
            return;
        }

        fireDelay += Time.deltaTime;
        isFireReady = equipWeapon.rate < fireDelay; //무기에 있는 공격속도 보다 현재 딜레이가 크면 공격 가능상태로 변경

        //공격키가 눌렸고 (fDonw) , 공격 준비 상태(isFireReady)고 점프 액션을 제외한 액션이 동작안할때 조건
        if(fDown && isFireReady && !isSwap && !isDodge)
        {
            equipWeapon.Use(); //장착하고 있는 무기의 Weapon Use함수 호출
            anim.SetTrigger("doSwing");
            fireDelay = 0; //공격을 했기에 공격딜레이 초기화
        }
    }

    private void Dodge()
    {
        //방향벡터가 0 이 아니므로 이동 하는 상태이기 때문에 회피
        if (jDown && moveVec != Vector3.zero && !isJump && !isDodge && !isSwap) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
        {
            dodgeVec = moveVec;  //회피시 현재 이동벡터를 회피벡터에 대입한뒤 Move()함수에서 대신 사용됨
            speed *= 2;
            anim.SetTrigger("doDodge");
            isDodge = true;

            Invoke("DodgeOut",0.4f);
        }
    }

    private void DodgeOut()//회피가 끝났을때
    {
        speed *= 0.5f;
        isDodge = false;
    }

    private void SwapOut()//교체가 끝났을때
    {
        isSwap = false;
    }

    private void Swap() //무기 교체
    {
        //무기 스왑키를 눌렀을때
        //1.무기를 같은 무기로 교체 안되게 함
        //2.획득하지 않은 무기는 교체를 못하게 하기위해
        //이 두가지 동작을 막기위한 로직 추가 
        if(sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0))
        {
            return;
        }
        if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1))
        {
            return;
        }
        if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2))
        {
            return;
        }

        //무기 인덱스를 초기화 하고 키 누르는것을 확인하고 인덱스 값 변경
        int weaponIndex = -1;
        if (sDown1) weaponIndex = 0;
        if (sDown2) weaponIndex = 1;
        if (sDown3) weaponIndex = 2;

        //액션중에는 무기 변경이 안되게 조건문을 설정
        if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge)
        {
            if (equipWeapon != null) //빈손이 아닌경우에만
                equipWeapon.gameObject.SetActive(false); //빈손이 아닌경우 장착 무기 비활성화

            equipWeaponIndex = weaponIndex;
            equipWeapon = weapons[weaponIndex].GetComponent<Weapon>(); // 장착할 무기 저장
            equipWeapon.gameObject.SetActive(true); //장착 무기 활성화

            anim.SetTrigger("doSwap");

            isSwap = true;
            Invoke("SwapOut", 0.4f);//애니메이션이 안보여서 지연시간을 줌 
        }
    }

    private void Interation() //아이팀과 무기를 상호작용하는 하는 함수 (조작키  : E)
    {   
        //상호작용키가 눌리고(idown) ,무기오브젝트가 비어있지 않고(nearGameObject) , 액션중에 동작이 안되게하는 조건
        if(iDown && nearGameObject != null && !isJump && !isDodge)
        {
            if(nearGameObject.gameObject.CompareTag("Weapon"))
            {
                //nearGameObject 의 Item 컴포넌트를 가져와서 저장
                Item item = nearGameObject.GetComponent<Item>();
                int weaponIndex = item.value; //해당 아이템의 Value값을 호출
                hasWeapons[weaponIndex] = true; //bool 배열에 해당 무기를 장착했다고 알림

                Destroy(nearGameObject); //무기를 획득했기에 해ㅔ당 오브젝트를 파괴 -> Player내에서 파괴한게아니라 WorldSpace에서의 파괴
            }
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        //충돌처리 함수로 점프 후 착지 확인
        if(collision.gameObject.CompareTag("Floor"))
        {
            anim.SetBool("isJump", false);
            isJump = false;
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.CompareTag("Item"))
        {
            //충돌된 오브젝트의 Item 컴포넌트를 호출
            Item item = other.GetComponent<Item>();

            //호출한 Item의 value값을 호출하여 조건문 진행
            switch (item.type)
            {
                case Item.Type.Ammo:
                    ammo += item.value;
                    if(ammo > maxammo) ammo = maxammo;

                    break;

                case Item.Type.Coin:
                    coin += item.value;
                    if (coin > maxcoin) coin = maxcoin;
   
                    break;

                case Item.Type.Grenade:
                
                    hasGrenades += item.value;
                    if (hasGrenades > maxhasGrenades) hasGrenades = maxhasGrenades;

                    //Player가 몇개의 수류탄을 가지고 잇는지 연출 설계
                    //Player 주위로 수류탄이 공전하는 공전물체 생성으로 갯수 파악
                    grenades[hasGrenades - 1].SetActive(true);

                    break;

                case Item.Type.Heart:
                    health += item.value;
                    if (health > maxhealth) health = maxhealth;
          
                    break;
            }
            //사용된 아이템 오브젝트는 파괴
            Destroy(other.gameObject);

        }

   
    }

    private void OnTriggerStay(Collider other)
    {
        //Player가 무기에 근접했다는것을 체크
        if(other.gameObject.CompareTag("Weapon"))
        {
            //근접한 무기 오브젝트를 저장
            nearGameObject = other.gameObject;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        //Player가 무기에서 벗어났다는것을 체크
        if (other.gameObject.CompareTag("Weapon"))
        {
            //벗어낫기에 오브젝트를 null로 초기화 
            nearGameObject = null;
        }
    }
}

 

Item.cs :: 드랍되있는 아이템들은 전부 Item.cs를 따른다. 

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

public class Item : MonoBehaviour
{
    //열거형 변수 enum
    public enum Type
    {
        Ammo, Coin, Grenade, Heart, Weapon
    };

    public Type type; //아이템 종류 변수
    public int value; //아이템 값을 저장할 변수 ex)회복량,총알량,무기의 코드번호 등등..

    void Update()
    {
        //Rotate(오일러 각도) : 오브젝트를 해당 각도(방향벡터)로 회전시키는 함수 
       transform.Rotate(Vector3.up * 10 * Time.deltaTime);
    }
}

 

Orbit.cs :: 물체를 타겟 주위로 공전시키는 스크립트

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

//수류탄이 공전하는 스크립트
public class Orbit : MonoBehaviour
{
    public Transform target;
    public float orbitSpeed;
    Vector3 offset;

    private void Start()
    {
        offset = transform.position - target.position;
    }

    void Update()
    {
        transform.position = target.position + offset;

        //타겟주위를 회전하는 함수 , 단점 : 목표(target)이 움직이면 일그러져서 회전 반경이 이상하게 됨
        //이 단점을 대처하기위해 Offset 변수 설정
        transform.RotateAround(target.position,
                               Vector3.up,
                               orbitSpeed * Time.deltaTime);

        //RotateAround() 후의 위치를 가지고 목표와의 거리를 유지
        offset = transform.position - target.position;
    }
}

 

Weanpon.cs :: 근접무기만 구현되있는 상태이며, 이 Weapon 스크립트는 드랍된 오브젝트가 아닌 Player 오브젝트에 자식으로 있는 무기 오브젝트들만 가지고 있다. 

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

public class Weapon : MonoBehaviour
{
    public enum Type { Melee, Range } //근거리, 원거리
    public Type type;//무기타입
    public int damage;//공격력
    public float rate;//공격속도
    public BoxCollider meleeArea; //근접 공격범위 콜라이더
    public TrailRenderer trailRenderer; //근정 공격시 연출 변수


    //Player에서 호출할것이기에 public으로 접근한정자 설정
    public void Use()
    {
        if(type == Type.Melee)
        {
            //똑같은 코루틴을 동작할때 Stop으로 멈추게하고 동작 해야만한다.(설계상 그렇다는거지 이걸 노리고 할수도 있음)
            //비동기로 돌아가기때문에 Start로 하면 코루틴이 2개 돌아가 로직이 꼬여버릴 수 있다.
            StopCoroutine(Swing());
            StartCoroutine(Swing());
        }
    }

    //열거형 함수 클래스 :: IEnumerator
    IEnumerator Swing()
    {
        //yield 결과를 전달하는 키웓 :: yield
        yield return new WaitForSeconds(0.1f);
        //근접공격 콜라이더와 트레일렌더러를 활성화 시킴
        meleeArea.enabled = true;
        trailRenderer.enabled = true;

        //근접공격을 하고 콜라이더를 바로 꺼버리면 공격이 안될 수도있으니
        //지연시간을 줘서 공격 콜라이더의 수명을 늘리기
        yield return new WaitForSeconds(rate);
        meleeArea.enabled = false;

        //근접공격의 연출을 바로 종료하지않고 지연시간을 줘서 기다리게 하고 종료
        yield return new WaitForSeconds(0.1f);
        trailRenderer.enabled = false;
    }
}

유니티 기능 정리

1.드랍된 오브젝트(Player 몸에 장착되는 오브젝트)의 콜라이더 관리

Player 가 획득시 손에 장착되는 망치(무기) 오브젝트

 

1-1.빨간상자 의 Collider는 땅위에 서있어야 되기때문에 추가

RigidBody를 적용시켜 중력을 받게 해야됨

 

1-2.파란상자의 Collider는 Player가 충돌시 Player의 스크립트 안에 미리 무기를 저장시키는 콜라이더이다.

무기를 완전하게 획득시 해당 오브젝트의 Item 컴포넌트를 호출한다.

public class Player : MonoBehaviour
{
    [SerializeField] GameObject[] weapons; // 플레이어가 무기를 들고있을때 활성화 해주는 오브젝트 배열
    [SerializeField] bool[] hasWeapons; // 플레이어가 해당 무기를 끼고있는것츨 체크하는 배열

   GameObject nearGameObject; // 무기에 근접했을때, 무기 오브젝트를 저장할 변수 
 	 
    private void Interation() //무기(item)와 상호작용하는 하는 함수 (조작키  : E)
    {   
        //상호작용키가 눌리고(idown) ,무기오브젝트가 비어있지 않고(nearGameObject) , 액션중에 동작이 안되게하는 조건
        if(iDown && nearGameObject != null && !isJump && !isDodge)
        {
            if(nearGameObject.gameObject.CompareTag("Weapon"))
            {
                //nearGameObject 의 Item 컴포넌트를 가져와서 저장
                Item item = nearGameObject.GetComponent<Item>();
                int weaponIndex = item.value; //해당 아이템의 Value값을 호출
                hasWeapons[weaponIndex] = true; //bool 배열에 해당 무기를 장착했다고 알림

                Destroy(nearGameObject); //무기를 획득했기에 해당 오브젝트를 파괴
                //Player내에서 파괴한게아니라 WorldSpace에서의 파괴
            }
        }
    }
     
     
   private void OnTriggerStay(Collider other)
    {
        //Player가 무기에 근접했다는것을 체크
        if(other.gameObject.CompareTag("Weapon"))
        {
            //근접한 무기 오브젝트를 저장
            nearGameObject = other.gameObject;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        //Player가 무기에서 벗어났다는것을 체크
        if (other.gameObject.CompareTag("Weapon"))
        {
            //벗어낫기에 오브젝트를 null로 초기화 
            nearGameObject = null;
        }
    }
}

Player.cs에서 해당되는 부분의 스크립트만 가져왔다.

 

OnTriggerStay/Exit 함수로 근처에 있는 무기(Item)오브젝트를 저장 및 초기화를 하고있다.

그리고 무기 오브젝트에 다가가서 <E>키를 누르면 Interation()함수가 호출되어 해당 오브젝트(무기)를 Player 오브젝트가 가지고 있다고 판단을 한다.

 

2.무기 오브젝트를 획득할때 스크립트로 무기를 넣어 오브젝트를 생성해줘야함?

물론, 그렇게 해도 된다. 하지만, 이번 프로젝트에서는 다른 방법으로 처리를 하였다.

 

우선 무기오브젝트를 장착할 때, 어떡해 하는지를 메모하겠다.

Player의 오른팔에 무기를 장착하기 위해 RightHand(자식) 으로 빈 오브젝트를 넣어 관리 할거다.

보통 3D게임에서 무기를 관리할떄 이 방법을 많이 사용하는것 같다. 

우선 오른손의 무기 잡는 위치를 조정해줘야한다. 

자식으로 실린더 오브젝트를 만들고 위치를 손 안으로 집어넣어서 위치 조정

손 안으로 실린더를 집어넣어 위치를 잡아주고 실린더는 빈오브젝트 처럼 보여야 되기 땜누에 Renderer 및 Collider를 비활성화 제거를 해준다.

실린더 오브젝트 -> Weapon Point 로 변경하여 무기가 위치 되는 되는곳으로 잡아준다.

해당 오브젝트 자식으로 무기 오브젝트들을 전부 자식으로 넣은뒤 비 활성화 해주고, 이 오브젝트들을 최상위 부모 클래스인 Player의 Inspector를 통해 Weapons 배열에 삽입해준다.

무기를 획득하여 Inspector에 체크가 되어있다.

1번과 2번을 통해 만들면 위 그림 처럼 완성된다. 

 

 

3.공전하는 물체 오브젝트 만들기 및 유니티 에디터에 표시하기

해당 프로젝트에서 수류탄이라는 아이템을 획득시 UI 표현이 아닌 Player 캐릭터 주위로 돌리는것으로 판단하기로 했다.

수류탄 회전 위치를 표시하기 위해 아이콘을 설정하면 이렇게 표시된다.

//수류탄이 공전하는 스크립트
public class Orbit : MonoBehaviour
{
    public Transform target;
    public float orbitSpeed;
    Vector3 offset;

    private void Start()
    {
        offset = transform.position - target.position;
    }

    void Update()
    {
        transform.position = target.position + offset;

        //타겟주위를 회전하는 함수 , 단점 : 목표(target)이 움직이면 일그러져서 회전 반경이 이상하게 됨
        //이 단점을 대처하기위해 Offset 변수 설정
        transform.RotateAround(target.position,
                               Vector3.up,
                               orbitSpeed * Time.deltaTime);

        //RotateAround() 후의 위치를 가지고 목표와의 거리를 유지
        offset = transform.position - target.position;
    }
}

 

Orbit.cs는 RotateAround() 함수를 통해서 Player 오브젝트 주위로 y 축을 회전 시키는 스크립트다.

하지만 RotateAround()를 사용하게되면 target 오브젝트가 이동시 회전 반경이 일그러지기 때문에 따로 보정이 필요하다.

 

Orbit.cs를 가지고 있는 transform.position 과 target.position의 차이를 구하고 회전 반경을 구할때 다시 offset를 더해서 계속 보정하는 로직을 가지고있다. 

 

또한 Player에서 수류탄을 획득한 갯수만큼 보여주게 하는 로직이 필요한데

public class Player : MonoBehaviour
{
	 public int hasGrenades;
 	[SerializeField] GameObject[] grenades; //수류탄들을 저장할 배열 변수, 공전 물체를 컨트롤하기 위해 배열로 생성
 
	 private void OnTriggerEnter(Collider other)
 	{
         if(other.gameObject.CompareTag("Item"))
         {
             //충돌된 오브젝트의 Item 컴포넌트를 호출
             Item item = other.GetComponent<Item>();

             //호출한 Item의 value값을 호출하여 조건문 진행
             switch (item.type)
             {
                 case Item.Type.Grenade:

                     hasGrenades += item.value;
                     if (hasGrenades > maxhasGrenades) hasGrenades = maxhasGrenades;

                     //Player가 몇개의 수류탄을 가지고 잇는지 연출 설계
                     //Player 주위로 수류탄이 공전하는 공전물체 생성으로 갯수 파악
                     grenades[hasGrenades - 1].SetActive(true);

                     break;
             }
             //사용된 아이템 오브젝트는 파괴
             Destroy(other.gameObject);

         }
 	 }

}

OnTriggerEnter() 아이템을 획득 할 때마다 해당 오브젝트의 수류탄 Mesh를 활성화 시켜주는 로직이다. 

 

4.TrailRenderer 컴포넌트의 기능

TrailRenderer는 오브젝트의 잔상을 그려주는 기능이다.

player의 자식 오브젝트(근접무기)의 자식 오브젝트에 Effect로 추가하여 관리한다.

 

TrailRenderer 기능 설명

 

5.Particle System 기능 설명

여기서 설명하는 Particle System 기능은 이동시에 파티클을 생성하는 기능을 설정하는 방법이다.

 

(좌)Simulation Space - Local 일때, 파티클이 부모오브젝트를 따라온다. (우)Simulation Space - World 일때, 파티클이 World Space 기준으로 따라온다.

 

6.Floor의 Material 재질 타일링 하는법

해당 오브젝트에 적용되는 Material 설정을 건들여야 한다.

Floor의 텍스쳐가 타일링 되어있다.

 


2024-09-05(목)

https://www.youtube.com/watch?v=u2DLOay5oO8&t=738s

: 아이템 로직 및 프리팹 저장  (12:20초까지 수강)

https://www.youtube.com/watch?v=APS9OY_p6wo

: 드랍 무기 획득 및 무기교체(FPS 무기의 1,2,3)

https://www.youtube.com/watch?v=esGkgvm9eSg

: 아이템 먹기 & 공전물체 만들기(획득한 수류탄 갯수 파악 겸 제작)

https://www.youtube.com/watch?v=Zfoyagdz1y0   

: 코루틴으로 근접공격 구현 및 공격 범위 , 잔상연출 추가 

 

 

유니티로 3D 다뤄본적이 별로 없기에 유튜브 강의 하나를 그대로 따라하려 한다.

 

https://www.youtube.com/watch?v=WkMM7Uu2AoA&list=PLO-mt5Iu5TeYkrBzWKuTCl6IUm_bA6BKy

유니티 강의를 많이 올려주시는 <골드메탈> 유튜브분의 영상인데 4년전 영상이지만

따라 만들어보면 실력이 많이 늘것 같아 시도해보려한다.


추가 및 수정한 스크립트

Player.cs

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

public class Player : MonoBehaviour
{
    [SerializeField] float speed; //캐릭터의 속도
    [SerializeField] float jumpPower; //캐릭터의 점프파워
    float hAxis; //수평선 축 value값
    float vAxis; //수직선 축 value값
    bool wDown = false; //Input Manager에서 추가한 Walk(Left Shift)의 키 유무 체크
    bool jDown = false; //점프 키 유무 체크

    bool isJump = false; //현재 점프 중인지 체크 유무
    bool isDodge = false; //현재 회피 중인지 체크 유무

    Vector3 moveVec; // hAxis와 vAxis 값을 받아와 3차원 벡터값(방향벡터) 변수
    // 회피시 캐릭터 방향전환을 막기위해 생성
    Vector3 dodgeVec; //회피할때 적용되는 방향벡터
    Animator anim;
    Rigidbody rigid;

    private void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();
    }

    private void Update()
    {
        GetInput();
        Move();
        Turn();
        Jump();
        Dodge();
    }

    private void GetInput()
    {
        //해당 프로젝트 InputManager를 이용하여 만듬 

        //GetAxisisRaw() : Axis 값을 정수로 반환하는 함수
        //GetButton() : 꾹 누르고 있을때만 호출
        hAxis = Input.GetAxisRaw("Horizontal");
        vAxis = Input.GetAxisRaw("Vertical");
        wDown = Input.GetButton("Walk");
        jDown = Input.GetButtonDown("Jump");
    }

    private void Move()
    {
        //정규화를 하는 이유? : 대각선 이동시 빨라지기 때문에 크기(속도)를 1로 만들어서 속도를 똑같이 만들어줘야한다.
        //쉽게 말하면 방향값이 1로 보정되는 벡터라고 생각하면 된다.
        //왜 빨라짐? : hAxis = 1 , vAxis = 1 인경우 대각성 이동시 피타고라스의 법칙으로 root(1^2+1^2) = root(2) = 1.141... 때문이다.
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;

        //회피 동작 중인경우 회피 방향벡터가 대입되어 방향전환을 못하게 막기 위한 코드
        if(isDodge) moveVec = dodgeVec;

        //transform 이동은 가끔 물리충돌을 무시하는 경우가 발생 함
        //Rigidbody 컴포넌트에서 CollisonDetection을 Continuos로 변경 
        //Cpu를 더 먹어 최적화는 떨어지지만 정확도는 올라가기 때문 

        if (!wDown) transform.position += moveVec * speed * Time.deltaTime;
        else transform.position += moveVec * speed * 0.3f * Time.deltaTime;

        //SetXXXX("애니메이션 이름" , bool 변수) 를 넣어 조건문처럼 사용 할 수 있다.
        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    private void Turn()
    {
        //지정된 벡터를 향해서 회전시켜주는 함수 - 3D기능
        //현재 플레이어 위치에 방향벡터를 더해서 우리가 나아가는 쪽으로 오브젝트를 회전한다는 뜻
        transform.LookAt(transform.position + moveVec);
    }

    //점프키와 회피키가 같지만 이동키가 눌리고 있을때는 회피, 이동 안할때는 점프를 하게된다.
    private void Jump()
    {
        //방향벡터가 0 이기에 이동 하지 않으므로 점프
        if(jDown && moveVec == Vector3.zero && !isJump && !isDodge) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
        {
            //Addforce : rigidbody에 포함된 메서드이며 해당 오브젝트에 힘을 가해주는 함수
            rigid.AddForce(Vector3.up * jumpPower,ForceMode.Impulse); // Impulse : 힘을 주는 종류 중 하나로, 즉발로 힘을 준다는 의미
            anim.SetBool("isJump",true);
            anim.SetTrigger("doJump");
            isJump = true;
        }
    }

    private void Dodge()
    {
        //방향벡터가 0 이 아니므로 이동 하는 상태이기 때문에 회피
        if (jDown && moveVec != Vector3.zero && !isJump && !isDodge) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
        {
            dodgeVec = moveVec;  //회피시 현재 이동벡터를 회피벡터에 대입한뒤 Move()함수에서 대신 사용됨
            speed *= 2;
            anim.SetTrigger("doDodge");
            isDodge = true;

            Invoke("DodgeOut",0.4f);
        }
    }

    private void DodgeOut()//회피가 끝났을때
    {
        speed *= 0.5f;
        isDodge = false;
    }

    private void OnCollisionEnter(Collision collision)
    {
        //충돌처리 함수로 점프 후 착지 확인
        if(collision.gameObject.CompareTag("Floor"))
        {
            anim.SetBool("isJump", false);
            isJump = false;
        }
    }
}

 

Follow.cs :: 카메라가 등록한 Target(오브젝트)를 따라다니는 기능

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

//카메라가 플레이어를 따라가는 기능
public class Follow : MonoBehaviour
{
    public Transform target; // 따라갈 오브젝트
    public Vector3 offset; // 위치 오프셋(카메라를 배치할 위치 )

    // Update is called once per frame
    void Update()
    {
        transform.position = target.position + offset;
    }
}

 

Item.cs 

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

public class Item : MonoBehaviour
{
    //열거형 변수 enum
    public enum Type
    {
        Ammo, Coin, Grenade, Heart, Weapon
    };

    public Type type; //아이템 종류 변수
    public int value; //아이템 값을 저장할 변수
}

1.정규화(Nomarlize)를 하는이유

 

벡터크기와 방향값을 둘다 가지는 단위이다.

간단히 말하면 정규화라는 것은 해당 벡터의 크기를 1로 변환하여 순수한 방향값을 얻을때 주로 사용된다.

 //정규화를 하는 이유? : 대각선 이동시 빨라지기 때문에 크기(속도)를 1로 만들어서 속도를 똑같이 만들어줘야한다.
 //쉽게 말하면 방향값이 1로 보정되는 벡터라고 생각하면 된다.
 //왜 빨라짐? : hAxis = 1 , vAxis = 1 인경우 대각성 이동시 피타고라스의 법칙으로 root(1^2+1^2) = root(2) = 1.141... 때문이다.
 moveVec = new Vector3(hAxis, 0, vAxis).normalized;

 

해당 코드에서 대각선으로 이동시 캐릭터가 더 빠르게 움직이게 된다.

피타고라스 법칙으로 hValue = 1, vValue =1 인경우 대각선 이동시 1.141.. 로 빠르게 이동된다.

그렇기 때문에 해당 벡터의 크기를 1로 정규화를 하면 순수한 방향벡터값을 구할 수 있다.

 

1시방향으로 대각선 이동 - 방향벡터
1시방향으로 대각선 이동 - 정규화 된 방향벡터

 

2. 하나의 키로 두가지 동작하기 (점프와 회피)  + 회피시 방향전환 못하게 막기 

 bool isDodge = false; //현재 회피 중인지 체크 유무

 Vector3 moveVec; // hAxis와 vAxis 값을 받아와 3차원 벡터값(방향벡터) 변수
 // 회피시 캐릭터 방향전환을 막기위해 생성
 Vector3 dodgeVec; //회피할때 적용되는 방향벡터

 private void GetInput()
 {
     //해당 프로젝트 InputManager를 이용하여 만듬 

     //GetAxisisRaw() : Axis 값을 정수로 반환하는 함수
     //GetButton() : 꾹 누르고 있을때만 호출
     hAxis = Input.GetAxisRaw("Horizontal");
     vAxis = Input.GetAxisRaw("Vertical");
     wDown = Input.GetButton("Walk");
     jDown = Input.GetButtonDown("Jump");
 }

private void Move()
{
	////////
     moveVec = new Vector3(hAxis, 0, vAxis).normalized;
 	Debug.Log("정규화된 벡터 : " + moveVec);
    
    //회피 동작 중인경우 회피 방향벡터가 대입되어 방향전환을 못하게 막기 위한 코드
    if (isDodge) moveVec = dodgeVec;

    ///////
}

//점프키와 회피키가 같지만 이동키가 눌리고 있을때는 회피, 이동 안할때는 점프를 하게된다.
private void Jump()
{
    //방향벡터가 0 이기에 이동 하지 않으므로 점프
    if(jDown && moveVec == Vector3.zero && !isJump && !isDodge) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
    {
        //Addforce : rigidbody에 포함된 메서드이며 해당 오브젝트에 힘을 가해주는 함수
        rigid.AddForce(Vector3.up * jumpPower,ForceMode.Impulse); // Impulse : 힘을 주는 종류 중 하나로, 즉발로 힘을 준다는 의미
        anim.SetBool("isJump",true);
        anim.SetTrigger("doJump");
        isJump = true;
    }
}

private void Dodge()
{
    //방향벡터가 0 이 아니므로 이동 하는 상태이기 때문에 회피
    if (jDown && moveVec != Vector3.zero && !isJump && !isDodge) //액션 도중에 다른 액션이 실행되지 않도록 조건 추가
    {
        dodgeVec = moveVec;  //회피시 현재 이동벡터를 회피벡터에 대입한뒤 Move()함수에서 대신 사용됨
        speed *= 2;
        anim.SetTrigger("doDodge");
        isDodge = true;

        Invoke("DodgeOut",0.4f);
    }
}

Player.cs에 있는 코드를 가져왔다.

 

*하나의 키로 두가지 동작하기 (점프와 회피) 

해당 프로젝트는 Player의 점프 키와 회피키를 같은것을 사용하고 있다.

if로 조건문을 걸어 각 액션동작 중에 다른 동작을 하지 못하게 막아놨다.

 

moveVec가 0이라는 것은 GetInput() 함수에서 키 입력이 없기에 0을 리턴한것이고

즉, 이동하지 않았다는 것이기 때문에 moveVec == Vector3.zero 로 조건을 걸어 각 액션시 동작을 다르게 하였다.

 

* 회피시 방향전환 못하게 막기 

moveVec가 아닌 회피할때만 사용하는 방향벡터를 만들면 해결되는 문제이기에

dodgeVec를 생성하고 회피행동시 dodgeVec = moveVec를 선언하고

현재 회피중 일때 (isdodge 가 true) moveVec 를 계속 dodgeVec로 초기화해주는 것이다. 

 

3.이번 프로젝트에서 사용된 모르는 함수들 정리

  GetAxisisRaw() : Axis 값을 정수로 반환하는 함수
  GetButton() : 꾹 누르고 있을때만 호출
  
  ex)
      hAxis = Input.GetAxisRaw("Horizontal");
      vAxis = Input.GetAxisRaw("Vertical");
      wDown = Input.GetButton("Walk");
      jDown = Input.GetButtonDown("Jump");
 
 /*=========================================================*/
      
SetXXXX("애니메이션 이름" , bool 변수) 를 넣어 조건문처럼 사용 할 수 있다.
ex)
 	anim.SetBool("isRun", moveVec != Vector3.zero);
 	anim.SetBool("isWalk", wDown);

 /*=========================================================*/

transform.LookAt(Vector3 vec) : 지정된 벡터를 향해서 회전시켜주는 함수 - 3D기능
ex)
	//현재 플레이어 위치에 방향벡터를 더해서 우리가 나아가는 쪽으로 오브젝트를 회전한다는 뜻
	transform.LookAt(transform.position + moveVec);
    
/*=========================================================*/

Addforce : rigidbody에 포함된 메서드이며 해당 오브젝트에 힘을 가해주는 함수
ex)
	// Impulse : 힘을 주는 종류 중 하나로, 즉발로 힘을 준다는 의미
	rigid.AddForce(Vector3.up * jumpPower,ForceMode.Impulse);

Unity 기능 살펴보기

1.InputManager 제어하기

현재는 InputSystem을 사용하여 프로젝트를 개발한다는데 기회가되면 InputSystem을 사용해봐야겠다.

InputSystem을 사용하면 크로스플랫폼 개발에 큰 도움을 준다.

 

2.테스트 중 캐릭터의 점프가 우주에서 하는것처럼 느껴질때 대처

오브젝트의 RigidBody 를 건드는것이 아닌 ProjectSetting의 중력 값을 조절하면된다.

y값이 작아질수록 중력이 쎄진다.

 

3.캐릭터가 벽과 충돌할때 벽에 끼이면서 느리게 떨어지는거 대처

벽을 뚫는거는 잠시 무시하자 , 집중해야되는 건 벽에서 내려올때 끼이는것이 문제

해당 문제를 해결하기 위해 3가지 방법을 적용해야한다..

3-1. Rigidbody의 Collision Detection > Continuos는 Static과 충돌할 때 효과적

3-1-1. transform 이동은 가끔 물리충돌을 무시하는 경우가 발생 함 ,

Rigidbody 컴포넌트에서 CollisonDetection을 Continuos로 변경하게 되면 계속 충돌처리를 탐색하게 된다.

이렇게 되면 Cpu 처리비용을 더 먹어 최적화는 떨어지지만 정확도는 올라가기 때문이다.


3-2.서로 두 오브젝트가 충돌할때 유니티가 효과적으로 계산하기 위해서는 두 오브젝트다 Rigidbody를 가지고 있어야함

3-2-1.고정 오브젝트인 경우 is Kinematic을 체크하고 Use Gravity를 체크해제 해주면됨 


3.점프하면서 벽에 비벼지며 느리게 떨어지는 것은 Physic Material을 이용하여 
마찰력이 없는 벽을 만들어 Collider에 적용

Physic Material은 물리가 적용되는 재질로 Collider에 적용된다.

 

이럼 이제 벽에 점프해서 충돌할때 매끄럽게 내려오는 모습을 볼 수 있다.

 

 

4.Animater의 Parameter 결정 조건

현재 프로젝트에 적용되고있는 Player - MeshObject의 Animator

Roop Animation들은 보통 bool 파라미터를 사용한다.

일회성 애니메이션(점프,회피)은 Trigger 파라미터를 사용한다.

Trigger 파라미터는 doXXX로 작성 하였고 스크립트에서 On을 하면 애니메이션이 재생되고

애니메이션이 끝난 후 Off하게 되는 파라미터이다.

 

5.3D에 오브젝트(부모,자식)배치하기 - 하이어라키(계층 구조의 이해)

오브젝트의 위치는 그대로고 자식오브젝트인 MeshObject만 조절하여 땅에서 올리고 회전시킨 모습

부모오브젝트는 (0,0,0) 위치에 배치하고 MeshObject의 위치(0,1.7,0)만 조절하여 땅위로 올린 모습이다.

플레이어 캐릭터의 하이어라키 구조

모든 프로젝트가 이런건 아니지만 3D에서는 Sprite가 아닌 Mesh들을 가지고 3D 오브젝트를 관리하기에

내 나름대로 정리하는게 좋을것 같아서 정리 하였다.

 

1.Player의 최상위 부모에 Rigid,Collider를 넣어서 물리현상 구현 및 Player 스크립트 동작을 하게 하였다.

2.Mesh Object는 캐릭터의 모습을 보여주는 기능이기에 따로 자식으로 뺸뒤 Animator 컴포넌트를 추가하여 캐릭터의 모습에 애니메이션을 추가하는 기능까지 부여하였고

3.기능적인건은 따로 자식오브젝트로 뺼 수도 잇고(무기를 든다거나,스킬을 가지고 있다거나 등등)

 

즉, 프로젝트 설계에 따라 부모 -자식 및 기능 적으로 분리를 잘 해야 프로젝트가 수월해진다.

 

 

6. Object에 Light 추가하기 + Particle

아이템에 Light , Particle 빈 오브젝트를 만든뒤 각각 Light , Particle System컴포넌트를 추가하였다.

Transforn . y 값 조절 : 위치를 조절하여 빛의 위치 조정

Light - Intensity : 빛의 세기 조절

Light - Range : 빛의 범위 조절

마지막 움짤을 보면 아이템 밑에 부위에도 빛이 비치는 장면이 있다.

 

*Particle System 간단 설명

Particle System의 Particle 카테고리
위에 설명한 기능들을 적용하는 모습

 

Particle System - Renderer 설정

Material을 변경하여 파티클 이미지를 변경 할 수 있다.

Particle System - Emission 기능

Emission(방출)은 파티클의 갯수에 영향을 준다. Time의 숫자가 커질수록 파티클의 갯수가 증가한다.

 

 

ParticleSystem-Shape 기능

Shape 기능은 파티클이 어떤 방식으로 뻗어 나갈건지 결정하는 기능이다. Corn 모양일때 원뿔 모양에서 파티클이 뿜어져 나왔고 Donut일때는 사방으로 퍼져 나오는 모습을 볼 수있으며 퍼져나오는 근원의 위치,각도,크기를 조절 할 수 있다.

 

ParticleSystem-Color over LifeTime 기능

Color over LifeTime 은 파티클의 색상을 결정하는 기능이다. 해당 이미지를 보면 이해가 될것이다. 

색상 변경을 하여 위로 올라갈수록 색이 변경되는 모습을 볼 수있다.
ParticleSystem-Size over LifeTime 기능

Size over LifeTime 은 파티클이 살아있는동안 크기를 변화하는 기능이다.

Unity에서 제공하는 프리셋이 있어 그걸 사용해도 된다. 위의 이미지 처럼 위로 올라갈수록 파티클의 크기가 커지는 모습을 볼 수있다.

 

아이템 연출 완성

 


2024-09-04(수)

https://www.youtube.com/watch?v=WkMM7Uu2AoA&list=PLO-mt5Iu5TeYkrBzWKuTCl6IUm_bA6BKy 

: 에셋 다운 + 플레이어 이동 및 카메라 설정

https://www.youtube.com/watch?v=eZ8Dm809j4c 

: 점프 / 회피 로직 구현

https://www.youtube.com/watch?v=u2DLOay5oO8  

: 아이템 생성 및 파티클 / 라이트 컴포넌트 조작 (12:20초까지 수강)

 

 

 

 

 

 

+ Recent posts