no image
네트워킹 및 멀티 플레이어 개요
1. 네트워킹 개요1.리플리케이션(Replication) 개념 UE에서 클라이언트와 서버 간 데이터 및 절차 호출을 동기화하는 프로세스 ⮕ 같은 게임 상태 유지멀티플레이 프로젝트에서 다양한 상황을 처리하기 위한 하이 레벨 추상성과 로우 레벨 커스터마이징 제공하이 레벨 추상성(High-Level Abstraction)쉽게 사용할 수 있도록 미리 만들어진 기능복잡한 네트워크 처리 없이 기본적인 멀티플레이 기능을 구현 가능예: bReplicates(액터의 리플리케이션 활성화), ReplicatedUsing(값이 변경될 때 호출할 함수 지정) 같은 기본 옵션로우 레벨 커스터마이징(Low-Level Customization)개발자가 직접 네트워크 통신 방식을 세밀하게 제어할 수 있는 기능기본 제공 기능으로 해결되..
2025.03.10
작성중
2025.02.26
no image
머티리얼 조작
목차1. 개요2. Material Graph 작업3. Blueprint Event Graph 작업4. 프로젝트 기능 시연1. 개요 오늘도 다름없이 몬스터를 구현에 몰두했습니다. 몬스터 중 은신 스킬을 사용하는 친구를 만들어보자는 계획이 있었는데 잘 다뤄본적이 없는 머티리얼을 조작해야되기 때문에 조금 미루고 있었습니다만.. 오늘 마주하게 됐습니다. 먼저 C++로 작성한 은신 스킬을 담당 컴포넌트를 보겠습니다.StealthSkillComponent.h#pragma once#include "CoreMinimal.h"#include "Monster/Component/BaseSkillComponent.h"#include "StealthModeSkillComponent.generated.h"UCLASS(Bluepr..
2025.02.25
no image
Enemy(Monster) 클래스
목차1.개요2. Enemy(Monster) 클래스 구조3. Monster 기능 컴포넌트4. Monster 클래스별 컴포넌트 구성5. 몬스터 컴포넌트 활용1. 개요프로젝트에서 적(몬스터) 구현 역할을 맡았고 특히 AI 구현은 복잡합니다. AI Controller, Behavior Tree, 그리고 Black Board에서는 각 몬스터의 기능을 활용하기 때문에 몬스터 클래스 구조부터 정리하고자 합니다.  2. Enemy(Monster) 클래스 구조몬스터 타입(일반, 보스) 별 각 기능을 구상하며 위와 같이 클래스를 설계하였습니다.3. 몬스터 컴포넌트공격 관련MonsterAttackComponent (일반 공격)상태 관련MonsterStatComponent (스탯 관리)HitReactionComponent (..
2025.02.24
no image
언리얼 엔진 AI 행동 제어 시스템
목차1. Behavior Tree(비헤이비어 트리)2. Black Board(블랙보드)3. AI Controller4. NavMesh1. Behavior Tree (비헤이비어 트리)비헤이비어 트리는 AI의 행동을 결정하는 계층적 구조를 가진 트리. 각 노드(Node)에는 특정 역할이 있으며, AI는 루트(Root) 노드에서 시작하여 조건에 따라 적절한 행동을 수행.1-1. 비헤이비어 트리의 주요 노드 유형Root 노드: 트리의 시작점Composite 노드 (합성 노드): 여러 개의 하위 노드를 가질 수 있음Selector (선택자): 왼쪽부터 실행하며, 성공하는 노드를 찾으면 중단Sequence (시퀀스): 왼쪽부터 순서대로 실행하며, 하나라도 실패하면 중단Decorator 노드 (데코레이터 노드): 특..
2025.02.17
no image
Animation Retargeting
목차1. 개요2. 이론3. 과정4. 결과5. 예시 1. 개요✏️ 곧 프로젝트를 시작하는데 한정적인 3D 모델 또는 애니메이션으로 인해원하는 콘셉트의 게임을 만들어내지 못하는 상황을 예방하기 위해 알아보게 되었습니다.정말 마음에 드는 3D 캐릭터 모델이 있으면 쓰고 싶잖아요..?ㅎㅎ저는 https://www.models-resource.com/에서 구한 임의의 모델을 사용하여서로 다른 Skeleton 구조를 가진 Skeletal Mesh의 Animation을 Retarget 해보았습니다.   2. 이론2.1 Animation Retargeting 이란?서로 다른 Skeletal Mesh 간에 애니메이션을 공유할 수 있도록 하는 기술캐릭터 모델이 다르지만 동일한 애니메이션을 재사용해야 할 때 사용2.2 Sk..
2025.02.14
no image
파티클 시스템
1. 언리얼 파티클 시스템( Cascade vs. Niagara)Cascade오래된 파티클 시스템.모듈 기반으로 작동.직관적이지만 한계가 많음.GPU 연산 지원이 제한적.커스텀 데이터 활용이 어려움.Niagara최신 파티클 시스템.노드 기반 비주얼 스크립팅.GPU 연산 최적화 지원.커스텀 데이터 처리 가능.파라미터 공유, 모듈 재사용 용이.Cascade보다 높은 확장성과 유연성 제공.주요 차이점 정리기능SpawnEmitterAtLocation (Cascade)SpawnSystemAtLocation (Niagara)사용하는 파티클 시스템UParticleSystem*UNiagaraSystem*GPU 최적화 지원❌✅런타임 변수 변경❌✅ (SetVariableFloat 등 사용 가능)커스텀 데이터 활용❌✅확장성..
2025.02.11
학생 성적 관리 시스템 (STL 활용)
#include #include #include #include #include using namespace std;// 학생 ID별 과목 점수 관리 (map 활용)map> studentScores;// 과목별 학생 점수 관리 (vector 활용)map>> subjectScores;// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)map, greater>>> sortedSubjectScores;// 학생 성적 추가 (점수 갱신 포함)void addScore(int id, string subject, int score) { if (score 100) { cout first; cout = minScore && p.second > choice; i..
2025.02.10

1. 네트워킹 개요

1.리플리케이션(Replication) 개념

 

  • UE에서 클라이언트와 서버 간 데이터 및 절차 호출을 동기화하는 프로세스 ⮕ 같은 게임 상태 유지
  • 멀티플레이 프로젝트에서 다양한 상황을 처리하기 위한 하이 레벨 추상성과 로우 레벨 커스터마이징 제공
    • 하이 레벨 추상성(High-Level Abstraction)
      1. 쉽게 사용할 수 있도록 미리 만들어진 기능
      2. 복잡한 네트워크 처리 없이 기본적인 멀티플레이 기능을 구현 가능
      3. 예: bReplicates(액터의 리플리케이션 활성화), ReplicatedUsing(값이 변경될 때 호출할 함수 지정) 같은 기본 옵션
    • 로우 레벨 커스터마이징(Low-Level Customization)
      1. 개발자가 직접 네트워크 통신 방식을 세밀하게 제어할 수 있는 기능
      2. 기본 제공 기능으로 해결되지 않는 고급 네트워크 처리 가능
      3. 예: RPC(Remote Procedure Call)를 사용한 서버-클라이언트 간 직접적인 데이터 전송, 네트워크 대역폭 최적화 등

 

2. 멀티플레이어 vs. 싱글 플레이어

  • 싱글 플레이어: 모든 정보가 하나의 컴퓨터에 저장됨
  • 멀티플레이어: 게임 스테이트 정보가 네트워크를 통해 여러 컴퓨터 간에 전달됨 → 더 복잡한 프로그래밍 요구됨
  • 플레이어 간 정보 공유는 추가적인 단계와 세심한 주의 필요

 

3. 멀티플레이 기능을 고려한 개발

  • 멀티플레이 가능성이 있다면 처음부터 이를 염두에 두고 개발
  • 초반부터 멀티플레이 지원을 고려하면 개발 과정에서 큰 추가 부담 없이 진행 가능
  • 사전 고려 없이 개발하면 네트워크 환경을 반영한 코드 리팩토링이 필요하며, 기술적 한계로 인해 기존 디자인을 변경해야 할 수도 있음

 

2. 클라이언트-서버 모델

  • 하나의 컴퓨터가 서버가 되어 멀티플레이 세션을 호스팅
  • 서버는 클라이언트와 게임 스테이트 정보를 공유하고 통신 지원

 

 

  • 서버의 역할
    • 서버는 하나의 진정한 오소리티(authority) 있는 게임 스테이트를 보유
    • 멀티플레이어 게임의 실제 실행이 이루어지는 곳
  • 클라이언트의 역할
    • 서버가 소유한 폰(Pawns)을 원격 제어하며, 게임 내 액션을 수행하도록 프로시저 호출 전송
    • 서버는 클라이언트에 직접 영상을 스트리밍하지 않음
  • 리플리케이션 과정
    • 서버는 게임 스테이트 정보를 각 클라이언트에 리플리케이트
    • 존재해야 하는 액터, 액터의 행동 방식, 보유해야 할 변수 값을 클라이언트에 전달
    • 클라이언트는 이를 바탕으로 서버의 게임 상태를 최대한 유사하게 시뮬레이션

 

3. 클라이언트-서버 게임플레이 예시

- 플레이어 1(Player 1) 과 플레이어 2(Player 2)가 서로에게 발사체를 발사하는 프로세스 -

 

  • 무기 발사 프로세스
    • 플레이어 1이 입력하여 무기 발사
    • 로컬 폰 → 서버 폰에 무기 발사 명령 전달
    • 서버에서 발사체 스폰 및 각 클라이언트에 사본 생성 지시
    • 서버가 무기 발사 사운드 및 비주얼 이펙트 재생 지시
  • 발사체 이동 및 충돌
    • 서버에서 발사체 이동 → 각 클라이언트에 이동 정보 리플리케이트
    • 서버에서 발사체가 플레이어 2와 충돌
    • 서버가 발사체 소멸 및 충돌 이펙트 재생 지시
    • 서버에서 플레이어 2의 폰이 대미지 받고, 클라이언트에 반응 이펙트 재생 지시
  • 네트워크 환경에서의 처리
    • 서로 다른 컴퓨터(서버 + 클라이언트)의 월드에서 동일한 이벤트가 발생하는 것처럼 보이게 처리
    • 서버가 클라이언트에 선별적으로 정보 전송하여 시각적 표현 유지
  • 리플리케이션 고려 사항
    • 필수 게임플레이 요소(콜리전, 무브먼트, 대미지)
    • 장식성 이펙트(비주얼, 사운드)
    • 개인 정보(HUD 업데이트)
    • 리플리케이트할 정보를 명확히 지정해야 하며, 네트워크 대역폭 최소화가 중요

 

4. 언리얼 엔진 네트워크 모드(Network Modes)

네트워크 모드는 게임 세션에서 각 기기의 역할을 정의.

네트워크 모드 ENetMode 설명
Standalone NM_Standalone 네트워크 기능 없이 로컬 플레이어만 존재하는 모드. 싱글 플레이 및 로컬 멀티플레이에 사용됨. 서버 기능을 포함하지만 클라이언트 연결은 받지 않음.
Dedicated
Server
NM_DedicatedServer 로컬 플레이어 없이 원격 클라이언트만 연결할 수 있는 서버. 그래픽, 사운드, 입력을 처리하지 않아 성능이 뛰어남. 대규모 멀티플레이어 게임에서 사용됨.
Listen
Server
NM_ListenServer 로컬 플레이어가 있는 서버로, 네트워크의 다른 플레이어가 참여 가능. 설정이 간단하지만 호스트 플레이어가 유리할 수 있음.
Client NM_Client 원격 서버에 연결된 클라이언트. 서버의 명령을 따르며, 자체적인 서버 기능을 수행하지 않음.

네트워크 모드를 확인하려면 GetNetMode() 함수를 사용.

5. 서버 유형(Server Types)

5.1 리슨 서버(Listen Server)

  • 사용자가 직접 서버를 호스팅할 수 있어 설정이 간단함.
  • UI를 통해 서버를 생성하거나 기존 서버를 검색할 수 있음.
  • 단점: 호스트가 서버 역할을 하면서 직접 플레이하기 때문에 다른 클라이언트보다 유리할 수 있으며, 성능 부담이 커질 수 있음.

5.2 전용 서버(Dedicated Server)

  • 서버 전용 기기에서 실행되며, 모든 플레이어가 네트워크를 통해 연결함.
  • 공정한 환경을 보장하고 성능이 뛰어나지만 설정이 복잡하고 비용이 발생할 수 있음.
  • 대규모 멀티플레이어 게임(MMO, MOBA, FPS 등)에 적합함.

'TIL > C++와 UE' 카테고리의 다른 글

Object Pooling  (0) 2025.03.28
언리얼 엔진 레플리케이션(Replication)  (0) 2025.03.11
작성중  (0) 2025.02.26
머티리얼 조작  (0) 2025.02.25
Enemy(Monster) 클래스  (0) 2025.02.24

작성중

언리얼 거토
|2025. 2. 26. 21:13

'TIL > C++와 UE' 카테고리의 다른 글

언리얼 엔진 레플리케이션(Replication)  (0) 2025.03.11
네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10
머티리얼 조작  (0) 2025.02.25
Enemy(Monster) 클래스  (0) 2025.02.24
언리얼 엔진 AI 행동 제어 시스템  (0) 2025.02.17

머티리얼 조작

언리얼 거토
|2025. 2. 25. 22:57

목차

1. 개요

2. Material Graph 작업

3. Blueprint Event Graph 작업

4. 프로젝트 기능 시연


1. 개요 

오늘도 다름없이 몬스터를 구현에 몰두했습니다. 몬스터 중 은신 스킬을 사용하는 친구를 만들어보자는 계획이 있었는데 잘 다뤄본적이 없는 머티리얼을 조작해야되기 때문에 조금 미루고 있었습니다만.. 오늘 마주하게 됐습니다. 먼저 C++로 작성한 은신 스킬을 담당 컴포넌트를 보겠습니다.

StealthSkillComponent.h
#pragma once

#include "CoreMinimal.h"
#include "Monster/Component/BaseSkillComponent.h"
#include "StealthModeSkillComponent.generated.h"

UCLASS(Blueprintable)
class PEPCCINE_API UStealthModeSkillComponent : public UBaseSkillComponent
{
	GENERATED_BODY()

public:
	UStealthModeSkillComponent();
	virtual void ActivateSkill() override;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill|Stealth")
	float StealthTime = 8.0f;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill|Stealth")
	float SpeedMultiplier = 1.2f;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Skill|Stealth")
	bool bIsStealthMode = false;

	// 블루프린트에서 머티리얼 Dissolve 및 복원 처리
	UFUNCTION(BlueprintImplementableEvent, Category = "Skill|Stealth")
	void OnStealthActivated();

	UFUNCTION(BlueprintImplementableEvent, Category = "Skill|Stealth")
	void OnStealthDeactivated();

	FTimerHandle StealthTimerHandle;

	virtual void BeginPlay() override;
	void BoostSpeed(float Amount);
	void ResetSpeed();
};
#include "Monster/Component/StealthModeSkillComponent.h"
#include "Monster/Component/MonsterStatComponent.h"
#include "GameFramework/Actor.h"

UStealthModeSkillComponent::UStealthModeSkillComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
}

void UStealthModeSkillComponent::BeginPlay()
{
    Super::BeginPlay();
}

void UStealthModeSkillComponent::ActivateSkill()
{
    if (bIsStealthMode || !IsCooldownOver())
    {
        UE_LOG(LogTemp, Warning, TEXT("Cooldown not over yet or already in Stealth Mode"));
        return;
    }

    bIsStealthMode = true;
    StartCooldown();
    PlaySkillMontage();
    BoostSpeed(SpeedMultiplier);

    // 블루프린트에서 머티리얼 Dissolve 적용
    OnStealthActivated();

    // StealthTime 후 속도 복구 타이머 설정
    GetWorld()->GetTimerManager().SetTimer(
        StealthTimerHandle,
        this,
        &UStealthModeSkillComponent::ResetSpeed,
        StealthTime,
        false
    );

    UE_LOG(LogTemp, Warning, TEXT("Stealth mode activated. Speed boosted by %.2f for %.2f seconds."), SpeedMultiplier, StealthTime);
}

void UStealthModeSkillComponent::BoostSpeed(float Amount)
{
    if (AActor* Owner = GetOwner())
    {
        if (UMonsterStatComponent* StatComp = Owner->FindComponentByClass<UMonsterStatComponent>())
        {
            StatComp->IncreaseSPD(Amount);

        }
    }
}

void UStealthModeSkillComponent::ResetSpeed()
{
    if (AActor* Owner = GetOwner())
    {
        if (UMonsterStatComponent* StatComp = Owner->FindComponentByClass<UMonsterStatComponent>())
        {
            StatComp->IncreaseSPD(1.0f / SpeedMultiplier);
            UE_LOG(LogTemp, Warning, TEXT("Stealth Mode End"));
        }
    }

    // 블루프린트에서 머티리얼 복원 처리
    OnStealthDeactivated();

    bIsStealthMode = false;
}

 

은신 모드 중 몬스터의 속도를 조금 빨라지게 해주자 하여 Speed 조작 관련 코드가 있습니다. 무엇보다 눈여겨봐야하는 것은 UFUNCTION 매크로 중 "Blueprintimplementable"로 선언된 OnStealthActivated()와 OnStealthDeactivated()입니다. 잠시 관련 지식을 짚고 넘어가겠습니다.

 

BlueprintImplementable 이란?

  • C++에서 정의한 함수나 메서드를 블루프린트에서 구현할 수 있도록 하는 UFUNCTION 매크로

다른 비슷한 UFUNCTION 매크로와의 차이

  • BlueprintCallable: C++에서 함수 정의. 블루프린트에서 호출 가능.
  • BlueprintPure: 상태를 변경하지 않고 계산만 하는 순수 함수. 입력값에만 의존해 값을 반환
  • BlueprintImplementable: C++에서 함수 시그니처만 정의, 로직은 블루프린트에서 구현

 

다시 한번 말하자면 은신 모드를 활성화하고 비활성화하는 동작은 결국 머티리얼 조작을 필요로 합니다. 하지만 머티리얼 조작은 Blueprint로 하는 것이 효과적입니다. 그래서 해당 동작 역할을 담당하는 두 함수의 정의를 Blueprint에서 해주겠다는 저의 의도가 담겨 있습니다. 참고로 UCLASS 선언에 Blueprintable (Blueprint 클래스로 상속 가능) 매크로를 추가하여야 OnStealthActivated()와 OnStealthDeactivated()가 제대로 Blueprint에 반영됩니다. 이제 머티리얼 부분을 보겠습니다.


2. 머티리얼 그래프 작업

 

Texture의 각 픽셀에는 RGB에 해당하는 값이 들어있고 그 값은 0~1에 사이 값입니다. 검은색에 가까울수록 RGB 값은 (0, 0, 0)에 가까워지고 반대로 흰색에 가까울수록 해당 픽셀에 들어있는 값은 (1, 1, 1)에 가깝다고 할 수 있습니다. 머티리얼 그래프 작업은 0~1까지의 값들을 사칙연산을 통해 조작하는 작업이라고 느꼈습니다. 이를 바탕으로 아래 노드들을 구성하였습니다.

 

우선 사라지는 효과를 담당하는 머티리얼 노드는 Opacity(투명도)와 Opacity Mask입니다. 특히 Opacity Mask는 마스크로 사용된 텍스처의 검은 부분을 투명하게 만듭니다.

 

1번 박스의 노드들을 살펴보겠습니다.

우선 일정하지 않은 패턴의 Noise Texture를 활용했고 검은색-회색-흰색들로만 분포된 픽셀들을 볼 수 있었습니다. 여기서 어떠한 값(Dissolve)를 빼게 되면 그 값만큼 픽셀 값이 작아져 점점 더 어두워질 것이고 결국 검은색에 해당하는 픽셀들이 많아집니다. 그래서 Dissolve 값을 조절하면 머티리얼의 투명한 부분의 양 또한 조절할 수 있게 됩니다. 그 결괏값을 Cheap Contrast 노드에 연결해 주어 간편하게 대비 효과를 줄 수 있었습니다. 그렇게 뚜렷한 경계가 표현된 결과를 Opacity Mask 노드에 연결해주어 Dissolve 값에 따라 머티리얼의 부분 부분을 서서히 투명하게 만들 수 있는 효과를 얻었습니다.

 

다음은 2번 박스입니다.


대비 효과를 준 결과를 1에서 빼주어 흑백 반전 시켜주었습니다. 그리고 특정 색(빨간색)에 해당하는 값을 곱해주었는데, 곱셈은 1에 가까운 값에 더 반영이 잘 되기 때문에 원래 검은색에 해당했던 부분들이 특정 색을 더 잘 띠게 됩니다. 그리고 다시 한번 특정 값(Emissive)를 곱해주면 값들이 더 커지게 됩니다. Emissive는 발광 효과를 낼 수 있고 최고 값이 없기 때문에 그 값이 더 커질수록 더 발광합니다.  

 

이렇게 만든 Emissive와 Opacity Mask를 활용하면 투명한 부분과 아직 Texture가 남아 있는 부분에 테두리 효과를 줄 수 있습니다. 비록 대비 효과를 주었지만, 비교적 0이나 1에 가깝지 않던, 회색에 가깝던 부분들에 특정 색이 입혀졌고 그 부분들이 미쳐 Opacity Mask로 투명화되지 않았기 때문입니다.

 

이렇게 연결한 노드를 Material Function으로 만들어 기존 Material에 연결해주었습니다.

 

3. Blueprint Event Graph 작업

아래와 같이 C++에서 BlueprintImplementable 매크로로 선언한 OnStealthActivated()와 OnStealthDeactivated() 함수를 구현했습니다. Dynamic Material Instance를 만들어 그 Material의 Dissolve 값을 Loop와 Timer를 사용하여 조절하였습니다.

 

4. 프로젝트 기능 시연

 

 

'TIL > C++와 UE' 카테고리의 다른 글

네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10
작성중  (0) 2025.02.26
Enemy(Monster) 클래스  (0) 2025.02.24
언리얼 엔진 AI 행동 제어 시스템  (0) 2025.02.17
파티클 시스템  (0) 2025.02.11

Enemy(Monster) 클래스

언리얼 거토
|2025. 2. 24. 21:38

목차

1.개요
2. Enemy(Monster) 클래스 구조
3. Monster 기능 컴포넌트
4. Monster 클래스별 컴포넌트 구성

5. 몬스터 컴포넌트 활용


1. 개요

프로젝트에서 적(몬스터) 구현 역할을 맡았고 특히 AI 구현은 복잡합니다. AI Controller, Behavior Tree, 그리고 Black Board에서는 각 몬스터의 기능을 활용하기 때문에 몬스터 클래스 구조부터 정리하고자 합니다. 

 

2. Enemy(Monster) 클래스 구조

몬스터 타입(일반, 보스) 별 각 기능을 구상하며 위와 같이 클래스를 설계하였습니다.

3. 몬스터 컴포넌트

  • 공격 관련
    • MonsterAttackComponent (일반 공격)
  • 상태 관련
    • MonsterStatComponent (스탯 관리)
    • HitReactionComponent (피격 반응)
    • BerserkModeComponent (광폭화 모드)
  • 스킬 관련
    • BaseSkillComponent (기본 스킬 추상 클래스)
      • JumpAttackComponent (점프 공격)
      • ChargeAttackComponent (돌진 공격)

MonstStatComponent(몬스터 스테이터스 컴포넌트) 코드 예시

//MonsterStatComponent.h
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MonsterStatComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class PEPCCINE_API UMonsterStatComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	UMonsterStatComponent();

protected:
	virtual void BeginPlay() override;

public:	
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat")
    float MaxHealth = 100.0f;
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stat")
    float CurrentHealth;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat")
    float Attack = 10.0f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat")
    float Defense = 5.0f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat")
    float Speed = 300.0f;

    UFUNCTION(BlueprintCallable, Category = "Stat")
    void DecreaseHealth(float Amount);
    UFUNCTION(BlueprintCallable, Category = "Stat")
    void IncreaseHealth(float Amount);
    UFUNCTION(BlueprintCallable, Category = "Stat")

    FORCEINLINE void IncreaseATK(float Amount) { Attack = Attack * Amount; }
    FORCEINLINE void IncreaseDEF(float Amount) { Defense = Defense * Amount; }
    FORCEINLINE void IncreaseSPD(float Amount){ Speed = Speed * Amount; }

    UFUNCTION(BlueprintPure, Category = "Stat")
    bool IsDead() const;
};
//MonsterStatComponent.cpp
#include "Monster/Component/MonsterStatComponent.h"

UMonsterStatComponent::UMonsterStatComponent()
{
	PrimaryComponentTick.bCanEverTick = false;
	CurrentHealth = MaxHealth;  // 초기 체력 = 최대 체력
}

void UMonsterStatComponent::BeginPlay()
{
	Super::BeginPlay();
}


void UMonsterStatComponent::DecreaseHealth(float Amount)
{
    float FinalDamage = FMath::Max(Amount - Defense, 1.0f);  // 방어력 적용, 최소 데미지 1
    CurrentHealth = FMath::Max(CurrentHealth - FinalDamage, 0.0f);

}

void UMonsterStatComponent::IncreaseHealth(float Amount)
{
    CurrentHealth = FMath::Min(CurrentHealth + Amount, MaxHealth);
}

bool UMonsterStatComponent::IsDead() const
{
    return CurrentHealth <= 0.0f;
}

 

4. 몬스터 클래스별 컴포넌트 구성

  • BaseMonster
    • MonsterAttackComponent
    • MonsterStatComponent
    • HitReactionComponent

일반 몬스터 ZombieCop의 컴포넌트

  • BossMonster
    • BerserkModeComponent
    • JumpAttackComponent
    • ChargeAttackComponent

보스 몬스터 JumpAttackComponent, BeserkModeComponent 추가 예시

5. 몬스터 컴포넌트 활용

  • AI Controller에서 사용
  • BehaviorTree 내 BTTask에서 호출


✏️ 스스로 Enemy AI의 이해를 돕기 위해 PPT를 활용하여 시각화해보았습니다. 어렵네요 ..ㅋㅋㅋ  

'TIL > C++와 UE' 카테고리의 다른 글

작성중  (0) 2025.02.26
머티리얼 조작  (0) 2025.02.25
언리얼 엔진 AI 행동 제어 시스템  (0) 2025.02.17
파티클 시스템  (0) 2025.02.11
게임 메뉴 UI 디자인  (0) 2025.02.07

목차

1. Behavior Tree(비헤이비어 트리)

2. Black Board(블랙보드)

3. AI Controller

4. NavMesh


1. Behavior Tree (비헤이비어 트리)

비헤이비어 트리는 AI의 행동을 결정하는 계층적 구조를 가진 트리. 각 노드(Node)에는 특정 역할이 있으며, AI는 루트(Root) 노드에서 시작하여 조건에 따라 적절한 행동을 수행.

1-1. 비헤이비어 트리의 주요 노드 유형

  • Root 노드: 트리의 시작점
  • Composite 노드 (합성 노드): 여러 개의 하위 노드를 가질 수 있음
    • Selector (선택자): 왼쪽부터 실행하며, 성공하는 노드를 찾으면 중단
    • Sequence (시퀀스): 왼쪽부터 순서대로 실행하며, 하나라도 실패하면 중단
  • Decorator 노드 (데코레이터 노드): 특정 조건을 검사하여 실행 여부를 결정
  • Task 노드 (태스크 노드): AI가 수행할 실제 동작 (ex. 이동, 공격)


2. Blackboard (블랙보드)

AI가 정보를 저장하고 공유하는 데이터 저장소.

비헤이비어 트리와 함께 사용되며, AI의 의사 결정에 필요한 변수(위치, 상태 등)를 관리.

2-1 블랙보드의 주요 기능

  • 키(Key) 시스템: 데이터 항목을 저장하는 변수 (ex. TargetLocation, HasEnemy)
  • AI가 환경 정보를 저장하고 활용 가능
  • 비헤이비어 트리 내에서 데코레이터 또는 태스크 노드를 통해 활용


3. AI Controller (AI 컨트롤러)

  • AI 캐릭터를 제어하는 핵심 클래스
  • AI가 어떤 논리를 따라야 하는지 결정하고, Behavior Tree를 실행하는 관리자.
    •   예: AI의 상태(Idle, Chase 등) 전환 관리

3-1. AIController의 주요 역할

  • 비헤이비어 트리 및 블랙보드 초기화
  • AI 이동 및 감지 기능 (Perception System)
  • Pawn(캐릭터) 제어 및 명령 전달

3-2. AI Controller 설정 과정

  1. AIController 클래스를 생성하고 AController를 상속
  2. UseBlackboard() 함수로 블랙보드를 초기화
  3. RunBehaviorTree() 함수로 비헤이비어 트리 실행
  4. Possess() 함수를 오버라이드하여 AI 캐릭터를 제어
#include "MyAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BehaviorTree.h"

void AMyAIController::BeginPlay()
{
    Super::BeginPlay();

    if (UseBlackboard(BlackboardAsset, Blackboard))
    {
        RunBehaviorTree(BehaviorTree);
        
        // 블랙보드에 초기 목표 위치 설정
        FVector RandomLocation(1000.0f, 500.0f, 0.0f);
        Blackboard->SetValueAsVector(TEXT("PatrolLocation"), RandomLocation);
    }
}
 

4. NavMesh (내비게이션 메시)

  • AI가 이동할 수 있는 경로를 결정하는 내비게이션 시스템.

4-1. NavMesh 설정 방법

  1. NavMesh Bounds Volume 추가
    • Modes 패널에서 Nav Mesh Bounds Volume을 배치
    • AI가 이동할 수 있는 영역을 지정
  2. RecastNavMesh 확인
    • World Settings에서 RecastNavMesh 활성화
  3. AI 이동을 위한 Pathfinding
    • GetNavAgentLocation() 및 FindPathToLocation() 등의 함수를 사용 가능

4-2. AI가 NavMesh를 활용해 이동하는 코드 예제

UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(this);
FNavLocation NavLocation;

if (NavSys->GetRandomPointInNavigableRadius(GetActorLocation(), 500.0f, NavLocation))
{
    MoveToLocation(NavLocation.Location);
}

AI가 500 유닛 반경 내에서 랜덤한 위치를 찾아 이동하는 방식.

 


참조: Behavior Tree Quick Start Guide

'TIL > C++와 UE' 카테고리의 다른 글

머티리얼 조작  (0) 2025.02.25
Enemy(Monster) 클래스  (0) 2025.02.24
파티클 시스템  (0) 2025.02.11
게임 메뉴 UI 디자인  (0) 2025.02.07
Pawn 클래스 캐릭터의 부드러운 움직임  (1) 2025.02.05

Animation Retargeting

언리얼 거토
|2025. 2. 14. 21:05

목차

1. 개요

2. 이론

3. 과정

4. 결과

5. 예시

 

1. 개요

✏️ 곧 프로젝트를 시작하는데 한정적인 3D 모델 또는 애니메이션으로 인해
원하는 콘셉트의 게임을 만들어내지 못하는 상황을 예방하기 위해 알아보게 되었습니다.
정말 마음에 드는 3D 캐릭터 모델이 있으면 쓰고 싶잖아요..?ㅎㅎ
저는 https://www.models-resource.com/에서 구한 임의의 모델을 사용하여
서로 다른 Skeleton 구조를 가진 Skeletal Mesh의 Animation을 Retarget 해보았습니다.

 

 

2. 이론

2.1 Animation Retargeting 이란?

  • 서로 다른 Skeletal Mesh 간에 애니메이션을 공유할 수 있도록 하는 기술
  • 캐릭터 모델이 다르지만 동일한 애니메이션을 재사용해야 할 때 사용

2.2 Skeletal Mesh와 Rig

  • Skeletal Mesh: 캐릭터의 뼈대 구조(Skeleton)와 스킨을 포함한 3D 모델
  • Rig: Skeletal Mesh에 애니메이션을 적용하기 위한 구조

2.3 Retargeting 방식

  • Different Skeleton Retargeting: 서로 다른 Skeletal Mesh 간 애니메이션을 적용할 때 사용
  • Same Skeleton Retargeting: 동일한 Skeletal 구조를 가진 캐릭터 간 애니메이션을 공유할 때 사용

2.4 IK Retargeter & IK Rig

  • IK Rig: Source(애니메이션이 제공된)와 Target Skeletal Mesh의 주요 뼈대를 매핑하는 도구
  • IK Retargeter: 매핑된 데이터를 활용해 애니메이션을 변환하는 기능 제공

 

3. 과정

3.1 Retargeting 준비

(1). Source 및 Target Skeletal Mesh 확인

위 3D Model 사이트에서 받은 록맨(메가맨) 3D Model 구성
Unreal 제공 Manny의 기본 Animation들을 위 록맨 친구에게 입히고 싶다

 

(2). IK Rig 설정 (Source와 Target 각각)

 

적용하고 싶은 Animation Sequence 우클릭 → Retarget Animations를 누르면 아래와 같은 창이 뜬다.

만약 Skeleton 구조가 같다면 Retargeter 항목의 Auto Generate Retargeter를 체크해도 알아서 Animation을 Retarget 하여 Export(추출) 할 수 있게 되지만, 위 상황에서는 임의의 Animation을 선택(더블 클릭) 하고 Viewport를 확인해 보면 Target(록맨)에는 아무런 미동이 없다. 그렇게 왼쪽 하단 메시지를 확인해 보면 Target에는 Root가 없다는 내용을 확인할 수 있었다. 이와 같이 자동으로 Animation을 추출할 수 없을 때는 Export Retarget Assets를 통해 IK Rig와 IK Retargeter를 생성하여 뼈대를 매핑해야 한다

 

Export Retarget Assets를 통해 생성한 IK 파일들

.

 

3.2 Source와 Target 매핑

(1). IK Rig에서 각 본의 대응 관계 설정

Target(록맨)의 IK Rig 파일에 들어가 보니 Retarget Chain 설정이 하나도 안 되어 있어,
Source의 IK Rig 파일을 참고하여 같은 Context(맥락)의 뼈대는 Retarget Chain 이름을 같게 설정해 주었다.

 

3.3 Retargeting 실행 및 애니메이션 적용

(1). IK Retargeter를 활용하여 애니메이션 변환

뼈대 설정이 끝나면 IK Retargeter를 열어 Export(추출)하고 싶은 Animation을 Asset Browswer에서 확인하며 하나씩 추출하면 된다.

 

추출한 Animation들. BS(Blend Space)파일은 추가로 만들어 주었다.

 

(3). 움직임 테스트 및 추가 보정

이제 위 추출이 잘 되었는지 Animation Sequence들을 확인해야한다. Transform 값이 조정이 필요한 경우, 값 조정 후

Create Asset → Create Animation → Current Animation → Preview Mesh로 새로 저장해주면 된다.


 

파티클 시스템

언리얼 거토
|2025. 2. 11. 19:32

1. 언리얼 파티클 시스템( Cascade vs. Niagara)

  1. Cascade
    • 오래된 파티클 시스템.
    • 모듈 기반으로 작동.
    • 직관적이지만 한계가 많음.
    • GPU 연산 지원이 제한적.
    • 커스텀 데이터 활용이 어려움.
  2. Niagara
    • 최신 파티클 시스템.
    • 노드 기반 비주얼 스크립팅.
    • GPU 연산 최적화 지원.
    • 커스텀 데이터 처리 가능.
    • 파라미터 공유, 모듈 재사용 용이.
    • Cascade보다 높은 확장성과 유연성 제공.

주요 차이점 정리

기능 SpawnEmitterAtLocation (Cascade) SpawnSystemAtLocation (Niagara)
사용하는 파티클 시스템 UParticleSystem* UNiagaraSystem*
GPU 최적화 지원
런타임 변수 변경 ✅ (SetVariableFloat 등 사용 가능)
커스텀 데이터 활용
확장성 및 성능 낮음 높음

 

2. C++에서 Niagara 활용

(1). Build.cs 파일에 Niagara 추가:

PublicDependencyModuleNames.AddRange(new string[] { "Niagara" });

💡 추가 후 에디터 창을 닫고 .uproject 파일 -> Generate Visual Studio project files 실행 필요

 

(2). 헤더 파일에서 필요한 라이브러리 포함:

#include "NiagaraFunctionLibrary.h" 
#include "NiagaraComponent.h"

 

3. NiagaraFuctionLibrary.h 주요 함수

(1). SpawnSystemAtLocation

static UNiagaraComponent* SpawnSystemAtLocation(
    const UObject* WorldContextObject,  // 월드 컨텍스트
    UNiagaraSystem* SystemTemplate,     // 생성할 Niagara 시스템
    FVector Location,                   // 생성 위치
    FRotator Rotation = FRotator::ZeroRotator, // 회전
    FVector Scale = FVector(1.0f),      // 스케일
    bool bAutoDestroy = true,           // 자동 제거 여부
    bool bAutoActivate = true,          // 자동 활성화 여부
    ENCPoolMethod PoolingMethod = ENCPoolMethod::None, // 풀링 방식
    bool bPreCullCheck = true           // 가시성 검사 여부
);
  • 특정 위치에서 Niagara 파티클 시스템을 생성

 

각 매개변수 설명

매개변수 설명
WorldContextObject Niagara 시스템을 생성할 월드 컨텍스트 객체 (보통 GetWorld() 사용)
SystemTemplate 생성할 Niagara 시스템 템플릿 (UNiagaraSystem*)
Location 파티클 생성 위치
Rotation 파티클 회전값 (기본값: FRotator::ZeroRotator)
Scale 파티클 크기 조정값 (기본값: FVector(1.0f))
bAutoDestroy true이면 파티클이 종료되면 자동 삭제 (false이면 관리 필요)
bAutoActivate true이면 생성 즉시 파티클 활성화
PoolingMethod 풀링 방식 선택 (기본값: ENCPoolMethod::None)
bPreCullCheck true이면 보이지 않는 파티클은 생성 안 함

 

풀링(Pooling) 방식 설명

None 풀링 안 함 (매번 새로 생성)
AutoRelease 사용 후 자동으로 풀에 반환
ManualRelease 수동으로 반환해야 함
ManualRelease_OnComplete 파티클이 끝나면 풀에 반환
  • 오브젝트 재사용을 통해 성능을 최적화할 수 있는 옵션.

 

(2). SetVariableFloat / SetVariableVec3 / SetVariableMaterial 등

// 파티클 크기,투명도, 속도 조절
NiagaraComp->SetVariableFloat(TEXT("MyFloatVar"), 10.0f);

// 파티클 이동 방향 설정
NiagaraComp->SetVariableVec3(TEXT("MyVecVar"), FVector(100.0f, 0.0f, 0.0f));

NiagaraComponent->SetVariableMaterial("MyMaterialVar", MyMaterialInstance);
  • Niagara 파티클 내부 변수 값을 실시간으로 변경.
  • Niagara에서 변수 등록하기
    1. Niagara System에서 Emitter 선택
    2. Parameters 창에서 새로운 Float 또는 Vector 변수 추가

 

(3).  OverrideSystemUserVariable

NiagaraComponent->OverrideSystemUserVariable("SpeedMultiplier", 2.5f);
  • Niagara 시스템의 사용자 정의 변수를 런타임에 동적으로 변경 가능.

 

(4). ResetSystem

NiagaraComponent->ResetSystem();
    • Niagara 시스템을 강제로 초기화하여 다시 실행 가능.

 

(5). SpawnSystemForPlayer

UNiagaraFunctionLibrary::SpawnSystemForPlayer(NiagaraSystem, PlayerController);
  • 특정 플레이어에 대한 파티클 시스템을 생성하는 함수.
  • 멀티플레이 환경에서 유용.
 

(6). SetNiagaraVariableBool

NiagaraComponent->SetNiagaraVariableBool("bIsActive", true);
  • 특정 Niagara 변수의 Bool 값을 설정.

 

(7) AdvanceSimulationByTime

// 생성된 후 3초 지난 상태로 시뮬레이션 강제 진행
NiagaraComp->AdvanceSimulationByTime(3.0f, 30);
  • Niagara 시스템의 시뮬레이션을 특정 시간만큼 미리 진행할 수 있음.
  • 즉, 파티클이 시작부터 일정 시간이 지난 상태로 나타나도록 만들 수 있음

 

4. Niagara 파티클 적용 예시

 


참조:

1. Using Niagara in C++

2. UNiagaraFunctionLibrary

3. UNiagaraComponent


✏️ 역시 시각적인 요소를 다루는게 재밌긴 하네요


 

'TIL > C++와 UE' 카테고리의 다른 글

Enemy(Monster) 클래스  (0) 2025.02.24
언리얼 엔진 AI 행동 제어 시스템  (0) 2025.02.17
게임 메뉴 UI 디자인  (0) 2025.02.07
Pawn 클래스 캐릭터의 부드러운 움직임  (1) 2025.02.05
비행체 움직임  (0) 2025.01.28

학생 성적 관리 시스템 (STL 활용)

언리얼 거토
|2025. 2. 10. 10:29

 

#include <iostream>
#include <map>
#include <vector>
#include <set>
#include <algorithm>

using namespace std;

// 학생 ID별 과목 점수 관리 (map 활용)
map<int, map<string, int>> studentScores;

// 과목별 학생 점수 관리 (vector 활용)
map<string, vector<pair<int, int>>> subjectScores;

// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)
map<string, set<pair<int, int>, greater<pair<int, int>>>> sortedSubjectScores;

// 학생 성적 추가 (점수 갱신 포함)
void addScore(int id, string subject, int score) {
    if (score < 0 || score > 100) {
        cout << "점수는 0~100 사이여야 합니다!\n";
        return;
    }

    // 기존 점수 갱신
    studentScores[id][subject] = score;

    // 과목별 점수 리스트에서 학생 ID가 있으면 갱신
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.first == id) {
            p.second = score;
            found = true;
            break;
        }
    }
    if (!found) {
        subjectScores[subject].push_back({ id, score });
    }

    // set에 점수 갱신 (기존 값 삭제 후 새 값 추가)
    sortedSubjectScores[subject].erase({ score, id });
    sortedSubjectScores[subject].insert({ score, id });

    cout << id << "번 학생 " << subject << " 점수 등록 완료: " << score << "점\n";
}

// 특정 학생의 전체 성적 조회
void viewStudentScores(int id) {
    if (studentScores.find(id) == studentScores.end()) {
        cout << "학생 " << id << "의 성적 정보가 없습니다.\n";
        return;
    }

    cout << "학생 " << id << " 성적 목록:\n";
    for (auto& it : studentScores[id]) {
        cout << "- " << it.first << ": " << it.second << "점\n"; // 과목명: 점수
    }
}

// 전체 학생의 과목별 평균 점수 출력
void printAverageScores() {
    if (subjectScores.empty()) {
        cout << "등록된 성적이 없습니다.\n";
        return;
    }

    cout << "과목별 평균 점수:\n";
    for (auto& subject : subjectScores) {
        double total = 0;
        int count = subject.second.size();
        for (auto& p : subject.second) {
            total += p.second;
        }
        double avg = total / count;
        cout << "- " << subject.first << ": " << avg << "점\n";
    }
}

// 특정 과목의 최고 점수 학생 조회 (동점자 포함)
void highestScoreStudent(string subject) {
    if (sortedSubjectScores.find(subject) == sortedSubjectScores.end() || sortedSubjectScores[subject].empty()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    int maxScore = sortedSubjectScores[subject].begin()->first;
    cout << subject << " 최고 점수: " << maxScore << "점 (학생 ID: ";

    bool first = true;
    for (auto& p : sortedSubjectScores[subject]) {
        if (p.first == maxScore) {
            if (!first) cout << ", ";
            cout << p.second;
            first = false;
        }
        else {
            break;
        }
    }
    cout << ")\n";
}

// 특정 과목에서 점수 구간에 해당하는 학생 조회
void searchScoreRange(string subject, int minScore, int maxScore) {
    if (subjectScores.find(subject) == subjectScores.end()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    cout << subject << "에서 " << minScore << " ~ " << maxScore << "점 학생 목록:\n";
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.second >= minScore && p.second <= maxScore) {
            cout << "- 학생 ID: " << p.first << ", 점수: " << p.second << "점\n";
            found = true;
        }
    }
    if (!found) cout << "해당 점수 구간에 있는 학생이 없습니다.\n";
}

// 메뉴 시스템
void menu() {
    while (true) {
        cout << "\n학생 성적 관리 시스템\n";
        cout << "1. 학생 성적 추가\n";
        cout << "2. 학생 성적 조회\n";
        cout << "3. 과목별 평균 점수 출력\n";
        cout << "4. 과목별 최고 점수 학생 조회\n";
        cout << "5. 특정 점수 구간 학생 조회\n";
        cout << "6. 종료\n";
        cout << "선택: ";

        int choice;
        cin >> choice;

        if (choice == 1) {
            int id, score;
            string subject;
            cout << "학생 ID, 과목, 점수를 입력하세요 (예: 1001 C++ 85): ";
            cin >> id >> subject >> score;
            addScore(id, subject, score);
        }
        else if (choice == 2) {
            int id;
            cout << "조회할 학생 ID를 입력하세요: ";
            cin >> id;
            viewStudentScores(id);
        }
        else if (choice == 3) {
            printAverageScores();
        }
        else if (choice == 4) {
            string subject;
            cout << "조회할 과목을 입력하세요: ";
            cin >> subject;
            highestScoreStudent(subject);
        }
        else if (choice == 5) {
            string subject;
            int minScore, maxScore;
            cout << "조회할 과목과 점수 구간을 입력하세요 (예: C++ 80 90): ";
            cin >> subject >> minScore >> maxScore;
            searchScoreRange(subject, minScore, maxScore);
        }
        else if (choice == 6) {
            cout << "프로그램을 종료합니다.\n";
            break;
        }
        else {
            cout << "올바른 숫자를 입력하세요!\n";
        }
    }
}

int main() {
    menu();
    return 0;
}

1.  코드 개요

기능 설명
학생 성적 추가 학생 ID, 과목, 점수를 저장 (동일 과목 입력 시 점수 갱신)
학생 성적 조회 특정 학생의 모든 성적을 조회
과목별 평균 점수 출력 각 과목의 평균 점수를 계산 후 출력
과목별 최고 점수 학생 조회 특정 과목에서 최고 점수를 받은 학생 ID 조회
특정 점수 구간 학생 조회 특정 과목에서 점수가 지정된 범위에 속하는 학생 조회

 

2. STL 컨테이너 선택 이유

컨테이너사용 목적설명

컨테이너 사용 목적 설명
map<int, map<string, int>> 학생별 성적 관리 학생 ID별로 과목과 점수를 저장 (studentScores)
map<string, vector<pair<int, int>>> 과목별 성적 관리 과목별로 학생 ID와 점수를 저장 (subjectScores)
map<string, set<pair<int, int>,
greater<pair<int, int>>>>
과목별 성적 정렬 점수 내림차순 + 학생 ID 오름차순 정렬 (sortedSubjectScores)

3. 코드 상세 설명

(1) 학생 성적 저장을 위한 자료구조

// 학생 ID별 과목 점수 관리 (학생 기준 조회)
map<int, map<string, int>> studentScores;

// 과목별 학생 점수 관리 (과목 기준 조회)
map<string, vector<pair<int, int>>> subjectScores;

// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)
map<string, set<pair<int, int>, greater<pair<int, int>>>> sortedSubjectScores;
 

studentScores

  • 학생 ID별로 여러 과목과 점수를 저장하는 map
  • 예: studentScores[1001]["C++"] = 85;

subjectScores

  • 과목별로 학생 ID와 점수를 저장하는 map
  • 예: subjectScores["C++"].push_back({1001, 85});

sortedSubjectScores

  • 특정 과목의 점수를 내림차순(큰 값 → 작은 값)으로 정렬하는 set
  • 예: sortedSubjectScores["C++"].insert({85, 1001});

(2) 학생 성적 추가 (addScore)

void addScore(int id, string subject, int score) {
    if (score < 0 || score > 100) {
        cout << "점수는 0~100 사이여야 합니다!\n";
        return;
    }

    // 기존 점수 갱신
    studentScores[id][subject] = score;

    // 과목별 점수 리스트에서 학생 ID가 있으면 갱신
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.first == id) {
            p.second = score;
            found = true;
            break;
        }
    }
    if (!found) {
        subjectScores[subject].push_back({id, score});
    }

    // set에 점수 갱신 (기존 값 삭제 후 새 값 추가)
    sortedSubjectScores[subject].erase({score, id});
    sortedSubjectScores[subject].insert({score, id});

    cout << id << "번 학생 " << subject << " 점수 등록 완료: " << score << "점\n";
}
  • 입력된 점수가 0~100 사이인지 검사
  • 학생 ID 기준으로 점수 갱신 (studentScores)
  • 과목별 성적 데이터 갱신 (subjectScores)
  • 기존 점수를 삭제하고 새로운 점수를 추가 (sortedSubjectScores)

(3) 특정 학생의 전체 성적 조회 (viewStudentScores)

void viewStudentScores(int id) {
    if (studentScores.find(id) == studentScores.end()) {
        cout << "학생 " << id << "의 성적 정보가 없습니다.\n";
        return;
    }

    cout << "학생 " << id << " 성적 목록:\n";
    for (auto& it : studentScores[id]) {
        cout << "- " << it.first << ": " << it.second << "점\n";
    }
}
  • 학생 ID가 존재하는지 확인
  • 학생 ID가 있으면 해당 학생의 모든 과목과 점수를 출력
  • 없으면 "데이터 없음" 메시지 출력

(4) 과목별 평균 점수 출력 (printAverageScores)

void printAverageScores() {
    if (subjectScores.empty()) {
        cout << "등록된 성적이 없습니다.\n";
        return;
    }

    cout << "과목별 평균 점수:\n";
    for (auto& subject : subjectScores) {
        double total = 0;
        int count = subject.second.size();
        for (auto& p : subject.second) {
            total += p.second;
        }
        double avg = total / count;
        cout << "- " << subject.first << ": " << avg << "점\n";
    }
}
  • 등록된 성적이 있는지 확인
  • 각 과목별로 모든 점수를 더하고 평균 계산
  • 평균 점수를 출력

(5) 특정 과목의 최고 점수 학생 조회 (highestScoreStudent)

void highestScoreStudent(string subject) {
    if (sortedSubjectScores.find(subject) == sortedSubjectScores.end() || sortedSubjectScores[subject].empty()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    int maxScore = sortedSubjectScores[subject].begin()->first;
    cout << subject << " 최고 점수: " << maxScore << "점 (학생 ID: ";

    bool first = true;
    for (auto& p : sortedSubjectScores[subject]) {
        if (p.first == maxScore) {
            if (!first) cout << ", ";
            cout << p.second;
            first = false;
        }
        else {
            break;
        }
    }
    cout << ")\n";
}

 

  • 해당 과목이 존재하는지 확인
  • 최고 점수를 찾고, 해당 점수를 받은 학생 ID 출력
  • 동점자도 함께 출력

4. 실행 예시

학생 성적 관리 시스템
1. 학생 성적 추가
2. 학생 성적 조회
3. 과목별 평균 점수 출력
4. 과목별 최고 점수 학생 조회
5. 특정 점수 구간 학생 조회
6. 종료
선택: 1
학생 ID, 과목, 점수를 입력하세요 (예: 1001 C++ 85): 1001 C++ 85
1001번 학생 C++ 점수 등록 완료: 85점

선택: 3
과목별 평균 점수:
- C++: 85점

선택: 4
조회할 과목을 입력하세요: C++
C++ 최고 점수: 85점 (학생 ID: 1001)

선택: 6
프로그램을 종료합니다.

'TIL > C++' 카테고리의 다른 글

Unreal Engine에서 Tick 사용법  (0) 2025.01.08
챕터 3-2: Unreal Engine 기본 개념  (2) 2025.01.07
챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02