Hello guys. Im very new to unreal engine and currently learning projectiles at university. But i have a problem - i have to do ranged weapon foe Egypt. So i chose bow. And i have a problem with attaching it to socket on mesh of bow. I want it to attach at start of shooting process so arrow will be pulled with string by animation that i made and in the end it will be deattached and fly to the target. I have no problem with shooting and launching projectiles but i really cant get how to attach it so it will stay on bow despite any movements. It always spawns somewhere near characters body but no more than that. I have to do it through c++… Any help will be appreciated. I will give a code samples below:
My arrow header class:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
#include "Arrow.generated.h"
UCLASS()
class MYPROJECT_API AArrow : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AArrow();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* ArrowMesh;
UPROPERTY(VisibleAnywhere, Category = Movement)
UProjectileMovementComponent* ProjectileMovement;
public:
UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
USphereComponent* CollisionComponent;
// Called every frame
virtual void Tick(float DeltaTime) override;
void Launch(const FVector& Direction);
};
My arrow cpp class:
#include "Arrow.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AArrow::AArrow()
{
PrimaryActorTick.bCanEverTick = true;
if(!RootComponent)
{
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
}
if(!CollisionComponent)
{
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
CollisionComponent->InitSphereRadius(15.0f);
RootComponent = CollisionComponent;
}
CollisionComponent->SetupAttachment(RootComponent);
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
ProjectileMovement->SetUpdatedComponent(RootComponent);
ProjectileMovement->InitialSpeed = 0.0f;
ProjectileMovement->MaxSpeed = 0.0f;
ProjectileMovement->bShouldBounce = true;
ProjectileMovement->bRotationFollowsVelocity = true;
ProjectileMovement->Bounciness = 0.3f;
ProjectileMovement->ProjectileGravityScale = 0.0f;
ArrowMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ArrowMesh"));
ArrowMesh->SetupAttachment(RootComponent);
}
// Called when the game starts or when spawned
void AArrow::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AArrow::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AArrow::Launch(const FVector& Direction)
{
ProjectileMovement->InitialSpeed = 600.0f;
ProjectileMovement->MaxSpeed = 600.0f;
ProjectileMovement->Velocity = Direction * ProjectileMovement->InitialSpeed;
UE_LOG(LogTemp, Warning, TEXT("Arrow launched with speed: %f"), ProjectileMovement->Velocity.Size());
}
My player character header class:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Arrow.h"
#include "MyAnimInstanceClass.h"
#include "MyBowAnimClass.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PlayerChar.generated.h"
UCLASS()
class MYPROJECT_API APlayerChar : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APlayerChar();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
UCameraComponent* CameraComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
USkeletalMeshComponent* GunSkeletalMeshComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FVector MuzzleOffset;
// Змінна для класу стріли
AArrow* Arrow;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void MoveForward(float Value);
UFUNCTION()
void MoveRight(float Value);
UFUNCTION()
void JumpAction();
void UpdateAimingAnimation(bool IsAiming, bool HasArrow);
void StartAiming();
void StopAiming();
void FireArrow();
UPROPERTY(EditDefaultsOnly, Category = "Projectile")
TSubclassOf<AArrow> ArrowClass;
};
And finally my character cpp class:
// Fill out your copyright notice in the Description page of Project Settings.
#include "PlayerChar.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "Engine/Engine.h"
// Sets default values
APlayerChar::APlayerChar()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
check(CameraComponent != nullptr);
CameraComponent->AttachTo(GetMesh(), "headSocket");
CameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, BaseEyeHeight));
CameraComponent->bUsePawnControlRotation = true;
GunSkeletalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("GunSkeletalMeshComponent"));
GunSkeletalMeshComponent->bCastDynamicShadow = false;
GunSkeletalMeshComponent->CastShadow = false;
GunSkeletalMeshComponent->SetupAttachment(CameraComponent);
GunSkeletalMeshComponent->AttachTo(GetMesh(), "bowSocket");
}
// Called when the game starts or when spawned
void APlayerChar::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APlayerChar::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void APlayerChar::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", this, &APlayerChar::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &APlayerChar::MoveRight);
PlayerInputComponent->BindAxis("Turn", this, &APlayerChar::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &APlayerChar::AddControllerPitchInput);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &APlayerChar::JumpAction);
PlayerInputComponent->BindAction("Aim", IE_Pressed, this, &APlayerChar::StartAiming);
PlayerInputComponent->BindAction("Aim", IE_Released, this, &APlayerChar::StopAiming);
PlayerInputComponent->BindAction("Fire", IE_Released, this, &APlayerChar::FireArrow);
}
void APlayerChar::MoveForward(float Value)
{
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void APlayerChar::MoveRight(float Value)
{
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
void APlayerChar::JumpAction() {
Jump();
}
void APlayerChar::StartAiming()
{
FVector MuzzleLocation = GunSkeletalMeshComponent->GetSocketLocation("arrowSocket");
FRotator MuzzleRotation = GunSkeletalMeshComponent->GetSocketRotation("arrowSocket");
MuzzleRotation.Yaw += 90.0f;
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
Arrow = GetWorld()->SpawnActor<AArrow>(ArrowClass, MuzzleLocation, MuzzleRotation, SpawnParams);
if (Arrow)
{
Arrow->AttachToComponent(GunSkeletalMeshComponent, FAttachmentTransformRules::SnapToTargetNotIncludingScale, "arrowSocket");
UpdateAimingAnimation(true, true);
UE_LOG(LogTemp, Warning, TEXT("Aiming started"));
}
}
void APlayerChar::StopAiming()
{
if (Arrow)
{
Arrow->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
UE_LOG(LogTemp, Warning, TEXT("Arrow detached from bow"));
}
UpdateAimingAnimation(false, true);
UE_LOG(LogTemp, Warning, TEXT("Aiming stopped"));
}
void APlayerChar::FireArrow()
{
FVector CameraLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(CameraLocation, CameraRotation);
FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
FRotator MuzzleRotation = CameraRotation;
if (Arrow)
{
FVector Direction = MuzzleRotation.Vector();
UE_LOG(LogTemp, Warning, TEXT("Arrow Direction: %s"), *Direction.ToString());
Arrow->Launch(Direction);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Arrow didn't spawn"));
}
}
void APlayerChar::UpdateAimingAnimation(bool isAiming, bool hasArrow)
{
UE_LOG(LogTemp, Warning, TEXT("Start anim func"));
if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
{
if (UMyAnimInstanceClass* MyAnimInstance = Cast<UMyAnimInstanceClass>(AnimInstance))
{
MyAnimInstance->IsAiming_1 = isAiming;
MyAnimInstance->HasArrow_1 = hasArrow;
UE_LOG(LogTemp, Warning, TEXT("Variables updated: IsAiming=%d, HasArrow=%d"), MyAnimInstance->IsAiming_1, MyAnimInstance->HasArrow_1);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Couldnt cast"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Couldnt get anim instance"));
}
UE_LOG(LogTemp, Warning, TEXT("Retrieving bow animations"));
if (UAnimInstance* AnimBowInstance = GunSkeletalMeshComponent->GetAnimInstance())
{
if (UMyBowAnimClass* MyBowAnimInstance = Cast<UMyBowAnimClass>(AnimBowInstance))
{
MyBowAnimInstance->IsAiming_1 = isAiming;
UE_LOG(LogTemp, Warning, TEXT("Variables updated: IsAiming=%d"), MyBowAnimInstance->IsAiming_1);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Couldnt cast"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Couldnt get anim instance"));
}
}