09.22 - TIL

김혁·2025년 9월 22일

TIL

목록 보기
23/84

오늘의 코드카타

퍼즐 조각 채우기

  • 시뮬레이션 + 정규화 (구현 어려움)
  • DFS
  • LV3 - 16% (복습 필요)
    -> 문제 풀이

오늘의 공부

팀 프로젝트 진행 내용

1. 액터들 아웃라인 보여주는 Ability 개발

  • Custom Depth를 통해 아웃라인 보여주는 머터리얼을 통해 동적 머터리얼 생성
  • 캐릭터의 Post Process Component에 동적 머터리얼 추가
// EGItem_OutlineAbility.cpp

void UEGItem_OutlineAbility::SetMaterial(ACharacter* InCharacter, UMaterialInterface* InMaterial, UMaterialInstanceDynamic* InDynamicMaterial)
{
	if (InCharacter && InMaterial)
	{
		InDynamicMaterial = UMaterialInstanceDynamic::Create(InMaterial, this);
		InDynamicMaterial->SetScalarParameterValue(FName("Opacity"), Opacity);
		InDynamicMaterial->SetVectorParameterValue(FName("Color"), Color);
		InDynamicMaterial->SetScalarParameterValue(FName("OutlineOpacity"), OutlineOpacity);
		InDynamicMaterial->SetVectorParameterValue(FName("OutlineColor"), OutlineColor);
		if (UPostProcessComponent* PPC = InCharacter->FindComponentByClass<UPostProcessComponent>())
		{
			FWeightedBlendable Blendable(1.0f, InDynamicMaterial);
			PPC->Settings.WeightedBlendables.Array.Add(Blendable);
		}
	}
}

  • 액터의 메시를 찾아서 Custom Depth를 볼 수 있게 활성화해주는 함수 구현
void UEGItem_OutlineAbility::SetCustomDepth(AActor* Target, bool bIsCustomDepth)
{
	TArray<UMeshComponent*> Meshes;
	Target->GetComponents<UMeshComponent>(Meshes);
	for (UMeshComponent* Mesh : Meshes)
	{
		if (Mesh)
		{
			Mesh->SetRenderCustomDepth(bIsCustomDepth);
		}
	}
}

  • 클라이언트에서만 렌더링 변하게 구현
  • Custom Depth를 활성화한 액터를 캐싱해서 비활성화할 때 효율적으로 사용
  • UAbilityTask_WaitDelay를 통해 일정시간 후에 EndAbility 실행
void UEGItem_OutlineAbility::ActivateAbility(...)
{
	...
    
	if (ActorInfo->IsLocallyControlled())
	{
		SetMaterial(Character, OutlineMaterial, DynamicMaterial);
		SetMaterial(Character, OutlineMaterial_Clip, DynamicMaterial_Clip);
	
		if (OutlineActorClass)
		{
			if (UWorld* World = GetWorld())
			{
				for (TActorIterator<AActor> It(World, OutlineActorClass); It; ++It)
				{
					AActor* Target = *It;
					if (Target && Target != Character)
					{
						SetCustomDepth(Target, true);
						OutlinedActors.Add(Target);
					}
				}
			}
		}
	}

	if (UAbilityTask_WaitDelay* WaitTask = UAbilityTask_WaitDelay::WaitDelay(this, AbilityDuration))
	{
		WaitTask->OnFinish.AddDynamic(this, &ThisClass::OnDurationFinished);
		WaitTask->ReadyForActivation();
	}
}

2. 랜덤으로 아이템을 생성시켜주는 아이템 스포너 개발

  • 랜덤으로 소환될 시에 가중치를 부여하는 데이터 에셋 생성
// EGItem_ConfigData.h

USTRUCT(BlueprintType)
struct FItemSpawnData
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<AEGItemBase> SpawnItem = nullptr;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float Probability = 0.f;
};

UCLASS(BlueprintType)
class EG_API UEGItem_ConfigData : public UDataAsset
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TArray<FItemSpawnData> SpawnDataList;
};

  • 가중치 누적 합을 통해서 랜덤으로 스폰될 아이템을 선택하는 로직
// AEGItem_Spawner.cpp

TSubclassOf<AEGItemBase> AEGItem_Spawner::GetRandomItemClass()
{
	float TotalWeight = 0.f;
	for (auto& Entry : ItemConfigData->SpawnDataList)
	{
		TotalWeight += Entry.Probability;
	}

	if (TotalWeight <= 0.f) return nullptr;

	float RandomValue = FMath::FRandRange(0.f, TotalWeight);
	float AccumulatedWeight = 0.f;
	for (auto& Entry : ItemConfigData->SpawnDataList)
	{
		AccumulatedWeight += Entry.Probability;
		if (RandomValue <= AccumulatedWeight)
		{
			return Entry.SpawnItem;
		}
	}

	return nullptr;
}

  • 아이템을 획득한 후에 일정시간 이후에 아이템을 리젠시키기 위해서 아이템에서 획득했다는 델리게이트 생성
// EGItemBase.h

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemPickUp, AEGItemBase*, Item);

UCLASS()
class EG_API AEGItemBase : public AActor, public IEGItemInterface
{
    ...

	UPROPERTY(BlueprintAssignable)
	FOnItemPickUp OnItemPickUp;
    
    ...
};

// EGItemBase.cpp

void AEGItemBase::OnOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor && OtherActor != this && OtherActor->IsA(AEGChickenCharacter::StaticClass()))
	{
		OnItemPickUp.Broadcast(this);
		
		Execute_OnPickUp(this, OtherActor);
	}
}

  • 아이템 획득했다는 델리게이트에 리스너 추가
  • 아이템 획득 시 일정 시간 후에 다시 스폰되게 구현
void AEGItem_Spawner::SpawnItem()
{
	if (HasAuthority())
	{
		...

		SpawnedItem = GetWorld()->SpawnActor<AEGItemBase>(ItemClass, GetActorLocation(), GetActorRotation(), SpawnParams);

		if (SpawnedItem)
		{
			SpawnedItem->OnItemPickUp.AddDynamic(this, &ThisClass::HandleItemPickUp);
		}
	}
}

void AEGItem_Spawner::HandleItemPickUp(AEGItemBase* PickedUpItem)
{
	SpawnedItem = nullptr;

	GetWorld()->GetTimerManager().SetTimer(SpawnTimerHandle, this, &ThisClass::SpawnItem, ItemConfigData->SpawnInterval, false);
}

오늘의 CS

가상 메모리

  • 개념
    • 프로세스의 일부만 메모리에 로드하고, 나머지는 디스크에 둔 상태로 프로세스를 실행하는 방식
    • 실제 물리 메모리(RAM)보다 더 큰 메모리 공간이 있는 것처럼 제공하는 기술
    • SSD/HDD 일부를 RAM처럼 사용
  • 장점
    • 물리 메모리 부족 문제 완화
    • 동시에 많은 프로그램을 실행하므로 CPU 이용률과 처리율을 높일 수 있음
    • 필요한 영역만 메모리에 로드해 스와핑 횟수를 줄여서 프로그램 실행 속도를 높일 수 있음
  • MMU(Memory Management Unit)가 두 주소 사이의 변환을 담당해서, CPU가 가상 주소를 요청하면, 이를 물리 주소로 변환하여 실제 메모리에 접근함


페이징

  • 가상 메모리 시스템은 메모리를 일정한 크기로 나눈 블록 단위로 관리함
  • 페이지
    • 가상 메모리 공간을 나눈 고정된 크기의 블록
  • 프레임
    • 물리 메모리 공간을 나눈 고정된 크기의 블록
  • 페이징
    • 가상 주소 공간의 페이지를 물리 메모리의 프레임에 매핑하는 기법
    • 동작 과정
      - 각 프로세스는 자신의 페이지 테이블을 가짐
      - 페이지 테이블은 가상 메모리의 페이지 번호와 물리 메모리의 프레임 번호를 연결해줌
      - CPU가 가상 주소를 생성하면, MMU는 페이지 테이블을 참조하여 해당하는 물리 주소를 찾음


페이지 폴트

  • CPU가 접근하려는 가상 주소에 해당하는 페이지가 현재 물리 메모리에 없는 경우 발생
  • 처리 과정
    1. 필요한 페이지가 물리 메모리에 있는지 없는지를 페이지 테이블에서 확인. 없는 것이 확인되면 CPU는 운영체제에게 인터럽트 발생
    2. 필요한 페이지가 메모리에 로드되지 않은 영역이라면 디스크에서 해당 영역을 찾아서 해당 페이지 영역을 스왑. 물리 메모리에 비어 있는 프레임이 있으면 페이지를 해당 영역에 바로 로드. 비어 있는 프레임이 없으면 페이지 교체 알고리즘을 호출해 기존에 로드된 페이지랑 새로운 페이지를 스왑.
    3. 페이지 테이블에서 새로 로드한 페이지의 값을 변경
    4. 중단되었던 명령어를 실행
  • 페이지 교체 알고리즘
    • FIFO (First-In, First-Out) : 가장 먼저 메모리에 들어온 페이지를 스왑
    • LRU (Least Recently Used) : 가장 오랫동안 사용하지 않은 페이지를 스왑
    • LFU (Least Frequently Used) : 사용 빈도가 가장 낮은 페이지를 스왑
profile
게임 개발자를 향해..

0개의 댓글