[UE5 TIL] Day 43 - Chpt 3. 콜리전, 트레이스, 데미지

JungHoon Eum·2026년 4월 24일

키워드

  • 콜리전
  • UKismetSystemLibrary::LineTraceMulti()
  • GetWorld()->AsyncLineTraceByChannel()
  • UDamageType

[코드카타] 행렬의 덧셈

문제 링크

문제 요약

  • 같은 크기의 두 행렬 arr1, arr2가 주어진다.
  • 두 행렬의 같은 위치에 있는 값을 서로 더한다.
  • 더한 결과를 새로운 2차원 벡터로 반환한다.

내가 제출한 코드

#include <string>
#include <vector>

using namespace std;

vector<vector<int>> solution(vector<vector<int>> arr1, vector<vector<int>> arr2) {
    vector<vector<int>> answer;
    for(int i = 0 ; i < static_cast<int>(arr1.size()) ; i++)
    {
        answer.push_back(vector<int>());
        for (int j = 0 ; j < static_cast<int>(arr1[i].size()) ; j++)
        {
            answer[i].push_back(arr1[i][j]+arr2[i][j]);
        }
    }
    return answer;
}

피드백

  • 다만 answer를 따로 만들지 않고 arr1을 정답 배열처럼 재활용하면 코드가 더 짧아질 수 있다.
  • answer.push_back(vector<int>())는 answer.push_back({}) 또는 answer.emplace_back()으로 더 간단하게
  • 2차원 벡터에 값을 넣을 때는 먼저 바깥 벡터에 빈 행을 추가한 뒤, 해당 행에 원소를 추가할 수 있다.
vector<int> sumColumn;
//두번째 for문
answer.push_back(sumColumn);
sumColumn.clear();
  • vector<int>는 자료형이고, vector<int>()는 실제 빈 벡터 객체.

언리얼 심화 라이브세션

  • 콜리전 - 트레이스 - 데미지 - 독데미지, 화염데미지 관리

좋은 코드 판별하기

뒤엉킨 변경

class AGameManager : publie AActor
void LoadData
void SaveData

void StartNewGame
void SpawnEnemies...

DataManager 와 GameplayManager 로 분리

샷건 수술

기능 편애

  • 어떤 함수가 자기 객체보다 남의 객체 기능/데이터와 많이 소통
  • 더 많이 쓰는 클래스로 옮기는 게 맞다. 가까운 기능끼리 모여야 코드가 자연스럽고 관리도 수월
  • Damage 계산 로직을 한 군데로 모으는 게 맞고, 근데 그 와중에 Character 객체와 더 많이 소통하는 함수가 있으면 어케 해야 하나...?
  • 해당 함수는 Character로 옮기고, DamageManager 클래스에선
    DamageCalculator(ACharacter* Target)
    Target->CalculateTakeDamage()
    식으로 한 번 감싸기

콜리전

  • Project Setting - Collision - Object Channel / 프리셋

    • 오브젝트 채널: "내가 누구인가"
    • 프리셋: 어떻게 상호작용 할 것인가
  • 프리셋 새로 생성할 때 CollisionEnabled 의 경우, 물리엔진 안 쓸거면 무조건 꺼 두는 게 맞다 (부하 문제). Query Only

  • ObjectType(위에): 이 프리셋 적용할 오브젝트 채널

  • 아래는 상호작용 방식

  • 액터들 Details-Collision에서 이 프리셋 집어넣으면 된다.

  • Cube 같은 스태틱 메쉬들은 콜리전 프리셋이 Default

    • 스태틱 메쉬마다 저장되어 있는 콜리전 프리셋 사용중.
  • Simple Collision - Complex Collision. 보통 이 두 가지 Collision 채널? 모양? 을 가지고 있다.

    • 스태틱 메쉬 애셋 열어서 이 Collision 직접 넣거나 수정도 가능
  • 스태틱 메쉬 - Details - Collisino - Simple & Complex : 평소엔 Simple. 특정 Query에 대해서 Complex 복합 사용 가능.

    • Query(트레이스) 에거 매개변수로 이 Simple&Complex 활용 가능
  • 여기서 bool bTraceComplex

트레이스

  • 하나만 인식하는 트레이스
  • 여려명을 인식하는 트레이스
  • 반사각을 인식하는 트레이스
  • 비동기 트레이스... 종류가 많다.
  • 콜리전 법칙 (오브젝트 끼리 상호작용)
  • Single, Multi - Kismet. 비동기 - GetWorld로
    매개변수 쪽에 E가 들어간다 - Enum 값. ...:: 으로 쓰는 것.

싱글/멀티 트레이스

  • 액터 생성해서 테스트 해보기.
    헤더에 void StartTrace 함수 만들고
    cpp에 #include "Kismet/KismetSystemLibrary.h"
UKismetSystemLibrary::LineTraceMulti(
const UObject* WorldContextObject, //GetWorld() 
const FVector Start, //시작지점
const FVector End, //끝지점-GetActorForwardVector()로 계산
ETraceTypeQuery TraceChannel, 
bool bTraceComplex, //위에 말한 Simple&Complex
const TArray<AActor*>& ActorsToIgnore, 
EDrawDebugTrace::Type DrawDebugType, //::ForOneFrame 넣으면 됨
TArray<FHitResult>& OutHits, 미리 TArray선언해두기
bool bIgnoreSelf, 
FLinearColor TraceColor, 
FLinearColor TraceHitColor, 
float DrawTime)
  • ETraceTypeQuery: 트레이스 채널 타입
    • UEngineTypes::ConvertToTraceType(ECC_Visibility) 로
    • Visibility, Camera도 원래는 Collision Channel
  • TArray<AActor*> 미리 선언해 두 고 .Add(this); 해두기.
  • bool bIgnoreSelf : 이거 하나만 한다고 완벽한 건 아니라 (추가 Component 등 문제 일으킬 수 있음)
    • ActorsToIgnore 이랑 bIgnoreSelf 둘 다 자기 넣기
  • 싱글 트레이스의 경우 UKismetSystemLibrary::LineTraceSingle
    HitResult 만 TArray 가 아닌 그냥 FHitResult로

비동기 트레이스

  • 코드는 기본적으로 위아래로 읽는 것 - 아래 코드 방해하지 않고 "다른 쓰레드" 에서 트레이스 지속 필요할 경우 비동기 트레이스 이용
    • 플레이어를 시야로 찾는 적 동작 등, 다른 행동들하고 별개로 트레이스를 지속할 때 이용
void ATraceTest::StartASyncTrace()
{
	FTraceDelegate TraceDelegate;
	TraceDelegate.BindUObject(this, &ATraceTest::OnAsyncTraceCompleted);
	
	FCollisionResponseParams ResponseParams;
	ResponseParams.CollisionResponse.WorldDynamic = ECR_Block;
	
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);
	QueryParams.bTraceComplex = false;
	GetWorld()->AsyncLineTraceByChannel(
		EAsyncTraceType::Multi,
		GetActorLocation(),
		GetActorForwardVector() * 1000.f + GetActorLocation(),
		ECC_Visibility,
		QueryParams,
		ResponseParams,
		&TraceDelegate
		);
 }
  • FTraceDelegate 델리게이트가 this 오브젝트 기반으로 트레이스를 실행하고
  • OnASyncTraceCompleted 함수를 Trace로 무언갈 찾았을 때 실행한다.
  • FCollisionResponseParams, FCollisinoQueryParams 로 콜리전 설정 관리.
  • CollisionResponse.WorldDynamic : 맵에 액터별로 콜리전 설정에서 WorldDynamic 설정을 할 수 있는데, 이 설정을 한 대상에 대해서만 다른 방식의 콜리전 방식을 설정할 수 있다.
  • 여기서 쓴 대로 UKismetSystemLibrary::LineTraceMulti 대신 GetWorld()->AsyncLineTraceByChannel 호출로 트레이스 만듦. 두 방법은 결과값은 비슷하다.
  • Kismet은 개발용, C++ 코드가 사아아알짝 더 가벼움. 근데 귀찮음.

Damage

  • 언리얼에서 제공하는 데미지 적용 관련 함수
  • #include "Kismet/GameplayStatics.h" 필요.
  • Damage Type을 관리해주는 클래스를 만들자
  • 이건 사실 데미지 타입마다 다른 데미지 적용 방식을 만들 수 있게 해 주는 "인터페이스" 에 불과. 부모 클래스 가도 내용에 UPROPERTY 들 밖에 없다. 분기점 같은 느낌이라고 보면 됨.
  • DamageType 클래스 만들고, 생성자 만들고, 거기에 부모클래스 UPROPERTY 들 가져와서 초기값 세팅해 주면 됨.
    • float DamageImpulse : 물리적 충격량
    • bool bScaleMomentumByMass : 무거워질수록 더 쎄게
    • bool bCausedByWorld : 데미지 근원지가 World 그 자체.
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float BurnDuration;
  • 이런 식으로 멤버변수로 새 내용 넣고 만들어도 물론 된다.
  • 이렇게 만들고 캐릭터 클래스에서
float AMasterProject02Character::TakeDamage(
float DamageAmount, 
FDamageEvent const& DamageEvent, 
AController* EventInstigator, 
AActor* DamageCauser
)
{
	float InitialDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);	
	const UDamageType* DT = DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>();

	if (DT && DT->bCausedByWorld) // DT가 있는지 확인은 항상 해줘야 한다. 포인터 쓸 때 특히
	{
		//낙사, 트랩, 은신공격 등등... 효과들을 여기에 넣는다
		UE_LOG(LogTemp, Warning, TEXT("Damage Received ByWorld"));
	}
	if (DT && EventInstigator)
	{
		UE_LOG(LogTemp, Warning, TEXT("Damage Received ByEnemy!"));
	}
	
	return InitialDamage;
}
  • 이렇게 쓴다. 데미지 계산 관련 정보를 Super::에서 할 수 있다.
  • 화상 데미지 등 새로운 멤버 변수를 넣은 Damage Type을 쓰고 싶을 땐
    InitialDamage *= (1.f + FireDamage->ArmorPenetration); 이런 식으로 가져올 수 있다.
profile
개발지망생

0개의 댓글