[D3D] Collision, Picking

vector·2025년 12월 5일
post-thumbnail

이번엔 충돌이다.
충돌은 Mesh끼리가 아닌 Collider를 통해서 이루어진다. 만약 Mesh로 하게된다면 연산량이 많아지기에 연산량을 줄이기 위해서 간단한 도형(Box, Sphere, 등)의 Collider를 활용해서 연산한다.

Collider

Collider클래스를 생성하기에 앞서 상위클래스로 BaseCollider클래스를 만들고 이 클래스를 BoxColliderSphereCollider가 상속을 받는 형식으로 구현하자.

  • BoxCollider : OBBAABB 두가지 방식이 있고, OBB가 더 상위호환이지만, 공부할겸 두개 다 만드는 방식으로 진행

BaseCollider

  • 충돌에 사용되는 모든 기등을 넣어주자
  • 기본적은 ColliderType과 레이케스팅의 충돌인지, 다른 물체와의 충돌인지를 체크하는 기능함수 추가
  • RaySimpleMath::Ray를 사용

BaseCollider.h

#pragma once
#include "Component.h"

enum class ColliderType
{
	Sphere,
	AABB,
	OBB,
};

class BaseCollider : public Component
{
public:
	BaseCollider(ColliderType colliderType);
	virtual ~BaseCollider();


	// Ray는 SimpleMath::Ray를 사용 (Types.h에서 using사용)
	virtual bool Intersects(Ray& ray, OUT float& distance) = 0;

	ColliderType GetColliderType() { return _colliderType; }


private:
	ColliderType _colliderType;
};

BaseCollider.cpp

  • ComponentTypecollider 추가
  • GameObject에서 GetCollider()함수 생성
#include "pch.h"
#include "BaseCollider.h"

BaseCollider::BaseCollider(ColliderType colliderType) : Component(ComponentType::Collider), _colliderType(colliderType)
{ }

BaseCollider::~BaseCollider() { }
// Component.h
enum class ComponentType : uint8
{
	Transform,
	MeshRenderer,
	ModelRenderer,
	Camera,
	ModelAnimator,
	Light,
	Collider, // 추가
	// ...
	Script,

	End,
};
// GameObject.h
class GameObject : public enable_shared_from_this<GameObject>
{
public:
	shared_ptr<BaseCollider> GetCollider();
}

// GameObject.cpp
shared_ptr<BaseCollider> GameObject::GetCollider()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::Collider);

	return static_pointer_cast<BaseCollider>(component);
}

SphereCollider

우선은 조금 더 간단한 SphereCollider 우선 생성하자.

  • BaseCollider를 상속받는 형식으로 생성
  • BoundingSphere : 원점 위치와 반지름으로 구성되어 있으며, 물체끼리의 충돌 및 레이케스팅 관련 기능이 들어가 있음

SphereCollier.h

#pragma once
#include "BaseCollider.h"

class SphereCollider : public BaseCollider
{
public:
	SphereCollider();
	virtual ~SphereCollider();

	virtual void Update() override;
	// OUT은 아무런 기능x, 리턴하는 용도로 사용한다는 힌트주는 정도
	virtual bool Intersects(Ray& ray, OUT float& distance) override;


	BoundingSphere& GetBoundingSphere() { return _boundingSphere; }
	void SetRadius(float radius) { _radius = radius; }

private:

	float _radius = 1.f;
	BoundingSphere _boundingSphere; //충돌 영역 표시
};

SphereCollier.cpp

#include "pch.h"
#include "SphereCollider.h"

SphereCollider::SphereCollider() : BaseCollider(ColliderType::Sphere)
{
}

SphereCollider::~SphereCollider()
{
}

void SphereCollider::Update()
{
	_boundingSphere.Center = GetGameObject()->GetTransform()->GetPosition();

	Vec3 scale = GetGameObject()->GetTransform()->GetScale();
	_boundingSphere.Radius = _radius * max(max(scale.x, scale.y), scale.z); // x y z중에 가장 큰값으로 설정

}

bool SphereCollider::Intersects(Ray& ray, OUT float& distance)
{
	// 피격이 되면 거기를 distance에 리턴해주겠다
	return _boundingSphere.Intersects(ray.position, ray.direction, OUT distance);
}

CollisionDemo

  • SphereCollider로 생성해보자

CollisionDemo.cpp

void CollisionDemo::Init()
{
// 위 코드는 생략

	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}
		// Collider
		{
			auto collider = make_shared<SphereCollider>();
			collider->SetRadius(0.5f);
			obj->AddComponent(collider);
		}

		CUR_SCENE->Add(obj);
}
void CollisionDemo::Update()
{
	if (INPUT->GetButtonDown(KEY_TYPE::LBUTTON))
	{
		int32 mouseX = INPUT->GetMousePos().x;
		int32 mouseY = INPUT->GetMousePos().y;

		// Picking
	}
}

Scene

  • 테스트를 위해서 피킹코드를 작성해야하는데 우선 Scene클래스에서 생성하자
  • 스크린 좌표에서 클릭한 부분을 3D로 변환

Scene.h

#pragma once
class Scene
{
public:
	shared_ptr<class GameObject> Pick(int32 screenX, int32 screenY);

};

Scene.cpp

shared_ptr<class GameObject> Scene::Pick(int32 screenX, int32 screenY)
{
	//						  카메라오브젝트->카메라컴포넌트
	shared_ptr<Camera> camera = GetCamera()->GetCamera();

	float width = GRAPHICS->GetViewport().GetWidth();
	float height = GRAPHICS->GetViewport().GetHeight();
	
	// 프로젝션Matrix
	Matrix projectionMatrix = camera->GetProjectionMatrix();

	// 스크린 -> 뷰포트
	float viewX = (2.0f * screenX / width - 1.0f) / projectionMatrix(0, 0);
	float viewY = (-2.0f * screenX / height + 1.0f) / projectionMatrix(1, 1);

	Matrix viewMatrix = camera->GetViewMatrix();
	Matrix viewMatrixInv = viewMatrix.Invert();

	const auto& gameObjects = GetObjects();

	float minDistance = FLT_MAX;
	shared_ptr<GameObject> picked;

	for (auto& gameObject : gameObjects)
	{
		if (gameObject->GetCollider() == nullptr)
			continue;

		// ViewSpace에서 Ray 정의, ViewSpace에서 0,0,0은 카메라 위치
		Vec4 rayOrigin = Vec4(0.f, 0.f, 0.f, 1.f);
		Vec4 rayDir = Vec4(viewX, viewY, 1.0f, 0.f);

		// WorldSpace에서 Ray 정의
		Vec3 worldRayOrigin = XMVector3TransformCoord(rayOrigin, viewMatrixInv);
		Vec3 worldRayDir = XMVector3TransformNormal(rayDir, viewMatrixInv);
		worldRayDir.Normalize();

		Ray ray = Ray(worldRayOrigin, worldRayDir);

		float distance = 0.f;
		if (gameObject->GetCollider()->Intersects(ray, OUT distance) == false)
			continue;

		if (distance < minDistance)
		{
			minDistance = distance;
			picked = gameObject;
		}
	}

	return picked;
}

메인함수(CollisionDemo)

void CollisionDemo::Update()
{
	if (INPUT->GetButtonDown(KEY_TYPE::LBUTTON))
	{
		int32 mouseX = INPUT->GetMousePos().x;
		int32 mouseY = INPUT->GetMousePos().y;

		Ray ray;
		
		// Picking
		auto pickObj = CUR_SCENE->Pick(mouseX, mouseY);
		if (pickObj)
		{
			CUR_SCENE->Remove(pickObj);
		}
	}
}

결과

  • Object를 클릭 시 Remove()
  • 간혹 위치가 조금 틀어지는 경우가 종종 있음

AABB, OBB

AABB

  • "축 정렬 경계 상자"로, 물체의 3D 공간에서 가장 작은 직육면체 형태의 경계 상자를 의미
  • 각 축에 대해 평행하며, 축에 정렬된 가장 작은 직육면체로 물체를 둘러싸는 방식
  • 물체의 위치나 회전과는 관계없이 항상 x, y, z축에 맞춰져 있다.
  • 특징
    1. 회전이 없다
    1. 간단한 계산 : 계산이 간단하고 빠르다. 물체의 최솟값, 최댓값만을 구하면 되기 때문
    2. 충돌 검사 : AABB끼리의 충돌검사 나 물체의 포함 여부를 쉽게 확인할 수 있다.
  • 사용 예시 : 충돌 감지 시스템에서 빠르게 충돌을 체크하거나, 간단한 물체가 회전하지 않는 경우

OBB

  • "회전된 경계 상자", 물체를 둘러싼 직육면체 상자가 회전할 수 있도록 허용한 것
  • 물체의 회전에 따라 상자가 회전하는 형태, 이로 인해 물체의 모양에 맞게 더 정확하게 경계를 정의할 수 있다.
  • 특징
    1. 회전 가능
    1. 정확성 : 물체와 더 밀접한 형태를 가지기 때문에 더 정밀한 충돌 검사나 포함 여부 확인이 가능하다.
    2. 복잡한 계산 : AABB보다 더 복잡하고 연산이 많이 필요하다. 물체의 회전 행렬를 고려해야 하므로 그만큼 성능에 부담을 줄 수 있다.
  • 사용 예시 : 복잡한 3D물체나, 물체가 회전하는 경우에 더 정밀한 충돌 감지와 경계 계산이 필요할 때 사용

BoxCollider

  • SphereCollider와 비슷하게

AABBBoxCollider

AABBBoxCollider.h

#pragma once
#include "BaseCollider.h"
class AABBBoxCollider : public BaseCollider
{
public:
	AABBBoxCollider();
	virtual ~AABBBoxCollider();

	virtual void Update() override;
	virtual bool Intersects(Ray& ray, OUT float& distance) override;

	BoundingBox& GetBoundingBox() { return _boundingBox; }

private:
	BoundingBox _boundingBox;
};

AABBBoxCollider.cpp

#include "pch.h"
#include "AABBBoxCollider.h"

AABBBoxCollider::AABBBoxCollider() : BaseCollider(ColliderType::AABB) {}

AABBBoxCollider::~AABBBoxCollider() {}

void AABBBoxCollider::Update() {}

bool AABBBoxCollider::Intersects(Ray& ray, OUT float& distance)
{
	return _boundingBox.Intersects(ray.position, ray.direction, OUT distance);
}

OBBBoxCollider

OBBBoxCollider.h

#pragma once
#include "BaseCollider.h"
class OBBBoxCollider : public BaseCollider
{
public:
	OBBBoxCollider();
	virtual ~OBBBoxCollider();

	virtual void Update() override;
	virtual bool Intersects(Ray& ray, OUT float& distance) override;

	BoundingOrientedBox& GetBoundingOrientedBox() { return _boundingBox; }

private:
	BoundingOrientedBox _boundingBox;
};

OBBBoxCollider.cpp

#include "pch.h"
#include "OBBBoxCollider.h"

OBBBoxCollider::OBBBoxCollider() : BaseCollider(ColliderType::OBB) {}

OBBBoxCollider::~OBBBoxCollider() {}

void OBBBoxCollider::Update() {}

bool OBBBoxCollider::Intersects(Ray& ray, OUT float& distance)
{
	return _boundingBox.Intersects(ray.position, ray.direction, OUT distance);
}

ColliderDemo.cpp

void CollisionDemo::Init()
{
	//RESOURCES->Init();

	_shader = make_shared<Shader>(L"23.RenderDemo.fx");

	// Camera
	{
		auto camera = make_shared<GameObject>();
		camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
		camera->AddComponent(make_shared<Camera>());
		camera->AddComponent(make_shared<CameraScript>());
		CUR_SCENE->Add(camera);
	}

	// Light
	{
		auto light = make_shared<GameObject>();
		light->AddComponent(make_shared<Light>());
		LightDesc lightDesc;
		lightDesc.ambient = Vec4(0.4f);
		lightDesc.diffuse = Vec4(1.f);
		lightDesc.specular = Vec4(0.1f);
		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
		light->GetLight()->SetLightDesc(lightDesc);
		CUR_SCENE->Add(light);
	}


	// Mesh
	// Material
	{
		shared_ptr<Material> material = make_shared<Material>();
		material->SetShader(_shader);
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		material->SetDiffuseMap(texture);
		MaterialDesc& desc = material->GetMaterialDesc();
		desc.ambient = Vec4(1.f);
		desc.diffuse = Vec4(1.f);
		desc.specular = Vec4(1.f);
		RESOURCES->Add(L"Veigar", material);
	}

	//for (int32 i = 0; i < 10; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		//{
		//	auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
		//	obj->GetMeshRenderer()->SetMesh(mesh);
		//	obj->GetMeshRenderer()->SetPass(0);
		//}
	
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Cube");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}

		// Collider
		//{
		//	auto collider = make_shared<SphereCollider>();
		//	collider->SetRadius(0.5f);
		//	obj->AddComponent(collider);
		//}

		{
			auto collider = make_shared<AABBBoxCollider>();
			collider->GetBoundingBox().Extents = Vec3(0.5f);
			obj->AddComponent(collider);
		}

		//{
		//	obj->GetOrAddTransform()->SetLocalRotation(Vec3(0, 45, 0));
		//
		//	auto collider = make_shared<OBBBoxCollider>();
		//	collider->GetBoundingOrientedBox().Extents = Vec3(0.5f);
		//	collider->GetBoundingOrientedBox().Orientation = Quaternion::CreateFromYawPitchRoll(45, 0, 0);
		//	obj->AddComponent(collider);
		//}

		CUR_SCENE->Add(obj);
	}

	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(3.f, 0.f, 0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}
		{
			auto collider = make_shared<SphereCollider>();
			collider->SetRadius(0.5f);
			obj->AddComponent(collider);
		}
		{
			obj->AddComponent(make_shared<MoveScript>());
		}

		CUR_SCENE->Add(obj);
	}
}

결과

물체끼리 충돌

  • Intersects()를 Ray충돌이 아닌 BaseCollider와 충돌하는 함수 추가하기

BaseCollider

class BaseCollider : public Component
{
public:
	ColliderType GetColliderType() { return _colliderType; }

}

SphereCollier

// SphereCollider.h
class SphereCollider : public BaseCollider
{
public:
	virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}

// SphereCollider.cpp
bool SphereCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType();
	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingSphere.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere());
	case ColliderType::AABB:
		return _boundingSphere.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingSphere.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingOrientedBox());
	}

	return false;
}

AABBBoxCollider

// AABBBoxCollider.h
class AABBBoxCollider : public BaseCollider
{
public:
	virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}

// AABBBoxCollider.cpp
bool AABBBoxCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType();
	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingBox.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere());
	case ColliderType::AABB:
		return _boundingBox.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingBox.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingOrientedBox());
	}

	return false;
}

OBBBoxCollier

// OBBBoxCollider.h
class OBBBoxCollider : public BaseCollider
{
public:
	virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}

// OBBBoxCollider.cpp
bool OBBBoxCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType();
	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingBox.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere());
	case ColliderType::AABB:
		return _boundingBox.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingBox.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingOrientedBox());
	}

	return false;
}

Scene

  • 충돌체크 함수 CheckCollision 추가
  • Update()가 호출된 이후에 충돌체크하는게 좋기에 LateUpdate()에서 호출
void Scene::CheckCollision()
{
	vector<shared_ptr<BaseCollider>> colliders;

	for (shared_ptr<GameObject> object : _objects)
	{
		if (object->GetCollider() == nullptr)
			continue;

		colliders.push_back(object->GetCollider());
	}


	// BruteForce
	for (int32 i = 0; i < colliders.size(); i++)
	{
		for (int32 j = i + 1; j < colliders.size(); j++)
		{
			shared_ptr<BaseCollider>& other = colliders[j];
			if (colliders[i]->Intersects(other))
			{
            	// BreakPoint 걸기위한 임시
				int a = 3;
			}
		}
	}
}

결과

Terrain Picking

Terrain

  • 그리드 형식의 Object를 만들어주자
  • 원래는 메인함수(Demo)의 Init()에서 만들어줬다면, 매번 만들어주기에는 번거롭고, 다른 물체와는 다르게 작동되기 때문에 따로 Terrain이라는 클래스를 생성하자

Terrain

Terrain.h

#pragma once
#include "Component.h"
class Terrain : public Component
{
public:
	Terrain();
	~Terrain();

	void Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material);

	bool Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance);

	int32 GetSizeX() { return _sizeX; }
	int32 GetSizeZ() { return _sizeZ; }

private:
	shared_ptr<Mesh> _mesh;
	int32 _sizeX = 0;
	int32 _sizeZ = 0;
};

Terrain.cpp

#include "pch.h"
#include "Terrain.h"
#include "MeshRenderer.h"

Terrain::Terrain() : Component(ComponentType::Terrain){}
Terrain::~Terrain(){}

void Terrain::Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material)
{
    _sizeX = sizeX;
    _sizeZ = sizeZ;

    // weakPointer는 변환을 해서 사용해줘야 함
    auto gameObject = _gameObject.lock();

    gameObject->GetOrAddTransform();
    
    if (gameObject->GetMeshRenderer() == nullptr)
    {
        gameObject->AddComponent(make_shared<MeshRenderer>());
    }

    _mesh = make_shared<Mesh>();
    _mesh->CreateGrid(sizeX, sizeZ);

    gameObject->GetMeshRenderer()->SetMesh(_mesh);
    gameObject->GetMeshRenderer()->SetPass(0);
    gameObject->GetMeshRenderer()->SetMaterial(material);

}

bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance)
{
    return false;
}

ColliderDemo

  • Terrain클래스를 만들어줌으로써 메인에서 Terrain생성 코드가 많이 단축된다.
//기본 코드
void CollisionDemo::Init()
{
	// Terrain
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
        {
        	auto mesh = make_shared<Mesh>();
            mesh->CreateGrid(10, 10);
            obj->GetMeshRenderer()->SetMeah(mesh);
            obj->GetMeshRenderer()->SetPass(0);
        }
        {
        	obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar");
        }
    }
}
// Terrain클래스 생성 이후
void CollisionDemo::Init()
{
	// Terrain
	{
		auto obj = make_shared<GameObject>();

		obj->AddComponent(make_shared<Terrain>());
		obj->GetTerrain()->Create(10, 10, RESOURCES->Get<Material>(L"Veigar"));

		CUR_SCENE->Add(obj);
	}
}

Terrain Picking

  • Terrain을 생성할때 코드를 보면 하나의 사각형을 두개의 삼각형으로 만들어서 크기에 맞게 생성해주는 방식이었다.
  • 이번 Picking검사도 동일하게 삼각형으로 검사하고, 모든 사각형에 반복한다.

Terrain

bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance)
{
    Matrix W = GetTransform()->GetWorldMatrix();
    Matrix V = Camera::S_MatView;
    Matrix P = Camera::S_MatProjection;

    Viewport& vp = GRAPHICS->GetViewport();

    Vec3 n = vp.Unproject(Vec3(screenX, screenY, 0), W, V, P);
    Vec3 f = vp.Unproject(Vec3(screenX, screenY, 1), W, V, P);

    Vec3 start = n;
    Vec3 direction = f - n;
    direction.Normalize();

    Ray ray = Ray(start, direction);

    const auto& vertices = _mesh->GetGeometry()->GetVertices();
    for (int32 z = 0; z < _sizeZ; z++)
    {
        for (int32 x = 0; x < _sizeX; x++)
        {
            uint32 index[4];
            index[0] = (_sizeX + 1) * z + x;
            index[1] = (_sizeX + 1) * z + (x + 1);
            index[2] = (_sizeX + 1) * (z + 1) + x;
            index[3] = (_sizeX + 1) * (z + 1) + (x + 1);

            Vec3 p[4];
            for (int32 i = 0; i < 4; i++)
            {
                p[i] = vertices[index[i]].position;
            }

            // [2]
            //  |  \
            // [0] - [1]
            if (ray.Intersects(p[0], p[1], p[2], OUT distance))
            {
                pickPos = ray.position + ray.direction * distance;
                return true;
            }
            // [2] - [3]
            //     \  |
            //       [1]
            if (ray.Intersects(p[3], p[1], p[2], OUT distance))
            {
                pickPos = ray.position + ray.direction * distance;
                return true;
            }
        }
    }

    return false;
}

Scene

  • ScenePick()에서 Terrain을 가져와서 Pick()함수를 호출해주면 된다.
shared_ptr<class GameObject> Scene::Pick(int32 screenX, int32 screenY)
{
// 위 코드는 동일해서 추가x

// Terrain
	for (auto& gameObject :_objects)
	{
		if (gameObject->GetTerrain() == nullptr)
			continue;

		Vec3 pickPos;
		float distance = 0.f;
		if (gameObject->GetTerrain()->Pick(screenX, screenY, OUT pickPos, OUT distance) == false)
			continue;

		if (distance < minDistance)
		{
			minDistance = distance;
			picked = gameObject;
		}
	}

	return picked;
}

결과

사진 출처

https://handhp1.tistory.com/6

참고 강의

https://www.inflearn.com/courses/lecture?courseId=329791&type=LECTURE&unitId=161170&tab=curriculum&subtitleLanguage=ko

profile
게임 클라이언트 프로그래머 준비중 (공부 및 기록용)

0개의 댓글