Unity 토이프로젝트 찍먹 - 플레이어 이동 & 회전 ( 이론 )

Changhoony·2022년 9월 30일
0

Unity_Toy_Project

목록 보기
2/4

이전 : OT

* 목표

* 대상의 이동과 회전을 조작할 수 있다.
* 가상 조이스틱과 키보드, 마우스를 이용할 수 있다.

* 방법

1. Transform - MoveTowards & eulerAngles 이용
2. Rigidbody - MovePosition & MoveRotation 이용
3. NavAgent - Move 이용
4. DOT - Translation & Rotation 이용

* 내용

1.Transform 이동


1.1 RigidyBody와 차이점

  • RigidBody의 유무에 따라서, 성능상의 차이를 가져온다.
  • 대상에 RigidyBody와 Collider 컴포넌트가 있다면, rigidbody.MovePosition()을 권장한다.
  • transform.position 이동은 RigidBody, Collider 위치를 재계산 하기 때문에 이 경우 권장되지 않는다.

1.2 transform.position 과 transform.Translate의 차이점

  • transform.position 이동은 transform의 월드 좌표 기준으로 이동한다. - transform 방향에 영향을 받지 않음.

  • transform.Translate 이동은 transform의 로컬 기준으로 이동한다. - transform 방향에 영향을 받음.

1.2.1 position 예시

  • transform.position = Vector3.Lerp(position , destination, Time.deltaTime * speed);

  • transform.position = Vector3.MoveTowards(position, destination, Time.deltaTime * speed);


1.2.2 Translate 예시

  • transform.Translate(destination);
  • transform.Translate(transform.position + destination);

1.3 Transform.LookAt(), Transform.Rotate() 그리고 EulerAngles, Quaternion

  • 인간/동물형 3D 오브젝트의 경우 Y축을 기준으로 회전하고,
    인간/동물형 2D 오브젝트의 경우 Z축을 기준으로 회전해야 자연스럽다.

1.31 transform.Rotate() transform.LookAt() 차이

  • Rotate 함수는, 자기 자신 또는 월드 기준으로 transform을 회전시킨다.
  • LookAt(target) 은 바라보는 (대상:target)을 기준으로 transform을 회전한다.

1.32 eulerAngles와 Quaternion의 차이

  • eulerAngle을 사용할 경우 발생할 수 있는 짐벌락 현상을 해결하기 위해 Quaternion이 존재한다.

  • 1.321 Quaternion 예시
    transform.rotation = Quaternion.Euler(0,90,0);

  • 1.322 eulerAngles 예시 (3D 기준)
    transform.eulerAngles = new Vector(0, Mathf.Atan2(getAxisX,getAxisY) * Mathf.Rad2Deg, 0);

  • 1.323 eulerAngels 예시 (2D 기준)
    transform.eulerAngles = new Vector(0, 0, Mathf.Atan(getAxisX,getAxisY) * Mathf.Rad2Deg);



2. RigidBody


2.1 MovePosition() vs RigidBody.position

  • Collider와 RigidBody가 있는 물체가 지속적으로 움직여야할 경우 MovePosition()을 사용한다.
  • 순간이동이나 좌표 변경은 RigidBody.position을 사용한다.
  • 만약 transform.position을 접근 한다면, 접근하는 영역에서 rigidbody.isKinematic을 true로 사용한다.

2.11 MovePosition 예시

RigidBody rigid;

Vector3 vel;
float inputMagnitude;
float inputDirection;
float moveSpeed = 5;
float smoothMoveTime = 0.1f;
float smoothMoveVel = 0;

void CalVelocity()
{
	inputDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0, (Input.GetAxisRaw("Vertical"));
	
    inputMagnitude = inputDirection.magnitude;

	float smoothInputMagnitude = Mathf.SmoothDamp(smoothInputMagnitude, inputMagnitude, ref smoothMoveVel, smoothMoveTime);

	vel = transform.forward * moveSpeed * smoothInputMagnitude;
}

void Update()
{
	...
	CalVelocity();
    ...
}

void FixedUpdate()
{
	rigid.MovePosition(rigid.position + vel * Time.deltaTime);
}

2.12 MoveRotation()

  • MovePosition과 함께 작성한다.
...
float angle;
float turnSpeed = 5;
...

void CalAngle()
{
	flaot target = Mathf.Atan2(inputDirection.x,inputDirection.z) * Mathf.Rad2Deg;
    angle = Mathf.LerpAngle(angle, targetAngle, Time.deltaTime * turnSpeed * inputMagnitude);
}
void Update()
{
	...
	CalVelocity();
    CalAngle();
    ...
}
void FixedUpdate()
{
	rigid.MoveRotation(Quaternion.Euler(Vector3.up * angle));
	rigid.MovePosition(rigid.position + vel * Time.deltaTime);    
}


3. NavMeshAgent


3.1 적용 예시

 NavmeshAgent agent;
 Transform target;
 
 void Start()
 {
 	agent = GetComponent<NavMeshAgent>();
 }
 void Update()
 {
 	agent.Move(target.position);
 }


4. ECS (1.0v - 기준 (계속 업데이트))


4.1 ECS Component Data

public struct PlayerSpeed : IComponentData
{
    float speed;
}
public struct InputVectorComponent : IComponentData
{
	float3 input;
}

4.2 Create Baker

  • 이동 데이터.
  • 접미사 혹은 접두사 Authoring을 에디터상에 올려서 사용.
  • Baker로 EntitySystem에 사용할 목록 등록
// moving speed authoring

public class PlayerSpeedAuthoring : MonoBehaviour
{
	public float speed;
}
public class PlayerSpeedBaker : Baker<SpeedAuthoring>
{
	public override void Bake(SpeedAuthoring authoring)
    {
    	AddComponent(new Speed()
        {
        	speed = authoring.speed;
        });
    }
}
// input Horizontal or Vertical authoring

public class InputVectorAuthoring : MonoBehaviour
{
	public float3 input;
}
public class InputVectorBaker : Baker<InputVectorAuthoring>
{
	public override void Bake(InputVectorAuthoring authoring)
    {
    	AddComponent(new InputVectorComponent()
        {
        	input = authoring.input;
        });
    }
}

4.3 Create Tag

  • 이동 적용 대상. 만약 태그가 없다면 모든 엔티티가 움직여 질 수 있음.
  • 별다른 내용이 없어도 작동함.
public struct MovingPlayerTag : IComponentData
{ 
	// stay empty..
}

public class MovingPlayerAuthoring : MonoBehaviour
{ 
	// stay empty..
}

public class MovingPlayerBaker : Baker<MovingPlayerAuthoring>
{
	public override void Bake(MovingPlayerAutohring authoring)
    {
    	AddComponent(new MovingPlayerTag());
    }
}

4.4 Create IAspect

  • 행동 정의 부분.
  • 주의
  1. 구조체여야 함
  2. readonly와 partial 반드시 사용
  3. 내부 필드 변수는 무조건 타입별로 한개만 인식됨.
  4. 내부에서 필드 값에 변화를 줘야 한다면 RefRW를 사용, 그 외에는 속도를 위해 RefRO 사용.
// must use 'readonly-partial' pair
public readonly partial struct PlayerMovementAspect : IAspect
{
	private readonly Entity _entity;
    private readonly TransformAspect _transformAspect;
    
    public void Move(float deltaTime, RefRO<Speed> speed, RewfRW<InputVectorComponent> input)
    {
    	_transformAspect.Position += input.ValueRW.input * deltaTime * speed.ValueRO.speed;
        
    }
}

4.5 Create SystemBase

  • 정의된 행동 실행 로직
  • ISystem은 주로 병렬 (JobParelle) 작동을 위해 사용. 4.7에서 다룸
  • SystemBase는 자동 등록 및 적용.
  • partial 반드시 사용.
  • class이기 때문에, 일반 클래스 자료들을 호출 할 수 있음.
    - 유니티 엔진 클래스는 SystemAPI. 클래스 참고
  • 접미사 System 사용

4.51 Not Rotate But Move

public partial class PlayerMovementSystem : SystemBase
{

	protected override void OnUpdate()
    {
		// Do not use UnityEngine.Time.DeltaTime;
    	// Use SystemAPI
		var deltaTime = SystemAPI.Time.DeltaTime;
    	// 2d 이동
    	var input = new float3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertial"),0);
    	// 3d 이동
    	// var input = new float3(Input.GetAxis("Horizontal"), 0,Input.GetAxis("Vertial"));
    	// Init InputVectorComponent, RW - Read & Write | RO - ReadOnly
    	var inputVector = SystemAPI.GetSingletonRW<InputVectorComponent>();
    
    	inputVector.ValueRW.input = input;
    
    	// 3 way
    	// 1. foreach 
    	/* 
    		foreach((var agent in SystemAPI.Query<PlayerMovmentAspect, PlayerTag, PlayerSpeed, InputVectorComponent>())
        	{
            	// item1 => PlayerMovementAspect : For Behaviour
                // item2 => PlayerTag : For Filtering
                // item3 => PlayerSpeed : Variable
                // item4 => InputVectorComponent : Controll
                
        		agent.Item1.Move(deltaTime, agent.item3, agent.item4);
        	}
    	*/
    	// Player Only one ..But It also Work.
        // 2. Entities.With-..
 		
        /*
        Entities
        .WithAll<PlayerTag>()
        .ForEach((PlayerMovementAspect player, RefRW<PlayerSpeed> speed) =>
        {
        	player.Move(deltaTime,speed,inputVectorComponent);
        }).Run();
        */
        
        // 3. GetSingletonEntity ( recommand )
        
        var player = GetSingletonEntity<PlayerTag>();
        var speed = GetSingletonRW<PlayerSpeed>();
        
        SystemAPI.GetAspectRW<PlayerMovementAspect>(player)
        .Move(deltaTime,speed, inputVectorComponent);        
    }
}

4.6 Rotation Self

  • math.mul로 계산한다.
  • quaternion.AxisAngle( 방향, 변화량 );
protected override void OnUpdate()
{
	
        _transformAspect.Rotation =
        math.mul(math.normalize(_transformAspect.Rotation.value),
        quaternion.AxisAngle(math.forward(),deltaTime * speed.ValueRW.value));
}

결과 ▼

4.61 Rotation & Move Orbit

  • 원형 움직임
    cos : x좌표 | sin : y좌표
    삼각함수 -> 주기를 가짐
  public void Move(float deltaTime,RefRW<PlayerSpeed> speed, RefRW<PlayerMoveComponent> _playerMoveComponent)
    {
    	// self speed
        _transformAspect.Rotation = math.mul(math.normalize(_transformAspect.Rotation.value), quaternion.AxisAngle(math.forward(),deltaTime * speed.ValueRW.value));

		// orbit speed
        _playerMoveComponent.ValueRW.speed += deltaTime * speed.ValueRW.value;
        
        // orbit Move
        _transformAspect.Position = new float3(math.cos(_playerMoveComponent.ValueRW.speed), math.sin(_playerMoveComponent.ValueRW.speed ), 0);
        
        // x와 y좌표에 angle을 float로 더해주고 여러개의 오브젝트를 배치하면, 원 위의 여러 점으로 나타난다.
        // ex. 위의 코드 재활용 
        // new float3(math.cos(_playerMoveComponent.ValueRW.speed + 45) , math.sin(_playerMoveComponent.ValueRW.speed + 45) , 0)
        // 당연히 45 대신 간단한 알고리즘을 사용하여 여러 개를 자동 배치 하자.
    }

결과 ▼

4.7 Random Move

4.71 Create IComponentData & Authoring & Baker

public struct Destination : IComponentData
{
	public float3 destination;
}

public class DestinationAuthoring : MonoBehaviour
{
	public float3 destination;
}

public class DestinationBaker : Baker<DestinationAuthoring>
{
	public override void Bake(DestinationAuthoring authoring)
    {
    	AddComponent(new Destination()
        {
        	destination = authoring.destination
        });
    }
}

public struct Random : IComponentData
{
	public Random random;
}

public class RandomAuthoring : MonoBehaviour
{
	// Empty
}

public class RandomBaker : Baker<RandomAuthoring>
{
	public override void Bake(RandomAuthoring authoring)
    {
    	AddComponent(new Random()
        {
        	random = new Random(seed : 1)
        });
    }
}

4.72 Set Random Destination & Check Arrived

 private float3 SetRandomDestination(RefRW<RandomComponent> random, float2 range)
 {
    return new float3(random.ValueRW.random.NextFloat(range.x, range.y), random.ValueRW.random.NextFloat(range.x, range.y), 0);
 }
    
    
 public bool IsArrived(float3 lhs, float3 rhs, float tolerence)
 {
    return math.distance(lhs, rhs) < tolerence;
 }

4.73 Move Aspect

public readonly partial struct MoveAspect : IAspect 
{
	private readonly Entity _entity;
    private readonly TransformAspect _transformAspect;
    private readonly RefRW<DetectComponent> _detectComponent;
    private readonly RefRW<Speed> _speed;
    private readonly RefRW<Destination> _destination;
    
    public void Move(float deltaTime, RefRW<RandomComponent> random, float2 range)
    {
        if (IsArrived(_destination.ValueRW.point, _transformAspect.Position, _detectComponent.ValueRO.detectingDistanceRangeSensitive))
       _destination.ValueRW.point = SetRandomDestination(random, range);
        
        var dir = math.normalize(_destination.ValueRW.point - _transformAspect.Position);

        _transformAspect.Position += dir * deltaTime * _speed.ValueRW.value;
    }
}

4.74 Use Job & Create SystemBase

  • partial 사용.
  • struct 사용.
public partial struct MoveJob : IJobEntity
{
    public float deltaTime;
    // Must use this attributte.
    [NativeDisableUnsafePtrRestriction]public RefRW<RandomComponent> random;
    public float2 range;

    public void Execute(MoveAspect agent)
    {
        agent.Move(deltaTime, random, range);
    }
}
public partial struct MovingISystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        
    }

    public void OnDestroy(ref SystemState state)
    {
        
    }
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var deltaTime = SystemAPI.Time.DeltaTime;
        var random = SystemAPI.GetSingletonRW<RandomComponent>();
        var range = new float2(10, -10);
        var moveJobHandle = new MoveJob()
        {
            deltaTime = deltaTime,
            random = random,
            range = range
        }.Schedule(state.Dependency);
        
        moveJobHandle.Complete();
    }
}

결과 ▼

profile
Unity 개발

0개의 댓글