오늘의 할일
1. 코드카타 정리
2. 팀프로젝트 정리
3. 다음 학습 목표
2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각 SUN,MON,TUE,WED,THU,FRI,SAT
입니다. 예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 "TUE"를 반환하세요.
제한 조건
2016년은 윤년입니다.
2016년 a월 b일은 실제로 있는 날입니다. (13월 26일이나 2월 45일같은 날짜는 주어지지 않습니다)
1월 1일은 총 일수가 1이고 배열에서 요일을 꺼낼 때 total_days % 7을 쓰는데 1을 7로 나누면 나머지는 1.
days[1] 칸에 1월 1일의 요일인 FRI가 있어야 맞아떨어짐
#include <string>
#include <vector>
using namespace std;
string solution(int a, int b) {
// 1월부터 12월까지 각 달이 며칠까지 있는지 만들기
// 2016년은 윤년 -> 2월은 29일
vector<int> months = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 요일을 순서대로 적어둔 배열
vector<string> days = {"THU", "FRI", "SAT", "SUN", "MON", "TUE", "WED"};
int total_days = 0; // 1월 1일부터 며칠째인지 누적할 변수
// 구하려는 달(a)의 이전 달까지의 날짜 전부 더하기
for (int i = 0; i < a - 1; i++) {
total_days += months[i];
}
// 마지막으로 이번 달 날짜(b)를 더하기
total_days += b;
// 총 일수를 7로 나눈 나머지로 요일 맞추기
return days[total_days % 7];
}
AI가 작성한 코드
#include <string>
using namespace std;
string solution(int a, int b) {
// 1. 누적합(Prefix Sum) 기법: 반복문(for)을 없애기 위해 '이전 달까지의 총 일수'를 미리 계산해 둡니다.
// 0번 인덱스는 비워두고, 1월은 0일, 2월은 31일, 3월은 60일(31+29)... 순서입니다.
constexpr int prefix_sum[] = {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
// 2. vector 대신 가벼운 C스타일 정적 배열을 사용하여 메모리 할당(이사)을 원천 차단합니다.
constexpr const char* days[] = {"THU", "FRI", "SAT", "SUN", "MON", "TUE", "WED"};
// 3. 반복문 없이 단 한 번의 사칙연산으로 끝냅니다.
return days[(prefix_sum[a] + b) % 7];
}
1년은 무조건 12달로 고정되어 있으므로, 개발자가 '미리 더해놓은 값'을 배열에 적어두면 컴퓨터가 덧셈을 여러 번 할 필요 없이 한 번에 값을 쏙 빼옵니다. (시간 복잡도 )
달의 개수(12개)와 요일(7개)이 절대 변하지 않는다는 것을 알고 있으므로, 가벼운 기본 배열([])과 C스타일 문자열(const char*)을 사용하여 메모리 낭비를 0으로 만들었습니다.
constexpr (컴파일 타임 상수):
배열 앞에 붙은 constexpr은 프로그램이 실행될 때마다 배열을 새로 만드는 것이 아니라, 프로그램을 설치(컴파일)할 때 아예 데이터를 고정시켜 놔라는 명령. 서버에 요청이 수백만 번 들어와도 이 배열들은 다시 생성되지 않고 그 자리에 빛의 속도로 존재.
너무 빨리 끝난 탓에 점프스퀘어라는 구현 항목을 얻게 되었다,,! (행복합니다..ㅎ)
일단 구현해야하는 항목은 총 13가지인데 윗 5가지가 필수 항목이다
시체 부활 전투담당+몬스터(ai)담당
복도에 시체가 있다 -> 플레이어는 안전하다고 생각하고 근처로 지나가거나 아이템을 파밍한다 -> 시체가 일어나서 달려든다.
아이템 미끼형 전투담당+몬스터(ai)담당 + 아이템담당
중요 아이템을 먹는다 -> 상호작용하는 순간 뒤에서 좀비출현
퍼즐 완료 직후 공격 전투담당+몬스터(ai)담당+ 퍼즐담당
퍼즐을 푼다 -> 문 열림 -> 성공효과음 -> 플레이어 안심 -> 뒤에서 소리 -> 적 추격 시작
사물함/캐비닛 은신 모두다
플레이어가 캐비닛에 숨는다-> 적이 바로앞까지와서 플레이어를 찾는 행동을 한다
우는 천사(공격불가 도망만 가능) 전투담당+몬스터(ai)담당
플레이어가 적을 바라본다 -> 적이 가만히 있는다 플레이어가 다른곳을 본다 -> 적이 빠르게 다가온다
좁은곳에서 이동시 등장하는 점프스케어 전투담당+몬스터(ai)담당
비좁은곳,작은 구멍을 지나간다 -> 바로 앞에서 괴물이 모습을 드러내고 사라짐
창문 너머 공포의 존재 전투담당+몬스터(ai)담당
진료실 유리창 너머에 누군가 지나가는 형체가 보임
천장 환풍구/ 문 돌파형 전투담당+몬스터(ai)담당+플레이어 시각효과x 청각효과o
문,환풍구 근처에 접근한다 -> 문,환풍구를 부수면서 좀비가 달려든다.
뒤돌아보기형 전투담당+몬스터(ai)담당
벽에 피로 뒤를 봐라는 메세지를 발견한다 -> 플레이어가 뒤를 돌면 귀신이나 깝툭튀 요소가 등장한다 or 불이꺼짐 or 비명소리
시야 밖 환경 변화형 전투담당 + 맵/레벨 담당 추가사항(시간 남으면)
플레이어가 방을 본다 -> 다른곳을 보다가 다시 본다 -> 방 구조,물건 위치,갯수 등이 바뀐다
정전 -> 등장형 전투담당+몬스터(ai)담당
복도-> 불깜빡 -> 멀리서 귀신 등장 -> 불꺼짐 -> 불켜짐 -> 중간거리 귀신 -> 불꺼짐 -> 불켜짐 -> 바로 앞 귀신 -> 소멸
소리를 이용한 가짜 점프 스케어 전투담당+맵/레벨 담당
뒤에서 쾅하고 문닫히는 소리 -> 플레이어가 급하게 뒤돌아봄 -> 아무것도 없음 아무것도 없는 폐병원에서 소녀의 콧노래 소리가 들림 -> 노래가 들리는 곳으로 가봤지만 아무것도 없음
정면 돌진형 전투담당+몬스터(ai)담당 문을 열거나 코너를 돌았을 때 적이 바로 앞으로 달려든다
UBoxComponent를 가진 AJumpScareTrigger 액터OnComponentBeginOverlap), Enum 타입에 따라 각기 다른 점프 스케어 로직을 실행하도록 switch문으로 분기bIsFakeDead 상태 변수를 추가 -> 초기에는 누워있는 애니메이션을 재생하고 AI Controller를 비활성화해 두었다가, 트리거 발동 시 일어나는 모션와 함께 AI 활성화 -> 구현완료!!PlayerLocation - PlayerForwardVector * Distance)으로 계산하여 등 뒤에서 나타나게 하기 -> 구현완료!!IsHiding() 상태가 true가 되면, AI의 비헤이비어 트리에서 추적 노드 멈추고 캐비닛 주변을 서성이는 수색 태스크 노드로 전환하도록 분기점 만들어놓기 -> 구현완료!!WasRecentlyRendered() 함수를 활용해서 화면에 렌더링 중일 때는 AI의 이동 속도를 0으로 만들기LineTrace나 렌더링 여부를 검사해 타이밍을 잡아야함bIsFakeDead 상태 변수를 추가 -> 초기에는 누워있는 애니메이션을 재생하고 AI Controller를 비활성화해 두었다가, 트리거 발동 시 일어나는 모션와 함께 AI 활성화public:
// 이 좀비가 죽은 척 대기 중인지 여부
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "JumpScare")
bool bIsFakeDead = false;
// 일어날 때 재생할 애니메이션
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "JumpScare")
class UAnimMontage* GetUpMontage;
// 트리거가 밟히면 호출
UFUNCTION(BlueprintCallable, Category = "JumpScare")
void WakeUp();
void AZombieCharacter::WakeUp()
{
// 이미 일어났거나, 애초에 일어날 시체가 아니면 무시
if (!bIsFakeDead) return;
bIsFakeDead = false;
// 일어나는 애니메이션 재생
if (GetUpMontage)
{
PlayAnimMontage(GetUpMontage);
}
// 비명 소리 재생
// if (ScreamSound) UGameplayStatics::PlaySoundAtLocation(...);
// 여기서 AI Controller를 활성화하거나 블랙보드 상태를 변경하도록 신호
UE_LOG(LogTemp, Warning, TEXT("좀비가 깨어납니다."));
}
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "JumpScareTrigger.generated.h"
class UBoxComponent;
class AZombieCharacter;
UCLASS()
class DEAD_HOSPITAL_API AJumpScareTrigger : public AActor
{
GENERATED_BODY()
public:
AJumpScareTrigger();
protected:
// 플레이어가 밟을 투명한 충돌 박스
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Trigger")
UBoxComponent* TriggerBox;
// 이 트리거를 밟았을 때 깨울 대상 좀비
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger")
AZombieCharacter* TargetZombie;
// 플레이어가 박스에 닿았을 때 실행될 이벤트
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
#include "JumpScareTrigger.h"
#include "Components/BoxComponent.h"
#include "../Player/PlayerCharacter.h"
#include "../AI/ZombieCharacter.h"
AJumpScareTrigger::AJumpScareTrigger()
{
// 박스 컴포넌트 생성 및 설정
TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
RootComponent = TriggerBox;
// 플레이어와만 충돌하도록 설정
TriggerBox->SetCollisionProfileName(TEXT("Trigger"));
// 오버랩 이벤트 바인딩
TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AJumpScareTrigger::OnOverlapBegin);
}
void AJumpScareTrigger::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// 밟은 대상이 플레이어인지 확인
APlayerCharacter* Player = Cast<APlayerCharacter>(OtherActor);
if (Player != nullptr)
{
// 연결된 좀비가 있고, 그 좀비가 죽은 척 중이라면
if (TargetZombie && TargetZombie->bIsFakeDead)
{
TargetZombie->WakeUp();
// 한 번만 발생해야 하므로 트리거 스스로 파괴
Destroy();
}
}
}
콘텐츠 드로어에서 컴파일된 JumpScareTrigger 를 찾아 드래그해서 맵에(복도 등) 배치
투명한 트리거 박스(TriggerBox)의 크기를 조절하여 플레이어가 밟기 좋게 키워주기
그 근처에 좀비(시체 역할)를 배치
배치된 좀비를 클릭하고, 디테일 패널에서 bIsFakeDead를 체크(true)한 뒤 일어날 때 쓸 애니메이션 넣기
배치된 트리거를 클릭 -> 디테일 패널에서 TargetZombie 칸 옆에 있는 스포이드 아이콘을 눌러서 아까 배치한 좀비를 서로 연결
AItemBaitSpawner) 생성#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemBaitSpawner.generated.h"
class AZombieCharacter;
class APlayerCharacter;
UCLASS()
class DEAD_HOSPITAL_API AItemBaitSpawner : public AActor
{
GENERATED_BODY()
public:
AItemBaitSpawner();
// 좀비의 종류
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "JumpScare")
TSubclassOf<AZombieCharacter> ZombieClassToSpawn;
// 플레이어 등 뒤 얼만큼 떨어져서 스폰될지 (기본값 3미터)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "JumpScare")
float SpawnDistance = 300.0f;
// 아이템 담당자가 아이템 습득 시 호출할 스위치 함수
UFUNCTION(BlueprintCallable, Category = "JumpScare")
void TriggerScare(APlayerCharacter* TargetPlayer);
};
#include "ItemBaitSpawner.h"
#include "../AI/ZombieCharacter.h"
#include "PlayerCharacter.h"
#include "Engine/World.h"
AItemBaitSpawner::AItemBaitSpawner()
{
PrimaryActorTick.bCanEverTick = false;
}
void AItemBaitSpawner::TriggerScare(APlayerCharacter* TargetPlayer)
{
// 플레이어 정보가 없거나, 에디터에서 스폰할 좀비 클래스를 안 넣었으면 무시
if (TargetPlayer == nullptr || ZombieClassToSpawn == nullptr) return;
// 플레이어의 등 뒤 위치 계산
FVector PlayerLocation = TargetPlayer->GetActorLocation();
FVector PlayerForward = TargetPlayer->GetActorForwardVector();
// 등 뒤 좌표 = 플레이어 위치 - (플레이어 앞방향 벡터 * 거리)
FVector SpawnLocation = PlayerLocation - (PlayerForward * SpawnDistance);
// 높이는 플레이어와 동일하게 맞춰서 공중에 뜨거나 땅에 박히지 않게 함
SpawnLocation.Z = PlayerLocation.Z;
// 좀비가 스폰되자마자 플레이어를 바라보도록 회전값 계산
FRotator SpawnRotation = (PlayerLocation - SpawnLocation).Rotation();
// 월드에 좀비 스폰 설정 (벽에 끼면 살짝 밀어내서라도 무조건 소환)
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
// 좀비 소환
AZombieCharacter* SpawnedZombie = GetWorld()->SpawnActor<AZombieCharacter>(
ZombieClassToSpawn,
SpawnLocation,
SpawnRotation,
SpawnParams
);
if (SpawnedZombie)
{
UE_LOG(LogTemp, Warning, TEXT("등 뒤에 좀비가 소환되었습니다."));
}
}
ItemBaitSpawner 액터를 보이지 않게 배치ZombieClassToSpawn에 소환할 좀비 블루프린트를 선택TriggerScare() 함수를 실행하도록 블루프린트나 C++에서 연결Event BeginPlay에서 어그로가 바로 끌리게 세팅일단 두개(+1 = 총 세개) 완성했으니 나머지 10개.. 할 수 있을진 모르겠지만 해보는 걸로