객체가 경사로를 이동할때 위로 뿅하고 튀어오르는 현상이 있을거다. 분명 나는 인풋으로 x,z축 데이터만 넣었는데 왜 경사로에서 튀어오르는지 의문일 것이다.

 

우리가 진행하는 방향이 moveDir , 경사로에 올라갈때 transform.up 값이 추가된다.

 

y값을 보정으로 하고있어도 경사로의 콜라이더와 플레이어의 콜라이더가 충돌이 일어나 생기는 힘이기 때문에 위로 튀어오를수 밖에 없을것이다.

 

이럴떄는 경사도의 법선벡터를 이용하여 투영벡터를 계산해서 진행방향을 알아내야한다.

 

우선 밝고있는 지형이 경사도인지 체크해야된다.

경사도를 오르는 객체의 아랫방향(Vector3.down)으로 ray를 쏴서 해당 경사면의 각도를 알아야한다.

 

p.s 경사도의 각도를 알아야하는 이유

더보기

경사면의 각도를 알아야 하는 이유는 이동오브젝트가 여기 경사면을 넘을수 있는지 없는지를 체크하기 위해서다.

즉, 완만한 경사면 오를수 있을것이고 급한경사는 못오르는 maxSlopeAngle 변수를 설정해서 컨트롤 해 줄수 있게 해야된다.

그러기위해서 경사면의 노말벡터(법선벡터,Slopehit.Normal)  윗 방향의 벡터(Vector3.up) 사이의 각도를 구한뒤에 넘어갈수 있는 각도인지 체크한다. 

 

넘어 갈수 있게되면 해당 경사면의 진행 방향을 알아야된다. 

ProjectOnPlane() : 평면에서의 투영벡터를 구하는 메서드

ProjectOnPlane() 메서드를 사용하여 내가 이동하는 방향벡터 movediretion경사도의 법선벡터 slopeHit.normal투영 벡터를 구해야만 해당 경사도에서의 이동 방향을 구 할수 있게된다. 

출처 : https://code-piggy.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-Vector3ProjectOnPlane?pidx=0

그림으로 간단히 표현한 블로그가 있어 사진을 퍼왔다.

 

위의 설명한 이론을 아래 코드로 구현해보았다.

   
   public class PlayerController : MonoBehaviour
{
 private Rigidbody rigid;
  public PlayerStatus status;
  
  
    [Header("Slope Handling")] //경사도에 올라갈시 위로 솟아오르는 현상 제어
    public float maxSlopeAngle; //해당 경사도 보다 작은경우 캐릭터가 올라갈수 있게 설정
    private RaycastHit slopeHit; //캐릭터 밑 경사도의 법선벡터 호출용으로 사용되는 변수

  
   [Header("Movement")]
    private Vector2 curMoveInput;
    private Vector3 moveDirection;
    public Vector3 ExtraDir; //외부에서 힘을 가할떄 x,z축 이동방향값을 보정해주는 변수
    
   [Header("IsGrounded")]
    public float RayDistance;
    public Transform GroundPivot;
    public LayerMask GroundMask;

  //y축 보정을 스크립트로 중력 추가 보정 함수
  private void ApplyCustomGravity()
  {
      // 중력을 수동으로 추가하여 y축 낙하를 더 강하게 설정
      Vector3 gravity = Physics.gravity * 3.0f;
      rigid.AddForce(gravity, ForceMode.Acceleration);
  }


//캐릭터의 이동을 담당하는 함수
 public void Move()
 {
 	//z축(forward)와 x축(right)방향 입력값을 받음
     moveDirection = curMoveInput.y * transform.forward + curMoveInput.x * transform.right;
     //점프를 하지 않는 이상 y축 값은 변동이 없기에 y축 보정
     float velocity_Y = rigid.velocity.y;
	
    //경사도 기울기 체크
     if (OnSlope())
     {
     	//경사로에 있기 때문에 경사로의 객체 속도 보정
         moveDirection = GetSlopeMoveDirection();
         //경사로에 진행방향에 맞춰 y축도 속도를 보정해준다.
         velocity_Y = moveDirection.y * status.CurSpeed;
     }

     Vector3 moveVelocity = moveDirection * status.CurSpeed;
     rigid.velocity = new Vector3(moveVelocity.x, velocity_Y, moveVelocity.z) ;
 }

//플레이어 객체가 경사도에 있는지 체크 및 이동 가능한지 확인하는 함수
 private bool OnSlope() 
 {
     Ray ray = new Ray(GroundPivot.position, Vector3.down);
     
     //0.3f는 경사도에 있기에 추가로 더 길게 쏜것
     if (Physics.Raycast(ray, out slopeHit, 0.3f, GroundMask))
     {
     	
         // Vector3.Angle : 두개의 벡터 사이의 각도값을 구하는 함수
         /*해당 로직은 월드 y축(uP) 방향과 경사로의 법선벡터(slopehit) 사이의 각도를 받아온다.*/
         float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
         return angle <= maxSlopeAngle && angle != 0;
     }

     return false;
 }

//경사로에 있기 때문에 경사로의 객체 속도를 보정해주는 함수
 private Vector3 GetSlopeMoveDirection()
 {
 	//경사로에 있는 객체의 진행 방향을 알기위해서는 투영벡터를 사용해야된다.
    //투영벡터를 사용하기 위해서 ProjectOnPlane() 메서드를 사용한다. 
     Vector3 reflectV = Vector3.ProjectOnPlane(moveDirection, slopeHit.normal).normalized;
     
     if (reflectV != Vector3.zero)
     {
         Debug.Log(reflectV);
     }
     return reflectV;
 }
}

 


해당 글을 작성하며 도움 받은 자료

https://www.youtube.com/watch?v=xCxSjgYTw9c&t=378s

https://code-piggy.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-Vector3ProjectOnPlane

 

Unity- Vector3.ProjectOnPlane()

Vector3.ProjectOnPlane() public static Vector3 ProjectOnPlane(Vector3 vector, Vector3 planeNormal); 파라미터 vector- 정사영하고자 하는 벡터 planeNormal - plane의 법선 벡터 반환 vector3 - vector를 평면의 법선 벡터 planeNormal에

code-piggy.tistory.com

 

개발중에 <포탈> 모작의 퍼즐류 게임에서 각종 스위치,오브젝트를 기믹대로 완수하여 길을 열거나 보상을 받는 것을 구현하고싶었다. 2차원 배열을 사용하여 하나의 행에 해당 기믹이 완료되면 그 행에 있는 모든 보상이 받아지게 하고 싶었다.

 

구현 자체는 쉽지만 Inspector에 개발자가 쉽게 접근하여 수정을 했으면 원했다.

 

Inspector로 2차원 배열을 표현하는 방법은 이랬다.

 

//행 안에 들어갈 Data(Class 및 Struct)를 만든다.
//이때 [System.Serializable] 는 필수
[System.Serializable]
public class OnRewardObject
{
    public bool isReward;
    public GameObject[] RewardObject;
}

 public class Stage : MonoBehaviour, IStageManager
{
    public ObjectZone[] objectZone; //기믹리스트
    public OnRewardObject[] RewardObjects; //기믹에 성공한경우 해당 오브젝트가 움직임
    
...    
}

코드를 이렇게 작성시에

 

인스펙터에 이렇게 표현된다.

이동발판을 이용하는 로직을 짤때 해당 발판의 자식으로 들어가 캐릭터를 같이 이동 시켰다.

 

 MovePlatform .cs

 private void OnCollisionEnter(Collision collision)
 {
     if (TargetLayerMask == (TargetLayerMask | 1 << collision.gameObject.layer))
     {
         collision.transform.SetParent(transform);
     }
 }

 private void OnCollisionExit(Collision collision)
 {
     if (TargetLayerMask == (TargetLayerMask | 1 << collision.gameObject.layer))
     {
         collision.transform.SetParent(null);
     }
 }

 

이 코드는 충돌한 오브젝트를 검사해서 이동발판 오브젝트에 자식으로 설정해서 캐릭터와 이동발판이 같이 이동하는 로직으로 만들었다. 

 

하지만 [Potal]게임처럼 물체를 잡고 움직이는 현상에 이와 같은 동작을 취했을때는 잡힌 물체의 Rigidbody로 인해 물리연산으로 인해 잡힌 물체가 원하는 위치에 있지 않았기에 키네마틱을 켜고 설정하였다.

 

하지만 키네마틱은 설정시에는 velocity나 force를 사용할순 있지만 외부의 물리충돌연산을 하지 않기에 다른 오브젝트들과의 충돌이 없어진게 문제였다.

 

물리충돌 연산은 필요하지만 , 잡힌 물체의 위치값이 계속 고정되어있기를 원했다.

 

튜터님께 질문해서 받은 답변은

<잡힌 물체의 좌표를 계속 갱신해줘야한다.> 였다.

방법 1. 잡힌 물체가 플레이어를 계속 따라다니게 하는 방법
방법 2. 잡힌 물체의 위치를 고정 좌표를 만든뒤 Player가 이동시 좌표를 갱신 하는 방법

두가지 방법이 있었다.

 

나는 방법1을 이용하여 그냥 고정좌표를 만들어주었고, 해당 좌표를 계속 갱신해주었다.

 ObjectGrip.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.HID;

public class ObjectGrip : MonoBehaviour
{
    [Header("Object Grip")]
    public Transform GripPivotTr; //잡고있는 오브젝트의 위치
    private bool isGrip; //오브젝트를 잡고있는상태
    private GameObject target; //잡고있는 오브젝트의 정보
    public LayerMask GripObjectLayerMask;
    public float GripDistance = 3.0f;

    //잡힌 물체의 좌표를 계속 갱신해줘야한다.
    //이유 : 잡힌 물체의 물리 충돌연산을 해야되려면 잡힌 위치를 계속 갱신하고 키네마닉을 꺼야만 사용이 물리연산이 가능하기 때문
    //방법 1. 잡힌 물체가 플레이어를 계속 따라다니게 하는 방법
    //방법 2. 잡힌 물체의 위치를 고정 좌표를 만든뒤 Player가 이동시 좌표를 갱신 하는 방법

    private void Update()
    {
        //방법 2. 잡힌 물체의 위치를 고정 좌표를 만든뒤 Player가 이동시 따라가게 하는 방법
        if (isGrip)
        {
            target.transform.position = GripPivotTr.position + new Vector3 (0,1,0);
        }
        
    }

    public void OnGrip(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Started)
        {
            if (!isGrip && target == null)
            {
                Ray ray = Camera.main.ScreenPointToRay(new Vector2(Screen.width / 2, Screen.height / 2));
                RaycastHit hit;

                if (Physics.Raycast(ray, out hit, GripDistance, GripObjectLayerMask))
                {
                    target = hit.transform.gameObject;
                    if(target.transform.TryGetComponent(out Rigidbody targetRigid))
                    {
                        targetRigid.freezeRotation = true;
                    }
                    
                    isGrip = true;
                }
            }
            else if (isGrip && target != null)
            {
                isGrip = false;
                if (target.transform.TryGetComponent<Rigidbody>(out Rigidbody targetRigid))
                {
                    targetRigid.freezeRotation = false;
                }
                target = null;

            }

        }

    }
}

 

 

 

 

오늘 객체의 이동 및 점프를 RigidBody.velocityRigidBody.AddForce 메서드를 사용하여 구현 하였다.

   void FixedUpdate()
    {
        Move();
    }
   
   public void Move()
    {
        moveDirection = curMoveInput.y * transform.forward + curMoveInput.x * transform.right;
        moveDirection *= status.CurSpeed;
        moveDirection.y = rigid.velocity.y;

        rigid.velocity = moveDirection * Time.FixeddeltaTime;
    }

 

이런식으로 구현을 하였다 하지만, 캐릭터의 이동 자체가 엄청 느린것이다. Speed 값을 100~200 단위는 올려야 좀 자연스럽고 , 점프도 내려올때 중력이 잘 적용되지만 아무리 무게값을 올려도 천천히 내려오는 현상이 반복됬다.

 

구글링 해본 결과

 

즉, RigidBody.velocity RigidBody.AddForce 는 내부적으로 이미 프레임간 시간 차이(deltaTime)를 적용하여 연산 처리 되어있기 때문에 한번더 곱해지면 값이 2번 보정되어 엄청 느려지는것이다. 

오브젝트 조작 및 카메라 회전을 이해하기 위해 작성하였고

예전에 작성하던 제 스타일하고 달라서 나중에 필요할때 보려고 TIL로 작성하였습니다. 

 

https://01149.tistory.com/120

 

Unity3D :: 3D월드에서 InputSystem을 사용하여 캐릭터 이동 + 카메라 회전 , enum InputActionPhase

Q. Unity2D랑 Unity3D랑 나눌 정도로 InpuySystem이 많이 바껴?A.우선 Vector2 -> Vector3로 바뀌는 정도가 있긴하지만이동은 x축과 z축으로 이동하기에 비슷하게 동작한다. 내 개인 얘기가 들어가기에 이 질

01149.tistory.com

 

https://01149.tistory.com/116

 

Unity3D :: Skybox에 대해서

Q.SkyBox가 뭐야?A.월드스페이스의 배경을 둘러싸는 환경 매핑 기술 이다. Unity에서는 Scene의 BackGround로 주로 사용되며 보통 자연적인 환경(하늘,구름,산)을 표현할때 많이 사용미리 구성된 스카이

01149.tistory.com

https://01149.tistory.com/117

 

Unity :: Rigidbody컴포넌트의 Rigidbody.AddForce() , ForceMode

Q.Rigidbody컴포넌트가 뭐야?A. Unity 내에서 제공하는 물리연산 컴포넌트이다.  Q.AddForce() 메서드는 어디다가 써?A.Rigidbody가 적용된 오브젝트에 AddForce 메서드를 사용하면 외부에서 물리적인 힘을

01149.tistory.com

https://01149.tistory.com/118

 

Unity3D :: Ray,RayCast,RayCastHit

https://01149.tistory.com/102 Unity :: 비트연산자와 레이어마스크Q.비트연산자는 뭐야?A.컴퓨터가 나타내는 제일 작은 단위인 비트(bit = 0 또는 1)비트 연산자는 bit(0,1)을 이동하거나 논리 연산을 할떄 사

01149.tistory.com

 

https://01149.tistory.com/115

 

Unity :: UnityEngine에서 제공하는 Pool 패키지(오브젝트 풀)

게임 개발을 공부하면 오브젝트 풀에 대해 배우면서오브젝트를 미리 생성하고 필요할때 꺼내서 사용하고 사용이 다되면 풀에 반납하여 비활성화 시켜주는 것을오브젝트 풀링이라고 배워왔다.

01149.tistory.com

 

오늘 유니티 2D 팀프로젝트 하고 튜터님들이 각 팀들마다 피드백 해주셨는데

내 설계에도 도움이 될것 같아서 작성해본다.

 

0.프리팹화 한거 Resources를 사용하여 동적 생성하여 Scene 채우기 할것


1.매니저에 다 모든 기능을 넣지마라


2.Find 메서들 지양해라


3.문자열을 사용하는 메서드들을 해쉬코드로 변경하여 사용할것


4.오브젝트 충돌 검사시 태그 검사가 아닌
오브젝트 비트연산자 + 레이어마스크 검사를 하거나
컴포넌트를 호출하여 검사하도록 하자


5.이름이니셜로 폴더링 하지말것, 만약 그 팀원 퇴사했다 치면
어떤 기능일 구현했는지 모르기 때문에 매우 큰 문제가 된다.결국 코드 작업시 남의 스크립트 영역에 침범하게 될일이 있으므로 기능 별로 폴더링하여 사용하여야된다.


6.게임매니저가 Player를 찾게 하지말고
Player에서 게임매니저를 접근하게 하는 로직을 생성하자
ex)Player 오브젝트에서 게임 저장
GameManager.Instance.GameSave() ....


7.매직 넘버 지양하자

코드에 하드코딩으로 숫자 집어넣는거 


8.파입 입출력을 이용하자 스테이지 구성을 지향하자


9.프로젝트 진행할때 플랫폼 설정하기-> 안드로이드,IOS,윈도우 등등...


10.프로젝트 세팅에서 fixedUpdate의 시간을 조정 가능하다. 


11.GetComponent<>는 최적화가 잘되어있지만 수시로 불러 사용하는건 지양해야된다.


12.Dirctinoray의 키값을 enum으로 해서 가독성을 올릴것 


13.코루틴은 효과만 관리하고 플래그(변수)는 메서드에서 관리하는것이 좋다.


14.코루틴이 실행중인데 중복 실행 방지 로직은 항상 생각할것

15.어디서든 UI매니저를 통해서 호출하게하면 좋다.

 

각팀들에게 해주셨던 피드백을 간단하게 정리한 내용이다.

설계할때 한번씩 보고 설계하도록 하자 

+ Recent posts