How to show the context menu after interacting with some object?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By astromis

Hello, everyone.

Faced with an issue that I cannot figure out for quite a long time.

I try to show a menu with some choices after the player hits the interact action near some specific object. Technically, the player model has a raycast2d and a function that detects a situation when the player hits an interact button near object, that has method called “on_interact”.

I just completely don’t understand how can I call an a UI and “deliver” it a control. It seems that I should create a condition in UI’s _unhandled_input that checks interact action and that the player stands near interactable object but I don’t know how it can be implemented. Moreover, I am not sure the this way is a correct at all.

Unfortunately, most of what I’ve found either tells about interactions inside UI only or tells too superficially.

Here is some code for context. Here is a Player code of an interaction

func _process(delta):
if Input.is_action_just_pressed("interact"):
    try_interact()

func try_interact ():
    rayCast.cast_to = facingDir * interactDist
    if rayCast.is_colliding():
        if rayCast.get_collider().has_method("on_interact"):
            rayCast.get_collider().on_interact(self)

On the other hand, a PopUp menu has a code like so

func _unhandled_input(event):
if not visible:
    #if Input.is_action_just_pressed("interact"):
    # Pause game
    already_paused = get_tree().paused
    get_tree().paused = true
    # Reset the popup
    selected_menu = 0
    change_menu_color()
    # Show popup
    #player.set_process_input(false)
    popup()
else:
    if Input.is_action_just_pressed("ui_down"):
        selected_menu = (selected_menu + 1) % 3;
        change_menu_color()
    elif Input.is_action_just_pressed("ui_up"):
        if selected_menu > 0:
            selected_menu = selected_menu - 1
        else:
            selected_menu = 2
        change_menu_color()
    elif Input.is_action_just_pressed("attack"):
        match selected_menu:
            # the code of processing the input
:bust_in_silhouette: Reply From: Inces

I was doing similar system very recently. You already got most of this. I created custom ControlNode scene and instanced it anew every time interaction was triggered, and it was queued free on cancelling interaction. The object who player interacted with was the one to instance this controlscene. This object also had various data, that determined what details Control node is going to have when it is instanced ( for example what items in shop ). GUI input also sent back and updated some of owners object data ( like already bought items ). There is no point in having menu instanced from the start and hidden all the time, when it is only used in relatively rare situations.

Amazing, so simple and at the same time so hard to realize. Thank you very much! You save me a lot of time.

For those who will stack into same issue.

Leaving the code I showed in the OP post conteptually unchanged, I modified the “on interact” method code in the next way

func on_interact (player):
    var scene = load("res://Context_menu.tscn")
    var menu = scene.instance()
    add_child(menu)

It does what Inces told about.

Next, you need to modifiy _unhandled_input in such a way that it can interact with, say, player object to give either a gold or an exp (like in Heroes 3). All you need is to add a pointer on player object like so onready var player = get_node("/root/MainScene/Player") somewhere outside the _unhandled_input and write a processing of a player choice like so (you can match it with the code in the op post)

match selected_menu:
                0:
                    # Resume game
                    if not already_paused:
                        get_tree().paused = false
                    hide()
                    queue_free()
                1:
                    player.give_gold(5)
                    if not already_paused:
                        get_tree().paused = false
                    hide()
                    queue_free()

                2:
                    player.give_xp(5)
                    if not already_paused:
                        get_tree().paused = false
                    hide()
                    queue_free()

I’m not sure that I’ve done it in a completely proper way because I’m still poor familiar with an engine, but it could be a start point though.

astromis | 2022-01-08 13:00