Basic Coding for a shop

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

So this is really the last stepping stone for my game besides localization that is giving me a lot of trouble. I was only able to find one tutorial for a shop in Godot and it was on a random Youtube channel in a 6 step process. The shop itself I think looks good. Its coded to subtract the price from the total money, gives you a popup if you don’t have enough money, has the button say purchase, selected, or select when applicable. But as far as implementing what they buy into the game itself is beyond me. In the tutorial he has it set as sprite skins and are able to transfer them into the main scene, but I am trying to figure out how to transfer an entire new AnimatedSprite with it’s own set of animations and have tried doing very unorthodox things that I won’t get into, but nothing has worked so far. In other words, a new “character.” Any help would be appreciated.

:bust_in_silhouette: Reply From: exuin

Can’t you swap out the SpriteFrames resource with a new one? All AnimatedSprites have one.

Yes, this is exactly the kind of thing I’m looking for, thank you. How would I go about doing that, and also as far as the engine knowing if THIS button is clicked for THIS item it loads THIS, I made “selected” a group, but I don’t know how to do what you are describing with it changing out the resource. KidsCanCode tried to explain it to me before but I was just as lost.

Spafnar | 2021-02-24 22:39

Well, if KidsCanCode couldn’t help you, I probably can’t, but I can try.

I don’t know how exactly your nodes are laid out, but I would connect every button’s “pressed” signal to the same function. Then, I would add an argument to the signal that indicates what SpriteFrames the button corresponds to. You can do this through the editor by clicking “Advanced” when connecting a signal and through code by adding an extra Array argument to the end of your connect function. I would pass an int or a String as an additional argument.

button.connect("pressed", self, "on_button_pressed", ["name_of_item"])

Next, you should have a dictionary that stores the paths to the SpriteFrames resources in the shop. The keys should be the arguments that you gave with the signals earlier.

const SPRITE_DICT = {
    "name_of_item": "res://path_to_item.tres",
}

With the key passed from the button’s signal, get the path of the SpriteFrames, load it, and set the player’s SpriteFrames to it.

player.frames = load(SPRITE_DICT[key])

exuin | 2021-02-25 02:09

I really appreciate this bro, I’ve been looking for a while on help with this. I think I figure out the first part of what your were saying but I still have a few questions;

  1. When you say “stores the paths to the SpriteFrames resources in the shop,” do I put the AnimatedSprite nodes in the shop scene? Or if not how would it know to call them if I have them in the player scene?

  2. For the second part of code where I make the dictionary, how would the “res://path_to_item.tres” work if its not a singular sprite, but a collection of them that make an animation. I’m guessing the answer is really obvious but I’ve never had to do much with AnimatedSprites before.

  3. For the last section of code, do I put it in the main scene, the shop scene, or the player scene?

Spafnar | 2021-02-25 16:07

It’s not necessary to have the AnimatedSprites in the shop scene since you probably won’t use every animation from them. You only need the paths to the SpriteFrames so that the player scene can load them.

SpriteFrames should be a singular resource.

I don’t know how your scenes are laid out, but probably in the shop scene or the player scene if you want to keep all player-related actions on the player.

exuin | 2021-02-25 16:26

Alright cool the first two questions I got down thanks for answering, I had no idea how to do that stuff with the resource and loading the sprite frames but I finally figured it out. I got the code written out but now I still need help with the third question again.

My store is a separate scene than main with the player pressing “enter” for it to load it in. Would it be best to have my store scene as a PackedScene and have my main like preload it so that it knows what the sprite dict is? Or how would I go about doing that.

Spafnar | 2021-02-25 20:49

I don’t think you should preload your shop scene. PackedScenes take up memory. If you don’t want to store the sprite dict in the shop scene though you don’t.

exuin | 2021-02-25 20:53

So, I did put the sprite dictionary in the store coding, but the issue is that if I put it in main or player, it doesn’t know what “spritedict” or “key” is. And if I put it in the store scene it doesn’t know what $Player is.

Spafnar | 2021-02-25 21:11

So just pass the data between the scenes then. Get the path of the SpriteFrames from the shop scene and then pass it to the main scene which then passes it to the player scene to load the SpriteFrames.

exuin | 2021-02-25 22:11

I see, and what would that look like as a code?

Also for the player.frames = load(SPRITE_DICT[key]) code, is “key” the right term? Does it just know which part of the dictionary is which? I’m only vaguely familiar with dictionaries, I’ve really only ever used arrays.

Spafnar | 2021-02-26 06:14

Dictionaries are sets of key-value pairs. You use the key to find the value. key is just a placeholder, and you shouldn’t actually use that to access a value in the dictionary.

So let’s say you have a player skin called “Godette”. The player clicks the “buy” button for her and it sends a signal to the shop scene, which triggers a function like this:

func buy_skin(key:String):
    player.animated_sprite.frames = load(SPRITE_DICT[key])

Where key is something like “godette”.
However, the shop scene might not know what the player is. You can either get the path to the player, or you can store a reference to the player in the main scene and get it from there. You can also emit a signal to the main scene that a skin has been bought, but that might be too complex right now.

exuin | 2021-02-26 14:57

That was very informative, thank you. I guess the only question I have left is how to “pass the data,” or “get the path to ___” I’ve only ever used

const preload “scene”
or packed scenes

to do anything like that. Is there a certain method you’re talking about where I can have the main scene understand what the “spritedict” is?

Spafnar | 2021-02-26 15:25

Hold on, is the shop scene a child of the main scene? How are your nodes arranged?

The path to a SpriteFrames resource should be something like "res://folder_name/sprite_frames_name.tres".

exuin | 2021-02-26 15:39

Yea sorry I should have clarified, The Store.tscn, the Main.tscn, and the Player.tscn are all separate, but I instanced the player scene to be a child of the main node.

Spafnar | 2021-02-26 18:17

If your store scene and the main scene aren’t in the scene tree at the same time you’ll need to store data in a Singleton.

exuin | 2021-02-26 18:41

ahhhhh okay awesome I will try that. I’ll put it in my global script

Spafnar | 2021-02-26 19:59

So I added the const in my global script and I think that part is good now, but now I’m getting an error from when I actually click the buttons. This is what it looks like:

func _on_ButtonO_pressed(extra_arg_0):
extra_arg_0.connect("pressed", self, "on_button_pressed", ["Player"])
_buy(0, 0)
(Global.button_sound())

func _on_Button2_pressed(extra_arg_0):
    extra_arg_0.connect("pressed", self, "on_button_pressed", ["RedPlayer"])
    _buy(price2, 1)
    (Global.button_sound())

func _on_Button3_pressed(extra_arg_0):
    extra_arg_0.connect("pressed", self, "on_button_pressed", ["BluePlayer"])
    _buy(price3, 2)
    (Global.button_sound())

After I click on any of the buttons I get this error:

"Invalid Call. Nonexistent function ‘connect’ in base ‘String’

All three are connected with the extra call you told me about.

Spafnar | 2021-02-26 23:47

No, you connect the buttons to the functions. “extra_arg_0” is a string, so you can’t connect it.

Okay, something like this:

button0.connect("pressed, self, "on_button_pressed", ["Player"])

and then the signature of your on_button_pressed should look like this

func on_button_pressed(key):

exuin | 2021-02-27 00:40

Ah yes, I see where all the confusion I’ve had has been from, I had all the shop buttons as their own function when I connected it because I misread. I followed the advice from the beginning and now connected all the buttons to the ready function.

I put it in as this:

button.connect("pressed", self, "on_button_pressed", [SPRITE_DICT["Player"]])

Am I supposed to define what button is like in an onready var? The engine doesn’t seem to be recognizing the “button” part.

Spafnar | 2021-02-27 04:50

Yeah, you need the node path to the button in order to connect its pressed signal.

exuin | 2021-02-27 05:09

do i make a separate

button.connect(“pressed”, self, “on_button_pressed”, [SPRITE_DICT[“Player”]])

code for every button in the shop? I tried defining “button” as an array with all the nodes for all the buttons but it’s saying function connect doesn’t exist in base array.

Spafnar | 2021-02-27 06:10

Just kidding answered my own question, obviously I have to because of the “Player” at the end and I have to assign it to the sprite resource in the dictionary!

However now I’m getting an error everytime I click on a new skin in the shop:

E 0:00:06.753 emit_signal: Error calling method from signal ‘pressed’: ‘Tabs(Characters.gd)::_ready’: Method expected 0 arguments, but called with 1…
<C++ Source> core/object.cpp:1260 @ emit_signal()

Spafnar | 2021-02-27 06:28

Does on_button_pressed have an argument then? That might be the issue.

exuin | 2021-02-27 06:30

I also realized that I never used the

func on_button_pressed(key):

That you gave me. You called it a signature of the on_button_pressed
What does that mean and could that be where the issue is coming from?

Spafnar | 2021-02-27 08:03

A signature is just another way of saying the method header. It’s the line that starts with func. If you don’t have the on_buttom_pressed function, what are you connecting the signals to?

exuin | 2021-02-27 13:24

Ah I see, I connected them to the ready function, let me try rearranging things

Spafnar | 2021-02-27 17:23

Cool so I fixed everything, but now I think that it is interfering with the original purpose of the button getting pressed;

func _ready():
	Global.load_store()
	Global.load_pricescore()
	for item in range(panels.get_child_count()-2):
		if Global.store.bought[item] == true:
			panels.get_node("Panel" +str(item+1)).get_node("Button").text = "Select"
	panels.get_node("Panel" +str(Global.store.Selected+1)).get_node("Button").text = "Selected"
	panels.get_node("Panel" +str(Global.store.Selected+1)).get_node("Button").add_to_group("Selected")


func _selected(_node, _no):
	Global.load_store()
	for buttons in get_tree().get_nodes_in_group("Selected"):
		buttons.text = "Select"
		buttons.remove_from_group("Selected")
	_node.text = "Selected"
	_node.add_to_group("Selected")
	Global.store.selected = _no
	Global.save_store()

Is there a way to lock the load spritedict until after the skin has been purchased with the same button so that it doesn’t give me a bunch of errors?

Spafnar | 2021-03-01 15:38

Which function loads the spritedict?

exuin | 2021-03-01 17:10

This is what I have for the 2 shop buttons currently. I defined spritedict as autoloaded singleton like you said earlier.

func _on_SButton_pressed(_key: String):
button.connect(“pressed”, self, “on_button_pressed”, [SPRITE_DICT[“Player”]])
button2.connect(“pressed”, self, “on_button_pressed”, [SPRITE_DICT[“RedPlayer”]])

Spafnar | 2021-03-01 17:18

Oh, I wouldn’t put the SPRITE_DICT["Player"] as an argument. I would only pass the key “Player” as the argument.

exuin | 2021-03-01 17:48

Ok I fixed it but I’m still getting the same error;

emit_signal: Error calling method from signal ‘pressed’: ‘Tabs(Characters.gd)::_ready’: Method expected 0 arguments, but called with 1…

This is what’s in my ready function:

func _ready():
Global.load_store()
Global.load_pricescore()
for item in range(panels.get_child_count()-2):
	if Global.store.bought[item] == true:
		panels.get_node("Panel" +str(item+1)).get_node("Button").text = "Select"
panels.get_node("Panel" +str(Global.store.Selected+1)).get_node("Button").text = "Selected"
panels.get_node("Panel" +str(Global.store.Selected+1)).get_node("Button").add_to_group("Selected")

Spafnar | 2021-03-01 20:45

Do you have Discord? QA isn’t really meant for super long discussions lol

exuin | 2021-03-01 20:47

haha yes that’s a better idea. It’s Spafnar#2606

Spafnar | 2021-03-01 20:49

Were you able to find me

Spafnar | 2021-03-02 15:48

Sent you a friend request. Did you get it?

exuin | 2021-03-02 17:22