Enhanced Input System를 활용한 총기 연사, 점사 연구

Woogle·2023년 10월 31일
0

언리얼 엔진 5

목록 보기
46/59

📄 개요

  • 총 게임에서 버튼을 가만히 눌렀을 때, 연사 총기(기관총)와 점사 총기(샷건)는 발사 로직이 다르다.

  • 플래그 변수와 Timer를 설정하는 것으로도 충분히 구현 가능하나, 로직이 복잡해지면 가독성이 떨어질 수 있다.

  • 그래서 Enhanced input system을 활용해 플래그 변수와 Timer를 전혀 사용하지 않고 로직을 깔끔하게 만드는 방법을 연구했다.


📄 Enhanced Input System

✏️ Input Action

  • 향상된 입력 시스템과 프로젝트 코드 사이의 통신이다.
  • 입력 값을 bool, FVector2D 등으로 나타낼 수 있다.
  • 점프하기, 총 쏘기 등이 있다.

✏️ Input Mapping Context

  • 사용자의 입력과 액션을 매핑한다.
  • 각 사용자에 대해 동적으로 추가, 제거, 우선순위 지정될 수 있다.

✏️ Modifier

  • 사용자 디바이스의 원본 입력(Raw input) 값을 조절한다.
  • 데드존, 벡터를 로컬에서 월드 스페이스로 변환 등이 있다.

✏️ Trigger

  • 모디파이어 이후 입력 액션의 활성화 여부를 결정한다.
  • 프레스, 홀드, 탭, 펄스 등이 있다.

📄 Input Action 설정

  • 엔진에서 제공해주는 Pulse Trigger를 활용했다.

✏️ 연사 (Full-automatic fire)

  • 버튼을 가만히 누르면 0.15초 간격으로 총알을 계속 발사한다.

✏️ 점사 (Non-automatic fire)

  • 버튼을 가만히 누르면 1발만 발사한다. (= 1점사)

✏️ 3점사 (Burst-automatic fire)

  • 버튼을 가만히 누르면 0.15초 간격으로 최대 3발까지 발사한다.

📄 C++ 샘플 코드

  • C++에서 Input Action 클래스를 수정하는 경우, 반드시 Request Rebuild Control Mappings를 호출해야 정상적으로 반영된다.
  • 블루프린트에서 Input Action 클래스를 수정할 때는 위의 함수가 자동 호출되니 신경쓰지 않아도 된다.

🚀 Header

	UPROPERTY(EditAnywhere, Category = "Stat|Firing Mode")
	float FireRate = 0.15f;	

	UPROPERTY(EditAnywhere, Category = "Stat|Firing Mode")
	bool bAutomatic = false;

	// For burst firing. If this value is 2 or more, weapon can burst fire. Set bAutomatic true first.
	UPROPERTY(EditAnywhere, Category = "Stat|Firing Mode")
	int32 BurstShot = 0;

	void SetupWeaponInput();
	void SetFiringMode();
	void FirePressed(const FInputActionValue& Value);
	void AimPressed(const FInputActionValue& Value);
	void AimReleased(const FInputActionValue& Value);
	void ReloadPressed(const FInputActionValue& Value);

	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputMappingContext* WeaponMappingContext;

	UPROPERTY(EditAnywhere, Category = "Input")
	class UInputAction* FireAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* AimAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* ReloadAction;

🚀 Cpp

void AWeapon::SetupWeaponInput()
{
	APlayerController* PlayerController = Cast<APlayerController>(OwnerCharacter->GetController());
	if (PlayerController)
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(WeaponMappingContext, 1);
			if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))
			{
				EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &AWeapon::FirePressed);
				EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &AWeapon::AimPressed);
				EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &AWeapon::AimReleased);
				EnhancedInputComponent->BindAction(ReloadAction, ETriggerEvent::Started, this, &AWeapon::ReloadPressed);
				SetFiringMode();
			}
			Subsystem->RequestRebuildControlMappings(FModifyContextOptions(), EInputMappingRebuildType::RebuildWithFlush);
		}
	}
}

void AWeapon::SetFiringMode()
{
	if (FireAction && FireAction->Triggers.Num())
	{
		UInputTriggerPulse* Pulse = Cast<UInputTriggerPulse>(FireAction->Triggers[0]);
		if (Pulse)
		{
			if (bAutomatic == false)	// Non-automatic fire
			{
				Pulse->Interval = 999.f;
				Pulse->TriggerLimit = 1;
			}
			else if (BurstShot < 2)	// Full-automatic fire
			{
				Pulse->Interval = FireRate;
				Pulse->TriggerLimit = 0;
			}
			else	// Burst-automatic fire
			{
				Pulse->Interval = FireRate;
				Pulse->TriggerLimit = BurstShot;
			}
		}
		else
		{
  			UE_LOG(LogTemp, Log, TEXT("FireAction's trigger pulse is null."));
		}
	}
}

📄 참고 자료

profile
노력하는 게임 개발자

1개의 댓글

comment-user-thumbnail
2024년 1월 15일

도움이 됐습니다.

답글 달기