Please keep making this, all of your tutorials helped me a lot to polish my game - it's a matter of time you will blow up due to the quality of materials and Godot getting more popularity. Thank you very much!
When adding the interaction area scene to your interactable node, you must add it exactly as they do at 8:23! Don't use "Add child node". Use "Instantiate Child Scene". You can do this a few ways: - Click the chain link button in the top left of the node list - Right click -> Instantiate child scene - Drag the interaction area scene (tscn) file into the node list If you add it as a child node, you can see that it won't have the little WiFi symbol in the node list (no signals attached). If you add it with "Instantiate Child Scene", it will have the little WiFi symbol, carrying over the signals you defined in the interaction area scene. I was having trouble getting the _on_body_entered/_on_body_exited signals to fire like some other here but finally figured out what I was doing wrong. Hopefully this helps someone else!
I got about half way through this before realising that it's not the tutorial I should have been following haha! But I'm saving it to a playlist for later because it's still awesome and easy to follow.
I really like this tutorial and the architecture of this interact system! One thing I would suggest, though, is trying to used .distance_squared_to() rather than .distance_to() in cases where you are directly comparing the length of two vectors. Obviously in this case, the .distance_to() function wouldn't get called that much, but its still a good thing to do ahead of time since from your perspective the two options are identical. For example, if someone wanted to use this code to implement picking up loot drops in a Diablo-like ARPG, there might be a lot of interactables in the queue, in which case the difference between the two variables would make a difference.
Thanks for the tip. I had no idea those two can be used interchangeably and that the squared one comes with a performance boost. Will keep that in mind
@@DashNothing Probably good to note that they aren't interchangeable in *every* case, like if you really do need the specific value for distance. But if all you need to know is which object is closer, .distance_squared_to() is the way to go! Great video, I definitely learned something new!
For those that having trouble with the label not showing above the NPC, try replace label.global_position.x = label.size.x / 2 to label.global_position.x = active_areas[0].global_position.x - 50 , it worked for me, the label is centered above the NPC.
Great tutorial, thanks very much. I've been looking for one that worked for a few days and hadn't managed until finding this. Really high quality code too. Liked and subscribed.
I have no idea about programming, like absolutely zero and it's like learning mandarine for me when I try to watch these tutorials as I just started to make games with Godot. But your Video was exactly what I was searching for. Thanks!
I tried to figure this by myself with almost zero knowledge in programming... I thought I was still far from the right result, but not by THAT much :D This seems so complicated for such a simple thing in a game
interaction area could have a signal that has a callable as the first argument that allows you to tell the interaction area when you're done interacting, instead of needing to create a reference then pass a callable to the area. Mainly because the current setup means only one function can be run to interact with the area, but if it's a signal any number of things could also happen when interacting.
Fair, but even this, in my opinion simpler approach, can easily be expanded with an interact or interaction_finished signal on the interaction area which can be emitted from the manager
Thanks for the great video! It helps a lot. I'd like to suggest an improvement for `interaction_manager.gd` ``` func register_area(area: InteractionArea): _active_areas.push_back(area) _active_areas.sort_custom(_sort_by_distance_to_player)
func unregister_area(area: InteractionArea): _active_areas.erase(area) _active_areas.sort_custom(_sort_by_distance_to_player) ``` - `erase` method can simplify the find & remove. - only sort whenever the area is registered or unregisted; no need to sort the array on every process cycle
perhaps not do the entire complex bits and reveal only in the end that there is a whole another dialogmanager you have to do? really feel like the entire thing can be made simple
I'm super new to this and would love to have seen how you set up the lamp from the beginning in order to implement it into the game. Woud there be a chance that you could show that in the future? Or lead me to a resource that would show me how to set up an object with an animation to interact with the player?
In the case of the lamp I really didn't use any animations. I just changed the frame of the sprite, as you can see in the code. To learn more about the topic check out Godot's documentation for Sprite2D, AnimatedSprite and AnimationPlayer. Hope that helps!
@@mkrojwvl8745 True, but I imagined the player would be around maybe half a dozen interactables at one time at most, which isn't that bad. If performance is an issue sorting could happen in a timer's timeout callback which would be called a few times a second.
Really awesome, your tutorials speed up my dev process by a ton, thanks! I just wanted to ask something, whenever I reload a scene, the InteractionManager doesn't because it's a singleton, which doesn't update the player variable and thus creating an error in the func _sort_distance_to_player because the variable player's value is null after a reset. What I did was just updating the player variable constantly. Am I doing it right lmao
I used your tutorials for the dialog manager and interaction manager and they seem to work great. But when I try to incorporate my inventory into the mix, and I'm trying to add a yes/no prompt to pick up items, I cannot at all get it to work. Any tips? Also, is it possible to have my main character's dialogue to appear over his head and whatever object I'm interacting with has its dialogue closer to it? Like a conversation. Thank you for whatever help you can give!
Amazing video, helped me a lot on developing my first 2d game I'm making! But I got 1 question though, how did you switch the sprites of the lamp with no Animation Player or AnimatedSprite2D? I have the open and closed sprites for a chest and im trying to make it change to that open sprite.
I used a texture with both frames next to eachother. Then I set the sprite's hframes property to 2, telling Godot to split the texture into two parts. To change which part is shown I change the frame property.
@@PiglioXDD at the end of the interact function you can do one of the following: - get interaction area's collision shape and set its disabled property to true - call interaction_area.queue_free() to delete it permanently This is off the top of my head so if it doesn't work let me know.
hey! this was an amazing tutorial! I have a question about the sprite frame changing at 10:00 Instead of using a light i am using a campfire which as a animated sprite. for func _toggle_fire how would i change the animated sprite from a animated unlit campfire to a animated lit campfire?
Nice implementation! One thing had me thinking though; what's the use of the "Action name" variable on this system? (The one you input "talk" for the player and "toggle" for the lamp), i've watched the video twice and I still don't think it has much of an use hehe
hello, i have an small problem running the code, so, everything works fine until I change to another scene, then when it enters another interaction area the game just stops and gives me the error: Invalid get index 'global_position' (on base: 'previously freed'). Hope you can respond to me.
oh and i forgot to mention that to change scenes I used get_tree().change_scene_to_file("res://niveles/nivel test/world_test.tscn"), dunno if it is the best way to do it but... it works
My bad, the issue is with the way of getting the player node in the InteractionManager script. When a new scene is loaded and the old player is deleted, the script doesn't fetch the new player. Good thing you found this! Here's the solution with a bit more info: gist.github.com/DashNothing/dd25d62a264bd581ee2ea2ffdac026a4
@@DashNothing Oh. My. GOD, you have done it, and so fast too, you have saved me (and many others), you are stablising a new generation of programmers, thank you for your very important service
Great tutorial, but can I use this in InteractionManager? func unregister_area(area): active_areas.erase(area) instead of this: func unregister_area(area): var index = active_areas.find(area) if index != -1: active_areas.remove_at(index)
Hi DashNothing! Thank you for the great video! It's super helpful. I have a question to you and to anyone in the chat really. I'm implementing the InteractionManager singleton and InteractionArea class into my current game, however I am getting a runtime error of: Invalid type in function 'register_area' in base 'Node2D (InteractionManager.gd)'. The Object-derived class of argument 1 (CharacterBody2D (Player.gd)) is not a subclass of the expected argument class. It seems that the instantiated version of InteractionManager within the InteractionArea class is not being recognized properly. Any ideas to why this happens?
This seems like you're passing the player scene to the reguster_area function, when you should be passing in the interaction area scene. Because register_area is called from the interaction area script you have to pass in "self".
Hey, great video, much appreciated! I have one issue though, it seems that I'm able to interact w/ an interactable object from any distance, and immediately when the two enter a scene together. It's confusing to me because I assume that you can't register an area as interactable until the player collides w/ the interaction_area's collisionShape. I'm not sure why it isn't acting like that. If I add two interactable objects to the scene, they do shift between distances, but I can interact with either of them from any distance as well. I don't know if that's relevant, but yeah. Again, thanks for the video!
Never mind, I realized it's because while my InteractionArea's collisionshape was masked to only detect the player, the actual object it was attached to was set on the default layer, which is player. It fired immediately on startup because it thought it was the player lmao.
Hi again Dash! Quick question with your Interaction Manager. Could it be adjusted if we wanted to interact with objects that open UI? (inventory, crafting menus, etc.?) If so, where? In the "input" section?
Yes, you can do that easily. You would do it the same way you do other interactions - by assigning a function to interaction_area's interact callable. Only instead of let's say starting a dialog there, you could get a reference to a hidden UI element in the scene and set its visible = true.
@@DashNothing I see - what Node would you recommend for a UI? I'm currently using a Control Node and it just doesn't seem to make UI appear, even when the function IS called. It works if I hardset a key to it like "i", but not with the interaction "e".
@@HammHammVT Any node that inherits from the Control node would work. You would probably use a PanelContainer for a background, and then place the text and buttons inside it. Then in the interact function you can write something like $PanelContainer.visible = true
@@DashNothing Hi Dash - no luck. Even with "visible = true", the UI doesn't want to appear. I can make it appear without the interaction object (Input.is_action..) but not with the object. My print statements tell me that the value has changed tho. Idk how to proceed from there. Any suggestions?
I'm very new to Godot and game development, so I copied this code exactly, but the part where the interaction manager awaits the interact call isn't connecting to that function (I don't even know the proper way to get across what my problem is, not do I understand what "asynchronous" means). I've copied the code in the video exactly, and every other aspect is working fine, but I can't get the _on_interact() function to ever trigger
sorry im getting "Invalid set index 'global_position' (on base: 'Label') with value of type Vector 3" everytime my player collides with the area :/ it redirects me to the "label.global_position = active_areas[0].global_position" line in the process function but i'm not seeing any typos so :(
@@bestyesecretclub Okay, then you can use the Label3D node instead, which is the easiest alternative. Check out your options here: docs.godotengine.org/en/stable/tutorials/3d/3d_text.html
Ok so I'm running into a problem trying to register the interaction area in the manager on body entered and exited (7:59). It's saying that InteractionManager is not declared in the current scope. Do I not need to import that somehow? Not sure how this works in godot, is that a singleton?
Yes, it's a singleton but you need to tell Godot to use it as such. Click on Projecr settings, then autoload tab and add interaction_manager.gd. Now it wiill be accessible from any script. This is covered in the video as well if you find yourself lost Hope that helps!
Hello, I am a bit confused what to do at 9:20 and in ''Cute and Simple dialog box'': It seems both script for NPC is different(10.45 other video)..So I am more confuse which NPC script to take in order to make the interaction and dialog work... Is it possible to share here what to write exactly? (Also, line 20 at 9:20 seems to be cut at the end, no idea what to write) (I am really a newbie in writing code) My other question is about the label: it doesn't show up above the NPC, I don't know why! I tried a few things as some peeps said below but no luck. I also have this message warning next to Label "Changing the Z index of a control only affects the drawing order, not the input even handling order" Thanks!
Use the NPc script from this video as a reference. I don't suggest copying the code that wasn't narrated in the video line by line - these sections just show you an example of how you can use the system. As for the label not showing up, I can't tell you why without seeing your code, but the warning you get about the z_index is not a problem and you can ignore that.
@@DashNothing Okay, I see thank you! As for the label, here's my script from InteractionManager: extends Node2D @onready var player = get_tree().get_first_node_in_group("player") @onready var label = $Label const base_text = "(E) to" var active_areas = [] var can_interact = true func register_area(area: InteractionArea): active_areas.push_back(area)
func unregister_area(area: InteractionArea): var index = active_areas.find(area) if index != -1: active_areas.remove_at(index)
@@DashNothingFor composition rather than inheritance, id have to think for a while. The way you structured it in the video works pretty similar to inheritance though. You essentially just decoupled the class to be overriden and set it as a singleton. For the inheritance route, you could have the player check for objects with an interactable component within an area, then call the objects override method. Currently though, i think I like your method better.
hello! i came from your textbox video. thank you so much for this high-quality content! i was wondering if you could help me - when using the interaction manager and dialogue managers together, how do we "tell" the interaction manager to display the textbox we set up in the dialogue manager? thank you!
Thanks for the kind words! You don't really tell the interaction manager what to do at all. The beauty of this is that each interaction area has its own oninteract callable that defines what happens. So when you're overriding the oninteract callable you can just call DialogManager.start_dialog(). I put an example of just that near the end of this video, when making an npc.
@@DashNothing Ahh ok, so to use the dialogue manager, we would go into the "triggerring" object's script and call the dialogue manager? Or would we call the dialogue manager in the interaction script? Thank you for your support I really appreciate it!
Hello there! Thanks a lot for the tutorial! I've spent a couple of hours rewatching the videos and fixing some errors, BUT there's still one. Everything works fine, but the text box doesn't disappear after the dialog and keeps hanging... Seeking for help :) Thanks!
I assume you followed my dialog system tjtorial, in which case the problem must be in the dialog amanger script. It the _unhandled_input function theres a line textbox.queue_free() at 10:09 in that video. Does this not destroy your textbox? It should destroy it after every line and finally at the end.
It doesn't! Looked through the script again and can see no mistakes (but mb I'm blind). That part looks like this: func _unhandled_input(event): if ( event.is_action_pressed("advance_dialog") && is_dialog_active && can_advance_line ): text_box.queue_free()
@@owps2121 Yeah, that looks right. Can you check if it gets deleted after a line finishes, but before the whole dialog is over? You should never have more than one text box in the scene. You can check it by playing the game and clicking on the remote tab in the scene tree. There you can find the dialog manager and a single text box should be its child at most.
Great tutorial! At first I was having trouble with the text appearing/disappearing until I realized that the "active_areas.size() > x" counts any CollisionShape2D node in its range so I needed to change it from "0" to "1" (at least in my code). My player character's CollisionShape and the Interactable Object's CollisionShape. My only current issue is now my "E to Interact" key press works even OUTSIDE of the Interaction Area. Any advice for that?
Thank you! The interaction area colission mask should be set to player only (1:02). This way it will not detect any other object. Of course, with this you should change the 1 back to a 0.
If you click on the root node of your player scene by default you see the inspector on the right side of the editor. Click on the "node" tab next to the inspector tab and you will see the node's signals. Click on the "groups" tab next to the signals tab. There you can add the scene to groups by typing in the name of the group. In this case type in "player" and click add group.
I'm having trouble, it seems that my interaction area is not detecting my player. I don't know if it has to do something with the play group or the collision layer + mask on the interaction area? I've checked and the Collision layer on my player is the same as mask on the interection thing. Which is set to "1". Then I am not sure about the group thing you mentioned, because am I supposed to have the entire 2D node in it and player node also named player? Or is it only supposed to be the player node by it self and the naming of the player node does not matter?
Your collission layer/mask setup is correct. As for the group, the second option is right. - ONLY the player node should be in tbe "player" group - it doesn't matter what the player scene is called, as long as it's in the "player" group it will work Let me know if you need further help!
Yeah so, turns out that that wasn't the problem, I tried multiple debugging points whilst using print to determine what's being run. It turns out that _process in InteractionManager is always doing the label.hide() function, no matter if I enter a interaction area or not, it just won't add the area to the array. And as a matter of fact, it does not register the player entering it at all. Thank you for your help btw, I am a total newbie and I appriciate the help with your tutorials!@@DashNothing
@@supergames6696 Okay, so it seems the problem is with the interaction area. If collision mask is set to player's collision layer, also check these: - does the area have monitoring turned on? It should by default and it's needed to detect when objects enter - does the area have a collision shape child with a big enough area so that the player can enter it? And it's no problem, I'm happy to help. You are doing very well in debugging this issue, especially for a newbie.
Hello! i have a problem where it says "Invalid get index 'global_position' (on_base: null instance) upon entering to areas in the same time. in Addition labels are not shown whilst entering areas. Have any ideas why?
There's a number of things that could be wrong: - Your player variable in InteractionManager is null - this could be if the player isn't in the player group - You added the interaction area scene the wrong way - you have to add it by clicking on the "instantiate child scene" button (link icon) and NOT on the "add child node" button (plus icon) - The null instance is one of the interaction areas, which means they most likely don't register / unregister themselves properly Use the debug tool to find out exactly what variable is causing the issue and I'll be happy to help you further!
@@DashNothingI think I fixed the problem, player var was null even tho it’s in group so I made my own function which gets player pos to global vector and its given to area_to_player. But the label “[E] to interact “ is not showing (wasn’t before fix) any idea why?
Can i use this method to add a value to a variable in another scene, for example i want to add ammo to my weapon?, i tried to use and a made a global var but i dont know if it is the best way to do.
You can either have a global singleton that holds the ammo value and then add to it from your interact function and read from it in the player script. I assume this is what you did. Or you can have a reference to the player in the object that adds the ammo and add it directly to the player. I think the first approach might be better because there's less coupling. But it's fine either way.
Make sure you got the @onready var label = $... line at the top of the interaction manager script. Also make sure that the name of the label node in the scene tree is the same as the name you put after the $ symbol.
I didn't know about the erase method until now lol. Coming from JS where you can only remove elements based on the index, I didn't even consider anything else. Thanks for letting me know
@@DashNothing No worries la, your coding style looks very JS now that you mention it (throwing awaits and functions-as-variables around)! Thanks for the vid
Hey so this tutorial is amazing! Though I do have a question. Using this code as a basis, i was able to create a simple item store for my game. However, I have a small idea that I would like some help in implementing. Basically, I want the item that the item displays are holding (a sprite2d that is nested within a scene with another sprite2d as its root node) to scale up a bit as soon as you enter the interaction area and scale back down when you leave, thus signifying which item is selected for purchase. The problem is, from what I can tell, the signal for detecting on_body_entered/exited only exists in the interaction_area script, which I am trying to keep as simple as possible for flexible implementation in other aspects of the game. do you have any ideas for what I could do? Again, thank you for the tutorial as it is amazing!
Thank you! You can connect to the interaction area's on body entered / exited signals from the store item scene. When in the store item scene click on the interaction area in the scene tree, go to the node tab on the right and double click the signals you want to connect to. By default new functions will be created in your store item script where you can scale them up without adding any code to the interaction area script. Hope that helps!
Do I need to connect _body_entered and _exited and write out that bit of code every time I want to make an object interactible, or am I doing something wrong? It doesn't seem to want to inherit that.
No, the signals are connected in the interaction area script. All you have to do is add the interaction area to an object you want to interact with and override it's interact callable. The interaction area + InteractionManager will handle the rest for you!
I was trying to figure something like this out so this was super helpful. One thing I am encountering is an invalid get index for global position in the sort by distance function. I am trying to figure out if this is something happening as I move just out of range of one item and it starts to trigger on another. I am pretty sure it is trying to unregister as it does this. I am using this for drops from monsters and it works pretty well except for this one thing.
Hi, i really love your tutorials but for some reason the interaction isn't triggered, the interactionmanager is in the autoload, the collision of the interaction area and the player are setup correctly, but it just dose nothing. I double checked the code with the video multiple times and didn't find an error.
@@doingitsidesways So it's either your active_areas is always empty shich means your interaction areas don't register themselves properly OR your can_interact starts at false. Can you print out those two into the output from InteractionManager's _unhandled_input() function when the player tries to interact?
Hi thanks for the help, i figured it out after breaking my head over it, for some reason connecting the signals trough the editor didnt work, even tho it looked like it did, i connected it trough code: func _ready() -> void: body_entered.connect(_on_body_entered) body_exited.connect(_on_body_exited) and now it works as expected. Thank you for replying to me, i looking forward to more of your videos.
@@doingitsidesways holy shit thank you. i was about to rip my hair out lmao you're a godsend. amazing tutorial @DashNothing this streamlines so much for me!
Hey! Great tutorial, but I'm running into some problems with my interaction: Nothing happens when I press E inside the interaction area. Everything else seems to work, but I can't get the interaction to work even with just a print on interact. I have watched the video multiple times but I just can't figure out what I'm doing wrong. Why could this be?
If you can see the "press key to interact" text above the correct object it means the area works correctly. In that case it could be one or more of these: - You didn't bind the interact key in the InputMap setrings - interaction manager's unhandled input function never registers an even (you can check that wjth print) - on_interact callable isn't assigned the right function Let me know if that helps.
@@DashNothing Thanks for the quick response! My interact key is bound and I have tried adding a print in my _input fuction in the interaction manager and that seems to be working, so I assume it's my on_interact callable. The _ready function looks exactly like in your video, and my _on_interact only has a single print statement in it. Do i need to change the "var interact: Callable = func(): pass" or is there anything else that might be wrong?
@@boozli It seems to be breaking somewhere between interaction manager regjstering the event in the input function and calling the interaction area's interact function. You can verify that at least the default interact function gets called if you change that pass into a print(). One more thing is how did you add the interaction area into the scene? Using the add node (plus icon) button or the instance scene (chain icon) button? It has to be added using the instance scene button. If you'd like you can join my brand new discord server and make an issue there. We can share code and solve the problem easier: discord.com/invite/y6bXkKwree
Thank you so much for this tutorial! I tried following it, but also converting it to 3d. I followed all of the steps that you made, but I'm getting this error "Invalid set index 'global_position' (on base: 'Label') with value type of 'Vector3'" and it's pointing to a line that states: label.global_position = active_areas[0].global_position Any idea what could be happening here? I'm not skilled enough to understand this yet 😅
Hey, All of this works but when i try do interaction_area and the stuff under on_body_entered/exited it says Identifier "InteractionManager" not Declared in the current scope.
Hey! Thank you for the video! I have a problem, I added InteractionArea component into the node I want to interact with. But for some reason signals do not emit when player collides with the area. I tried to put print in this functions inside InteractionArea to at least check what is going on, but they basically do not call at all. Do you have any ideas why it can happen?
When you add the interaction area into the scene make sure to do it by clicking on the "Instantiate Child Scene" button (link icon) and NOT "Add Child Node..." (plus icon). There should a a signal icon next to the InteractionArea in the scene hierarchy view if you did it correctly. Hope that helps!
@@DashNothing Yeah, now the signal icon new the InteractionArea is shown, but signals still do not emit. I also checked the collision layers and it's exactly like in the video. Is there something I could miss?
@@nikolayborzunov6960 One reason an Area2D wouldn't detect collisions is if it has monitoring turned off. It's right at the top of the inspector and should be on by default. If it's off the area will not detect anything.
@DashNothing I just checked, so monitoring is turned on for both InteractionArea for my NPC and for it's generic class. Signals are highlighted with a green icon, so it seems like they work. Also InteractionArea has the same mask level as Player's layer. Sorry for taking your time, I just have no idea what's wrong with it :(
I've followed your instructions and everything works fine except one thing: My label-position seems to be fixed to a specific coordinate rather than to be relative to the position of the InteractionArea. I've also tried to set the label position relative to the players position, which works fine when just testing the scene but when I then test the whole project/run a complete debug, it crashes as soon as I get into the Interaction area. Any tips?
Check that you are setting the label's global position in the process function. Global position should place it independent of its parent's position. Without seeing the code, it's hard to say more
I have a few questions: Can this not be done with a tile from a tile set? Do i have to export every object I want the player to interact with as a separate image, then use it as an image file? (or what type of file, anyway?) I also find it weird that I need a separate window for every item? Can't I create every interactable object in my main scene from scratch? pls someone help 😩
Hello! Amazing tutorial and really what I was looking for, but got a little problem with mine. I did almost the same thing with the lamp, but a door and for some reason, the door opens or closes even if I'm in the other side of the room. The label "[E] to" also is not appearing. Idk, is like the InteractionArea is not getting that the door is very far away or is just not working, prob I did something wrong but I really can't find out what. Making a game for school in a very short time lol If you may know what is going on pls send help thanks anyway!! edit1.: sooo, I was messing with it and found out that the global.position y and x for the label was so far away from where my player is that it was out of the window (I should say that my game the camera doesn't move), but now I ended up thinking that prob I can't put the labels in different positions if the InteractionManager is always the same. Also, I ended up discovering that the InteractionManager always has me on reach so it's always showing the label and I can open the door from very far away. Maybe u know how to go around it?
Hey thanks for sharing this, very useful! I tried implementing this in c# last night and I struggle to get the callable propagation called. I define the callable in InteractionArea like this "public Callable interact;". Then on the object i want to interact with i assign the callable like this "interactionArea.interact = new Callable(this, nameof(OnInteract));". But the OnInteract is not triggered. I think it has to do with how the callable is defined in InteractionArea nut not initialized properly maybe, but not sure how to implement this overridable callable from GDScript to C#. Any Godot c# dev have an idea?
I would definitely avoid putting it on the player. We should try to seperate the concerns of each script, so that one script only knows and does things only it needs. Since interaction logic concerns many objects, their proximities to the player and the unique behaviour that's tied to each interactable, implementing this in the player script would quickly get unmanagable. The approach shown in the video seperates these concerns well. A manager keeps track of all interactables and the logic behind it. Each interactable implements only its own interaction behaviour, without caring about how this interaction comes about. And the player node does fuck all. So putting interaction logic on the player pros: - It may be easier to get something done quickly Cons: - You will curse the day you were born adding new interactable objects
When my player collides with an object that I attached the interaction_area to, I get Invalid get index 'global_postiion' on base: Array[Node] in the custom sort function. The player group is player and everything else is just like in the video. What could be wrong?
@@ivanchistyakovyt hmm, it seems like the function is trying to get the global_position of a nonexistant node. Could it be that an interaction area registered itself on the manager, and then it was deleted from the scene before unregistering? Or something other than an interaction area registered itself?
@@DashNothing Found mistake! Was @onready var player = get_tree().get_nodes_in_group("player") instead of @onready var player = get_tree().get_first_node_in_group("player"). Big thanks for the video and trying to help! Liked and recommended!
Great vid! I had to modify the "player" var to be set during the "_process()" function in the InteractionManager. For some reason whenever trying to set "player" like you showed in the video, it would try to get the node before it was defined, leading to null reference errors. My line was identical to yours (@onready var player = get_tree().get_first_node_in_group("player"), but it would be set to null because the player node did not exist when it was trying to set the variable. Not sure why my situation was different, but seems to work okay with my modification.
Maybe some other script creates the player at the start of the game? Unlikely, but I can't think of anything else. Godot creates nodes bottom up, so autoloaded nodes should be created last, unless I'm mistaken. By the time they're created, all other nodes should be available. If you didn't already, before setting it in _proccess() I would put an if to check if the player is already not null. Just so you don't set it over and over again.
mines having the same issue can you possibly tell me4 how you called it cus for me var player = get_tree().get_first_node_in_group("Player") doesnt work
@@Lupin_VA, sure! Instead of declaring the player variable on line 4 (3:42 in video), i moved this line inside of the _process(delta) function. Here are the first lines of my _process(delta) function: func _process(delta): if activeAreas.size() > 0 && canInteract: if player == null: player = get_tree().get_first_node_in_group("player") ... Notice i also removed the @onready decorator. For me, this change makes sure the player is never null and also does not get called more times than necessary.
Hi! I hope you can help me. I tried to code this to a T but I'm getting errors. The first i can't resolve (at least, cannot understand how to resolve) is about our test_guy. In my case his name is Opal. I'm getting the error on her func _ready, stating: "Invalid call. Non-existent function 'CharacterBody2D(opal.gd)::_on_interact (Callable)'." Could you help me figure out how to fix? Is it because she's a character body 2d node instead of anything else? If so, what should an NPC's node be?
on_interact is a var of the interaction area, not of the NPC. So it should be interaction_area.on_interact, so make sure you have that interaction_area part.
@@DashNothing I appreciate your prompt response so much on a 7 month old video! I fixed all the errors and now I'm left with just a few differences. Please know I'm new to the Godot language! From your code example in the cute text box video for test_guy, you have some sections labeled InteractionLabel. I don't think I have this identifier from the tutorial. So, I figured it might be a rename from the InteractionArea, so I renamed it to such, but that doesn't seem to be it (getting more errors, then they disappeared, so unsure as of now) Also I noticed from that video you have some signals connected to the body entered and body excited lines for the test _guy. What signal is that connected to and receiving from? (Also on the body exited function inside the test_guy I never could see what code was underneath it. What happens when body exited?) I'm sorry I'm not as intuitive with the language just yet, so I am very grateful for your patience as I learn the basics, in order to later be able to change them to my game's specific mechanics. Thank you so much!
@@kebu2579 No worries! The code in the test guy script in the dialog tutorial was just a placeholder interaction system that I cobbled together to demonstrate the text box. You can just use the code from this video to start dialogs on interact, as show in the example at the end of this video.
help! i followed your tutorial step by step and i've redone it multiple times, but the label won't show up or the interaction won't happen even when i press E ;; do you have any ideas what could be wrong?
Could be a few things. Is the interaction area setup to detect the llayer entering? Its collision mask should be the same as the player's collision layer.
@@DashNothing i figured it out - yes that was the problem, when i set up the interaction area i did set up the signals and the collision layers, but when i add interactionarea as a child node of a scene, for some reason those settings - signal connections and collision layers - don't carry over. i had to redo it under the scene i put interactionarea as a child node of
@@Forrestte That would probably be because you afded it with the add node button (plus icon). To keep the settings you should add it with the instance scene button (link icon).
hey! i have a error, in the: func _sort_by_distance_to_player(area1, area2): var area1_to_player = player.global_position.distance_to(area1.global_position) var area2_to_player = player.global_position.distance_to(area2.global_position) return area1_to_player < area2_to_player i have a Invalid get index 'global_position' (on base: 'null instance'). is there a way to fix this? i tried looking it up, but nothing seemed to help.
Is the null instance your player var? If so make sure your player is in the "player" group, otherwise the interaction manager won't be able to get a reference to it.
@@Qas22w oninteract callable might be asynchronous itself - it might create and wait for a timer, wait for an animation or a tween to finish etc. So we await the oninteract to finish completely before ending the interaction. If you followed my dialog / textbox tutorial, that is another example of where the await is useful. Since the dialog manager has to wait for the player to go through all the textboxes, it's really an asynchronous process which the interaction manager will wait until it's completely finished before ending the interaction.
Please keep making this, all of your tutorials helped me a lot to polish my game - it's a matter of time you will blow up due to the quality of materials and Godot getting more popularity.
Thank you very much!
Thank you for the kind words!
When adding the interaction area scene to your interactable node, you must add it exactly as they do at 8:23! Don't use "Add child node". Use "Instantiate Child Scene". You can do this a few ways:
- Click the chain link button in the top left of the node list
- Right click -> Instantiate child scene
- Drag the interaction area scene (tscn) file into the node list
If you add it as a child node, you can see that it won't have the little WiFi symbol in the node list (no signals attached). If you add it with "Instantiate Child Scene", it will have the little WiFi symbol, carrying over the signals you defined in the interaction area scene.
I was having trouble getting the _on_body_entered/_on_body_exited signals to fire like some other here but finally figured out what I was doing wrong. Hopefully this helps someone else!
Thank you! That was exactly what I was missing :D
Thanks man, this answer should be pinned!
MVP comment right here! I couldn't figure out why it wasn't working when it looked all correct in the code. Thank you!
thx.i was searching comments for solution to this :D
Thanks for your comment, I triple checked the video but did not notice this.
I can notice the ammount of work you are putting into these videos!!! Looking forward to more of these!
Really high quality code here. Thanks for sharing.
This tutorial helped me so much, its easy to understand, easy to do, and wonderfully made, please keep making these!
I got about half way through this before realising that it's not the tutorial I should have been following haha! But I'm saving it to a playlist for later because it's still awesome and easy to follow.
I don’t know why the hell I’m watching this when I’m still doing the official tutorial thing but it’s just too damn interesting
Came for the Godot information, stayed for the Hot Mulligan lo-fi. Thanks for such a great video!
I really like this tutorial and the architecture of this interact system!
One thing I would suggest, though, is trying to used .distance_squared_to() rather than .distance_to() in cases where you are directly comparing the length of two vectors. Obviously in this case, the .distance_to() function wouldn't get called that much, but its still a good thing to do ahead of time since from your perspective the two options are identical. For example, if someone wanted to use this code to implement picking up loot drops in a Diablo-like ARPG, there might be a lot of interactables in the queue, in which case the difference between the two variables would make a difference.
Thanks for the tip. I had no idea those two can be used interchangeably and that the squared one comes with a performance boost. Will keep that in mind
@@DashNothing Probably good to note that they aren't interchangeable in *every* case, like if you really do need the specific value for distance. But if all you need to know is which object is closer, .distance_squared_to() is the way to go! Great video, I definitely learned something new!
Awesome! Great tutorial as always.
This is fantastic! Please keep making these sorts of videos
Bro literally that's really hard but pretty amazing thanks for the help man!
For those that having trouble with the label not showing above the NPC, try replace label.global_position.x = label.size.x / 2 to label.global_position.x = active_areas[0].global_position.x - 50 , it worked for me, the label is centered above the NPC.
The amount of work you did to make that video is astounding. Excellent work. Also, great content!
Much appreciated!
No way you're still sub-thousand with such videos! Here before this'll blow up! :D
Excellent tutorial! I subbed and will be watching your other Godot content. Thanks for making it!
Great tutorial, thanks very much. I've been looking for one that worked for a few days and hadn't managed until finding this. Really high quality code too. Liked and subscribed.
This is excellent. New subscriber as I'm building up courage to dive back into Godot after a long break.
A legend, an unsung hero, cute animal saver
Thank you sir, keep up the great work
Concise yet clear! Amazing video
Thank you so much! Please keep making such awesome tutorials.
These are honestly so well done, great job man. Keep em coming!
Composition makes me happy I strive to be like you
Great video! Quick and easy to understand. Thanks!
I have no idea about programming, like absolutely zero and it's like learning mandarine for me when I try to watch these tutorials as I just started to make games with Godot. But your Video was exactly what I was searching for. Thanks!
I've never done programming before and this was so easy to follow thank you for this
Loved the video, explained all i needed to know, will watch the rest of your videos, thank you for making them
Thanks for the video, I'm creating dialogues using Dialogic addon and this method really helped.
This is really great tutorial. You are doing amazing job mate! Subscribed, for sure.
Amazing video, clear, well edited and produced, and super helpful with good practices and explanations, thanks you! +sub
Subscribed. really appreciate you are sharing good design patterns here
I tried to figure this by myself with almost zero knowledge in programming... I thought I was still far from the right result, but not by THAT much :D This seems so complicated for such a simple thing in a game
Im a unity refugee so this has been a massive help!
this so great. i love this approach. You are awesome! thank you SO MUCH! ♥
interaction area could have a signal that has a callable as the first argument that allows you to tell the interaction area when you're done interacting, instead of needing to create a reference then pass a callable to the area. Mainly because the current setup means only one function can be run to interact with the area, but if it's a signal any number of things could also happen when interacting.
Fair, but even this, in my opinion simpler approach, can easily be expanded with an interact or interaction_finished signal on the interaction area which can be emitted from the manager
Thanks for the great video! It helps a lot.
I'd like to suggest an improvement for `interaction_manager.gd`
```
func register_area(area: InteractionArea):
_active_areas.push_back(area)
_active_areas.sort_custom(_sort_by_distance_to_player)
func unregister_area(area: InteractionArea):
_active_areas.erase(area)
_active_areas.sort_custom(_sort_by_distance_to_player)
```
- `erase` method can simplify the find & remove.
- only sort whenever the area is registered or unregisted; no need to sort the array on every process cycle
perfect for what i needed, thanks!
This is much better than my raycast method, Good job 👍
Amazing as always
perhaps not do the entire complex bits and reveal only in the end that there is a whole another dialogmanager you have to do? really feel like the entire thing can be made simple
@@tartarusdivision1054 This is as simple as I could make it for RUclips, but if you find a simpler way that works let me know
nice video as always thankyou,
i also implemented the same in 3d ,so simple and reusable
I'm super new to this and would love to have seen how you set up the lamp from the beginning in order to implement it into the game. Woud there be a chance that you could show that in the future? Or lead me to a resource that would show me how to set up an object with an animation to interact with the player?
In the case of the lamp I really didn't use any animations. I just changed the frame of the sprite, as you can see in the code. To learn more about the topic check out Godot's documentation for Sprite2D, AnimatedSprite and AnimationPlayer. Hope that helps!
Process is a preset function that is called every frame. Sorting an array every frame has a big toll on the perfomance if the array is getting bigger.
@@mkrojwvl8745 True, but I imagined the player would be around maybe half a dozen interactables at one time at most, which isn't that bad. If performance is an issue sorting could happen in a timer's timeout callback which would be called a few times a second.
Really awesome, your tutorials speed up my dev process by a ton, thanks!
I just wanted to ask something, whenever I reload a scene, the InteractionManager doesn't because it's a singleton, which doesn't update the player variable and thus creating an error in the func _sort_distance_to_player because the variable player's value is null after a reset. What I did was just updating the player variable constantly.
Am I doing it right lmao
I used your tutorials for the dialog manager and interaction manager and they seem to work great. But when I try to incorporate my inventory into the mix, and I'm trying to add a yes/no prompt to pick up items, I cannot at all get it to work. Any tips? Also, is it possible to have my main character's dialogue to appear over his head and whatever object I'm interacting with has its dialogue closer to it? Like a conversation. Thank you for whatever help you can give!
Amazing video, helped me a lot on developing my first 2d game I'm making! But I got 1 question though, how did you switch the sprites of the lamp with no Animation Player or AnimatedSprite2D? I have the open and closed sprites for a chest and im trying to make it change to that open sprite.
I used a texture with both frames next to eachother. Then I set the sprite's hframes property to 2, telling Godot to split the texture into two parts. To change which part is shown I change the frame property.
@@DashNothing Oh thanks a lot!
@@DashNothing Also is there a way to make the interact option appear and work only once?
@@PiglioXDD at the end of the interact function you can do one of the following:
- get interaction area's collision shape and set its disabled property to true
- call interaction_area.queue_free() to delete it permanently
This is off the top of my head so if it doesn't work let me know.
@@DashNothing Used the second option and worked perfectly, again thank you very much! Keep it up with the videos!
hey! this was an amazing tutorial!
I have a question about the sprite frame changing at 10:00
Instead of using a light i am using a campfire which as a animated sprite. for func _toggle_fire how would i change the animated sprite from a animated unlit campfire to a animated lit campfire?
You can have multiple animations per animated sprite. You can change which animation is playing programatically with sprite.animation = ...
Muy buenos tus videos, aprovecha el declive de Unity y métele mas a Godot, te va a ayudar mucho a crecer, tus videos son muy interesantes!
Thank you!
i followed the tutorial but nothing happens when i walk past my npc
Nice implementation! One thing had me thinking though; what's the use of the "Action name" variable on this system? (The one you input "talk" for the player and "toggle" for the lamp), i've watched the video twice and I still don't think it has much of an use hehe
It's for the interact prompt label ([E] to ..). So you can change the prompt based on the context of the interaction.
hello, i have an small problem running the code, so, everything works fine until I change to another scene, then when it enters another interaction area the game just stops and gives me the error:
Invalid get index 'global_position' (on base: 'previously freed').
Hope you can respond to me.
oh and i forgot to mention that to change scenes I used get_tree().change_scene_to_file("res://niveles/nivel test/world_test.tscn"), dunno if it is the best way to do it but... it works
My bad, the issue is with the way of getting the player node in the InteractionManager script. When a new scene is loaded and the old player is deleted, the script doesn't fetch the new player. Good thing you found this!
Here's the solution with a bit more info:
gist.github.com/DashNothing/dd25d62a264bd581ee2ea2ffdac026a4
@@DashNothing Oh. My. GOD, you have done it, and so fast too, you have saved me (and many others), you are stablising a new generation of programmers, thank you for your very important service
Thank you! I was having this same problem too and had no idea what was going on.@@DashNothing
@@DashNothing I was having the same issue, thanks for this!
Well played. Now I have to watch the DialogManager-Video aswell. ;-)
Very good Video, I learned a lot!
This implementation has potentially a lot of bugs for an actual game, but its good enough to get the idea
I'd love to know so I could potentially improve on this solution - what do you think would cause bugs?
Great tutorial, but can I use this in InteractionManager?
func unregister_area(area):
active_areas.erase(area)
instead of this:
func unregister_area(area):
var index = active_areas.find(area)
if index != -1:
active_areas.remove_at(index)
Yes you can. At the time I didn't know about the erase function, but yeah it's cleaner.
Hi DashNothing!
Thank you for the great video! It's super helpful. I have a question to you and to anyone in the chat really. I'm implementing the InteractionManager singleton and InteractionArea class into my current game, however I am getting a runtime error of: Invalid type in function 'register_area' in base 'Node2D (InteractionManager.gd)'. The Object-derived class of argument 1 (CharacterBody2D (Player.gd)) is not a subclass of the expected argument class.
It seems that the instantiated version of InteractionManager within the InteractionArea class is not being recognized properly. Any ideas to why this happens?
This seems like you're passing the player scene to the reguster_area function, when you should be passing in the interaction area scene. Because register_area is called from the interaction area script you have to pass in "self".
Hey, great video, much appreciated! I have one issue though, it seems that I'm able to interact w/ an interactable object from any distance, and immediately when the two enter a scene together. It's confusing to me because I assume that you can't register an area as interactable until the player collides w/ the interaction_area's collisionShape. I'm not sure why it isn't acting like that.
If I add two interactable objects to the scene, they do shift between distances, but I can interact with either of them from any distance as well. I don't know if that's relevant, but yeah. Again, thanks for the video!
Never mind, I realized it's because while my InteractionArea's collisionshape was masked to only detect the player, the actual object it was attached to was set on the default layer, which is player. It fired immediately on startup because it thought it was the player lmao.
@@enderalex300 Easy mistake to make. Glad you figured it out.
wow. needed this
Hi again Dash!
Quick question with your Interaction Manager.
Could it be adjusted if we wanted to interact with objects that open UI? (inventory, crafting menus, etc.?)
If so, where? In the "input" section?
Yes, you can do that easily. You would do it the same way you do other interactions - by assigning a function to interaction_area's interact callable. Only instead of let's say starting a dialog there, you could get a reference to a hidden UI element in the scene and set its visible = true.
@@DashNothing I see - what Node would you recommend for a UI? I'm currently using a Control Node and it just doesn't seem to make UI appear, even when the function IS called.
It works if I hardset a key to it like "i", but not with the interaction "e".
@@HammHammVT Any node that inherits from the Control node would work. You would probably use a PanelContainer for a background, and then place the text and buttons inside it.
Then in the interact function you can write something like $PanelContainer.visible = true
@@DashNothing
DashNothing, Godot Sensei LOL
I'll give it a try - wish me luck!
@@DashNothing
Hi Dash - no luck.
Even with "visible = true", the UI doesn't want to appear.
I can make it appear without the interaction object (Input.is_action..) but not with the object.
My print statements tell me that the value has changed tho. Idk how to proceed from there.
Any suggestions?
I'm very new to Godot and game development, so I copied this code exactly, but the part where the interaction manager awaits the interact call isn't connecting to that function (I don't even know the proper way to get across what my problem is, not do I understand what "asynchronous" means). I've copied the code in the video exactly, and every other aspect is working fine, but I can't get the _on_interact() function to ever trigger
sorry im getting "Invalid set index 'global_position' (on base: 'Label') with value of type Vector 3" everytime my player collides with the area :/
it redirects me to the "label.global_position = active_areas[0].global_position" line in the process function but i'm not seeing any typos so :(
It looks like you used an Area3D node instead of an Area2D for the interaction area. Hope this helps!
@@DashNothing sorry aahah i forgot to mention that my project is in in fact 3D
@@bestyesecretclub Okay, then you can use the Label3D node instead, which is the easiest alternative. Check out your options here: docs.godotengine.org/en/stable/tutorials/3d/3d_text.html
Ok so I'm running into a problem trying to register the interaction area in the manager on body entered and exited (7:59). It's saying that InteractionManager is not declared in the current scope. Do I not need to import that somehow? Not sure how this works in godot, is that a singleton?
Yes, it's a singleton but you need to tell Godot to use it as such. Click on Projecr settings, then autoload tab and add interaction_manager.gd. Now it wiill be accessible from any script.
This is covered in the video as well if you find yourself lost
Hope that helps!
@@DashNothing awesome man. Yes sorry looking back I realized I missed this in the video. Thank you so much for the reply!
Hello, I am a bit confused what to do at 9:20 and in ''Cute and Simple dialog box'': It seems both script for NPC is different(10.45 other video)..So I am more confuse which NPC script to take in order to make the interaction and dialog work... Is it possible to share here what to write exactly? (Also, line 20 at 9:20 seems to be cut at the end, no idea what to write) (I am really a newbie in writing code)
My other question is about the label: it doesn't show up above the NPC, I don't know why! I tried a few things as some peeps said below but no luck. I also have this message warning next to Label "Changing the Z index of a control only affects the drawing order, not the input even handling order"
Thanks!
Use the NPc script from this video as a reference. I don't suggest copying the code that wasn't narrated in the video line by line - these sections just show you an example of how you can use the system.
As for the label not showing up, I can't tell you why without seeing your code, but the warning you get about the z_index is not a problem and you can ignore that.
@@DashNothing Okay, I see thank you!
As for the label, here's my script from InteractionManager:
extends Node2D
@onready var player = get_tree().get_first_node_in_group("player")
@onready var label = $Label
const base_text = "(E) to"
var active_areas = []
var can_interact = true
func register_area(area: InteractionArea):
active_areas.push_back(area)
func unregister_area(area: InteractionArea):
var index = active_areas.find(area)
if index != -1:
active_areas.remove_at(index)
func _process(delta):
if active_areas.size() > 0 && can_interact:
active_areas.sort_custom(_sort_by_distance_to_player)
label.text = base_text + active_areas[0].action_name
label.global_position = active_areas[0].global_position
label.global_position.y -= 36
label.global_position.x = label.size.x / 2
label.show()
else:
label.hide()
func _sort_by_distance_to_player(area1, area2):
var area1_to_player = player.global_position.distance_to(area1.global_position)
var area2_to_player = player.global_position.distance_to(area2.global_position)
return area1_to_player < area2_to_player
func _input(event):
if event.is_action_pressed("interact") && can_interact:
if active_areas.size() > 0:
can_interact = false
label.hide()
await active_areas[0].interact.call()
can_interact = true
Wouldn't using a singleton for interactionmanager restrict the game to being singleplayer? Or are multiple players able to use it with no problem?
@@lordbarron3352 I've never made a multiplayer game, so I don't know. What do you suggest?
@@DashNothingFor composition rather than inheritance, id have to think for a while. The way you structured it in the video works pretty similar to inheritance though. You essentially just decoupled the class to be overriden and set it as a singleton.
For the inheritance route, you could have the player check for objects with an interactable component within an area, then call the objects override method.
Currently though, i think I like your method better.
hello! i came from your textbox video. thank you so much for this high-quality content!
i was wondering if you could help me - when using the interaction manager and dialogue managers together, how do we "tell" the interaction manager to display the textbox we set up in the dialogue manager? thank you!
Thanks for the kind words!
You don't really tell the interaction manager what to do at all. The beauty of this is that each interaction area has its own oninteract callable that defines what happens.
So when you're overriding the oninteract callable you can just call DialogManager.start_dialog(). I put an example of just that near the end of this video, when making an npc.
@@DashNothing Ahh ok, so to use the dialogue manager, we would go into the "triggerring" object's script and call the dialogue manager? Or would we call the dialogue manager in the interaction script?
Thank you for your support I really appreciate it!
@@bellehuberty9457 You'd do ti from the triggering object's script. Check out how I made an NPC at the end of this video.
does this work with 3d?
Hello there! Thanks a lot for the tutorial! I've spent a couple of hours rewatching the videos and fixing some errors, BUT there's still one. Everything works fine, but the text box doesn't disappear after the dialog and keeps hanging... Seeking for help :) Thanks!
I assume you followed my dialog system tjtorial, in which case the problem must be in the dialog amanger script. It the _unhandled_input function theres a line textbox.queue_free() at 10:09 in that video. Does this not destroy your textbox? It should destroy it after every line and finally at the end.
It doesn't! Looked through the script again and can see no mistakes (but mb I'm blind). That part looks like this:
func _unhandled_input(event):
if (
event.is_action_pressed("advance_dialog") &&
is_dialog_active &&
can_advance_line
):
text_box.queue_free()
I GOT IT. I didn't assign the "advance_dialog" input... Now it works perfectly. Thank you! You're doing a great job and helped me a lot.@@DashNothing
@@owps2121 Yeah, that looks right. Can you check if it gets deleted after a line finishes, but before the whole dialog is over? You should never have more than one text box in the scene.
You can check it by playing the game and clicking on the remote tab in the scene tree. There you can find the dialog manager and a single text box should be its child at most.
Great tutorial!
At first I was having trouble with the text appearing/disappearing until I realized that the "active_areas.size() > x" counts any CollisionShape2D node in its range so I needed to change it from "0" to "1" (at least in my code). My player character's CollisionShape and the Interactable Object's CollisionShape.
My only current issue is now my "E to Interact" key press works even OUTSIDE of the Interaction Area. Any advice for that?
Thank you!
The interaction area colission mask should be set to player only (1:02). This way it will not detect any other object.
Of course, with this you should change the 1 back to a 0.
@@DashNothing ah, I'm new to Layers and Masks. Will take another look back.
worked! Thanks
"make sure you player is in the player group" Can you expand? @3:09 Great videos, btw.
If you click on the root node of your player scene by default you see the inspector on the right side of the editor. Click on the "node" tab next to the inspector tab and you will see the node's signals. Click on the "groups" tab next to the signals tab. There you can add the scene to groups by typing in the name of the group. In this case type in "player" and click add group.
I'm having trouble, it seems that my interaction area is not detecting my player. I don't know if it has to do something with the play group or the collision layer + mask on the interaction area? I've checked and the Collision layer on my player is the same as mask on the interection thing. Which is set to "1". Then I am not sure about the group thing you mentioned, because am I supposed to have the entire 2D node in it and player node also named player? Or is it only supposed to be the player node by it self and the naming of the player node does not matter?
Your collission layer/mask setup is correct. As for the group, the second option is right.
- ONLY the player node should be in tbe "player" group
- it doesn't matter what the player scene is called, as long as it's in the "player" group it will work
Let me know if you need further help!
Yeah so, turns out that that wasn't the problem, I tried multiple debugging points whilst using print to determine what's being run. It turns out that _process in InteractionManager is always doing the label.hide() function, no matter if I enter a interaction area or not, it just won't add the area to the array. And as a matter of fact, it does not register the player entering it at all. Thank you for your help btw, I am a total newbie and I appriciate the help with your tutorials!@@DashNothing
@@supergames6696 Okay, so it seems the problem is with the interaction area. If collision mask is set to player's collision layer, also check these:
- does the area have monitoring turned on? It should by default and it's needed to detect when objects enter
- does the area have a collision shape child with a big enough area so that the player can enter it?
And it's no problem, I'm happy to help. You are doing very well in debugging this issue, especially for a newbie.
Hello! i have a problem where it says "Invalid get index 'global_position' (on_base: null instance) upon entering to areas in the same time. in Addition labels are not shown whilst entering areas. Have any ideas why?
There's a number of things that could be wrong:
- Your player variable in InteractionManager is null - this could be if the player isn't in the player group
- You added the interaction area scene the wrong way - you have to add it by clicking on the "instantiate child scene" button (link icon) and NOT on the "add child node" button (plus icon)
- The null instance is one of the interaction areas, which means they most likely don't register / unregister themselves properly
Use the debug tool to find out exactly what variable is causing the issue and I'll be happy to help you further!
@@DashNothingI think I fixed the problem, player var was null even tho it’s in group so I made my own function which gets player pos to global vector and its given to area_to_player. But the label “[E] to interact “ is not showing (wasn’t before fix) any idea why?
@@kamolr6539 Do you update the player's position in the proccess function? If not, you should do that to always have the newest position available.
@@DashNothing function which changes global vector is inside player script and is called after move and slide function and play animation function
@@kamolr6539 Can you interact even though the label doesn't show up?
Keep it up ! Let’s godot !!!
Can i use this method to add a value to a variable in another scene, for example i want to add ammo to my weapon?, i tried to use and a made a global var but i dont know if it is the best way to do.
You can either have a global singleton that holds the ammo value and then add to it from your interact function and read from it in the player script. I assume this is what you did.
Or you can have a reference to the player in the object that adds the ammo and add it directly to the player.
I think the first approach might be better because there's less coupling. But it's fine either way.
@@DashNothing thanks
good job
I keep getting the error "Invalid set index 'text' (on base: null instance) with value type of String." for my label. What do I do?
Make sure you got the @onready var label = $... line at the top of the interaction manager script. Also make sure that the name of the label node in the scene tree is the same as the name you put after the $ symbol.
Random question: why did you remove the interactionarea at an index instead of doing erase (interactionarea)?
I didn't know about the erase method until now lol. Coming from JS where you can only remove elements based on the index, I didn't even consider anything else. Thanks for letting me know
@@DashNothing No worries la, your coding style looks very JS now that you mention it (throwing awaits and functions-as-variables around)! Thanks for the vid
Hey so this tutorial is amazing! Though I do have a question. Using this code as a basis, i was able to create a simple item store for my game. However, I have a small idea that I would like some help in implementing. Basically, I want the item that the item displays are holding (a sprite2d that is nested within a scene with another sprite2d as its root node) to scale up a bit as soon as you enter the interaction area and scale back down when you leave, thus signifying which item is selected for purchase. The problem is, from what I can tell, the signal for detecting on_body_entered/exited only exists in the interaction_area script, which I am trying to keep as simple as possible for flexible implementation in other aspects of the game. do you have any ideas for what I could do? Again, thank you for the tutorial as it is amazing!
Thank you!
You can connect to the interaction area's on body entered / exited signals from the store item scene. When in the store item scene click on the interaction area in the scene tree, go to the node tab on the right and double click the signals you want to connect to. By default new functions will be created in your store item script where you can scale them up without adding any code to the interaction area script.
Hope that helps!
Thank you so much!
Do I need to connect _body_entered and _exited and write out that bit of code every time I want to make an object interactible, or am I doing something wrong? It doesn't seem to want to inherit that.
No, the signals are connected in the interaction area script. All you have to do is add the interaction area to an object you want to interact with and override it's interact callable. The interaction area + InteractionManager will handle the rest for you!
I was trying to figure something like this out so this was super helpful. One thing I am encountering is an invalid get index for global position in the sort by distance function. I am trying to figure out if this is something happening as I move just out of range of one item and it starts to trigger on another. I am pretty sure it is trying to unregister as it does this. I am using this for drops from monsters and it works pretty well except for this one thing.
actually seems to be something with the two items being inside the same area....
ah I figured it out... my group for player is Player and following I did player. So excellent tutorial. Much appreciated.
@@prof_nobody Awesome! Glad you got it working
Hi, i really love your tutorials but for some reason the interaction isn't triggered, the interactionmanager is in the autoload, the collision of the interaction area and the player are setup correctly, but it just dose nothing. I double checked the code with the video multiple times and didn't find an error.
Thank you!
Does the text "[E] to interact" appear when the player is in range or does that not work either?
@@DashNothing That dosnt work either.
@@doingitsidesways So it's either your active_areas is always empty shich means your interaction areas don't register themselves properly OR your can_interact starts at false. Can you print out those two into the output from InteractionManager's _unhandled_input() function when the player tries to interact?
Hi thanks for the help, i figured it out after breaking my head over it, for some reason connecting the signals trough the editor didnt work, even tho it looked like it did, i connected it trough code:
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
and now it works as expected. Thank you for replying to me, i looking forward to more of your videos.
@@doingitsidesways holy shit thank you. i was about to rip my hair out lmao you're a godsend. amazing tutorial @DashNothing this streamlines so much for me!
Hey! Great tutorial, but I'm running into some problems with my interaction: Nothing happens when I press E inside the interaction area. Everything else seems to work, but I can't get the interaction to work even with just a print on interact. I have watched the video multiple times but I just can't figure out what I'm doing wrong. Why could this be?
If you can see the "press key to interact" text above the correct object it means the area works correctly. In that case it could be one or more of these:
- You didn't bind the interact key in the InputMap setrings
- interaction manager's unhandled input function never registers an even (you can check that wjth print)
- on_interact callable isn't assigned the right function
Let me know if that helps.
@@DashNothing Thanks for the quick response! My interact key is bound and I have tried adding a print in my _input fuction in the interaction manager and that seems to be working, so I assume it's my on_interact callable. The _ready function looks exactly like in your video, and my _on_interact only has a single print statement in it. Do i need to change the "var interact: Callable = func(): pass" or is there anything else that might be wrong?
@@boozli It seems to be breaking somewhere between interaction manager regjstering the event in the input function and calling the interaction area's interact function. You can verify that at least the default interact function gets called if you change that pass into a print().
One more thing is how did you add the interaction area into the scene? Using the add node (plus icon) button or the instance scene (chain icon) button? It has to be added using the instance scene button.
If you'd like you can join my brand new discord server and make an issue there. We can share code and solve the problem easier: discord.com/invite/y6bXkKwree
Thank you so much for this tutorial! I tried following it, but also converting it to 3d. I followed all of the steps that you made, but I'm getting this error "Invalid set index 'global_position' (on base: 'Label') with value type of 'Vector3'" and it's pointing to a line that states:
label.global_position = active_areas[0].global_position
Any idea what could be happening here? I'm not skilled enough to understand this yet 😅
Label is a control node, meant to work in 2D space. Maybe you could try using the Label3D node which was meant to work in 3D space.
Hey, All of this works but when i try do interaction_area and the stuff under on_body_entered/exited it says Identifier "InteractionManager" not Declared in the current scope.
Hey! Thank you for the video! I have a problem, I added InteractionArea component into the node I want to interact with. But for some reason signals do not emit when player collides with the area. I tried to put print in this functions inside InteractionArea to at least check what is going on, but they basically do not call at all. Do you have any ideas why it can happen?
When you add the interaction area into the scene make sure to do it by clicking on the "Instantiate Child Scene" button (link icon) and NOT "Add Child Node..." (plus icon).
There should a a signal icon next to the InteractionArea in the scene hierarchy view if you did it correctly.
Hope that helps!
@@DashNothing Yeah, now the signal icon new the InteractionArea is shown, but signals still do not emit. I also checked the collision layers and it's exactly like in the video. Is there something I could miss?
@@nikolayborzunov6960 One reason an Area2D wouldn't detect collisions is if it has monitoring turned off. It's right at the top of the inspector and should be on by default. If it's off the area will not detect anything.
@DashNothing I just checked, so monitoring is turned on for both InteractionArea for my NPC and for it's generic class. Signals are highlighted with a green icon, so it seems like they work. Also InteractionArea has the same mask level as Player's layer. Sorry for taking your time, I just have no idea what's wrong with it :(
I've followed your instructions and everything works fine except one thing:
My label-position seems to be fixed to a specific coordinate rather than to be relative to the position of the InteractionArea.
I've also tried to set the label position relative to the players position, which works fine when just testing the scene but when I then test the whole project/run a complete debug, it crashes as soon as I get into the Interaction area.
Any tips?
Check that you are setting the label's global position in the process function. Global position should place it independent of its parent's position. Without seeing the code, it's hard to say more
@@DashNothing func _process(delta):
if active_areas.size() > 0 && can_interact:
active_areas.sort_custom(_sort_by_distance_to_player)
label.text = base_text + active_areas[0].action_name
label.global_position = active_areas[0].global_position
label.global_position.y -= 100
label.global_position.x -= label.size.x / 2
label.show()
else:
label.hide()
Thats my current code
@@johnnyballalla Did you fix it?
I have a few questions:
Can this not be done with a tile from a tile set?
Do i have to export every object I want the player to interact with as a separate image, then use it as an image file? (or what type of file, anyway?)
I also find it weird that I need a separate window for every item?
Can't I create every interactable object in my main scene from scratch?
pls someone help 😩
Hello! Amazing tutorial and really what I was looking for, but got a little problem with mine. I did almost the same thing with the lamp, but a door and for some reason, the door opens or closes even if I'm in the other side of the room. The label "[E] to" also is not appearing. Idk, is like the InteractionArea is not getting that the door is very far away or is just not working, prob I did something wrong but I really can't find out what. Making a game for school in a very short time lol
If you may know what is going on pls send help
thanks anyway!!
edit1.: sooo, I was messing with it and found out that the global.position y and x for the label was so far away from where my player is that it was out of the window (I should say that my game the camera doesn't move), but now I ended up thinking that prob I can't put the labels in different positions if the InteractionManager is always the same. Also, I ended up discovering that the InteractionManager always has me on reach so it's always showing the label and I can open the door from very far away. Maybe u know how to go around it?
Hey thanks for sharing this, very useful! I tried implementing this in c# last night and I struggle to get the callable propagation called. I define the callable in InteractionArea like this "public Callable interact;". Then on the object i want to interact with i assign the callable like this "interactionArea.interact = new Callable(this, nameof(OnInteract));". But the OnInteract is not triggered. I think it has to do with how the callable is defined in InteractionArea nut not initialized properly maybe, but not sure how to implement this overridable callable from GDScript to C#. Any Godot c# dev have an idea?
are there any pros and cons putting the interaction logic on the player vs the interactables?
I would definitely avoid putting it on the player. We should try to seperate the concerns of each script, so that one script only knows and does things only it needs.
Since interaction logic concerns many objects, their proximities to the player and the unique behaviour that's tied to each interactable, implementing this in the player script would quickly get unmanagable.
The approach shown in the video seperates these concerns well. A manager keeps track of all interactables and the logic behind it. Each interactable implements only its own interaction behaviour, without caring about how this interaction comes about. And the player node does fuck all.
So putting interaction logic on the player pros:
- It may be easier to get something done quickly
Cons:
- You will curse the day you were born adding new interactable objects
@@DashNothing Thanks for the quick reply !
When my player collides with an object that I attached the interaction_area to, I get Invalid get index 'global_postiion' on base: Array[Node] in the custom sort function. The player group is player and everything else is just like in the video. What could be wrong?
Check if the word "global_position" is spelled correctly in the sorting function.
func _sort_by_distance_to_player(area1, area2):
var area1_to_player = player.global_position.distance_to(area1.global_position)
var area2_to_player = player.global_position.distance_to(area2.global_position)
return area1_to_player < area2_to_player@@DashNothing
@@ivanchistyakovyt hmm, it seems like the function is trying to get the global_position of a nonexistant node. Could it be that an interaction area registered itself on the manager, and then it was deleted from the scene before unregistering? Or something other than an interaction area registered itself?
@@DashNothing Found mistake! Was @onready var player = get_tree().get_nodes_in_group("player") instead of @onready var player = get_tree().get_first_node_in_group("player"). Big thanks for the video and trying to help! Liked and recommended!
very nice!
Why did you/he decide to get the player node by using a group instead of doing it with "$" like done with var label?
With $ you can only get children of the node. Player is not a child of InteractionManager
Great vid! I had to modify the "player" var to be set during the "_process()" function in the InteractionManager. For some reason whenever trying to set "player" like you showed in the video, it would try to get the node before it was defined, leading to null reference errors.
My line was identical to yours (@onready var player = get_tree().get_first_node_in_group("player"), but it would be set to null because the player node did not exist when it was trying to set the variable. Not sure why my situation was different, but seems to work okay with my modification.
Maybe some other script creates the player at the start of the game? Unlikely, but I can't think of anything else.
Godot creates nodes bottom up, so autoloaded nodes should be created last, unless I'm mistaken. By the time they're created, all other nodes should be available.
If you didn't already, before setting it in _proccess() I would put an if to check if the player is already not null. Just so you don't set it over and over again.
mines having the same issue can you possibly tell me4 how you called it cus for me var player = get_tree().get_first_node_in_group("Player") doesnt work
@@Lupin_VA, sure! Instead of declaring the player variable on line 4 (3:42 in video), i moved this line inside of the _process(delta) function. Here are the first lines of my _process(delta) function:
func _process(delta):
if activeAreas.size() > 0 && canInteract:
if player == null:
player = get_tree().get_first_node_in_group("player")
...
Notice i also removed the @onready decorator. For me, this change makes sure the player is never null and also does not get called more times than necessary.
@@aflyingcougar8290, thanks so much! i had this same problem
If I wanted to make an interaction that teleported the player, how would I go about that?
Hi! I hope you can help me. I tried to code this to a T but I'm getting errors. The first i can't resolve (at least, cannot understand how to resolve) is about our test_guy. In my case his name is Opal. I'm getting the error on her func _ready, stating:
"Invalid call. Non-existent function 'CharacterBody2D(opal.gd)::_on_interact (Callable)'."
Could you help me figure out how to fix? Is it because she's a character body 2d node instead of anything else? If so, what should an NPC's node be?
on_interact is a var of the interaction area, not of the NPC. So it should be interaction_area.on_interact, so make sure you have that interaction_area part.
@@DashNothing I appreciate your prompt response so much on a 7 month old video! I fixed all the errors and now I'm left with just a few differences. Please know I'm new to the Godot language! From your code example in the cute text box video for test_guy, you have some sections labeled InteractionLabel. I don't think I have this identifier from the tutorial. So, I figured it might be a rename from the InteractionArea, so I renamed it to such, but that doesn't seem to be it (getting more errors, then they disappeared, so unsure as of now)
Also I noticed from that video you have some signals connected to the body entered and body excited lines for the test _guy. What signal is that connected to and receiving from? (Also on the body exited function inside the test_guy I never could see what code was underneath it. What happens when body exited?)
I'm sorry I'm not as intuitive with the language just yet, so I am very grateful for your patience as I learn the basics, in order to later be able to change them to my game's specific mechanics. Thank you so much!
@@kebu2579 No worries! The code in the test guy script in the dialog tutorial was just a placeholder interaction system that I cobbled together to demonstrate the text box. You can just use the code from this video to start dialogs on interact, as show in the example at the end of this video.
help! i followed your tutorial step by step and i've redone it multiple times, but the label won't show up or the interaction won't happen even when i press E ;; do you have any ideas what could be wrong?
Could be a few things.
Is the interaction area setup to detect the llayer entering? Its collision mask should be the same as the player's collision layer.
@@DashNothing i figured it out - yes that was the problem, when i set up the interaction area i did set up the signals and the collision layers, but when i add interactionarea as a child node of a scene, for some reason those settings - signal connections and collision layers - don't carry over. i had to redo it under the scene i put interactionarea as a child node of
@@Forrestte That would probably be because you afded it with the add node button (plus icon). To keep the settings you should add it with the instance scene button (link icon).
@@DashNothing i see!! my fault for missing the detail. thank you for explaining it to me 😭
It says that there is no label shown
hey! i have a error, in the: func _sort_by_distance_to_player(area1, area2):
var area1_to_player = player.global_position.distance_to(area1.global_position)
var area2_to_player = player.global_position.distance_to(area2.global_position)
return area1_to_player < area2_to_player i have a Invalid get index 'global_position' (on base: 'null instance'). is there a way to fix this? i tried looking it up, but nothing seemed to help.
Is the null instance your player var? If so make sure your player is in the "player" group, otherwise the interaction manager won't be able to get a reference to it.
I just wanted to know how to make the interaction text return as in the video because when I first interact with the NPC it doesn't appear again
@@matezu It should be working like that with the code in the video. Maybe you didn't set can_interact back to true?
can you tell me what's the point of "await" keyword at 7:14?
@@Qas22w oninteract callable might be asynchronous itself - it might create and wait for a timer, wait for an animation or a tween to finish etc. So we await the oninteract to finish completely before ending the interaction.
If you followed my dialog / textbox tutorial, that is another example of where the await is useful. Since the dialog manager has to wait for the player to go through all the textboxes, it's really an asynchronous process which the interaction manager will wait until it's completely finished before ending the interaction.
@@DashNothing thanks!