위 두함수는 AMyActor의 부모클래스인 AActor에서 상속 받은 함수이기때문에 부모클래스의 함수를 자식클래스에서 덮어쓴다는 의미로 virtual override 키워드를 함께 사용해줘야합니다. 컨트롤 + Dot(.)을 누를 시 cpp에 함수를 정의해줍니다.
Super::PostInitProperties()
Super 클래스는 클래스가 상속받은 부모 클래스에 있는 기본 프로퍼티나 함수를 가져오는데 사용되는 키워드입니다.
이런작업을 해주는 이유는 전에 부터 하는 작업을 다시하도록 만든것입니다. 함수 또한 블루프린터에서 볼 수 있도록 설정하려면 UFUNCTION을 이용한다.
(BlueprintCallable 라고 설정하고 Category = "~"를 반드시 적어줘야합니다.
블루프린터에서 디자이너와 협력 시 디자이너가 만들어논걸 기능 구현을 하는 방법은 두가지가 있습니다.
첫 번째로 블루프린트 맨터블 방식 헤더에서만 함수선언 UFUNCTION에서 BlueprintImplementable 를 사용합니다. 요기서 알 수 있듯이 언리얼은 자체 함수의 기능이 관련있는건 앞에 이름이 붙는거 같습니다. Event를 설정 후 함수 형식의 기본 이름 반환형만 설정 후 블루프린터로 갑니다. (cpp엔 정의 안합니다.) 그리고 블루프린터에서 오른쪽 클릭 자신이 정한 카테고리에 있는 함수 이름을 생성해 블루프린터에서 기능을 구현합니다. 디자이너가 블루프린터로 만든 기능을 프로그래머가 cpp파일로 호출 하는 방식, 블루프린터에서 기능을 만들어놓지 않으면 빈 함수를 호출하는 방식으로 동작하게 됩니다.
두 번째로 블루프린트네이티브 이벤트 지정자는 디자이너가 블루프린터로 기능을 만들 수 있게하면서 만약 디자이너가 블루프린터에서 기능을 구현하지 않으면 동작할 기본 기능을 프로그래머가 cpp로 만들 수 있게 됩니다.
BlueprintNativeEvent를 사용합니다. 그리고 virtual void 함수이름_Implementation 가상 함수를 구현해주고 cpp에 기본 기능을 구현한 함수를 정의해줍니다. 이런식으로 하면 블루프린트내에서 구현하지 않아도 cpp에 구현해논 함수가 작동합니다.
하지만 기본 함수와 블루프린터에서 작동하고 하고 있는 함수 둘 다 호출하고 싶을땐 함수이벤트 생성 후 함수 이벤트에 오른쪽 클릭 부모 함수의 호출 추가를 누르면 부모:함수 이름을 가진 노드가 생성됩니다. 그리고 호출 순서를 생각하여 하고 싶은대로 이어주면 됩니다. 이런식으로 협업의 용이합니다.
이렇게 변수의 기본형으로 사용합니다. UPROPERTY 매크로의 역할은 프로퍼티가 있음을 알리고 연결되었을때 어떻게 동작할지 지정하기 위한 것입니다.
그리고 보통 short , int , long을 알고 있는데 이런 타입은 플랫폼마다 길이가 달라질 수 있기 때문에 언리얼엔진에서는 길이가 고정되있는 타입으로 int8, int16, int32, int64 int 뒤 숫자는 정수를 표현하는데 몇개의 비트를 사용할것인지를 의미합니다.
까지 표현가능합니다. 만약 해당 변수가 숫자를 음수로 표현할 필요가 없다면 int 대신 uint 등으로 사용 가능합니다. 음수의 범위를 양수의 범위로 대체되어 2배만큼 표현이 가능해집니다.
uint8 - 0 ~ 255
uint16 - 0 ~ 65,535
uint32 - 0 ~ 4,294,967,295
uint64 - 0 ~ 18,446,744,073,709,551,615
까지 표현할 수 있습니다. 이렇게 정수타입을 사용할때는 크기를 잘 생각해서 어느타입을 사용할지 결정하면 됩니다.
만약 int8 변수에 127에서 +1 할시 -128 오버플로우 (Overflow)
-128에서 -1을 해버리면 127 언더플로우(Underflow) 라고 부릅니다.
게임에서 돈을 모았는데 어느순간 0이나 -가 되어버리는 문제등이 발생하게 됩니다. 그게 바로 위 문제때문에 발생합니다.
위 문제를 없애기 위해 uint64를 사용하게 된다면 메모리 낭비가 심해집니다. 최적화를 위해 적절한 범위를 사용하는게 관건인것 같습니다.
만약 그 범위를 가늠할수 없다면 예외 처리를 통해서 그 숫자 범위를 벗어나지 못하도록 하는것이 좋습니다. 예를 들어 돈을 소지한도를 설정해논 사례인것 같습니다.
그다음은 float 부동소수점 입니다. float, double 이 있는데 float 32 비트 double은 64 비트의 크기를 가지고 float은 5자리 소숫점 정밀도, double 은 두배인 10자리의 소숫점 정밀도를 가집니다.
다음은 문자열 입니다. c++에선 std string을 사용하고 언리얼 cpp에서는 필요에 따라서 여러가지 클래스로 문자열을 제공합니다. 가장 기본 타입은 FString 변수이름; 타입입니다. 저장되는 글자의 숫자에 따라서 변수의 길이가 자동으로 달라지는 타입으로 기본 Cpp의 std string 과 유사하게 동작합니다. 하지만 보통 string에선 " "안에 바로 상소 문자열을 적어서 바로 넣지만 FString에서는 텍스트 매크로를 사용해야합니다. 이외에도 현지화 텍스트를 위해서 사용하는 FTest, 자주 사용되는 문자열을 식별자로 지정해서 문자열을 비교할때 소모되는 메모리와 CPU 시간을 절약하는데 쓰이는 FName, 플랫폼마다 다를 수 있는 문자열 세트와 상관없이 문자열을 저장하는 용도로 사용되는 TCHAR가 있습니다.
FString을 제외한 타입은 나중에 필요 시 다룬다고 합니다. 그리고 bool 이 있습니다.
그리고 public , private , protected 공개 범위를 적용 시 변수 앞이 아닌
public: (접근 지정자를 지정하여 작성합니다. 다음 접근 지정자를 정하기 전까지 public이 됩니다.)
하지만 유니티처럼 퍼블릭 설정을 해도 디테일 패널에서 수정 및 볼 수 있지 않습니다. 이때 UPROPERRTY를 사용합니다.
BlueprintReadWrite - 블루프린트에서 읽기, 쓰기를 모두 가능하다는 뜻입니다.
BlueprintReadOnly - 블루프린트에서 읽기만 가능
Transient - 해당 프로퍼티가 휘발성 프로퍼티로 저장되지 않음을 의미합니다.
Category = " " - 블루프린트 편집 툴, 디테일 패널에서 이 프로퍼티를 데미지라는 카테고리로 묶어서 보여준다는 뜻입니다. 밀접한 관계를 가진 프로퍼티들은 같은 카테고리로 묶어줄 시 작업할때 빠르게 찾을 수 있습니다.
MyActor 생성자 소스 파일에서 프로퍼티를 초기화 해줍니다 두가지 방법이 있습니다.
AMyActor::AMyActor()
: TotalDamage(200), DamageTimeInSeconds(1.0f), CharacterName(TEXT("탄산수")), bAttackable(true)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
TotalDamage = 200;
DamageTimeInSeconds = 1.0f;
CharacterName = TEXT("레몬");
bAttackable = true;
}
This 객체가 생성될때 호출되는 함수이며 주로 생성된 액터의 변수의 기본값을 설정해주는데 사용됩니다.
BeginPlay 함수
This 객체가 월드에 스폰되었을때 한번 실행되는 함수로 게임 플레이어 로직을 초기화하는데 사용
Tick 함수
매 프레임마다 호출되는 함수, Tick 함수에 DeltTime 매개 변수는 전에 호출된 이후로 얼마나 시간이 경과한 뒤에 다시 Tick가 호출되었는지에 대한 시간을 전달받을 수 있습니다. 주로 게임의 로직을 구현하는데 사용됩니다. 하지만 매 프레임마다 호출되는 Tick 함수가 불필요한 경우 지워 포퍼먼스를 상승시킬 수 있습니다. 방법은 헤더 파일과 소스 파일에서 Tick 함수를 제거 소스 파일 생성자에서 PrimaryActorTick.bCanEverTck = true; 제거해주면 됩니다. 앞에서 지운 생성자에 있는 명령어는 액터가 Tick 함수를 매프레임마다 호출하도록 설정하는 코드입니다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CameraDirector.generated.h"
UCLASS()
class KIMUNREAL_API ACameraDirector : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACameraDirector();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "CameraDirector.h"
#include <Kismet/GameplayStatics.h>
// Sets default values
ACameraDirector::ACameraDirector()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void ACameraDirector::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ACameraDirector::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
액터 Cpp 클래스 생성 시 기본 형태입니다.
UE_LOG(LogTemp, Log, TEXT("")); 언리얼 엔진에서 로그를 남기는 함수
따로 메인에서 초기화를 안할 시 쓰레기값이 들어가 잘못된 결과값이 날 올 수 있기때문에 외에도 여러 상황에서도 사용 가능합니다. 이와 같이 객체를 만들자 마자 초기화하는 함수를 생성자라고합니다. 반드시 자기 자신과 똑같은 이름을 가져야합니다. 객체를 만들자마자 자동으로 호출되기때문에 함수 호출 이름을 명시 할 방법이 없습니다. 함수가 종료되기전에 Finalize도 자동으로 명시 할 방법 또한 없습니다. 그래서 특별한 이름을 가져야합니다.
KPeople만 입력 시 클래스 이름과 같아 모호함이 발생하므로 KPeople에 생성자를 호출한다고 명시해줍니다. 소멸자 경우 ~가 붙어있어 상관없습니다.
하지만 전에도 말했듯이 C++에선 new와 delete를 사용합니다.
KPeople* p;
p = (KPeople*)operator new (sizeof(KPeople)); // new 함수 ( new function)
p->KPeople::KPeople();
p->~KPeople();
operator delete(p);
operator new 또한 보이스 포인터이므로 (KPeople*)로 캐스팅 해줍니다. operator new 경우 전역함수라 생각하면됩니다. operator new를 함수 이름이라고 생각하는게 나중에 헷갈리지 않는다고 합니다.
하지만 이또한 생성자를 따로 호출해줘야합니다. 하지만 C++ 설계자들이 동적으로 할당 하고 자동으로 생성자를 호출 하도록 operator new와 생성자를 호출하도록 new expression을 만들었습니다 .
p = new KPeople;
delete p;
이것의 의미는 동적으로 KPeople만큼 메모리에 할당하고 자동으로 생성자 자동으로 호출하라는 뜻입니다.
소멸자 경우도 마찬가지 입니다. p의 경우는 KPeople 포인터입니다.
하지만 그럼 operator new는 필요없는거 아니냐 할 수 있지만 new expression은 오버로딩이 불가합니다 그래서 new의 오버로딩이 필요한 경우 operatorn new로 오버로딩을 해야합니다. 하지만 동적으로 메모리를 할당할 일이 있다면 new expression, delete expression을 사용하면 됩니다.
8강입니다.
생성자의 파라미터가 들어갈 시 () 안에 들어갑니다. 없을 시 생략해도됩니다. 있어도 생략안하고 적어도 작동합니다.
p = new KPeople();
이런 식으로 처리 가능합니다.
p = new KPeople[3];
연속적으로 메모리 할당이 가능합니다. 뜻은 KPeople의 크기의 메모리를 3개를 할당한것입니다. new 에 []를 사용 시 delete[] p 와 같이 딜리트에도 표시해줘야합니다. 물론 3개를 만들기때문에 생성자 3번 소멸자 3번이 작동합니다.
그리고 접근 시엔 아래와 같이 접근 가능합니다. 0 , 1, 2 등 직접적인 접근이기 때문에 포인터가 아닌 객체가 되므로 . 을 사용합니다.
오류 시 strcpy_s를 사용합니다. 요기서 주의 해야할 점은 이미 복사한 값을 호출 할 시 복사 생성자가 호출된다라는 점입니다.
Test(temp)로 변경 이유는 KPeople("Mike")를 작성 시 생성자 한번 복사 생성자 한번이 호출이 되는데 컴파일 최적화로 생성자 한번만 호출되어 복사생성자를 호출하지 않는 문제가 있어서 이미 복사된 값을 사용했습니다. 위 메모리를 그리며 정리한걸 봐도 ?를 Test로 복사한 형태라서 생성 후 복사 호출되는게 맞지만 최적화된것 같습니다.
struct People // type definition
{
int age; // member variable
char name[10];
}/*a*/;
People 부분은 태그네임이라고 합니다. struct People이라는 타입으로 정의된 동시에 a라는 변수로 정의된 상태입니다. 구조체는 타입도 정의할 뿐만 아니라 변수도 정의할 수 있는 특별한 구조를 가지고 있습니다.
그리고 구조체로 타입을 정의 할때 변수를 정의 할 수 있지만 가독성을 위해 변수를 바로 선언하지 않고 이후 사용 할때 다시 변수로 선언하여 사용합니다.
구조체를 사용 시
void main ()
{
People a;
a.age = 28;
strcpy_s(a.name, "Hello");
//strut People b; strut People이 타입 이름
People b; // strut 생략가능
b.age = 29;
strcpy_s(b.name, "World");
}
strut에 경우 C에선 생략이 불가능합니다. strcpy_s에 경우 문자열을 복사하는 함수입니다.
SetAge는 매개변수로 들어온 구조체(A).age를 newAge로 들어온 값으로 바꿔주는 역할을 합니다.
원래 함수는 값의 변경이 안되었는데 이번 함수의 다른 점은 변경을 필요로 한 값을 포인터로 넘겨주는겁니다. 메모리로 가서 직접 수정을 하는것이죠. 그리고 코드상에선 함수에게 줄 땐 & 주소연산자로 넘겨주고 함수에선 * 참조연산자로 받습니다. 이렇게 받으면 A는 현재 구조체 a를 복사한게아닌 구조체 a를 가르키는 포인터입니다. 그래서 (*A)를 사용했습니다. 이렇게 한 이유는 A는 더이상 구조체가 아니기 때문에 . 을 사용할 수 없습니다.
그래서 (*A) *A포인터가 가르키는 내용이 이게 구조체가 됩니다.
그리고 그값에 newAge를 대입하고 SetAge 함수를 빠져나옵니다. 그러면 SetAge부분이 스택에서 팝됩니다.
그리고 PrintPeople( a ) 부분이 실행되면서 함수가 호출됩니다. 이때 SetAge에 의해서 a.age는 47에서 48로 대입된 상태입니다. 48이 출력됩니다.
메모리 또한 위에서 본것과 같이 a 구조체를 복사 스택에 push 출력 후 pop됩니다. 그리고 메인으로 돌아옵니다.
해보고 알 수 있는건 포인터로 전달 시 값에 대해서 변경 가능한 점과 포인터 경우 4바이트만 사용하여 메모리를 효율좋게 사용 할 수 있습니다.
void PrintPeople(People *A)
{
printf("%s : %i\r\n", (*A).name, (*A).age);
}
// main 부분
PrintPeople(&a);
구조체가 클 경우 단순 복사만 해도 메모리에 부담이 갑니다. 그래서 포인터로 메모리 최적화를 요 할 수 있습니다.
4강입니다.
구조체에 접근 하기 위해선 . 을 사용합니다. 하지만 구조체 포인터일 때는 People *A;에 A는 구조체가 아닌 포인터입니다. 그래서 *A가 가르키는 내용이 구조체입니다. 포인터는 가르키는것이기 때문에 연산전에는 포인터인채이고 연산자 우선 순위로 인해 먼저 계산이 안될 수 있으므로 (*A)로 표기해줍니다. 하지만 구조체를 메모리 효율을 위해 포인터를 사용하는 일이 많습니다. 매번 (*A)를 적긴 많이 번거롭습니다. 그래서 만들어진게 arrow operator 이며 dot operator 도 있습니다. 둘다 구조체를 위한 오버레이터입니다. dot operator는 구조체 자체가 선언되었을때 구조체 멤버로 접근합니다. arrow operator는 구조체 포인터로 선언되었을때 구조체 멤버로 접근합니다. 요기서 dot operator는 이때까지 써온 . 이란걸 알 수 있습니다. 그럼 arrow operator는 어떻게 사용하냐면 (*A).age ===> A -> age , * . 가 -> 로 바뀐 형식입니다.
People *b;
b -> age = 48;
strcpy_s(b -> name, "World");
PrintPeople(b);
이런식의 예제도 만들 수 있습니다. 구조체 변수로 생성하지 않고 포인터로 바로 생성 시는 dot이 아닌 arrow operator로 표기하며 포인터를 받는 함수의 경우 & 주소연산자를 사용하지말고 바로 포인터인 b를 넘겨주면 됩니다.
하지만 요기서 오류가 발생합니다. 이유는 그림으로 보면 이렇습니다.
b -> age = 48; 대입을 하는 순간 할당되어있지 않은 메모리를 사용해서 에러가 발생하는 상태입니다. 그래서 구조체가 가르킬 곳을 메모리를 할당한 다음 사용해줘야합니다.
void main()
{
People *b;
b = (People*)malloc(sizeof(People));
b -> age = 48;
strcpy_s(b -> name, "World");
SetAge( b, b->age + 1 );
PrintPeople(b);
free(b);
}
malloc으로 가르키고 있는 곳에 메모리를 할당 해주고 sizeof 해서 사이즈를 정해줍니다. 하지만 이렇게 하면 void리턴이라서 오류가 납니다. malloc은 자동적으로 자료형을 정해주지 않아서 캐스팅을 해주어야합니다. 그래서 (People*) 를 적어줍니다.
코드를 보면 포인터는 따로 초기화 하지 않아 갈비지로 초기화 되어있습니다. 그리고 malloc으로 People (14) 사이즈 만큼 할당 됩니다. 위치는 알 수 없습니다. 임의로 정하겠습니다. malloc을 쓰고 나선 항상 free()를 사용해줍니다. 스택 메모리 처럼 그려놨지만 malloc이나 new 같은 동적 할당 메모리는 크기를 넓게 쓰기 위해 만들어진 곳이라서 heep 메모리에 저장됩니다.
b -> age = 48;
strcpy_s(b -> name, "World");
위에 값을 기입하게 됩니다. 그 후에
SetAge( b, b->age + 1 );
PrintPeople(b);
그 후에 함수를 실행하게 될 시 b 이미 포인터인 녀석을 넘겨주게 됩니다. 포인터 b를 넘겨주고 b를 구조체화하여 age 값에 +1 하여 함수를 계산합니다.
그후 출력합니다. 또한 밑에 함수도 정상 출력됩니다.
5강입니다.
메모리 계산 부분에서 실수를 해서 그림을 다시 고쳤습니다.
메모리 할당을 malloc로 하고 free로 했었는데 C++에선 같은 역할을 하지만 다른 형식이 있습니다. new 와 delete입니다. new는 메모리 할당을 하고 생산자 Constructro 라는 특수한 함수를 호출하고 delete는 메모리 해제를 하기 전에 파괴자라는 특별한 함수를 호출 합니다.
약간 다릅니다. 다른 부분은 나중에 따로 포스팅하겠습니다.
C++의 구조체 대해서 알아보겠습니다. C++ 구조체는 구조체 안에 함수를 가질 수 있습니다.
struct People // type definition
{
int age; // member variable
char name[10];
void PrintPeople(People *this_) { // member function
printf("%s : %i\r\n", this_->name, this_->age);
}
void SetAge(People * this_, int newAge)
{
this_->age = newAge;
}
};
특이한 형태입니다 구조체안에 들어간 함수는 이제 함수라고 하지 않고 멤버 함수라고 합니다.
호출 방법은 구조체에 접근 후 멤버함수를 호출하면됩니다.
6강입니다.
위와 같이 사용하게 될 시 위 함수를 보면 매개변수로 this_ 라는 포인터를 따로 main에서 받아 함수를 실행하는데 구조체에 멤버함수가 포함되어있는 구조체에서 멤버함수를 사용 시 구조체를 매개변수로 받을 일이 있을 시 매개변수가 아닌 함수내에서 this로 사용할 수 있습니다. 위와 같이 멤버함수는 항상 this 자기 자신을 항상 매개변수로 전달하는데 이걸 this 콜이라고 합니다.
void PrintPeople() // member function
{
printf("%s : %i\r\n", this->name, this->age);
}
void SetAge(int newAge)
{
this->age = newAge;
}
멤버함수를 수정한 코드입니다. 그래서 PrintPeople은 전달하는 인자가 0개가 아닌 1개입니다.
그리고 5강을 통해 C의 함수와 C++의 함수가 다르는걸 알 수 있었습니다. 실제로 오버로딩때문에 C++에서 함수를 정의하면 함수이름 그대로 유지하지 않고 컴파일러가 변경합니다. 이걸 네임 맹글링 (Name Mangling)이라고 합니다. 그리고 멤버함수 역시 그냥 함수와 다릅니다. 자기 자신인 객체의 시작주소가 항상 전달됩니다. 그리고 멤버 함수의 경우 this라고 적어되지만 보통 this->를 생략해서 사용한다고 합니다.
void PrintPeople() // member function
{
printf("%s : %i\r\n", name, age);
}
void SetAge(int newAge)
{
age = newAge;
}
void main()
{
People *b;
b = new People;
b -> age = 48;
// C 호출 관례
// C++ 호출 관례
strcpy_s(b -> name, "World");
// this 호출 관례
b->SetAge( b->age + 1 );
b->PrintPeople();
delete b;
}
등 구별이 가능합니다. 함수 호출 관례에 대해선 나중에 따로 다루겠습니다.
그래서 이렇게 변수와 함수를 다 가진 구조체를 class라고 합니다. 그래서 struct를 class로 바꿔주면 됩니다. 하지만 접근 권한 문제가 발생하는데 아래와 같이 수정하면 됩니다.
class People // type definition
{
public:
int age; // member variable
char name[10];
void PrintPeople() // member function
{
printf("%s : %i\r\n", name, age);
}
void SetAge(int newAge)
{
age = newAge;
}
};
링크드 리스트를 작성하기 위해서는 구조체 포인터를 자기자신의 멤버로 사용해야합니다.
그리고 구조체 멤버를 int 같은 타입이 아니라 특정한 몇 비트를 차지하도록 비트 필드 구조체를 만들수도 있습니다. 또 공용체는 구조체는 멤버를 위해서 모두가 메모리가 할당됩니다. 이런 멤버들 중에서는 가장 큰 멤버를 위해서만 메모리 할당이 됩니다. 하지만 이런것들은 나중에 따로 알아보고 지금은 문법에 집중하겠습니다.
코딩 테스트 치고 나오면서 C++에 대한 기초가 부족한거같아서 C++을 한번 완전 기초부터 다시 해보려고 합니다. 그리고 테스트 준비하면서 공부한걸 다시 정리해서 올릴 생각입니다. 공부도 먼저 책으로 공부 한다고 포스팅은 공부를 다하고 정리하면서 적습니다. 너무 포스팅을 미뤘네요.
C++ 기초전에 C 부분과 기초이론부분을 좀 더 공부하고 왔습니다.
계산적 사고 Computational Thinking
입력 - > 알고리즘 - > 출력 (와 같은 형식은 다 알고리즘이라고 할 수 있지만)
자신이 원하는 답을 도출해낼 수 있는 알고리즘을 만들어야합니다. 문제를 해결 할 수 있는 단계를 찾는 행위
제어 장치 Control Unit , 산술 논리 장치(ALU) Arithmetic Logic Unit , 그리고 갖가지 레지스터로 구성 되어 있습니다.
레지스터 Registers는 CPU에 책상역할을 할 수 있다고 볼 수 있습니다. 여러 값을 올려놓는 역할을 합니다.
시각적으로 표현하면 이렇습니다.
포인터는 정확히 주소 값을 가르키는 것입니다. 메모리 값이 아닌 이점을 유의해야합니다.
CPU가 작동하는 원리는 영상에 잘 설명되어있지만 한번 제가 따로 순서를 적은걸 말로 풀어보겠습니다.
1. 프로그램 카운터에 주소값이 들어옵니다. (시작점) 5. 다음 카운터 주소값이 들어옵니다.
2. 메모리 주소 레지스터로 주소값을 넘겨주고 3. 메모리 데이터 레지스터에 주소에 입력되어 있는 값이 들어옵니다.
4. 값이 명령어 일 시 명령어 레지스터로 들어갑니다. 6. 명령어 레지스터에 있는 값이 제어장치로 들어갑니다.
7. 디코드를 실행해서 명령어를 해독해서 실행합니다. (주소 10에 있는 주소를 로드해라) 8. 메모리 주소 레지스터에 10을 넣어줍니다. 9. 3을 메모리 데이터로 가져옵니다. 10. 연산에 사용되는 값은 어큐뮬레이터에 저장된다. 산술 연산 장치를 보조해주는 레지스터 11. 다음 카운터 주소값을 실행합니다. ( 명령이 끝날 때까지 반복하며 명령어를 실행해서 산술 논리 장치에 넣어줍니다. ALU에서 계산된 값을 102 주소에 명령에 따라 12주소에 저장해서 끝납니다.
정보의 기본 단위 Bit 비트
바이트 ( 8개의 비트 ) 메모리 주소의 기본 단위
16, 32 비트 CPU가 데이터를 다루는 기본 단위 - word 워드 - 레지스터의 크기 (고정은 아님)
C++ 출력만 해보고 앞으로 C#으로 해보겠습니다. 유니티로 취업을 할거고 계속 사용해서 친해져야하는 언어이므로 앞으로 문제는 C#으로 풀어보겠습니다.
두 수 비교하기
using System;
class Program {
static void Main(){
string str;
int a,b;
str = Console.ReadLine();
string[] strArr = str.Split(' ');
a = Convert.ToInt32(strArr[0]);
b = Convert.ToInt32(strArr[1]);
if(a < b)
Console.WriteLine("<");
else if(a > b)
Console.WriteLine(">");
else if(a == b)
Console.WriteLine("==");
else
Console.WriteLine("잘못된 입력 값입니다.");
}
}
string str;
int year;
int a = 4;
int b = 100;
int c = 400;
bool yearBool;
str = Console.ReadLine();
year = Convert.ToInt32(str);
yearBool = year%a == 0 && year%b != 0;
if(yearBool || year%c == 0)
Console.WriteLine("1");
else
Console.WriteLine("0");
사분면 고르기
int a,b;
a = int.Parse(Console.ReadLine());
b = int.Parse(Console.ReadLine());
if (a > 0 && b > 0)
Console.WriteLine("1");
else if(a < 0 && b > 0)
Console.WriteLine("2");
else if(a < 0 && b < 0)
Console.WriteLine("3");
else if(a > 0 && b < 0)
Console.WriteLine("4");
else
Console.WriteLine("잘못 입력했습니다.");
Convert.ToInt32 보다 int.Parse 속도, 메모리 면에서 더 유리합니다.
알람 시계
int h,m;
int setM;
string[] arrStr = Console.ReadLine().Split(" ");
setM = 45;
h = int.Parse(arrStr[0]);
m = int.Parse(arrStr[1]);
m -= setM;
if (m < 0) {
m = Math.Abs(m);
m = 60 - m;
h -= 1;
}
if(h < 0)
{
h = 23;
}
Console.WriteLine("{0} {1}", h,m);
팀프로젝트를 마무리 단계는 코드를 분석하여서 정리하기로 하였습니다. 전 플레이어 연출인 카메라 부분 및 낙하 판정을 하는 부분을 분석해보려고 합니다.
협업한 친구가 코드를 잘 정리하여 보내주었습니다.
굿
카메라가 벽에 닿을 시 처리하는 부분
카메라 값
/*초기 값 설정*/
private float defaultPosition;
private float targetPosition;
public Transform cameraTransform;
defaultPosition = cameraTransform.localPosition.z;
cameraTransform.localRotation = Quaternion.identity;
cameraTransform.parent = pivotTransform;
// TPS 카메라 보정
// 카메라와 플레이어 사이에 물체가 있을 때 레이로 충돌판정, z 거리조절
private void CameraCollision()
{
// 카메라 기본위치
// 충돌이 없는경우 이 값으로 이동 될 수 있게 따로 변수 만듦
targetPosition = defaultPosition;
RaycastHit hit;
// 카메라에서 카메라 pivot 으로 향하는 방향벡터
Vector3 direction = cameraTransform.position - pivotTransform.position;
// 정규화(방향만 필요함)
direction.Normalize();
// 스피어캐스트 사용, pivotTransform.position위치에서 direction 방향으로 광선을 쏘고 그 광선을 따라서
// 반지름 0.2크기의 구를 날려서 부딪히는지 체크, 마지막 매개변수는 광선의 사거리로 카메라와 플레이어 사이 기본거리
if (Physics.SphereCast(pivotTransform.position, 0.2f, direction, out hit, Mathf.Abs(targetPosition)))
{
// 플레이어와 카메라 사이 물체가 있는경우
// distance = pivot과 충돌물체 사이의 거리값 저장
float distance = Vector3.Distance(pivotTransform.position, hit.point);
// 카메라 위치값 보정
// 카메라z 위치 = distance - 보정값(카메라 얼마나 플레이어 쪽으로 땡길지 값)
// - 붙은 이유 : 카메라 로컬 좌표, 플레이어 뒤에서 찍기 때문에
targetPosition = -(distance - tps_CameraCollisonDistance);
}
// 위에서 보정 후에
// 카메라와 pivot 사이거리가 보정값보다 짧은경우, 즉 너무 벽에 붙어있어서 벽과 플레이어 사이거리가 보정값 보다 짧은 경우
if (Mathf.Abs(targetPosition) < tps_CameraCollisonDistance)
{
// 최소 플레이어와 카메라 사이 거리를 지정해줌
// 최소값 따로 안만들고 보정값이 적당해서 그대로 사용함
targetPosition = -tps_CameraCollisonDistance;
}
// 보정한 값
// Lerp로 부드럽게 이동시킴
cameraPosition.z = Mathf.Lerp(cameraTransform.localPosition.z, targetPosition, Time.deltaTime / tps_cameraOffsetSpeed);
// 카메라 z위치 최종 보정값 실제 반영
cameraTransform.localPosition = cameraPosition;
// 카메라가 바라보는 기준을 pivot으로 해놓은 이유 : 플레이어에 변동사항이 생겨도 수정이 용이하게 하기위함
// 스피어캐스트 사용이유 : 더 정밀하게 보정할 수 있기 때문에, 그냥 레이캐스트 사용시 체크하지 못하는 부분까지 체크해줌
}
플레이어 낙하 판정 부분
레이캐스트를 이용한 판정 (빨간선)값 설정
// 플레이어 낙하 판정
// 바닥 거리판정이기 때문에 여기서 플레이어 y위치 처리도 함
private void PlayerFalling()
{
RaycastHit hit;
// 캡슐콜라이더의 높이의 절반 길이
float length = capsuleCollider.height * 0.5f;
// 콜라이더 박스의 중심 점
Vector3 origin = capsuleCollider.bounds.center;
// 레이캐스트, 플레이어의 콜라이더 중심으로 부터 아래 방향으로 콜라이더 절반 길이 + 최소 낙하판정 거리
// 사거리 저렇게 두고 충돌하면 바닥에 닿고 있거나 떨어져도 낙하 동작 나올만큼의 높이가 아닌 경우
if (Physics.Raycast(origin, Vector3.down, out hit, length + minimumDistanceFalling))
{
// 확인용
Debug.DrawRay(origin, Vector3.down * (length + minimumDistanceFalling), Color.red);
// 이 함수 안에 들어왔으면 바닥에 닿고 잇는 상태
inputCtrl.isGround = true;
// hit.distance = 부딪힌 지점까지의 거리
// 이 거리가 콜라이더 절반 길이보다 길면(= 바닥에 서 있는 모든 경우)
if (hit.distance > length)
{
// 플레이어의 위치 저장, 현재 위치에서 y값만 보정해주기 위해서
Vector3 newPos = transform.position;
// 현재 위치의 y값을 충돌지점의 y값으로 바꾸어준다. 즉 바닥의 y좌표랑 플레이어 y좌표랑 똑같이해서 바닥아래 발이 안빠지게 함
newPos.y = hit.point.y;
// 변경한 위치를 실제 플레이어 위치에 반영
transform.position = newPos;
}