I'm trying to make a dating sim as a easy first game programming-wise. I don't know how to change the character sprites inside the scripts.
character_sprite.gd
extends Sprite
var char_tex = load("res://Sprites/Lu2.png")
func _ready():
set_texture(char_tex)
func _input(event):
if event is InputEventMouseButton:
char_tex = load("res://Sprites/Lu1.png")
update()
Just set the texture property to the desired texture. You could also preload the textures and then just switch them instead of loading them again.
extends Sprite
var char_tex = load("res://Sprites/Lu2.png")
func _ready():
set_process_input(true)
texture = char_tex
func _input(event):
if event is InputEventMouseButton:
texture = load("res://Sprites/Lu1.png")
The problem in your example was that you only assigned a new image to the char_tex variable, but that doesn't change the texture of the sprite. The texture will still refer to the previous image until you assign the new one with texture = or set_texture. Gdscript is relatively similar to Python in this regard, so I recommend taking a look at Ned Batchelder's talk Facts and myths about Python names and Values.
Related
I am trying to get my rock.png to move to the side(+x axis) when my mouse is moving. You can see that _target is the position of the event and the event is the mouse moving.
I then got the node image and set it to a variable.
In the else statement I made the rock.position the position of the _target and gave it somee space away from the _target
I want this to move because my camera moves and I want it to move with the flow of the camera
``
extends Camera2D
const zone = 10
onready var rock = get_node("rocks_png_1")
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var _target = event.position
if _target.length() < zone:
self.position = Vector2(640,350)
else:
self.position = _target.normalized() * (_target.length() - zone) * 0.5
rock.position = Vector2(int(_target)+40, int(_target)+20)
``
From the code I used above I get this error
Invalid get Index 'position' (on base: 'TextureRact')
I tried just using the same code as I used in my self.position for the camera that made it move, but when I try it for the rock.position it gives me an error that tells me I need a Vector?
Invalid get Index 'position' (on base: 'TextureRact')
The error is telling you that TextureRect does not have a position property.
What happens is that a TextureRect is a Control which are positioned differently from Node2D (Camera2D is a Node2D). You can try using rect_position with it. Although I think you would be better off using a Sprite instead of a TextureRect (Sprite is a Node2D).
Notice that your TextureRect is a child of the Camera2D in the scene tree. Well, a Node2D would move with its parent automatically. So changing it to Sprite would negate the need of the line of code you are using to move it.
… Actually, due to the way Camera2D works (it has drag and limits) you might still want do something (depending what your goal is). So, know that the get_camera_screen_center method will give you the actual visual center of the screen (which is not the target of the Camera2D, again due to drag and limits).
I'm trying to create a rigid body like Item using only Physics2DServer and VisualServer
like this:
extends Node2D
var _body:RID
var canvasItem:RID
func _enter_tree():
_body=Physics2DServer.body_create();
Physics2DServer.body_set_space(_body, get_world_2d().space);
var shape= RectangleShape2D.new();
shape.extents=Vector2(30,30);
var ci_rid = VisualServer.canvas_item_create() # ci= Canvas Item
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
Physics2DServer.body_add_shape(_body, shape.get_rid(), self.global_transform, false);
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved",ci_rid);
var image=Image.new();
image.load("res://icon.png")
var texture_rid := VisualServer.texture_create_from_image(image)
VisualServer.texture_set_flags (texture_rid,VisualServer.TEXTURE_FLAG_ANISOTROPIC_FILTER)
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(image.get_size() * -0.5, image.get_size()), texture_rid);
VisualServer.canvas_item_set_transform(ci_rid, self.transform)
func _body_moved(state:Physics2DDirectBodyState,ci_rid):
VisualServer.canvas_item_set_transform(ci_rid,state.transform)
but for some reason the collision is not working
Edit:
I think the main problem is the var shape= RectangleShape2D.new();
because when I added a export(Shape2D) var shape; instead and added a RectangleShape2D manually, then the collision worked properly
Edit for transform based problem:
extends Node2D
var _body:RID
var _shape:RID
var canvasItem:RID
func _enter_tree():
_body=Physics2DServer.body_create();
Physics2DServer.body_set_space(_body, get_world_2d().space);
var ci_rid = VisualServer.canvas_item_create() # ci= Canvas Item
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
_shape = Physics2DServer.rectangle_shape_create()
Physics2DServer.shape_set_data(_shape, Vector2(30,30))
Physics2DServer.body_add_shape(_body,_shape);
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved",ci_rid);
var texture:Texture = load("res://icon.png")
var image:Image = texture.get_data()
var texture_rid := VisualServer.texture_create_from_image(image)
VisualServer.texture_set_flags(texture_rid,VisualServer.TEXTURE_FLAG_ANISOTROPIC_FILTER)
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(image.get_size() * -0.5, image.get_size()), texture_rid);
VisualServer.canvas_item_set_transform(ci_rid, Transform2D.IDENTITY)
set_notify_transform(true)
func _exit_tree():
if(_body.get_id()!=0):
Physics2DServer.free_rid(_body)
if(_shape.get_id()!=0):
Physics2DServer.free_rid(_shape)
func _body_moved(state:Physics2DDirectBodyState,ci_rid):
VisualServer.canvas_item_set_transform(ci_rid,state.transform)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
if _body.get_id() != 0:
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, transform)
Missing Shape
The problem with the shape is that it is getting freed.
If you declare a field with the shape at the top of the script, and then initialize it _enter_tree, it works correctly. But declared inside _enter_tree it lives until the end of the method when it goes out of scope.
The lesson is that a RID is not a reference.
The alternative is to create the shape and the texture with the Physics2DServer:
var shape_rid := Physics2DServer.rectangle_shape_create()
Physics2DServer.shape_set_data(shape_rid, Vector2(30,30))
And since that is a RID created with the Physics2DServer you would free it by calling the free_rid method of the Physics2DServer.
By the way, I'm getting a warning about the texture. You can load it as a Texture instead:
var texture:Texture = load("res://icon.png")
var image:Image = texture.get_data()
Which will the warning go away.
And then we are tempted to do this:
var texture_rid := texture.get_rid()
But we get errors… The issue is the same, the texture is getting freed when the method ends because the variable is the only reference and it is going out of scope. Declaring the texture at the top of the file fixes the error spam.
And, yes, we could create the texture with the VisualServer. But since you are loading it from a resource, it is not practical.
Transform
First of all, the canvas item is positioned relative to the node where the code is running:
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
VisualServer.canvas_item_set_transform(ci_rid, Transform2D.IDENTITY)
Second, the body is placed in global coordinates. So we should do this:
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)
Notice global_transform instead of transform.
And that brings us to this:
state.transform is global coordinates.
canvas_item_set_transform wants coordinates relative to the parent (the node where the code is running).
This a way to deal with it:
VisualServer.canvas_item_set_transform(ci_rid, global_transform.inverse() * state.transform)
Alternatively, you could move the node to follow the body:
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
global_transform = state.transform
And that would move the canvas item too, because, remember that it is parented (with canvas_item_set_parent).
Since you are moving the body when the node moves (in _notification), I believe moving the node to follow the body is the correct solution. This way, they stay in sync all the time.
About the shape: it is placed relative to the body. Both the shape and the body can move. The physics engine will move the body (and the shape follows because it is positioned relative to the body), but the physics engine will not move the shape directly. You, however, could move the shape directly if you wanted. This is the same relationship that a RigidBody2D and its CollisionShape2D would have.
I am probably overthinking this or going about it the completely wrong way. Basically I want to use move_and_slide on the instanced player (aPlayer) where I've commented, #MOVE. This script is attatched to my Tilemap on an instanced Scene Level_Test.
Code in screenshot pared down for this post because it is not relevant and nonfunctional at this time.
extends TileMap
onready var aPlayer = preload("res://Scenes/Player.tscn").instance()
var cSpawn1 = Vector2.ZERO
func _ready() -> void:
aPlayer.position = cSpawn1
add_child(aPlayer)
func _physics_process(delta) -> void:
if Input.is_action_just_released("iPlayerInteract"):
_Interact()
else:
var vInputDirection = Vector2(Input.get_action_strength("iPlayerRight") - Input.get_action_strength("iPlayerLeft"), Input.get_action_strength("iPlayerDown") - Input.get_action_strength("iPlayerUp"))
if vInputDirection != Vector2.ZERO:
_Move(aPlayer, vInputDirection)
func _Interact():
pass
func _Move(vSelf, vDirection):
#MOVE
pass
The player.tscn scene as depicted in the screenshot is structured as follows:
Player (Node2d)
└ Body (KinematicBody2D)
├ Camera (Camera2D)
├ Collision (CollisionShape2D)
└ Sprite (Sprite)
This is what gets instanced here:
onready var aPlayer = preload("res://Scenes/Player.tscn").instance()
That sets aPlayer to the root node of the instanced scene, which is a Node2D.
And thus what you pass into _Move (_Move(aPlayer, vInputDirection)) is a Node2D, which do not have move_and_slide, and thus calling move_and_slide on it results in error.
Instead move_and_slide exists in KinematicBody(3D) and KinematicBody2D.
The simple and elegant solution is make the KinematicBoyd2D (Body) the root of the scene. There is a "Make Scene Root" in the contextual menu you get on secondary click on the Scene panel, which will do that.
Alternatively you could also reach into the scene with get_node, for example:
vSelf.get_node("Body").move_and_slide(...)
Or for example:
onready var aPlayer = preload("res://Scenes/Player.tscn").instance().get_node("Body")
Addendum: There is another way if you think about it. You can add a script to the root node (Player) and declare whatever functions you want there, including but not limited to a move_and_slide that delegates to the KinematicBody2D (Body).
I should also point out that move_and_slide will move the KinematicBody2D, but not its parent. So the position property on the Player node will no longer be the position of the KinematicBody2D. And, well, you avoid all the trouble if you make the KinematicBody2D the root.
This is difficult to explain with typing...
I have a GameController scene (Node2D) that holds 3 instanced scenes within:
Mouse (scenes/Mouse.tscn) - this just swaps the mouse cursor for a custom graphic
HeaderBar (scenes/HeaderBar.tscn) - this is the score/label that just sits up top
Messages (scenes/Messages.tscn) - this is the "popup" message box that displays text to the user
In the main scene (Level1.tscn) I instance the GameController scene and it "works" fine. The header bar is there with the score/label and the custom mouse cursor is there and the message box is there (but I have it hidden by default but if I toggle its visibility in the remote it will show up).
Here's where my confusion comes in...
If I attempt, in the GameController script, to manipulate any of those nodes in the GameController scene (the mouse, header, messages) they return as null and will throw an error. For example; if I try to update the score in the $HeaderBar/Control/score I get the following:
Invalid set index 'text' (on base: 'null instance') with value of type 'String'.
The code completion will autofill the node names as I type them (so it recognizes them in the group) but any attempt to reference/use them in script throws similar errors to above.
I'm very new to Godot so I'm sure it's just some misunderstanding I have on how this type of thing works but I'm stumped! Any insight is much appreciated!
UPDATE
I will try to simplify my explanation a bit (I have made some changes). Okay here is the object script:
extends StaticBody2D
onready var main = load("res://scenes/MainGame.gd").new()
func _ready():
pass
# mouse [left] clicked on object
func _on_trigger_input_event(viewport, event, shape_idx):
if Input.is_action_just_pressed("left-click"):
main.display_message("you [left] clicked on the object!")
the call to main.display_message() works. It does call the function but here is that function in the MainGame.gd
extends Node2D
onready var box = $Message/Control
onready var label = $Message/Control/Label
func _ready():
# hide the mouse cursor (will use custom cursor)
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
func display_message(msg):
label.text = msg
box.visible = true
It errors out because label (and box) are null. If I call display_message from the _ready function (in the MainGame.gd) it works as it should. Call it from outside (in the Object.gd) and the nodes are null for some reason.
This instances the scene as expected:
onready var main = load("res://scenes/MainGame.gd").new()
However, this code will run when the instanced scene is added to the scene tree:
onready var box = $Message/Control
onready var label = $Message/Control/Label
func _ready():
# hide the mouse cursor (will use custom cursor)
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
Yes, both onready variables and _ready will run when this is added to the scene tree.
And when do you add main to the scene tree? I don't see where.
Then when you do this:
main.display_message("you [left] clicked on the object!")
Both box and label will still be null (which is the default value).
Thus, add it to the scene tree, for example:
func _ready():
add_child(main)
Except that does not work either, does it? Look at the code again:
onready var main = load("res://scenes/MainGame.gd").new()
You are instancing a script. A script. Not a scene. It will not have its children nodes and so on. You want to instance a scene, for example:
onready var main = load("res://scenes/MainGame.tscn").instance()
However, I find it odd that you are doing this in a StaticBody2D. I don't think it makes sense that MainGame belongs to a StaticBody2D.
I suspect, you are instancing MainGame in multiple places expecting it be the same instance. But it is not the same instance, it is a new instance.
Here is where I suggest you make MainGame into a autoload, so there is a single instance that you can access from anywhere. However, perhaps that is not right either. Perhaps you should use a Signal Bus. See Why does this variable keep returning to its original value? which had a similar problem (they where trying to open a door, you are trying to show a message, but still).
I'm attempting to set a texture on a quad that I generate with the surface tool. On the mesh instance I set a material override of spatial material. I then set the texture to a png file in the albedo section. The texture I am using is a 16 x 16 png with an alpha channel. The top half of the texture is red and bottom half is blue. When I run the code I see a quad that is one solid color and that is purple. I'm new to godot and am completely lost to what is wrong. Any help is appreciated.
extends MeshInstance
func _ready():
var surfTool = SurfaceTool.new()
var mesh = Mesh.new()
var vert_array = Array()
var uv_array = Array()
var st = SurfaceTool.new()
vert_array.push_back(Vector3(0,0,0))
vert_array.push_back(Vector3(0,1,0))
vert_array.push_back(Vector3(1,1,0))
vert_array.push_back(Vector3(0,0,0))
vert_array.push_back(Vector3(1,1,0))
vert_array.push_back(Vector3(1,0,0))
uv_array.push_back(Vector2(0,0))
uv_array.push_back(Vector2(0,1))
uv_array.push_back(Vector2(1,1))
uv_array.push_back(Vector2(0,0))
uv_array.push_back(Vector2(1,1))
uv_array.push_back(Vector2(1,0))
st.begin(Mesh.PRIMITIVE_TRIANGLES)
for i in range(6):
st.add_vertex(vert_array[i])
st.add_uv(uv_array[i])
st.commit(mesh)
self.set_mesh(mesh)
You can do it remotely in the inspection panel.
go to geometry - material override - and make a new spatial material.