Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need some clarity on signals #199

Open
shayneoneill opened this issue Jun 15, 2020 · 8 comments
Open

Need some clarity on signals #199

shayneoneill opened this issue Jun 15, 2020 · 8 comments

Comments

@shayneoneill
Copy link

shayneoneill commented Jun 15, 2020

Hi, I'm trying to work out how the signals work in this, I cant really find it documented.

I'm trying to create a listener that sits on a root node , that can accept "towns" and "ids"

so something like register_town.= signal(id,town)

That of course doesn't work (For a start its unpythonic, whats an id , whats a town), but I cant really see any other way to specify those paramaters.

Secondly, even if I do just register_town = signal(), I never see that signal turn up in the inspector. Should it?

@adracea
Copy link

adracea commented Jun 15, 2020

@shayneoneill hey , I've been toying around with using this project as well . What I've noticed :

  1. You declare signals indeed like register_town = signal() . In order for them to pop up you have to restart the godot editor/reload project .
  2. Currently , the only way I've found of "emitting signals" is using self.call("emit_signal","register_town")

I'm currently still struggling with finding a way to wait for signals or finding any documentation on doing it .( using the yield method that godot has)

@shayneoneill
Copy link
Author

Ah ok. Thats troublesome. I'm now kind of wondering if I should perhaps just roll my own signal method. Though the usual call-back instant dispatch wouldn't work as its crossing too many barriers. Kinda feels like fighting against the system, which is never a good thing in programming.

@cr8ivecodesmith
Copy link

cr8ivecodesmith commented Jul 20, 2020

I'm currently still struggling with finding a way to wait for signals or finding any documentation on doing it .( using the yield method that godot has)

Were you able to figure out a way to do this? I'm currently trying out the "Your first game" tutorial and that uses the yield call of GDScript. I had to settle with boolean flags for now. What's a good workaround for this in the mean time?

@ghost
Copy link

ghost commented Sep 5, 2020

This is literally the only documentation I can find on how one does signals in godot-python, but, how does one do signals that pass parameters? Do you just add arguments to the end of the self.call("emit_signal", "signal_name", ... )?

@omardelarosa
Copy link

This thread is unfortunately the only docs I have found. However, through some experimentation and looking at the source code for more information on how Signals are implemented, they definitely support argument passing and here's how it seems to work (at least as of my current godot version of 3.2.4beta):

# something.py

from godot import *

class Something(Node)
    # this variable name must match the string below
    player_move_success = signal()


   def player_has_moved_to(self, vec3_from_position, vec3_to_position):
        # After ensuring that both vec3 args are instances of Vector3() class...
        self.call("emit_signal", "player_move_success", vec3_from_position, vec3_to_position)

Then from the GDScript side:

extends Node

# Assuming there is a Something node as a child in the scene with the Python something.py attached to it.
onready var something_node = $Something

func _ready():
     # Observe the Python-based Something node's "player_move_success" signal
     something_node.connect("player_move_success", self, "_on_player_move_success")

func _on_player_move_success(from_position: Vector3, to_position: Vector3):
     print("player has successfully moved from ", from_position, " to ", to_position)

That being said keep in mind that the function signature's arity (i.e. the number of arguments) must match between the Python call and the observer. For example, these will not work and silently fail:

# Sending a mismatched function with only 1 argument
self.call("emit_signal", "player_move_success", vec3_from_position)

# Sending a mismatched function with no arguments
self.call("emit_signal", "player_move_success")

@MarioMey
Copy link

MarioMey commented Jul 8, 2022

Anyone: this is still the best way to "emit signals"? Is this the best way to comunicate between code files? I used to use bge.logic.sendMessage(subject, body) a lot to comunicate between objects in Blender Game Engine. I think signals are the alternative in Godot, aren't they? Is there something better?

@adracea Did you find the way to wait for signals?

@cr8ivecodesmith Could you share the "Your first game" converted to Python? I'm doing the same right now, it is almost complete.

@shayneoneill Could you share your "own signal method"?

@cridenour Thanks for bring me here.

@MarioMey
Copy link

MarioMey commented Jul 11, 2022

Well, after learning a bit about yield, I think I found an alternative in Python. Also with yield, but a bit more complicated. This is HUD.py, the GDScritp to Python converted code of "Your first game" (I only add comments where it is relevant to this thread):

from godot import exposed, export, signal, CanvasLayer
import time

@exposed
class HUD(CanvasLayer):
	start_game = signal()

	def _ready(self):
		self.connect("start_game", self.get_node("/root/Main"), "new_game")
		self.get_node("StartButton").connect("pressed", self, "_on_StartButton_pressed")
		self.get_node("MessageTimer").connect("timeout", self, "_on_MessageTimer_timeout")

	def show_message(self, text):
		self.get_node("Message").text = text
		self.get_node("Message").show()
		self.get_node("MessageTimer").start()

	# New function name
	def show_game_over_yield(self):
		self.show_message("Game Over")
                # Needed for the generator creation
		while True:
			# Wait for 'MessageTimer_timeout' message
			if (yield) == 'MessageTimer_timeout':
				self.get_node("Message").text = "Dodge the\nCreeps!"
				self.get_node("Message").show()
				time.sleep(1)
				self.get_node("StartButton").show()
				# We need to delete this generator now
				del self.sgo

        # This is the main function that is executed by Main.py
	def show_game_over(self):
                # Create the yield generator and run it till the yield line.
		self.sgo = self.show_game_over_yield()
		next(self.sgo)
		
	def update_score(self, score):
		self.get_node("ScoreLabel").text = str(score)

	def _on_StartButton_pressed(self):
		self.get_node("StartButton").hide()
		self.call("emit_signal", "start_game")

	def _on_MessageTimer_timeout(self):
		self.get_node("Message").hide()
                # Check if the generator is created and send message to it
		if hasattr(self, 'sgo'):
			self.sgo.send('MessageTimer_timeout')

@MarioMey
Copy link

MarioMey commented Jul 11, 2022

Here are the other codes. I added lines to connect signals by code, instead of doing at hand. I always tried to do it in this way, when working with Blender Game Engine.

Main.py
from godot import exposed, export, signal, Vector2, Node, PackedScene, ResourceLoader
import random, math

@exposed
class Main(Node):

	mob_scene = ResourceLoader.load("res://Mob.tscn")
	score = int()

	def _ready(self):
		self.get_node("MobTimer").connect("timeout", self, "_on_MobTimer_timeout")
		self.get_node("ScoreTimer").connect("timeout", self, "_on_ScoreTimer_timeout")
		self.get_node("StartTimer").connect("timeout", self, "_on_StartTimer_timeout")
		pass
		
	def game_over(self):
		self.get_node("ScoreTimer").stop()
		self.get_node("MobTimer").stop()
		self.get_node("HUD").show_game_over()
		self.get_node("Music").stop()
		self.get_node("DeathSound").play()

	def new_game(self, mensaje=None):
		print(mensaje)
		self.score = 0
		self.get_node("Player").start(self.get_node("StartPosition").position)
		self.get_node("StartTimer").start()
		self.get_node("HUD").update_score(self.score)
		self.get_node("HUD").show_message("Get Ready")
		self.get_tree().call_group("mobs", "queue_free")
		self.get_node("Music").play()

	def _on_ScoreTimer_timeout(self):
		self.score += 1
		self.get_node("HUD").update_score(self.score)

	def _on_StartTimer_timeout(self):
		self.get_node("MobTimer").start()
		self.get_node("ScoreTimer").start()

	def _on_MobTimer_timeout(self):
		mob = self.mob_scene.instance()

		mob_spawn_location = self.get_node("MobPath/MobSpawnLocation")
		mob_spawn_location.offset = random.randrange(pow(2,32))

		direction = mob_spawn_location.rotation + math.pi / 2

		mob.position = mob_spawn_location.position

		direction += random.uniform(-math.pi / 4, math.pi / 4)
		mob.rotation = direction

		velocity = Vector2(random.uniform(150.0, 250.0), 0.0)
		mob.linear_velocity = velocity.rotated(direction)

		self.add_child(mob)
Player.py
from godot import exposed, export, signal, Vector2, Area2D, InputEventKey
from godot import KEY_RIGHT, KEY_LEFT, KEY_UP, KEY_DOWN

@exposed
class Player(Area2D):

	hit = signal()

	speed = export(int, default=400)
	screen_size = int()
	velocity = Vector2.ZERO
	velocity_real = Vector2.ZERO
	
	def _ready(self):
		self.connect("hit", self.get_node("/root/Main"), "game_over")
		self.connect("body_entered", self, "_on_Player_body_entered")
		screen_size = self.get_viewport_rect().size
		self.hide()

	def _unhandled_input(self, event):
		if type(event) is InputEventKey:
			if event.pressed and event.scancode == KEY_RIGHT and not event.echo:
				self.velocity.x += 1
			if event.pressed and event.scancode == KEY_LEFT and not event.echo:
				self.velocity.x -= 1
			if not event.pressed and event.scancode in [KEY_RIGHT, KEY_LEFT]:
				self.velocity.x = 0
			
			if event.pressed and event.scancode == KEY_DOWN and not event.echo:
				self.velocity.y += 1
			if event.pressed and event.scancode == KEY_UP and not event.echo:
				self.velocity.y -= 1
			if not event.pressed and event.scancode in [KEY_UP, KEY_DOWN]:
				self.velocity.y = 0
			
			if self.velocity.length() > 0:
				self.velocity_real = self.velocity.normalized() * self.speed
				self.get_node("AnimatedSprite").play()
			else:
				self.velocity_real = self.velocity
				self.get_node("AnimatedSprite").stop()
		
	def _process(self, delta):

		self.position += self.velocity_real * delta
		self.screen_size = self.get_viewport_rect().size
		self.position.x = max(0, min(self.position.x, self.screen_size.x))
		self.position.y = max(0, min(self.position.y, self.screen_size.y))

		if self.velocity.x != 0:
			self.get_node("AnimatedSprite").animation = "walk"
			self.get_node("AnimatedSprite").flip_v = False
			self.get_node("AnimatedSprite").flip_h = self.velocity.x < 0
		
		elif self.velocity.y != 0:
			self.get_node("AnimatedSprite").animation = "up"
			self.get_node("AnimatedSprite").flip_v = self.velocity.y > 0

	def _on_Player_body_entered(self, _body):
		self.hide()
		self.call("emit_signal", "hit")
		self.get_node("CollisionShape2D").set_deferred("disabled", True)
		
	def start(self, pos):
		self.position = pos
		self.show()
		self.get_node("CollisionShape2D").disabled = False
Mob.py
from godot import exposed, RigidBody2D
import random

@exposed
class Mob(RigidBody2D):

	def _ready(self):
		self.get_node("VisibilityNotifier2D").connect("screen_exited", self, "_on_VisibilityNotifier2D_screen_exited")
		self.get_node("AnimatedSprite").playing = True
		mob_types = self.get_node("AnimatedSprite").frames.get_animation_names()
		anim = mob_types[random.randint(0, len(mob_types) - 1)]
		self.get_node("AnimatedSprite").animation = anim

	def _on_VisibilityNotifier2D_screen_exited(self):
		self.queue_free()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants