1교시
무기 시스템 - 워프
- 데칼은 Attachment에 있다.
- DoAction의 BeginPlay에서 받아올거다.
마우스 위치 정보 가져오기 테스트
- Attachment는 따로 소유하지 않을것이다.
- BeginPlay에서 필요할때 꺼내서 쓸 것이다.
- 같은 레벨은 서로 소유하지 않아야하기 때문이다.
- 마우스의 움직이은 Tick 컴포넌트에서 계속 감지해야한다.
- 이때, 마우스의 위치를 구해서 데칼을 마우스에 위치로 움직일것이다.
- CDoAction
- 헤더 파일 - BeginPlay의 매개변수에 Attachment 클래스 추가 - Tick 함수 선언하고 재정의할 수 있도록 virtual을 선언하고 정의한다.
//header virtual void BeginPlay ( class ACharacter* InOwner, class ACAttachment* InAttachment, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas ); //틱에서는 필요한것만 재정의한다. virtual void Tick(float InDeltaTime) {}- CPP 파일 - BeginPlay 매개 변수 수정
- CDoAction_Warp
- 헤더 파일 - BeginPlay 부모 함수 override - Tick 부모함수 override - 카메라의 정보를 가져오기 위해 플레이어 컨트롤러 클래스의 ‘플레이어 컨트롤러’ 변수를 선언한다.
//header private: class UDecalComponent* Decal; class APlayerController* PlayerController;- CPP 파일 - BeginPlay 함수 매개변수에 Attachment 추가 - 데칼 컴포넌트 자료형을 넣어 ‘컴포넌트 가져오기’ 함수를 호출하고 ‘데칼’ 변수를 선언하고 저장한다. - 매개변수로 들어온 ‘오너 캐릭터’의 ‘컨트롤러 가져오기’ 함수를 ‘플레이어 컨트롤러’ 자료형으로 선정하고 호출하여 ‘클레이어 컨트롤러’ 변수에 저장한다. - Tick 함수 정의 - 부모 Tick 함수 호출 - 마우스 히팅 결과를 저장할 ‘히트 결과’ 변수를 선언한다. - ‘플레이어 컨트롤러’의 ‘채널에 의한 마우스 커서 히팅 정보 가져오기’ 함수를 ‘VIsibility(보여지는 객체’ 전부 히팅되는 방식, 복합 충돌 여부, 히팅 결과를 저장할 변수’를 넣고 호출한다. - ‘히팅 결과’의 ‘블록 출돌했는지 확인’ 변가 false인지 확인한다.
//CPP void UCDoAction_Warp::BeginPlay(ACharacter* InOwner, ACAttachment* InAttachment, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas) { Super::BeginPlay(InOwner, InAttachment, InDoActionDatas, InHitDatas); //데칼 가져오기 Decal = CHelpers::GetComponent<UDecalComponent>(InAttachment); PlayerController = InOwner->GetController<APlayerController>(); } void UCDoAction_Warp::Tick(float InDeltaTime) { Super::Tick(InDeltaTime); //커서 히팅시키고 히팅정보 가져오기 FHitResult hitResult; PlayerController->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1, false, hitResult); CheckFalseResult(hitResult.bBlockingHit, false); } - CWeaponComponent
- 웨폰 컴포넌트에서 DoAction 클래스를 소유하고 있으니, GetDoAction이 Null이 아니라면 DoAction의 Tick을 실행한다. → DoAction에서의 Tick은 재정의할 수 있도록 정의를 하지 않았고, 파생 클래스에서 Tick을 사용할 수 있게 하였으니 각각의 파생 Tick에서 사용할때 호출된다.
- CPP 파일 - Tick 함수 확인 - GetDoAction이 null이 아니라면 GetDoAction을 호출해서 가져온 DoAction의 Tick 함수를 호출해라
//CPP if (!!GetDoAction()) { GetDoAction()->Tick(DeltaTime); //DoAction의 틱 이벤트 실행 }
가져온 마우스 좌표에 데칼 이동시키기
- 월드 함수를 쓰는 이유 : 마우스 히팅 정보가 리턴되는 값이 월드 값이기 때문이다.
- CDoAction_Warp
- CPP 파일 - Tick 함수 확인 - ‘데칼’의 ‘월드 위치 설정’ 함수에 ‘히팅 결과’의 ‘위치’를 넣어 호출한다.
//CPP //hitResult.Location : 임펙트는 '충돌한 위치'를 가져오고, 로케이션은 '충돌한 메쉬의 위치'를 가져온다. Decal->SetWorldLocation(location);
데칼이 벽에도 제대로 출력되도록 데칼 회전시키기
- 월드 함수를 쓰는 이유 : 마우스 히팅 정보가 리턴되는 값이 월드 값이기 때문이다.
- 벽은 단순하게 해도 괜찮기 때문에 충돌체의 위치를 가져와도 괜찮다.
- CDoAction_Warp
- CPP 파일 - Tick 함수 확인 - ‘데칼’의 ‘월드 회전 설정’ 함수에 ‘히팅 결과’의 ‘충돌 수직 벡터’의 ‘회전값 반환’ 함수 넣고 호출한다.
//CPP //벽의 데칼 회전시키기 Decal->SetWorldRotation(rotation);
커서 히팅 정보 가져오기 정리
- 히팅 되면 True, 아니라면 false로 리턴시킨다.
- CDoAction_Warp
- 헤더 파일 - ‘커서 위치와 회전값 가져오기’ 함수를 ‘위치값, 회전값’을 받아서 선언
//header private: bool GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation);- CPP 파일 - Tick 함수 확인 - 커서의 위치와 회전값을 저장할 ‘위치’ 와 ‘회전’ 변수를 선언하고 초기화한다. - ‘플레이어 컨트롤러’가 비었는지 확인한다. - ‘커서 위치와 회전 가져오기’ 함수에 ‘위치’ 와 ‘회전’ 변수를 넣은 결과가 false 라면 : 커서 히팅이 안된것이니 ‘데칼’의 ‘보여지기 설정’을 false로 하고 return 한다. - 히팅 되었다면 ‘데칼’의 ‘보여지기 설정’ 함수에 true를 넣는다, ‘데칼’의 위치와 회전을 ‘위치’ 변수와 ‘회전’ 변수에 들어온 값을 넣고 호출한다. - 커서 히팅정보를 가져오는 코드를 가져온다. - ‘히팅 결과’의 위치값을 매개변수로 받은 ‘위치’ 변수에 저장한다. - ‘히팅 결과’의 회전값을 매개변수로 받은 ‘회전’ 변수에 저장한다.
//CPP void UCDoAction_Warp::Tick(float InDeltaTime) { Super::Tick(InDeltaTime); //커서의 위치와 회전값을 저장할 변수 선언 및 초기화 FVector location = FVector::ZeroVector; FRotator rotation = FRotator::ZeroRotator; //히팅이 안되있으면 데칼을 숨긴다. if (GetCursorLocationAndRotation(location, rotation) == false) { Decal->SetVisibility(false); return; } Decal->SetVisibility(true); //커서 히팅 확인 //CLog::Print(hitResult.ImpactPoint, 9999); //hitResult.Location : 임펙트는 '충돌한 위치'를 가져오고, 로케이션은 '충돌한 메쉬의 위치'를 가져온다. Decal->SetWorldLocation(location); //벽의 데칼 회전시키기 Decal->SetWorldRotation(rotation); } //커서의 위치와 회전값 가져오기 bool UCDoAction_Warp::GetCursorLocationAndRotation(FVector& OutLocation, FRotator& OutRotation) { CheckNullResult(PlayerController, false); //커서 히팅시키고 히팅정보 가져오기 FHitResult hitResult; PlayerController->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1, false, hitResult); CheckFalseResult(hitResult.bBlockingHit, false); //히팅 정보를 매개변수에 저장하기 => 레퍼런스 매개변수이기 때문에 값이 바뀐다. OutLocation = hitResult.Location; OutRotation = hitResult.ImpactNormal.Rotation(); return true; }
플레이어 - 워프 위치로 이동시키기
- 이동할 위치는 DoAction 함수에서 가져오고, 실제 이동은 Begin_DoAction 함수에서 실행한다.
- 몽타주가 실행되는 도중에 Begin_Action 이벤트가 실행될때 이동시키는 것이다.
- CDoAction_Warp
- 헤더 파일 - 부모의 DoAction 재정의 - 부모의 Begin_DoAction 재정의 - ‘위치로 이동’ 변수 선언
//header public: void DoAction() override; //DoAciton 재정의 void Begin_DoAction() override; //DoAciton 재정의 private: FVector MoveToLocation;- CPP 파일 - DoAction 정의 - ‘상태 컴포넌트’의 ‘기존 모드 확인’ 함수를 호출해서 false인지 확인한다. - ‘DoAction 데이터’의 ‘요소 수 확인’ 함수를 호출해서 0개 이상인지 확인한다. - 부모의 DoAction 함수 호출 (액션 모드 실행) - ‘회전’ 변수 선언 - ‘커서 위치와 회전 가져오기’ 함수에 ‘위치오 이동, 회전’ 변수를 넣고 호출한 결과가 false 라면 : ‘DoActionDatas’의 0번쨰 순서의 ‘DoAction’ 함수에 ‘오너 캐릭터’를 넣고 호출한다. (워프 몽타주 실행하는 것이다.) - Begin_DoAction 함수 정의 - 부모 함수 호출 - ‘오너 캐릭터’의 ‘엑터 위치 설정’ 함수에 ‘위치로 이동’ 변수를 넣고 호출한다. - ‘위치로 이동’ 값을 초기화한다.
//CPP void UCDoAction_Warp::DoAction() { CheckFalse(State->IsIdleMode()); CheckFalse(DoActionDatas.Num() > 0); //액션 실행 데이터가 없다면 워프를 실행하지 않는다. Super::DoAction(); FRotator rotation; if (GetCursorLocationAndRotation(MoveToLocation, rotation)) { DoActionDatas[0].DoAction(OwnerCharacter); } } void UCDoAction_Warp::Begin_DoAction() { Super::Begin_DoAction(); OwnerCharacter->SetActorLocation(MoveToLocation); //워프로 이동 실행 MoveToLocation = FVector::ZeroVector; //워프이동 초기화 } - ABP_Player
- 위자드 모션 스테이트 머신 추가 - 위자드 블렌드 스페이스 연결 - 위자드 스테이트 머신 핀 끌고 ‘Save Pose’ 검색 - 블렌드 포즈에 Warp 핀 추가 - ‘Wizard 포즈 사용’ 검색 후 연결
- 워프 몽타주
- Begin_Action 노티파이 추가 - End_Action 노티파이 추가 - 파티클 : 샤인 아우라 추가
- DA_Warp
- DoActionDatas 추가 - 워프 몽타주 할당 - 워프 몽타주 실행 중 이동 정지
벽이나 바닥에 끼는 문제 방지 - 워프 위치 보정
- 중심점이 캡슐 절반높이이니 캡슐의 절반 높이정도까지만 보정해주면 된다.
- CDoAction_Warp
- CPP 파일 - DoAction 함수 확인 - ‘오너 캐릭터’의 ‘캡슐 컴포넌트 가져오기’ 함수 호출의 ‘캡슐 중심 높이 가져오기’ 함수를 호출하고 ‘높이’ 변수를 선언하고 저장한다. - ‘위치로 이동’의 Z 값에 ‘높이’만큼 더한다.
//CPP //캐릭터의 크기만큼 워프 위치를 보정한다. => 캡슐의 크기를 구한다. float height = OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); MoveToLocation.Z += height; //캡슐 크기만큼 워프 z 위치를 보정한다.
워프하는 지점 바라보게하기
- CDoAction_Warp
- CPP 파일 - ‘바라보는 회전값 찾기’ 함수에 ‘오너캐릭터의 엑터 위치 가져오기 함수 호출, 위치로 이동’ 변수를 넣고 호출하고 그중, Yaw 값만 ‘요’ 변수를 선언하고 저장한다. - ‘오너 캐릭터’의 ‘엑터 회전 설정’ 함수에 ‘회전자(0, 요, 0)’을 넣고 호출한다.
//CPP //워프하는 위치 바라보기 float yaw = UKismetMathLibrary::FindLookAtRotation(OwnerCharacter->GetActorLocation(), MoveToLocation).Yaw; OwnerCharacter->SetActorRotation(FRotator(0, yaw, 0));
무기 시스템 - 콤보 수정
플레이어 타격시 위치 보정
- 콤보 공격할때 충돌한 적이 밀려서 타격점을 벗어나는 문제가 있다.
- 플레이어가 타격한 적들 중 가장 가까운 적을 바라보도록 설정한다.
- 충돌이 끝났을때 확인한다.
- Normalize는 3D 공간상에서 일반화이기 때문에 z축까지 1로 줄인다.
- 평면상의 일반화는 Nomalize2D를 사용해야한다.
- Yaw 평면의 방향을 일반화할때 사용했다.
- 즉, 필요한건 타겟을 바라보는 Yaw의 회전값이니 Yaw값만 바라보는 회전값을 넣고 나머지는 컨트롤러의 회전값을 사용한다.
- CDoAction_Combo 35 13
- CPP 파일 - OnAttachmentEndCollision 함수 확인 - 각도를 -2.0f 로 저장하고 선언한다. - 바라볼 적을 저장할 캐릭터 클래스의 ‘타겟 후보자’ 변수를 null로 초기화하고 선언한다. - ‘맞았음’ 배열의 요소를 하나씩 꺼내서 ‘맞음’ 변수에 저장한다 : ‘맞음’ 변수의 ‘엑터 위치 가져오기’ 함수를 호출한 결과에서 ‘오너 캐릭터’의 ‘엑터 위치 가져오기’ 함수를 호출한 결과를 뺀 결과를 ‘방향’ 변수를 선언하고 저장한다. - ‘오너 캐릭터’의 ‘엑터 전방 벡터 가져오기’ 함수를 호출한 결과를 ‘전방’ 변수를 선언하고 저장한다. - ‘방향, 전방’ 변수를 ‘내적’ 함수에 넣고 호출한 결과를 ‘내적’ 변수를 선언하고 저장한다. - ‘내적’의 값이 0.75(45도)보다 작거나, ‘내적’ 값이 ‘각도’ 값보다 작다면 : 반복을 건너뛴다. - 조건을 모두 넘기면 ‘내적’ 값을 ‘각도’에 저장하고, ‘맞음’ 을 ‘타겟 후보자’ 변수에 저장한다. - ‘타겟 후보자’가 null이 아니라면 : ‘바라보는 회전값 찾기’ 함수에 ‘오너 캐릭터의 엑터위치 가져오기 함수 호출, 타겟 후보자의 엑터위치 가져오기 함수호출’을 넣고 호출한 결과를 ‘회전자’ 변수를 선언하고 저장한다. , ‘오너 캐릭터’의 ‘컨트롤러 가져오기’ 함수를 ‘플레이어 컨트롤러’로 지정하고 호출한 결과를 ‘컨트롤러’ 변수를 선언하고 저장한다. , ‘컨트롤러’가 null이 아니라면 : ‘컨트롤러’의 ‘컨트롤 회전 가져오기’ 함수를 호출한 결과를 ‘컨트롤 회전’ 변수를 선언하고 저장한다. , ‘회전자’ 함수에 ‘컨트롤 회전의 X 회전값, 회전자의 Z 회전값, 컨트롤 회전의 Y 회전값’을 넣고 호출한 결과를 ‘타겟’ 변수를 선언하고 저장한다. - ‘컨트롤러’의 ‘컨트롤 회전 설정’ 함수에 ‘타겟’을 넣고 호출한다.
//CPP void UCDoAction_Combo::OnAttachmentEndCollision() { Super::OnAttachmentEndCollision(); //타격 보정 float angle = -2.0f; ACharacter* candidate = nullptr; for (ACharacter* hitted : Hitted) { FVector direction = hitted->GetActorLocation() - OwnerCharacter->GetActorLocation(); direction = direction.GetSafeNormal2D(); //요 회전은 2D로 가져와야한다. FVector forward = OwnerCharacter->GetActorForwardVector(); //정면에서 가장 가까운 적 탐색 float dot = FVector::DotProduct(direction, forward); if (dot < 0.75f || dot < angle) { continue; } angle = dot; candidate = hitted; } //보정 시작 if (!!candidate) { FRotator rotator = UKismetMathLibrary::FindLookAtRotation(OwnerCharacter->GetActorLocation(), candidate->GetActorLocation()); APlayerController* controller = OwnerCharacter->GetController<APlayerController>(); if (!!controller) { FRotator controlRotation = controller->GetControlRotation(); FRotator target = FRotator(controlRotation.Pitch, rotator.Yaw, controlRotation.Roll); controller->SetControlRotation(target); } } //충돌 리스트 초기화 Hitted.Empty(); }
무기 시스템 - 활
활 밑준비
- 애니메이션 준비
- 블렌드 스페이스 생성
- 활 모델 준비
- 스켈레톤 선택할때 스켈레톤을 새로 만들지 말고 아니요 - 활 스켈레톤 선택 으로 진행한다.
- 활 메테리얼 할당
- 활을 등장시킬 소켓 추가
- Holster_Bow
- Bow 데이터 에셋 생성
워프
- 카메라 관련 조정은 ‘플레이어 컨트롤러’에서 한다.
- 컨트롤러가 ‘카메라’를 가지고 있다.
- frustum(절두체) : 원근감이 있는 카메라 영상을 투영한다.
- 화면에 투영하는 view 영역은 피라미드의 윗부분을 자른 형태로 투영된다.
- 이러한 투영을 ‘원근 투영 (Perspective)’이라고 한다.
- 좁은 부분 : Near / 넓은 부분 : Far
- width와 height의 비율 : 종횡비(aspect)
- W / H
- fullHD : 1920 * 1080
- 즉, 이때의 종횡비는 대략 1.7 : 1 이다.
- near ~ far 수직선과 대각선 각도 : feild of view (FOV)
- 크게 주면 줄수록 가로 세로 폭이 커진다.
- 일반적으로 FOV 45도일때 H 값이 1배수에 맞게 조절을 한다.
- 3D 공간상에서 마우스의 위치를 가져오는것은 FOV값의 비율에 따라 가져와진다.
- 화면에 비춰지는 Near부터 마우스까지의 레이에서 히팅되는 물체의 정보를 가져오는것과 같다고 생각하면 될것같다.
- ‘피킹’이라고 한다.
- 화면에 투영하는 view 영역은 피라미드의 윗부분을 자른 형태로 투영된다.
Impact와 Normal의 차이
- Impact : 충돌체와 삼각형 정보를 가진 메쉬를 탐지했을때, ‘충돌체’의 위치 정보를 가져오는것이 ‘Impact’다.
- 메쉬 위에 충돌체는 살짝 띄워져서 그려지기 때문에 메쉬를 탐지하는것보다는 정밀하지 않다.
- 메쉬를 이루는 정밀한 삼각형의 위치를 가져오는것이 ‘Location’같이 Impact가 안붙은 변수들이다.
메쉬 확인 방법
- 메쉬 파일 - 좌측 상단 라이팅 포함 버튼 클릭 - 와이어 프레임으로 변경
워프 벽이나 바닥에 끼는 문제
- 캐릭터의 충돌체의 중심점은 ‘배’인데 중심점을 기준으로 위치이동을 하다가 충돌체끼리 겹쳐서 끼는 것이다.
- 이동 직후 캐릭터의 위치만큼 올린다.
- 충돌체가 있는 벽에 갖히는 이유 : 충돌체를 딱 맞춰서 디자인하기 때문이다.
- 충돌체를 살짝 크게 만들어야한다.
- 다른 해결법 : BlockingVolum : 접근을 못하게 물리적으로 막는 엑터다.
BlockingVolume
- 물리적으로 접근을 못하게 막는 엑터다.
- 엑터 배치 창 - BlockingVolume 배치
- 배경을 디자인할때 충돌체보다 조금 크게 배치한다.
콤보 보정
- 플레이어가 적을 타격할때 적을 바라보게 살짝 보정을 해야한다.
- 적도 위치와 회전을 보정한다.