Jump to content
  • Advertisement
  • 01/23/18 04:04 PM

    Designing Player World Interaction in Unreal Engine 4

    Engines and Middleware

    Martin H Hollstein

    Originally posted on Troll Purse development blog.

    Unreal Engine 4 is an awesome game engine and the Editor is just as good. There are a lot of built in tools for a game (especially shooters) and some excellent tutorials out there for it. So, here is one more. Today the topic to discuss is different methods to program player world interaction in Unreal Engine 4 in C++. While the context is specific to UE4, it can also easily translate to any game with a similar architecture.

    Interaction via Overlaps

    By and far, the most common tutorials for player-world interaction is to use Trigger Volumes or Trigger Actors. This makes sense, it is a decoupled way to set up interaction and leverages most of the work using classes already provided by the engine. Here is a simple example where the overlap code is used to interact with the player:

    Header

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "InteractiveActor.generated.h"
    
    UCLASS()
    class GAME_API InteractiveActor : public AActor
    {
    	GENERATED_BODY()
    
    public:
    	// Sets default values for this actor's properties
    	InteractiveActor();
    
        virtual void BeginPlay() override;
    
    protected:
    	UFUNCTION()
    	virtual void OnInteractionTriggerBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
    
    	UFUNCTION()
    	virtual void OnInteractionTriggerEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
    
        UFUNCTION()
        virtual void OnPlayerInputActionReceived();
    
    	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Interaction)
    	class UBoxComponent* InteractionTrigger;
    }

    This is a small header file for a simple base Actor class that can handle overlap events and a single input action. From here, one can start building up the various entities within a game that will respond to player input. For this to work, the player pawn or character will have to overlap with the InteractionTrigger component. This will then put the InteractiveActor into the input stack for that specific player. The player will then trigger the input action (via a keyboard key press for example), and then the code in OnPlayerInputActionReceived will execute. Here is a layout of the executing code.

    Source

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "InteractiveActor.h"
    #include "Components/BoxComponent.h"
    
    // Sets default values
    AInteractiveActor::AInteractiveActor()
    {
    	PrimaryActorTick.bCanEverTick = true;
    
    	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
    	RootComponent->SetMobility(EComponentMobility::Static);
    
    	InteractionTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("Interaction Trigger"));
    	InteractionTrigger->InitBoxExtent(FVector(128, 128, 128));
    	InteractionTrigger->SetMobility(EComponentMobility::Static);
    	InteractionTrigger->OnComponentBeginOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyBeginOverlap);
    	InteractionTrigger->OnComponentEndOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyEndOverlap);
    
    	InteractionTrigger->SetupAttachment(RootComponent);
    }
    
    void AInteractiveActor::BeginPlay()
    {
        if(InputComponent == nullptr)
        {
            InputComponent = ConstructObject<UInputComponent>(UInputComponent::StaticClass(), this, "Input Component");
            InputComponent->bBlockInput = bBlockInput;
        }
    
        InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnPlayerInputActionReceived);
    }
    
    void AInteractiveActor::OnPlayerInputActionReceived()
    {
        //this is where logic for the actor when it receives input will be execute. You could add something as simple as a log message to test it out.
    }
    
    void AInteractiveActor::OnInteractionProxyBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
    	if (OtherActor)
    	{
    		AController* Controller = OtherActor->GetController();
            if(Controller)
            {
                APlayerController* PC = Cast<APlayerController>(Controller);
                if(PC)
                {
                    EnableInput(PC);
                }
            }
    	}
    }
    
    void AInteractiveActor::OnInteractionProxyEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
    {
    	if (OtherActor)
    	{
    		AController* Controller = OtherActor->GetController();
            if(Controller)
            {
                APlayerController* PC = Cast<APlayerController>(Controller);
                if(PC)
                {
                    DisableInput(PC);
                }
            }
    	}
    }

     

    Pros and Cons

    The positives of the collision volume approach is the ease at which the code is implemented and the strong decoupling from the rest of the game logic. The negatives to this approach is that interaction becomes broad when considering the game space as well as the introduction to a new interactive volume for each interactive within the scene.

    Interaction via Raytrace

    Another popular method is to use the look at viewpoint of the player to ray trace for any interactive world items for the player to interact with. This method usually relies on inheritance for handling player interaction within the interactive object class. This method eliminates the need for another collision volume for item usage and allows for more precise interaction targeting.

    Source

    AInteractiveActor.h

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "InteractiveActor.generated.h"
    
    UCLASS()
    class GAME_API AInteractiveActor : public AActor
    {
    	GENERATED_BODY()
    
    public:
        virtual OnReceiveInteraction(class APlayerController* PC);
    }

     

    AMyPlayerController.h

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/PlayerController.h"
    #include "AMyPlayerController.generated.h"
    
    UCLASS()
    class GAME_API AMyPlayerController : public APlayerController
    {
    	GENERATED_BODY()
    
        AMyPlayerController();
    
    public:
        virtual void SetupInputComponent() override;
    
        float MaxRayTraceDistance;
    
    private:
        AInteractiveActor* GetInteractiveByCast();
    
        void OnCastInput();
    }

     

    These header files define the functions minimally needed to setup raycast interaction. Also note that there are two files here as two classes would need modification to support input. This is more work that the first method shown that uses trigger volumes. However, all input binding is now constrained to the single ACharacter class or - if you designed it differently - the APlayerController class. Here, the latter was used.

    The logic flow is straight forward. A player can point the center of the screen towards an object (Ideally a HUD crosshair aids in the coordination) and press the desired input button bound to Interact. From here, the function OnCastInput() is executed. It will invoke GetInteractiveByCast() returning either the first camera ray cast collision or nullptr if there are no collisions. Finally, the AInteractiveActor::OnReceiveInteraction(APlayerController*)  function is invoked. That final function is where inherited classes will implement interaction specific code.

    The simple execution of the code is as follows in the class definitions.

    AInteractiveActor.cpp

    void AInteractiveActor::OnReceiveInteraction(APlayerController* PC)
    {
        //nothing in the base class (unless there is logic ALL interactive actors will execute, such as cosmetics (i.e. sounds, particle effects, etc.))
    }

     

    AMyPlayerController.cpp

    AMyPlayerController::AMyPlayerController()
    {
        MaxRayTraceDistance = 1000.0f;
    }
    
    AMyPlayerController::SetupInputComponent()
    {
        Super::SetupInputComponent();
        InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnCastInput);
    }
    
    void AMyPlayerController::OnCastInput()
    {
        AInteractiveActor* Interactive = GetInteractiveByCast();
        if(Interactive != nullptr)
        {
            Interactive->OnReceiveInteraction(this);
        }
        else
        {
            return;
        }
    }
    
    AInteractiveActor* AMyPlayerController::GetInteractiveByCast()
    {
        FVector CameraLocation;
    	FRotator CameraRotation;
    
    	GetPlayerViewPoint(CameraLocation, CameraRotation);
    	FVector TraceEnd = CameraLocation + (CameraRotation.Vector() * MaxRayTraceDistance);
    
    	FCollisionQueryParams TraceParams(TEXT("RayTrace"), true, GetPawn());
    	TraceParams.bTraceAsyncScene = true;
    
    	FHitResult Hit(ForceInit);
    	GetWorld()->LineTraceSingleByChannel(Hit, CameraLocation, TraceEnd, ECC_Visibility, TraceParams);
    
        AActor* HitActor = Hit.GetActor();
        if(HitActor != nullptr)
        {
            return Cast<AInteractiveActor>(HitActor);
        }
    	else
        {
            return nullptr;
        }
    }

     

    Pros and Cons

    One pro for this method is the control of input stays in the player controller and implementation of input actions is still owned by the Actor that receives the input. Some cons are that the interaction can be fired as many times as a player clicks and does not repeatedly detect interactive state without a refactor using a Tick function override.

    Conclusion

    There are many methods to player-world interaction within a game world. In regards to creating Actors within Unreal Engine 4 that allow for player interaction, two of these potential methods are collision volume overlaps and ray tracing from the player controller. There are several other methods discussed out there that could also be used. Hopefully, the two implementations presented help you decide on how to go about player-world interaction within your game. Cheers!

     

     

    Originally posted on Troll Purse development blog.



      Report Article


    User Feedback


    There is a typo in class declaration should be class AInteractiveActor instead of class InteractiveActor. Also identation is broken (mix of spaces and tabs).

    Edited by RootKiller

    Share this comment


    Link to comment
    Share on other sites
    On 1/23/2018 at 12:20 PM, RootKiller said:

    There is a typo in class declaration should be class AInteractiveActor instead of class InteractiveActor. Also identation is broken (mix of spaces and tabs).

    Thanks, I will fix that up in time. Any thoughts on the content other than that? Was this useful?

    Share this comment


    Link to comment
    Share on other sites

    Thanks for the post.

    As a none programmer I never find the time to develop new code like this, so having someone post there work and explain it is great for me to read.

    Share this comment


    Link to comment
    Share on other sites


    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

  • Advertisement
  • Advertisement
  • Latest Featured Articles

  • Featured Blogs

  • Popular Now

  • Similar Content

    • By Atomic_Slugcat
      Greetings, reader.
      Our company, the 'Node Collective', is looking for more people to help on our current project, "Where Ravens Fly".
      What is "Where Ravens Fly?"
      As a brief description:
      "Where Ravens Fly" is a FPS open-world adventure game in which the player is in control of the protagonist Raden Roche, a soldier determined to overthrow two factions, the Militia and the Federation that would both bring America and its citizens into a dystopian future if either would win.
      More information on the game's lore and story will be revealed if you sign up.
      What is it that we offer?
      For joining our project and help us with completion of the game, we provide:
      Placement in the game's credits, considerably helpful items for your portfolio and references. A copy of the game for all the employees upon completion.* We ensure meetings are well planned as we have members from all over the globe. This ensures the maximum amount of people can attend and minimising annoyance to where everyone is equally annoyed create a comfortable environment. Some of our staff have had professional experiences with modelling, screen writing, programming, etc... Leniency - Since you are not being paid, we're more than happy to let you work at your own pace (Though we still expect you try and make arrangements for meetings our directors plan if possible, as well as work on projects handed to you in a reasonable amount of time provided by your director / executive). *For your contribution, we provide game as a base item. However, depending on how much you have contributed to the game, we may be more than willing to give you extra content (in shape of cosmetics, etc...) as bonus(es) to thank you. Problems or questions revolving around this should be taken to the executive directors for more information.
      What do we need?
      We are currently looking for the following:
      Concept Artists. Programmers (Mainly those adept in using and working with Unreal Engine 4). Screen Play Writers (For scenes and story). Right now, this is really needed! Please contact us about having this position if you are able to do it! PR Representatives. During your time at the Node Collective, we expect good behaviour and standards. We also expect you to follow the guidelines provided by Discord.
      If you are interested in joining the team, contact me or Sakarin on Discord. Our tags are: AtomicTiger#0238, Sakirin is Cool#0850.
       
    • By kubera
      Hi!
      Could someone suggest a solution for my problem?
      I am new to the UE4. I want to start developing an app. I need a texture of R16_UINT texels. It is an index in a shader, which maps other texels from a next resource (indirectly). How can I load such type texture to the engine? This type would be optimal for me. (or how to replace the idea?) It looks like the UE loads .tga files, wchich do not support it.
    • By Shaarigan
      Hey everyone,
      I was fiddling these days with Raspberry Pi and emulation software to run good old retro games from my childhood and came across the question why there isn't a common runtime for C++ code. I avoid the L (for language) because I know a lot about the .NET CLR and it's general assembly model.
      The idea for a C++ common runtime is to allow precompiled code to run everywhere, on whatever archcitecture like C# does. Why isn't there something like that so we won't need emulation software for our 20 year old games? To prevent the discussion for platform dependent code and C++ optimizations, those are done by the C# CLR too and STL is also similar to the Framework code so bases are there.
      What are your thoughts?
    • By lonewolff
      Hi Guys,
      I have a struct of int's which I am trying to initialise by pushing and popping a vector of integers.
      All is working well if I initialise manually from the vector (for example)
      options.vsync = boolQuery(optionsList.back(), mem); optionsList.pop_back();  
      Instead of doing this for every member I am trying to use a simple 'for loop'. (Ignore the name boolQuery - this actually returns an int 0 for false, 1 for true, and -1 for indeterminate).
      for (int i = 0; i < optionsList.size(); i++) { int value = boolQuery(optionsList.back(), mem); optionsList.pop_back(); memcpy(&options + (i * sizeof(int)), &value, sizeof(int)); }  
      This works for the first member but the remainder stay uninitialised.
      Debugging shows that the expected values are occurring during the loop, but they just aren't getting stored in the member integers.
      It looks as though the way I am trying to access the memory for the memcpy seems to be wrong.
      Any advice would be hugely appreciated.
    • By Toastmastern
      So have been struggeling with this issue now for 20 hours or so. I'm using DirectInput, I know it's not the best but I have tried other approaches aswell which yields me the same results. What is happening is that I'm orbiting around a sphere with my up and down cursor buttons. Nothing to complicated really. What my issue is however is that when I press the key and hold it for a while(atleast this seems to be when the issue occurs, I have seen it at other times aswell but not as common) the keystate I get from DirectInput doesn't recognize that I have released the key. I can see from my debugging that I get a new keyboardState, and I can also see that from that keyboardState the key is still down, even tho I don't press it. This lag, delay or freeze or whatever one wants to call it can be up to several seconds. Here is a hastebin that includes the code in question:

      https://hastebin.com/ayucajobaw.cpp

      Any kind of ideas what my issue could be, or pointers for me what to google, cause I'm really running out of ideas here

      Best Regards
      Toastmastern
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!