오늘의 할일
1. 코드카타 정리
2. 팀프로젝트 정리
3. 다음 학습 목표
문자열 s가 주어졌을 때, s의 각 위치마다 자신보다 앞에 나왔으면서, 자신과 가장 가까운 곳에 있는 같은 글자가 어디 있는지 알고 싶습니다.
예를 들어, s="banana"라고 할 때, 각 글자들을 왼쪽부터 오른쪽으로 읽어 나가면서 다음과 같이 진행할 수 있습니다.
b는 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.
a는 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.
n은 처음 나왔기 때문에 자신의 앞에 같은 글자가 없습니다. 이는 -1로 표현합니다.
a는 자신보다 두 칸 앞에 a가 있습니다. 이는 2로 표현합니다.
n도 자신보다 두 칸 앞에 n이 있습니다. 이는 2로 표현합니다.
a는 자신보다 두 칸, 네 칸 앞에 a가 있습니다. 이 중 가까운 것은 두 칸 앞이고, 이는 2로 표현합니다.
따라서 최종 결과물은 [-1, -1, -1, 2, 2, 2]가 됩니다.
문자열 s이 주어질 때, 위와 같이 정의된 연산을 수행하는 함수 solution을 완성해주세요.
#include <string>
#include <vector>
using namespace std;
vector<int> solution(string s) {
vector<int> answer;
// a부터 z까지 알파벳이 가장 마지막으로 발견된 인덱스를 적어둘 배열
// 아직 한 번도 보지 못했으니까 처음에는 다 -1로 해두기
vector<int> last_position(26, -1);
for (int i = 0; i < s.length(); i++) {
// 현재 글자가 26칸 중 몇 번째 칸인지 계산 (예: a는 0, b는 1)
int alphabet_index = s[i] - 'a';
// last_position 확인했는데 -1이면 (처음 나온 글자)
if (last_position[alphabet_index] == -1) {
answer.push_back(-1);
}
// last_position에 숫자가 적혀 있다면 (예전에 나온 적이 있는 글자)
else {
// 현재 i에서 예전에 발견됐던 위치를 빼면 몇 칸 떨어져 있는지 거리계산
int distance = i - last_position[alphabet_index];
answer.push_back(distance);
}
// 똑같은 글자가 또 나올 경우
// last_position에 적힌 글자의 마지막 발견 위치를 'i'로 업데이트
last_position[alphabet_index] = i;
}
return answer;
}
AI가 작성한 코드
#include <string>
#include <vector>
using namespace std;
vector<int> solution(string s) {
vector<int> answer;
// 1. 메모리 재할당(이사) 방지: 들어올 데이터의 크기를 아니까 미리 자리를 깔아둡니다.
answer.reserve(s.length());
// 알파벳 26개의 위치를 기억할 배열
vector<int> last_pos(26, -1);
for (int i = 0; i < s.length(); i++) {
int char_idx = s[i] - 'a';
// 2. if ~ else 문을 없애고 삼항 연산자( ? : )로 한 줄로 압축합니다.
// 해석: 처음 나온 글자(-1)면? -1을 넣고 : 아니면? (현재 위치 - 예전 위치)를 넣어라.
answer.push_back(last_pos[char_idx] == -1 ? -1 : i - last_pos[char_idx]);
// 현재 위치로 수첩 갱신
last_pos[char_idx] = i;
}
return answer;
}
answer.reserve(s.length());
문자열 길이가 10,000자라면, 이 코드가 없을 때 answer 배열은 자리가 꽉 찰 때마다 더 큰 메모리를 발급받고 기존 데이터를 복사해 이사하는 과정을 14번이나 겪게 됨.
하지만 시작하자마자 reserve로 "10,000개 들어갈 거니까 미리 비워둬!"라고 선언하면, 복사 연산이 '0'이 되어 속도가 비약적으로 상승
if-else 대신 삼항 연산자(? :) 사용
정답 바구니에 데이터를 넣는다(push_back)는 하나의 명확한 목적을 가진 로직이 위에서 아래로 끊김 없이 흐르게 만들어 가독성 극대화
Hash Map(unordered_map)을 쓰지 않은 이유
"알파벳(Key)마다 위치(Value)를 저장하니까 해시 맵을 써야 하는 거 아냐?"라고 생각하기 쉽습니다. 하지만 다뤄야 할 Key가 '소문자 알파벳(딱 26개)'으로 고정되어 있다면 무겁고 복잡한 해시 맵 대신 그냥 26칸짜리 단순 배열을 쓰는 것이 압도적으로 빠릅니다. 해시 함수를 계산할 필요 없이 메모리 주소로 직행하기 때문입니다(Cache Friendly).
가장 핵심 기능부터 살을 붙여나가는 순서로 코딩
탄약 제한(30발)이나 쿨타임은 나중에 하고 일단 마우스를 클릭했을 때 데미지가 들어가는지부터 확인하는 것부터 할 수 있게 만들기
LineTraceSingleByChannel 쏘는 로직 작성. 처음에는 DrawDebugLine 함수를 써서 쏜 궤적이 레이저로 화면에 보이게 만들면 테스트하기 편할 거 같음.총알이 계에에에속 나가는 것 막고 게임 규칙 세우기
ResetPrimaryAttack): 공격 직후 bCanPrimaryAttack = false로 바꾸고 언리얼 타이머(GetWorld()->GetTimerManager().SetTimer) 달아서 공격 속도 제어.ReloadWeapon & FinishReload): 공격하면 currentAmmo를 1씩 깎고, 0이 되면 공격 못하게 막기. R키를 눌러 예비 탄약(totalAmmo)에서 탄창으로 총알을 옮기는 로직.GetCurrentAmmo(), GetTotalAmmo() 함수 완성해서 UI 담당자가 화면에 총알 갯수를 띄울 수 있게 해주기SpecialAttack): 헤드샷 모드(버프 스위치 bIsHeadshotModeActive)를 켜는 로직 추가, PrimaryAttack의 데미지 판정 조건문을 작성해주면서 즉사(9999 데미지) 처리. 투사체 스킬 ApplyAoEDamage까지 구현.FinishReload), 인벤토리 담당자가 만든 ConsumeItem 함수를 호출해서 가방 안의 실제 총알 아이템 데이터를 -처리 해주기대용량 탄창, 개머리판, 견착 등 파츠 생각하기
일단 1단계 뼈대부터 작업 시작!!!
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CombatComponent.generated.h"
USTRUCT(BlueprintType)
struct FWeaponData
{
GENERATED_BODY()
public:
// --- 탄약 스탯 ---
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
int32 CurrentAmmo; // 현재 탄창의 총알 수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
int32 MagazineCapacity; // 탄창 최대 용량
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
int32 TotalAmmo; // 보유 중인 전체 예비 총알
// --- 공격 스탯 ---
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float BaseDamage; // 기본 공격력
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float AttackSpeed; // 공격 속도
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float AttackRange; // 사거리
// --- 스킬 스탯 ---
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float AoERadius; // 범위 공격 반경
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Stats")
float SpecialCooldownTime; // 특수 스킬 쿨타임
FWeaponData()
{
CurrentAmmo = 30;
MagazineCapacity = 30;
TotalAmmo = 90;
BaseDamage = 25.0f;
AttackSpeed = 0.2f; // 0.2초마다 1발 발사
AttackRange = 5000.0f;
AoERadius = 300.0f;
SpecialCooldownTime = 10.0f;
}
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ASSETMIGRATE_API UCombatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCombatComponent();
protected:
virtual void BeginPlay() override;
public:
// 무기 데이터 및 상태 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
FWeaponData CurrentWeapon; // 현재 장착 중인 무기 데이터 참조
UPROPERTY(BlueprintReadOnly, Category = "Combat|State")
bool bCanPrimaryAttack; // 기본 공격 가능 여부 - 연사 쿨타임 체크
UPROPERTY(BlueprintReadOnly, Category = "Combat|State")
bool bCanSpecialAttack; // 스킬 사용 가능 여부 - 스킬 쿨타임 체크
UPROPERTY(BlueprintReadOnly, Category = "Combat|State")
bool bIsReloading; // 재장전 중인지 여부 - 장전 중 공격 불가능
UPROPERTY(BlueprintReadOnly, Category = "Combat|State")
bool bIsHeadshotModeActive; // 헤드샷 스킬 모드
// 외부 시스템 연동용 Getter 함수 (UI, 인벤토리 등)
UFUNCTION(BlueprintPure, Category = "Combat|UI")
int32 GetCurrentAmmo() const; // 총알 갯수
UFUNCTION(BlueprintPure, Category = "Combat|UI")
int32 GetTotalAmmo() const; // 보유 중인 전체 예비 총알 갯수
UFUNCTION(BlueprintPure, Category = "Combat|UI")
float GetWeaponDamage() const; // 총 데미지
};
#include "CombatComponent.h"
UCombatComponent::UCombatComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UCombatComponent::BeginPlay()
{
Super::BeginPlay();
bCanPrimaryAttack = true;
bCanSpecialAttack = true;
bIsReloading = false;
bIsHeadshotModeActive = false;
}
// Getter 함수 구현
int32 UCombatComponent::GetCurrentAmmo() const
{
return CurrentWeapon.CurrentAmmo;
}
int32 UCombatComponent::GetTotalAmmo() const
{
return CurrentWeapon.TotalAmmo;
}
float UCombatComponent::GetWeaponDamage() const
{
// 헤드샷 모드일 경우 즉사 데미지 수치 리턴, 아닐 경우 기본 데미지 리턴
if (bIsHeadshotModeActive)
{
return 9999.0f;
}
return CurrentWeapon.BaseDamage;
}
플레이어 캐릭터 블루프린트에 방금 만든 Combat Component를 Attach
디테일 패널에 Weapon Stats 카테고리 - 장탄수나 데미지 조절 가능
마우스를 클릭하면 레이저가 나가고 맞은 적에게 데미지가 들어간다
class ASSETMIGRATE_API UCombatComponent : public UActorComponent
{
public:
// 기본 공격
UFUNCTION(BlueprintCallable, Category = "Combat|Action")
void PrimaryAttack();
// 타격 처리 및 데미지 전달
UFUNCTION(BlueprintCallable, Category = "Combat|Action")
void ProcessHit(AActor* HitTarget, float AppliedDamage);
#include "CombatComponent.h"
#include "Kismet/GameplayStatics.h" // ApplyDamage 쓰기 위해 추가
#include "DrawDebugHelpers.h" // 레이저 선 그리기 위해 추가
void UCombatComponent::PrimaryAttack()
{
AActor* Owner = GetOwner();
if (Owner == nullptr) return;
// 플레이어의 카메라 위치와 회전값 가져오기
FVector EyeLocation;
FRotator EyeRotation;
Owner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
// 총알이 날아갈 도착 지점 계산 (현재 위치 + 시선 방향 * 사거리)
FVector ShotDirection = EyeRotation.Vector();
FVector TraceEnd = EyeLocation + (ShotDirection * CurrentWeapon.AttackRange);
FHitResult HitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(Owner); // 내가 쏜 총에 내가 맞지 않도록 예외 처리
// 레이캐스트(보이지 않는 선) 발사
bool bSuccess = GetWorld()->LineTraceSingleByChannel(
HitResult,
EyeLocation,
TraceEnd,
ECC_Visibility, // 눈에 보이는 물체들과 충돌
QueryParams
);
// 테스트용 빨간색 궤적 그리기 (에디터에서 눈으로 확인하기 위함, 2초간 유지)
DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 2.0f, 0, 2.0f);
if (bSuccess)
{
// 맞은 지점에 초록색 점 표시
DrawDebugPoint(GetWorld(), HitResult.ImpactPoint, 20.0f, FColor::Green, false, 2.0f);
// 어떤 액터를 맞췄는지, 데미지는 얼마를 줘야 하는지 계산해서 ProcessHit로 넘김
AActor* HitActor = HitResult.GetActor();
float DamageToApply = GetWeaponDamage(); // 1단계에서 만든 함수 재사용 (헤드샷 모드면 9999 반환)
ProcessHit(HitActor, DamageToApply);
}
}
void UCombatComponent::ProcessHit(AActor* HitTarget, float AppliedDamage)
{
if (HitTarget)
{
// 데미지 전달 함수 호출
UGameplayStatics::ApplyDamage(
HitTarget, // 맞는 대상
AppliedDamage, // 데미지 수치
GetOwner()->GetInstigatorController(), // 공격한 사람의 컨트롤러
GetOwner(), // 공격한 사람(무기 또는 플레이어)
UDamageType::StaticClass()
);
// 출력 로그 창에 맞은 대상의 이름과 들어간 데미지를 글자로 띄움 (확인용)
UE_LOG(LogTemp, Warning, TEXT("타격 성공! 맞은 대상: %s, 데미지: %f"), *HitTarget->GetName(), AppliedDamage);
}
}
플레이어 블루프린트의 마우스 좌클릭(Left Mouse Button) 이벤트에 컴포넌트를 드래그해 놓은 뒤 PrimaryAttack 함수를 연결
사거리 없게 수정
코드 작성한 거 에디터에서 확인
대용량 탄창, 개머리판, 견착 등 파츠 생각하기