오늘의 할일
1. 코드카타 정리
2. 세션 정리
매개변수 left부터 right까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 하는 함수
-> 제일 먼저 떠오른 게 count를 하나씩 늘려가서 그 count로 짝수 홀수를 가려내자 라고 생각함
#include <string>
#include <vector>
using namespace std;
int solution(int left, int right) {
int answer = 0;
for (int i = left; i <= right; i++) {
int count = 0;
for (int j = 1; j <= i; j++) {
if (i % j == 0) {
count++;
}
}
if (count % 2 == 0) {
answer += i;
} else {
answer -= i;
}
}
return answer;
}
AI가 만든 코드
#include <cmath>
using namespace std;
int solution(int left, int right) {
int answer = 0;
// 후위 연산자(i++) 대신 전위 연산자(++i)를 사용하여 미세한 성능 최적화
for (int i = left; i <= right; ++i) {
int root = sqrt(i);
// 삼항 연산자를 통해 완전제곱수면 빼고(-i), 아니면 더합니다(+i)
answer += (root * root == i) ? -i : i;
}
return answer;
}
💡 이 방식을 선택한 3가지 이유
1. 압도적인 성능 (O(N)): 약수를 일일이 세지 않게 하기
2. 삼항 연산자 사용: 코드가 위아래로 길어지는 if-else 블록 대신 (조건) ? 참일 때 값 : 거짓일 때 값 형태의 삼항 연산자를 사용하여, 변수 answer에 값이 어떻게 누적되는지 한 줄로 보여줄 수 있음
3. ++i를 쓰는 습관: for문에서 흔히 쓰는 i++ 대신 ++i를 사용. 기본 정수형(int)에서는 속도 차이가 거의 없지만, 나중에 무거운 객체(이터레이터 등)를 반복문으로 돌릴 때 ++i를 쓰는 것이 복사본을 만들지 않아 메모리 측면에서 훨씬 유리.
IMC 활성화
Local Player - Local Player Subsystem - IMC 활성화
Loacal Player = 사용자
class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue; // Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
protected:
// 입력 바인딩을 처리할 함수
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// IA_Move와 IA_Jump 등을 처리할 함수 원형
// Enhanced Input에서 액션 값은 FInputActionValue로 전달
UFUNCTION() //입력 바인딩 함수->안붙이면 바인딩 실패됨
void Move(const FInputActionValue& value);
UFUNCTION()
void StartJump(const FInputActionValue& value);
UFUNCTION()
void StopJump(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
UFUNCTION()
void StartSprint(const FInputActionValue& value);
UFUNCTION()
void StopSprint(const FInputActionValue& value);
};
#include "SpartaCharacter.h"
#include "SpartaPlayerController.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
void ASpartaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Enhanced InputComponent로 캐스팅
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
// IA를 가져오기 위해 현재 소유 중인 Controller를 ASpartaPlayerController로 캐스팅
if (ASpartaPlayerController* PlayerController = Cast<ASpartaPlayerController>(GetController())) {
if (PlayerController->MoveAction) {
// IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
EnhancedInput->BindAction( //이벤트랑 함수를 연결하는 핵심 코드
PlayerController->MoveAction, //IA 가져온 것
ETriggerEvent::Triggered, //키가 눌려서 이벤트가 발생했을 때
this, //호출된 함수의 객체
&ASpartaCharacter::Move //Move를 연결시키겠다
);
}
if (PlayerController->JumpAction) {
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StartJump
);
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StopJump
);
}
if (PlayerController->LookAction) {
EnhancedInput->BindAction(
PlayerController->LookAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::Look
);
}
if (PlayerController->SprintAction) {
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StartSprint
);
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StopSprint
);
}
}
}
}
어제 만들었던 컨트롤러와 IA, IMC를 바탕으로 코드 짜기(2D, bool 등)
//Sprint 함수 만들 때 필요한 변수 정의
private:
float NormalSpeed; // 기본 걷기 속도
float SprintSpeedMultiplier; // "기본 속도" 대비 몇 배로 빠르게 달릴지 결정
float SprintSpeed; // 실제 스프린트 속도
#include "SpartaCharacter.h"
#include "SpartaPlayerController.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
ASpartaCharacter::ASpartaCharacter()
{
CameraComp->bUsePawnControlRotation = false;
//변수 할당하기(생성자)
NormalSpeed = 600.0f;
SprintSpeedMultiplier = 1.7f; //기본 속도의 1.7배
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
FInputActionValue::Get<FVector2D>() : 2차원 벡터 형태 입력받기void ASpartaCharacter::Move(const FInputActionValue& value) {
if (!Controller) return;
const FVector2D MoveInput = value.Get<FVector2D>();
if (!FMath::IsNearlyZero(MoveInput.X)) {
//AddMovementInput(방향, 크기)
//방향 : 월드 좌표 기준 이동 방향(Forward, Right 등)
//크기 : 이동 스케일(속도)
//내부적으로 CharacterMovementComponent가 이 명령어로 속도 계산하고, 실제 이동을 구현
AddMovementInput(GetActorForwardVector(), MoveInput.X);
}
if (!FMath::IsNearlyZero(MoveInput.Y)) {
AddMovementInput(GetActorRightVector(), MoveInput.Y);
}
}
void ASpartaCharacter::StartJump(const FInputActionValue& value) {
if (value.Get<bool>()) {
Jump();
}
}
void ASpartaCharacter::StopJump(const FInputActionValue& value) {
if (!value.Get<bool>()) {
StopJumping();
}
}
//왜 Jump에서는 Controller 체크를 안했냐고 하면 일단 이 점프랑 스탑 점핑이 내부적으로 구현된, 이 엔진에서 구현된 이 코드 내부에
// //Controller 체크가 한 번 있음
//그래서 그런 탓도 있지만 GetActorForwardVector 같은 걸 쓰려면 밖에서 Controller 체크를 또 해줘야함
//점프는 그런 걸 안 쓰기 때문에 구현 안한 거임
void ASpartaCharacter::Look(const FInputActionValue& value) {
FVector2D LookInput = value.Get<FVector2D>();
//AddControllerYawInput(): 카메라의 Yaw 축(수평 회전)을 변경
AddControllerYawInput(LookInput.X);
//AddControllerPitchInput(): 카메라의 Pitch 축(수직 회전)을 변경
AddControllerPitchInput(LookInput.Y);
}
void ASpartaCharacter::StartSprint(const FInputActionValue& value) {
//스프린트 속도
if (GetCharacterMovement()) {
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
}
}
void ASpartaCharacter::StopSprint(const FInputActionValue& value) {
// 평상시 속도
if (GetCharacterMovement()) {
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
}
UBT가 UHT 실행 → 리플렉션용 코드 생성 → C++ 컴파일러 실행
언리얼 엔진 내부의 컴파일러(UBT, UHT)가 기본 C++ 코드 외의 언리얼 규격 코드를 해석하고 변환
1) UHT 실행 : UBT가 먼저 UHT를 실행해서 언리얼 관련 헤더를 분석하고 UObject 시스템에 필요한 코드를 생성
-> 모든 코드를 포함할 때 발생하는 성능 이슈를 방지하기 위해
2) C++ 컴파일 실행 : 일반 C++ 컴파일러가 호출되어서 사용자가 작성한 프로젝트 코드와 UHT가 자동 생성한 코드를 함께 컴파일
UBT (작업 반장): 전체 프로젝트의 변경 사항을 추적해서 "수정된 부분만" 다시 컴파일하도록 지휘(컴퓨터 안 터지게 방지). 흩어진 부품을 모아주는 역할(빌드 시스템)
UHT (번역가/정보원): 매크로(UCLASS 등)가 달린 코드를 읽고 "이건 언리얼에서 쓸 수 있는 클래스" 하고 엔진이 알아먹을 수 있는 부가 코드를 자동으로 짜주는(알려주는) 역할
1) 구현만 변경했을 때 -> Live Coding
2) 구조나 헤더가 변경됐을 때 -> 에디터 종료 후 IDE 빌드(전체 빌드 하기)
새 클래스를 추가할 때는 반드시 언리얼 에디터(VOD)를 통해 생성해야함!!!
에디터를 통해 생성해야 헤더/소스 파일 생성과 함께 게임 모듈 및 프로젝트 파일 갱신이 정상적으로 이루어짐.
블루프린트 컴파일 : 게임에서 사용되기 전, 노드 연결/변수/함수/이벤트 그래프를 검사하고 실행 가능한 코드로 생성.
(예시 : 배그는 블루프린트 성능 이슈를 해결하기 위해 C++로 전환 -> 10배 성능 향상)
셰이더 컴파일 : c++ 컴파일이나 에셋 로드와는 별개의 작업. 캐시된 셰이더 맵이 없는 머티리얼이 로드되면 큐에 들어가며, 'Shader Complie Worker' 프로세스를 통해 GPU용 코드를 병렬로 준비
++ 머티리얼도 별도의 컴파일 과정 존재함
리플렉션 : 프로그램이 런타임에 자기 자신(클래스, 변수, 함수 정보 등)에 대한 정보를 알 수 있게 해주는 기능. 언리얼은 UHT와 매크로를 통해서 리플렉션 시스템 구축함.
가비지 컬렉션 : 더이상 사용되지 않는 오브젝트를 엔진이 자동으로 감지해서 메모리에서 정리해 주는 메모리 관리 시스템
-> 엔진이 "이게 쓰레기인지 아닌지" 알 수 있게 만들려면 코드에 UPROPERTY() 같은 매크로를 달아줘서 수동 등록을 해줘야 함.
#pragme once : 전처리 지시문. 헤더 파일이 중복으로 인클루드되는 것을 방지.generated.h : 헤더 파일에서 무조건 가장 마지막에 인클루드되어야 함. 아니면 컴파일 에러 발생UCLASS() : C++ 클래스를 언리얼 오브젝트 시스템(UObject)에 등록하는 매크로. 이 매크로를 통해서 엔진, 에디터, 블루프린트, 리플렉션, 가비지 컬렉션 시스템과 연결됨.UCLASS()
class CROSSFIRE_API ATestActor : public AActor
{
GENERATED_BODY()
};
//ATestActor는 AActor를 상속
//클래스가 언리얼의 에디터, 블루프린트, 리플렉션, 가비지 컬렉션 시스템과 연결이 되려면
//엔진이 이 클래스를 알아야 알 수 있음
//그 표시가 UCLASS()
Blueprintable - 상속 가능, NotBlueprintable - 상속 불가능, BlueprintType - 변수 타입으로 사용 가능)_API 매크로 : 모듈 API 지정자. (ex: MYPROJECT_API) 해당 클래스나 함수를 다른 모듈(Core, Engine, UMG 등)의 DLL 파일에 Public으로 노출해서 외부에서 사용할 수 있게끔 함!!최종 컴파일 파이프라인 요약!!
컴파일 : UBT가 빌드 -> UHT가 코드를 언리얼에 맞게 생성 -> 헤더와 매크로 읽어서 처리 -> 컴파일러가 .cpp 변환 -> 어셈블 -> DLL 생성