Make Godot "Script Variables" control appearance in 2D editor? - sprite

I have a "Player" scene, which holds everything about a player, the sprite, the animations, the shadow, and a collection of other things.
A script variable 1,2,3,4, etc.. controls which image/texture/sprite data is loaded, to control the appearance.
But when I drop them on the 2D editor, they all have the default image, so they all look identical. Which is confusing. Whats the best way to make each player appear different in the editor?
This code in the player script is good at allowing setting a player number, and loading a different image set, but it doesn't show in the 2D editor. Changing the character number makes the image change at runtime:
export var characterNumber = 1
var player1 = preload("res://Player/Player1.tres")
var player2 = preload("res://Player/Player2.tres")
func _ready():
if characterNumber == 1:
get_node("Sprite").set_texture(player1)
if characterNumber == 2:
get_node("Sprite").set_texture(player2)
Alternatively, I could have multiple sprites in a generic player scene, and turn them on and off depending on the characterNumber, but again, the same problem occurs, the change isn't visible until runtime.
One idea is multiple children scenes, but I cant work out how to turn sprites on or off depending on a variable or some other type of setting.
What am I doing wrong?

In your script your want to make it a 'tool' so it can execute inside your editor, and not only when you launch the game.
see https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html
In your case, you might use something like the following: (make sure to do "Scene -> Reload saved scene" to reload the constructor if your change the node's property)... a more ergonomic (but takes more processing) solution is to put it in the _process func
tool
export var characterNumber = 1
var player1 = preload("res://Player/Player1.tres")
var player2 = preload("res://Player/Player2.tres")
func _ready():
if Engine.editor_hint:
if characterNumber == 1:
print("executing custom in editor logic")
if characterNumber == 1:
get_node("Sprite").set_texture(player1)
if characterNumber == 2:
get_node("Sprite").set_texture(player2)
func _process(_delta):
if Engine.editor_hint:
# only rotate the object if we set the character number to 1
if characterNumber == 1:
rotate(Vector3(0,1,0), deg2rad(15*_delta))
set_my_texture() #quick n dirty way for fast reload
func set_my_texture():
if characterNumber == 1:
get_node("Sprite").set_texture(player1)
if characterNumber == 2:
get_node("Sprite").set_texture(player2)

Ryu's approach of using a tool script would work. You can use Engine.editor_hint to identify when your script is running in the editor.
By the way, combine it with setget, so you can define a setter function that will run when you modify the variable. You should not need _process, which would be running every frame.
Anyway, let me cover a few alternatives:
You can make inherited scenes (from the context menu of an existing scene in the FileSystem panel choose "New Inherited Scene"), and there modify it however you want. You can change properties such as the texture of sprites, or you can add other scene instances as children. This solves the problem of placing an instance of a particular variation of your scene, and being able to tell it apart in the editor, without the need to run your script on the editor.
If you have all the textures you want in single atlas/sprite sheet, you can use an AnimatedSprite, except you are not going to use it to animate. Instead put all the textures in a single animation, and then change the frame to pick the texture you want. This gets close to what you want in that you just change a number (the frame).
For completeness I'll also mention that you can use an AtlasTexture to load a texture form an sprite sheet. On the texture of your Sprite (for example), select "New AtlasTexture", then atlas of the AtlasTexture load your sprite sheet, and set region to take the area of the texture atlas you want. I suggest setting the size (width and height of the region) first. This approach will also work for anywhere you might need a texture, although tweaking the region is more work that simply setting a number. By the way, AnimatedTexture can serve a similar role, but it does not give you a way to specify the frame you want.
And yes, you could control any of these form a tool script.
Addendum: by the way, you know you can enable "Editable Children" on the scene instances on the Scene panel, right? It is in the context menu. That lets you edit the instances directly. I wanted to mention it, just in case that helps you.

Related

What is the ideal way to change the value of a progress bar with on-screen button press (Godot)

I'm new to coding and Godot and I need help with changing the value of my progress bar from the script inside my button node. The error I get is 'get_node: (Node not found: "/root/ProgressBar" (absolute path attempted from "/root/Node2D/Button").)' I want every time the button is '_pressed()' to increment health by 10 which would change 'value' in ProgressBar to that value.
extends Button
var health= 0
func _ready():
pass
func _process(delta):
pass
func _pressed():
health += 10
print(health)
get_node("/root/ProgressBar").set_value(health)
The path /root/ProgressBar is unlikely to be correct. Usually, as a child of root, you would have the current scene, and as child of that something else (So the path could be something like /root/MySceneName/ProgressBar). And relative paths work too… Usually, we would solve this by means of figuring out the correct path…
But things have changed (For Godot 3.5 and newer). What you are going to do is open the script while the scene is open, and then drag - with CTRL pressed - the Control you want to access (i.e. the ProgressBar) on script, outside of any method (func).
By doing that Godot will generate a line of code that looks something like this:
onready var progress_bar: ProgressBar = $"Path/To/ProgressBar"
In other words, Godot will figure out the path you need, and set up a variable you can use. Godot will set the variable to the Node you dragged as part of the initialization of you script. Assuming all goes well.
You might get an error saying that the script is not used in the scene. Perhaps you have the wrong script open, or you use the script in multiple places and Godot got confused… Either way, close the script and open it from the scene, and it should work.
And, of course, you can use the variable to set its properties, for example:
progress_bar.value = health
You might also be interested in scene unique names. On the contextual menu of the Nodes in the Scene panel you can select "% Access As Scene Unique Name", this will allow you to access the Node with this syntax:
$"%ProgressBar"
Regardless of where it is in the scene tree. Except, be aware that the name must be unique for that scene (you won't be able to do that with two Nodes that have the same name).
This has the advantage that if later you change where the Node is in the scene tree (for example to add some Container to organize your Controls), you don't have to update the path to where you use it. Instead it should continue to work, as long as you don't change the name.
Let that be yet another reason to pick good Node names.
And yes, dragging the Node to the script also works with these.
The problem with your path is, that your missing the name of your main node in it.
So let's say your node tree looks like this:
MainNode
ProgressBar
Button
To get the ProgressBar from anywhere you would need to use get_node("/root/MainNode/ProgressBar")

New to Coding Very confused

I'm new to coding in general and I'm trying to make a sprite change texture so it has a walking animation but I can't seem to figure out how to apply a wait() or something to my code.
if Input.is_action_pressed("move_up"):
vel.y -= 1
facingDir = Vector2(0, -1)
$LilBoiTexture.texture = load("res://LilBoiAssets/LilBoiBackward.png")
$LilBoiTexture.texture = load("res://LilBoiAssets/LilBoiBackward2.png")
Any help is appreciated. I'm trying to change from the first texture to the second one within idk 0.5, or something ill mess with it if i can figure out what to do.
There is an easier way of doing this instead of changing sprite image manually. You can use "AnimatedSprite" node as shown in the tutorial. Here are the steps:
1- Add an AnimatedSprite node to your character.
2- In properties of AnimatedSprite, Frames-> select new SpriteFrames.
3- Click SpriteFrames you just created, another menu will appear at the bottom of editor. Drag and drop your animation images to center of this menu.
4- Change animation name from default to something else (for example walkback).
5- In your code you just need to do this:
if Input.is_action_pressed("move_up"):
$AnimatedSprite.play("walkback")
else:
# you can also play an idle animation if you have one
$AnimatedSprite.stop()

Adding different layout options from the 2D editor

I would like to make the following views for portrait and landscape mode:
In portrait, show the views vertically as
A
B
C
(looks like they are in a VBox)
In landscape, show the views as
A C
B
(looks like HBox(VBox(A, B), C))
I am able to do this with the use of a custom container, but it means measuring sizes myself, and requiring that all 3 nodes by immediate children in my container.
I was wondering if there was a different approach where we can define two completely separate layouts in the editor, and associate certain nodes with the nodes we want; this is a pattern I'm more familiar with through android dev, where we can create two completely different layouts and associate certain views by having the same id.
For this case, I would be able to create the layouts I mentioned above with the existing VBox and HBox, and tell the root note to place A, B, C where they should be. The children no longer need to be immediate children of the root container, and I can add other nodes to one of the layouts and not the other. If a user switches between these two layouts, I expect that the contents within the shared nodes (A, B, C) be retained.
Is there a way of doing something like this in Godot?
You can do what you're describing by creating two separate layouts in the editor and instantiating + replacing when needed.
extends Control
const LANDSCAPE_LAYOUT = preload("res://Landscape.tscn")
const PORTRAIT_LAYOUT = preload("res://Portrait.tscn")
var orientation = 0
onready var layout = $Layout // root node of UI you will replace
func _process(delta):
if orientation != OS.screen_orientation:
_change_layout()
func _change_layout():
orientation = OS.screen_orientation
var new_layout = null
match orientation:
0:
new_layout = LANDSCAPE_LAYOUT.instance()
1:
new_layout = PORTRAIT_LAYOUT.instance()
_:
return // feel free to include the other cases in ScreenOrientation enum.
var layout_position = layout.get_position_in_parent()
remove_child(layout)
add_child(new_layout)
move_child(new_layout, layout_position)
layout = new_layout
Similar to the Android activity lifecycle, you are destroying and recreating a portion of your UI whenever the orientation changes.
I'm checking orientation manually because there's no way to get a callback for it currently. If you're going to have multiple nodes checking the orientation, it might be useful to set up a singleton instead and emit a signal when it detects a change.

Particles generated behind sprite

I just started out with Phaser.
I have a simple sprite in the middle of the screen, and whenever I click the sprite, I emit a particle at the clicked x,y coordinates.
My problem is that the particles are generated behind the sprite. I have tried setting z on the sprite to 1 and the emitter to 1000 without luck.
What am I missing?
var emitter = game.add.emitter(game.world.centerX, game.world.centeryY);
emitter.makeParticles('phaser');
var sprite = game.add.sprite(game.world.centerX, game.world.centerY, 'phaser');
sprite.scale.setTo(2, 2);
sprite.inputEnabled = true;
sprite.events.onInputDown.add(function(sender, pointer){
emitter.emitX = pointer.x;
emitter.emitY = pointer.y;
emitter.emitParticle();
}, this);
http://phaser.io/sandbox/cxBVeHrx
EDIT
My actual code is based on the Phaser-ES6-Boilerplate. Even though BdRs answer solves the issue in the sandbox code, I'm not able to utilize this in my real code.
I have uploaded both the code and a running example. Hopefully someone can tell me where I have screwed things up...
Separate Phaser items don't have a z-order, instead it just depends on the order you create and add them to game. Each new sprite or emitter or group etc. will be displayed on top of all previously added items.
So, simply changing your code to something like this should work.
// first the sprite
var sprite = game.add.sprite(game.world.centerX, game.world.centerY, 'phaser');
sprite.scale.setTo(2, 2);
// then the particles in front of sprite
var emitter = game.add.emitter(game.world.centerX, game.world.centeryY);
emitter.makeParticles('phaser');
// then maybe text in front of particles and sprite
var mytest = game.add.bitmapText(10, 20, 'myfont', 'Level 1', 16);
// etc.
Btw sprites do have a .z value but that only used when it's part of a Phaser.Group, it will then be used as the display z-order but only within that group of sprites.
By default, phaser will not sort objects that get added to any group, it will just render them in the order that they get added. In your case, you can just add the emitter to the group after you add the sprite (the group in this case is the 'game' object).
Of course, having to add objects in the drawing order is not ideal, and if you need to have them sorted dynamically, not possible.
Another way is you can sort objects within a group using the 'sort' function, in which you give it the name of a parameter to sort by, and you sort whenever you need to (in some cases, in the Update callback).
Sorting every frame can be a performance hit though, especially if you have a lot of objects. Another way you could go about this is by adding groups, sorting those groups in draw order (think of them like layers), and then adding objects to those groups in any order. Any group that needs sorting within itself you can sort as well. This way, you can choose to have (for example) a background layer not needing to be sorted but everything added to that layer will be behind every other layer.
Good answers from everybody, but you are missing that every GameObject has a depth property which serves exactly the z-index purpose. This way you do not need to rely on the order of objects creation.
There is also an official
example.
Hope this helps.

I don't want to change color of JButton when pressed

Color newColor = new Color(197,222,90);
JButton newButton;
newButton = new JButton(icon);
newButton.setBacgroundColor(newColor);
When it is pressed it changes color. How can I keep it from changing color? I have multiple buttons, so if there is solution in one or two rows please help me, and keep in mind that I'm beginner, writing some huge classes won't help me, because I have multiple buttons with different names to be affected with this.
EDIT: Solution in one line is:
UIManager.put("Button.select", newColor);
But it changes all button colors but I need another to have different a color.
EDIT2: After some research I figured out there isn't an easy solution (but it should be). How I see it I have 2 solutions, 1. is to break buttons to separate classes and set UIManager for them, and second is to make custom buttons. It is just too much work for button.
I've found nothing that can change that particular behavior on a normal JButton. The problem being, that whatever you write in your actionlistener for the button, will occur AFTER you've let go of the mousebutton, and not "while clicking".
There are workarounds, however.
My preferred choice is, to remove all graphics from the button, and then add your own images to the button's regular and pressed states. You could take a screenshot of your GUI, cut out the button, and set that image to be both states.
JButton myButton = new JButton();
// Sets button x, y, width, height. Make the size match the image.
myButton.setBounds(5, 30, 100, 30);
// Remove border-graphics.
myButton.setBorder(null);
// Remove default graphics from the button
myButton.setContentAreaFilled(false);
// Remove the focus-indicating dotted square when focused (optional)
myButton.setFocusPainted(false);
// Here, myImage is a simple BufferedImage object.
// You can set one like this, provided you have an "images" package,
// next to your main class (ex: com.somecompany.someprogram.images),
// that contains an image:
BufferedImage myImage = ImageIO.read(getClass().getResource("images/myImage.png"));
// Then we simply apply our image to both states for the button, and we're done.
myButton.setIcon(new ImageIcon(myImage));
myButton.setPressedIcon(new ImageIcon(myImage));
Obviously there are many ways to retain and load an image, but since that's not the issue here, I'll leave additional methods out of it.
There's no need to go through it all countless times, though. It should be pretty easy to write your own custom implementation of the JButton class, in which a custom constructor takes a single parameter, being the BufferedImage, and then the constructor sets it up accordingly (changes the icons). Then all you have to do when you create a new JButton, is to use your own class, and pass it an image:
JButton btn = new MyCustomJButton(myImage);
You could also easily get along with very few images. All you need is a HashMap which holds all the images, with a String as a key. Imagine you need 4 OK-buttons. You make a single image of a button with the text "OK" written on it. Then you put that image into the HashMap, like so:
myMap.put("OK", myImage);
Then you could do this when creating a button, over and over again if you'd like more:
JButton btn = new MyCustomJButton(myMap.get("OK"));
Alternatively:
Another way of achieving this, which is pretty elaborate, but probably considered "the right way", is to use ButtonUI, as presented in this answer to another post.
If the OP is referring to the temporary change of background colour on a button with an icon at the moment the mouse is pressed, the following statement does the trick:
button.setContentAreaFilled(false);
"If you wish to have a transparent button, such as an icon only button, for example, then you should set this to false."
This took me a long time to figure out. It seems to be a little known technique, perhaps since its name gives little clue as to its effect.
With only first lane we can still see that it is clicked. You need to combine those two:
button1.setContentAreaFilled(false);
button1.setEnabled(false);
and if you don't wanna in grey color you put another button under him.
panelname.add(button1,+5,+5); \\(first not clicable, not visible button, notice +5)
panelname.add(button2,-5,-5); \(-5,-5 means it is 5 points under panel)

Resources