Code Monkey home page Code Monkey logo

ue4-chest2d-sample's Introduction

ue4-chest2d-sample

UE5 GitHub license

Sample of an interactable 2D chest done in Unreal Engine 5 with Paper2D.

Preview

This project is an example of how to write an interactable chest in a Paper2D game, with the constraint of being fully replicated over network.

Prerequisites:

For an Unreal Engine 4 version, check the branch ue4.25.

Table of contents:

RPC to replicate Interact action

Pressing the Interact button client-side simply calls an RPC server-side to handle the action. In a multiplayer game, the client may be totally desynchronized and may not see the correct state of the game. This step is necessary to let the server validate whether or not the client can interact with something and to let the server perform the action.

Here is the definition in ASampleCharacter.h:

UFUNCTION()
void Interact();

UFUNCTION(reliable, Server, WithValidation)
void Server_Interact();

Here is the implementation in ASampleCharacter.cpp:

void ASampleCharacter::Interact()
{
    if (GetLocalRole() < ROLE_Authority) {
        Server_Interact();
    }
    else
    {
        TSet<AActor*> Actors;
        GetOverlappingActors(Actors, TSubclassOf<ASampleInteractableActor>());
        for (auto Actor : Actors)
        { 
            static_cast<ASampleInteractableActor*>(Actor)->Interact(this);
        }
    }
}

bool ASampleCharacter::Server_Interact_Validate()
{
    return true;
}

void ASampleCharacter::Server_Interact_Implementation()
{
    Interact();
}

Please note that this RPC has to be called from an Actor owned by the client for it to works. Per consequence it would not be possible to move it directly into the class of our interactable Actor. Missing this point can give you a hard time trying to figure out why the RPC is not called on server.

Detect possible interaction on server and client

To visually show when the player can use the Interact action, we have to detect when the character is overlapping the chest. To do this, simply use the two NotifyActorBeginOverlap and NotifyActorEndOverlap to detect and track overlaps with Actors. This step is necessary to let the client compute itself whether or not it can interact with something and give and feedback to the player.

Here is the definition in SampleInteractableActor.h:

void NotifyActorBeginOverlap(class AActor* Other) override;
void NotifyActorEndOverlap(class AActor* Other) override;

/** Called when at least one actor can interact with this interactable **/
UPROPERTY(BlueprintAssignable, Category="Sample")
FOnBeginInteractableDelegate OnBeginInteractable;

/** Called when no more actors can interact with this interactable **/
UPROPERTY(BlueprintAssignable, Category="Sample")
FOnEndInteractableDelegate OnEndInteractable;

/** Called when at least one actor can interact with this interactable **/
UFUNCTION(BlueprintNativeEvent, Category = "Sample")
void NotifyBeginInteractable();

/** Called when no more actors can interact with this interactable **/
UFUNCTION(BlueprintNativeEvent, Category = "Sample")
void NotifyEndInteractable();

Note that the custom NotifyBeginInteractable and NotifyEndInteractable functions are used to react to the overlap directly from C++, while OnBeginInteractable and OnEndInteractable are used to react from Blueprint.

Here is the implementation in SampleInteractableActor.cpp:

void ASampleInteractableActor::NotifyActorBeginOverlap(class AActor* Other)
{
    Super::NotifyActorBeginOverlap(Other);

    if (IsValid(Other) && !IsPendingKill() && bIsEnabled)
    {
        if (OverlappingActors.Num() == 0)
        {
            NotifyBeginInteractable();
        }
        OverlappingActors.Add(Other);
    }
}

void ASampleInteractableActor::NotifyActorEndOverlap(class AActor* Other)
{
    Super::NotifyActorEndOverlap(Other);

    if (IsValid(Other) && !IsPendingKill() && bIsEnabled)
    {
        OverlappingActors.Remove(Other);
        if (OverlappingActors.Num() == 0)
        {
            NotifyEndInteractable();
        }
    }
}

void ASampleInteractableActor::NotifyBeginInteractable_Implementation()
{
    OnBeginInteractable.Broadcast();
}

void ASampleInteractableActor::NotifyEndInteractable_Implementation()
{
   OnEndInteractable.Broadcast();
}

In this sample you can see two buttons indicating when you can interact with the chest:

LevelInteractButtons

They are bound to the chest from Blueprint using OnBeginInteractable and OnEndInteractable delegates:

InteractButtonsBP

Replicate the state of our chest

In this sample, we use a boolean bIsEnabled to tell whether the chest can be interacted with or not. Once someone interacted with the chest, this boolean becomes false and is later resetted to true to make the interaction available once again. This step is necessary to synchronize the client with the server.

Here is the definition in SampleInteractableActor.h:

/** [Server] Set if the interaction is enabled or not */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Sample")
void SetIsEnabled(bool State);
 
/** [Client] Called when bIsEnabled get replicated */
UFUNCTION()
virtual void OnRep_IsEnabled();

/** Indicate if the interaction is enabled **/
UPROPERTY(replicated, ReplicatedUsing = OnRep_IsEnabled)
bool bIsEnabled;

And the implementation in SampleInteractableActor.cpp:

void ASampleInteractableActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(ASampleInteractableActor, bIsEnabled);
}

void ASampleInteractableActor::SetIsEnabled_Implementation(bool State)
{
    bIsEnabled = State;

    // If re-enabled, check currently overlapping actors
    if (bIsEnabled)
    {
        OverlappingActors.Empty();
        GetOverlappingActors(OverlappingActors, TSubclassOf<ASampleCharacter>());
        if (OverlappingActors.Num() == 0)
        {
            NotifyEndInteractable();
        }
        else
        {
            NotifyBeginInteractable();
        }
    }
    else
    {
        NotifyEndInteractable();
    }
}

void ASampleInteractableActor::OnRep_IsEnabled()
{
    SetIsEnabled(bIsEnabled);
}

The key is to use the ReplicatedUsing attribute to call a function when bIsEnabled get replicated. When the function is called, we simply detect overlapping Actors again.

Now to also replicate the visual state of our chest (closed or opened), we mark its components as replicated:

ASampleChestActor::ASampleChestActor(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
    , bIsOpened(false)
    , ChestConfig(nullptr)
{
    ClosedComponent = ObjectInitializer.CreateDefaultSubobject<UPaperSpriteComponent>(this, ClosedSpriteComponentName);
    ClosedComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    ClosedComponent->SetIsReplicated(true);
    ClosedComponent->SetupAttachment(RootComponent);

    OpenedComponent = ObjectInitializer.CreateDefaultSubobject<UPaperSpriteComponent>(this, OpenedSpriteComponentName);
    OpenedComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    OpenedComponent->SetIsReplicated(true);
    OpenedComponent->SetupAttachment(RootComponent);
    bReplicates = true;
}

Credits

Sprites are coming from The Spriters Resource.

Font from FontSpace.

License

Licensed under the MIT License.

ue4-chest2d-sample's People

Contributors

nauja avatar www avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.