공부/Unreal

24.08.22

월러비 2024. 8. 24. 23:45

무기 시스템 - 활

  • 이전과 이어서 시작

화살 발사 / 추가 발사

  • 화살은 ‘카메라’가 바라보는 전방으로 발사되어야한다.
    • 캐릭터 전방으로 맞추면 화살이 날아가는 방향이 바라보는 시점의 방향과 어긋날 수 있다.
  • CDoAction_Bow
    • CPP 파일 - ‘엑션 시작’ 함수 확인 - ‘쿼터니온’에 ‘오너 캐릭터’의 ‘카메라 회전값 가져오기’를 호출하여 넣은 호출의 ‘전방 벡터 가져오기’ 함수를 호출하고 ‘전방’ 변수를 선언하여 저장한다. - ‘화살’의 ‘발사’ 함수에 ‘전방’ 변수를 넣고 호출한다. - ‘엑션 종료’ 함수 확인 - ‘화살 생성’ 함수 호출
    //CPP
    //카메라가 바라보는 전방
    FVector forward = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();
    
    arrow->Shoot(forward); //화살 발사
    
    void UCDoAction_Bow::End_DoAction()
    {
    	Super::End_DoAction();
    
    	CreateArrow(); //화살 생성
    
    }
    
  • BP_CArrow
    • 속도 조정은 블루프린트에서 실행한다.
    • 발사체 컴포넌트 - 디테일 - 초기 속도 : 200

화살 충돌

  • 화살의 히트 이벤트들은 ‘DoAction_Bow’에 준다.
    • DoAction_Bow에서 화살이 충돌하면 삭제시키는 작업도 할것이기 때문이다.
  • 화살을 현재 충돌체에 꽂힌다.
    • 해결법
      1. 맞자마자 삭제되도록 설정한다.
      2. 충돌 체크를 충돌체와 하는것이 아니라 메쉬와 한다. (복잡 하다)
      3. 맞은 위치에서 가장 가까운 ‘본’의 위치를 가져와서 그곳에 붙힌다.
  • 현재 충돌 후 발사시 문제점 : 화살은 충돌 후 일정시간이 지나면 삭제되도록 했지만, 배열 상으로는 안의 요소가 삭제되어 null이 되는것이지 공간이 삭제되는 것이 아니다.
    • 해결 방법 : 충돌 되었다는 사실이 알려지면 리스트도 삭제한다.
    • 모든 엑터는 사라지는 이유를 반환한다. : EEndPlayReason::Type
    • 즉, 델리게이트를 선언하고 EEndPlayReason::Type를 받도록 매개변수를 설정한다.
      • 들어온 매개변수를 이용하여 삭제되었다는 것을 알았고, 그걸 이용해서 배열의 공간을 삭제한다.
  • CArrow
    • 화살의 충돌 프로파일 설정을 ‘전부 블록’으로 지정했으니, Hit 이벤트로 충돌을 처리한다.
    • 충돌된 객체가 ‘Ignores’에 넣어진 객체와 같으면 충돌 시키지 않는다.
    • 헤더 파일 - 델리게이션 선언 , ‘화살 충돌’ 이름으로 ‘충돌 무기 엑터, 충돌된 캐릭터’ 를 받는다. - 델리게이션 자료형인 ‘화살 충돌’의 ‘충돌 활성’ 변수 선언 - 캡슐 컴포넌트와 연결할 ‘컴포넌트 히트’ 이벤트 함수 선언 - ‘충돌 후 스폰 시간’ 변수 선언
    //header
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FArrowHit, class AActor*, InCauser, class ACharacter*, InOtherCharacter);
    
    private:
    	UPROPERTY(EditDefaultsOnly, Category = "LifeSpawn")
    		float LifeSpawnAfterCollision = 1; //객체가 사라지기까지 걸리는 시간
    		
    private:
    	UFUNCTION()
    		void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
    
    public:
    	FArrowHit OnHit; //델리게이트 화살 히트 변수
    
    • CPP 파일 - ‘컴포넌트 히트’ 이벤트 함수 정의 - ‘스폰 유지 설정’ 함수에 ‘충돌 후 스폰 시간’을 넣고 호출한다. - ‘무시들’의 요소를 하나씩 ‘엑터’변수에 넣고 반복한다. : ‘엑터’와 ‘충돌된 다른 엑터’가 같은지 확인한다. - ‘충돌된 다른 엑터’를 ‘캐릭터’ 클래스로 ‘캐스트’ 함수를 호출하고 ‘캐릭터’ 변수를 선언하고 저장한다. - ‘캐릭터’가 비어있지 않고, ‘히트 활성’에 ‘함수 연결 확인’이 false가 아니라면 : ‘히트 활성’의 ‘연결된 모든 함수 실행’에 ‘충돌 무기 : 현재 클래스(화살), 충돌된 캐릭터’를 넣고 호출한다. - BeginPlay 함수 확인 - ‘캡슐’의 ‘컴포넌트 히트 활성’의 ‘연결 함수 추가’에 ‘연결될 함수가 있는 클래스 : CArrow, 연결할 함수 : OnCOmponentHit’를 넣고 호출한다. - 타이머 델리게이션을 위해 타이머 핸들 구조체의 ‘핸들’ 변수 선언 - 타이머 델리게이스 구조체의 ‘타이머 델리게이스 선언 후 ‘타이머 델리게이트 : 람다 생성[외부 변수 받기]로 설정한다. : ‘캡슐’의 ‘콜리전 활성 설정’ 함수에 ‘콜리전 활성 열거 :오버랩과 충돌’을 넣고 호출한다. - ‘월드 가져오기’ 함수 호출의 ‘타이머 매니저 가져오기’ 함수 호출의 ‘타이머 설정’ 함수에 ‘핸들, 타이머 델리게이트, 몇초동안 실행할건지 : 0.1초, 반복 여부 : false’를 넣고 호출한다. - ‘컴포넌트 히트 활성’ 함수 확인 - ‘캡슐’의 ‘콜리전 활성 설정’ 함수에 ‘콜리전 활성 : 충돌 안함’을 넣고 호출한다.
    //CPP
    //두 액션의 화살충돌 델리게이션 연결
    Capsule->OnComponentHit.AddDynamic(this, &ACArrow::OnComponentHit); 
    
    void ACArrow::Shoot(const FVector InForward)
    {
    	//전방 벡터 * 초기 속도
    	Projectile->Velocity = InForward * Projectile->InitialSpeed;
    	Projectile->SetActive(true); //날아가기 활성
    
    	//플레이어 충돌때문에 발사 지연시켜서 충돌 활성화
    	FTimerHandle handle;
    	FTimerDelegate timerDelegate = FTimerDelegate::CreateLambda([&]()
    		{
    			Capsule->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); //충돌 활성
    		});
    
    	GetWorld()->GetTimerManager().SetTimer(handle, timerDelegate, 0.1f, false);
    
    }
    
    void ACArrow::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
    {
    	//생성한 객체 파괴되기까지의 시간설정
    	SetLifeSpan(LifeSpawnAfterCollision);
    
    	for (AActor* actor : Ignores)
    	{
    		CheckTrue(actor == OtherActor);
    	}
    	
    	Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision); //충돌 비활성화
    
    	ACharacter* character = Cast<ACharacter>(OtherActor);
    
    	if (!!character && OnHit.IsBound())
    	{
    		OnHit.Broadcast(this, character);
    	}
    
    }
    
  • DoAction_Bow
    • 헤더 파일 - ‘화살 충돌 활성’ 함수에 ‘공격 무기 엑터, 충돌된 캐릭터’를 넣고 선언한다.
    //header
    private:
    	//화살 충돌 활성 함수
    	UFUNCTION()
    		void OnArrowHit(class AActor* InCauser, class ACharacter* InOtherCharacter);
    
    • CPP 파일 - ‘엑션 시작’ 함수 확인 - 화살이 쏴지기 전 손에서 떨어진 화살에 ‘히트’ 이벤트 델리게이션을 연결하기 위해 전방 벡터 저장 전 확인 - ‘화살’의 ‘충돌 활성’ 의 ‘함수연결’ 함수에 ‘연결 함수가 있는 클래스, 연결 함수 : 화살 충돌’을 넣고 호출한다 - ‘화살 충돌’ 함수 정의 - ‘히트 데이터들’의 ‘요소 수 반환’ 함수를 호출한 결과가 0개가 아닌지 확인한다. - ‘히트 데이터즈’의 ‘0번 순서’의 ‘데미지 전달’ 함수에 ‘오너 캐릭터, 공격 무기, 충돌된 캐릭터’를 넣고 호출한다.
    //CPP
    arrow->OnHit.AddDynamic(this, &UCDoAction_Bow::OnArrowHit); //화살 충돌 델리게이션 연결
    arrow->OnEndPlay.AddDynamic(this, &UCDoAction_Bow::OnArrowEndPlay); //화살 파괴 알리기 델리게이션 연결
    	
    void UCDoAction_Bow::OnArrowHit(AActor* InCauser, ACharacter* InOtherCharacter)
    {
    	CheckFalse(HitDatas.Num() > 0);
    
    	HitDatas[0].SendDamage(OwnerCharacter, InCauser, InOtherCharacter);
    }
    
  • CAttachment_Bow
    • CPP 파일 - 생성자 확인 - ‘스켈레탈 메쉬’의 ‘콜리전 프리팹 이름으로 설정’ 함수에 ‘충돌 안함’을 넣고 호출한다.
    //CPP
    SkeletalMesh->SetCollisionProfileName("NoCollision");
    
  • 델리게이션 순서 정리
    1. 화살 클래스에 충돌이 일어나면 다른곳의 함수를 호출하는것을 연결하기 위한 ‘델리게이트’ 와 변수 선언
    2. 화살 클래스 객체에서 충돌이 발생하면 ‘OnComponentHit’ 이벤트가 실행된다.
      1. 델리게이트 변수에 연결된 함수가 있다면 그것을 실행시킨다.
    3. DoAction_Bow에서 충돌 관련 함수를 정의한다.
      1. 화살 클래스를 가져오고, 그 안의 ‘델리게이트 변수’에 함수를 연결한다.
    4. 충돌이 시작되면 화살 클래스가 충돌 이벤트를 실행하고 그 안의 다른곳과 연결된 함수가 있는지 확인하고, DoAction_Bow에 연결된 함수가 있으니 그것을 실행시키는 것이다.

화살 충돌 되어 삭제됐다는것을 알리고 화살 리스트에서 삭제하기

  • CArrow
    • 헤더 파일 - 델리게이트 선언 , ‘발사체 실행 종료’라고 명명하고 ‘삭제할 화살’을 받아 선언한다. - 델리게이트 자료형으로 ‘실행 종료 활성’ 변수 선언 - ‘실행 종료’ 함수에 ‘실행 종료 이유’를 받을 수 있도록 재정의 선언한다.
    //header
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FProjectileEndPlay, class ACArrow*, InDestroyer);
    
    FProjectileEndPlay OnEndPlay; //화살 삭제 알리기 델리게이트
    
    void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
    
    • CPP 파일 - ‘실행 종료’ 함수 정의 - 부모 함수 호출 - ‘실행 종료 활성’ 델리게이트 변수에 ‘연결 함수 확인’ 함수를 호출하고 true 라면 : ‘실행 종료 활성’의 ‘연결 함수 실행’에 ‘삭제할 화살 : 현재 클래스’를 넣고 호출한다.
    //CPP
    void ACArrow::EndPlay(const EEndPlayReason::Type EndPlayReason)
    {
    	Super::EndPlay(EndPlayReason);
    
    	//화살 파괴 알리기 연결 되었는지 확인
    	if (OnEndPlay.IsBound())
    	{
    		OnEndPlay.Broadcast(this);
    	}
    }
    
  • CDoAction_Bow
    • 헤더 파일 - 델리게이트를 연결할 ‘화살 실행 종료 활성’ 함수를 선언하고 화살 클래스 포인터의 ‘삭제할 화살’을 받도록 한다.
    //header
    //화살 파괴 활성 알리기 함수
    	UFUNCTION()
    		void OnArrowEndPlay(class ACArrow* InDestroyer);
    
    • CPP 파일 - ‘화살 실행 종료 활성’ - ‘화살들’의 ‘제거’ 함수에 매개변수인 ‘삭제될 화살’을 넣고 호출한다. - ‘엑션 실행’ 함수 확인 - ‘화살’의 ‘실행 종료 활성’ 함수의 ‘함수 연결’에 ‘연결할 함수가 있는 클래스, 연결할 함수’를 넣고 호출한다.
    //CPP
    void UCDoAction_Bow::OnArrowEndPlay(ACArrow* InDestroyer)
    {
    	//호출되면 들어온 객체를 화살 리스트에서 삭제한다.
    	Arrows.Remove(InDestroyer);
    }
    

화살 충돌 데미지 전달

  • CDoAction_Bow
    • CPP 파일 - ‘화살 히트 활성’ 함수 확인 - ‘히트 데이터’의 ‘요소 수 반환’ 함수 호출 결과가 0보다 큰지 확인 - ‘히트 데이터’의 ‘0번’의 ‘데미지 전달’에 ‘오너 캐릭터, 공격 무기, 충돌된 캐릭터’를 넣고 호출한다.
    //CPP
    void UCDoAction_Bow::OnArrowHit(AActor* InCauser, ACharacter* InOtherCharacter)
    {
    	CheckFalse(HitDatas.Num() > 0);
    
    	HitDatas[0].SendDamage(OwnerCharacter, InCauser, InOtherCharacter);
    }
    
  • DA_Bow
    • 활은 스킬이나 ‘대궁’같은 특이한 무기가 아닌 이상 필수적인 것만 넣는다.
    • HitData에 데미지나 넉벡 등의 정보를 넣는다.

활 줄 수정

  • 활 줄은 노티파이를 이용한다.
    • 쏠 때(Begin_DoAction)는 줄을 떨어뜨리고, 끝날때는 다시 붙인다.
  • 활 줄을 다시 되돌릴때 사용할 ‘벤딩’ 값은 Attachment에 있다.
    • DoAction_Bow에서는 Attachment를 사용할 일이 없기에 Attachment_Bow로 바꿀 수 있다.
  • CDoAction_Bow
    • 헤더 파일 - ‘줄 붙은지 확인’ 변수를 선언하고 true로 초기화한다. - ‘활 줄 종료’ 함수 선언 - 활 줄을 되돌릴때 사용하기 위해 ‘벤딩’ 변수를 포인터로 선언한다.
    • CPP 파일 - 매개변수로 받은 Attachment를 Attachment_Bow로 ‘캐스팅’ 함수를 호출하고 결과를 ‘활’ 변수를 선언하여 저장한다.(이게 다운캐스팅이다.) - ‘벤딩’에 ‘활’의 ‘애님 인스턴스 벤딩 가져오기’ 함수 호출 결과를 저장한다. - ‘엑션 시작’ 함수 확인 - 공격을 시작할 때 ‘벤딩’ 값을 0으로 초기화한다. - 활 줄을 떨어뜨리기 위해 ‘줄 붙은지 확인’ 변수는 false로 설정한다. - ‘활 줄 종료’ 함수 정의 - ‘벤딩’ 값을 100으로 설정한다. (벤딩은 0 ~ 100까지의 값이 들어가고, 100일때 활이 당겨지는 상황이다.) - ‘줄 붙은지 확인’ 변수는 true로 설정한다. - Tick 함수 확인 - ‘줄 붙은지 확인’ 변수가 false라면줄을 손에 붙이는 작업은 실행하지 않는다.
    //CPP
    //활 줄 되돌리기 준비
    ACAttachment_Bow* bow = Cast<ACAttachment_Bow>(InAttachment);
    Bending = bow->GetAnimInstance_Bending();
    
    *Bending = 0.0f;
    bAttachedString = false; //활 줄 떨어뜨리기
    
    void UCDoAction_Bow::End_BowString()
    {
    	*Bending = 100.0f;
    
    	bAttachedString = true; //활 줄 다시 붙이기
    }
    
  • CAnimNotify_EndBowString
    • 헤더 파일 - 이전 노티파이와 같이 ‘노티파이 이름수정’과 ‘노티파이 실행’ 함수를 사용한다.
    //header
    private:
    	FString GetNotifyName_Implementation() const override;
    	void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
    
    • CPP 파일 - 이전 노티파이와 같이 작성하고, 실행할 함수가 있는 클래스만 ‘화살 클래스’로 바꾼다. - ‘화살’의 ‘활 줄 종료’ 함수를 호출한다.
    //CPP
    FString UCAnimNotify_EndBowString::GetNotifyName_Implementation() const
    {
    	return "EndBowString";
    }
    
    void UCAnimNotify_EndBowString::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
    {
    	Super::Notify(MeshComp, Animation, EventReference);
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    	CheckNull(weapon);
    	CheckNull(weapon->GetDoAction());
    
    	UCDoAction_Bow* bow = Cast<UCDoAction_Bow>(weapon->GetDoAction());
    	CheckNull(bow);
    
    	bow->End_BowString();
    }
    
  • 활 쏘는 몽타주
    • 노티파이 할당

UI

플레이어 무기 타입 출력 - 결과 출력은 안하고 화면에만 올렸다.

  • UI 요소 하나하나를 ‘위젯’이라고 부른다.
  • C에서 ‘메세지’를 블루프린트로 전달할 수는 있다.
    • 단점 : SendMessage가 느리다. / 제약 사항이 심하다.
    • 해결책 : C에서 UUserWidget을 상속받아 ‘클래스’를 만들고, 블루프린트에서 재정의할 수 있도록 ‘함수’를 만든다.
  • 한글 폰트 넣는법은 인터넷에 많으니 찾아봐라
  • 원래는 플레이어의 UI는 플레이어에 작성해야하지만, 무기의 상태를 출력할것이니 웨폰 컴포넌트에 작성한다.
    • 하는것은 똑같으니 나중에 플레이어에 넣어라
  • CUserWidget_Player
    • 생성 - UserWidget 상속
  • CPlayer
    • 헤더 파일 - 유저 위젯_플레이어로 ‘클래스 제한’을 한 ‘유아이 클래스’ 선언 - 유저 위젯_플레이어 포인터 자료형의 ‘유저 인터페이스’ 변수 선언
    //header
    private:
    	UPROPERTY(EditAnywhere, Category = "Evade")
    		TSubclassOf<class UCUserWidget_Player> UiClass; //플레이어 위젯 클래스 저장
    		
    private:
    	class UCUserWidget_Player* UserInterface; //유아이 플레이어 저장
    
    • CPP 파일 - 생성자 확인 - 유저 위젯_플레이어 클래스로 ‘클래스 가져오기’ 함수 호출하고 ‘유아이 클래스, 위젯 블루프린트 레퍼런스’ 를 넣는다. - BeginPlay 함수 확인 - ‘유아이 클래스’가 비어있지 않다면 : ‘유저 인터페이스’에 ‘위젯 생성’ 함수에 <유저 위젯_플레이어, 플레이어 컨트롤러>를 넣고 ‘컨트롤러 가져오기를 플레이어 컨트롤러로 지정하고 호출, 유아이 클래스’를 넣고 호출한다. - ‘유저 인터페이스’의 ‘뷰포트에 추가’ 함수를 호출한다.
    //CPP
    CHelpers::GetClass<UCUserWidget_Player>(&UiClass, "WidgetBlueprint'/Game/Widgets/WB_Player.WB_Player_C'");
    
    //위젯 있으면 출력
    //유저 인터페이스 설정
    if (!!UiClass)
    {
    	UserInterface = CreateWidget<UCUserWidget_Player, APlayerController>(GetController<APlayerController>(), UiClass);
    	UserInterface->AddToViewport();
    }
    
  • WB_Player
    • 콘텐츠 - 유저 인터페이스 - 위젯 플루프린트
    • 캔버스 패널 배치 - 그리드 패널 배치 - 채우기 규칙 : 열 채우기 : 2개 추가 / 행 채우기 : 2개 추가 - 그리드 패널 앵커 ‘우 하단’으로 이동 - 정렬 : 1.25 / 1.5 - ‘콘텐츠 크기에 맞춤’ 체크 - 텍스트 위젯 배치 - 텍스트 위젯 복사 - 텍스트 변경 : Player, Weapon - Player 텍스트 오퍼시티 : 0.5 - Weapon 텍스트 컬러 : 0, 1, 0 - Weapon 텍스트 ‘양쪽맞춤’ - 그리드 패널에 ‘텍스트’ 위젯 추가 - 웨폰 타입 숫자를 출력할것이다. - 텍스트 폰트 크기 변경 - Player 텍스트는 0행을 혼자 쓸것이니 ‘열 범위’를 2로 설정한다. - Weapon 패딩 : 5 - 웨폰 타입 숫자 패딩 : 5

UI 설명

  • UMG (Unreal Motion Graphic) : 게임 상에서 사용하는 UI를 부르는 이름이다.
  • Slate UI : 에디터 상의 UI를 부르는 이름이다.
  • Widget은 UMG나 Slate나 똑같은것을 사용한다.
    • 디자인만 다르게 보여지는 것이다.
    • 즉, 게임 UI를 다룬다면, 에디터 UI도 다룰 수 있다.

위젯 블루프린트 설명

  • DPI (Dot Per Inch) : 인치당 점이 몇개 찍혀있냐를 수치로 나타내는 단위이다.
    • 예전에는 ‘픽셀’로 수치를 디자인했지만, 현재는 기기마다 해상도가 달라졌기 때문에 ‘인치’ 단위로 바뀌었다.
    • 1인치에 ‘점(dot)’이 몇개 들어가느냐다.
      • 보통 1 / 96 (1인치에 96개의 점)으로 잡는다.
  • 보통 위젯의 디자인은 1280 * 720 (16 : 9) 비율을 맞춰서 디자인한다.
  • ‘패널’ 위젯은 애당 위젯 위에 어떠한 위젯들을 올릴 수 있는 위젯이다.
  • 일반 - 패널
    • 캔버스 패널 : 실제로 게임 화면에 보여질 화면과 1 : 1로 디자인되는 위젯이다.
      • 적측은 캔버스를 가지지 않는다.
      • 스크린에 배치될 위젯들은 ‘캔버스 패널’로부터 시작한다.
    • 그리드 패널 : 공간을 균등하게 분할하는 패널이다.
      • 디테일 - 슬롯의 ‘행’과 ‘열’이 패널 내부의 요소를 행 과 열 로 공간 분할이 가능하다.
      • 행 합치기 : 행 스판
      • 열 합치기 : 열 스판(열 범위)
  • 앵커 : 위젯의 위치 중심점을 옮기는 옵션이다.
  • 정렬 : 앵커를 기준으로 위치를 맞추는 옵션이다.
  • 콘텐츠 크기에 맞춤 ; 체크하면 콘텐츠의 크기에 맞춰서 패널의 크기가 늘어난다.

'공부 > Unreal' 카테고리의 다른 글

24.08.26  (1) 2024.08.27
24.08.23  (0) 2024.08.24
24.08.21  (3) 2024.08.23
24.08.20  (1) 2024.08.23
24.08.19  (0) 2024.08.20