Proper yield() usage with for and tween: how to keep the execution in a sequence mode

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

Hello there,

I built a program that is supposed to manage a battle, wait for one of the players to get its turn to play, and then should execute a tween (movement and animation) for this player before resuming the “who’s turn is it” calculation.

Basically it looks like this :
BattleManager has a child that is an instance of player. For a battle I instantiate more than one player, and keep track of them in a dictionary

After setting up the battle in my battleManager, I have got a bunch of code like this :

func _process(delta):
	yield(get_tree().create_timer(0.3),"timeout")
	call_deferred("turn_preparation")

func turn_preparation():
	set_process(false)
        var counter = 0
	while counter !=1:
		for i in range(battle_teams.size()):
			battle_teams[i].Init=battle_teams[i].Init+battle_teams[i].Speed
			if battle_teams[i].Init >= 100:
				print("A player is ready : ",battle_teams[i].Name)
				battle_teams[i].Init=0
				call_deferred("player_turn",i)
				yield(battle_teams[i].Battler,"finished_moving")
				counter =1
				break

    func player_turn(player):
	print("Current Player: ",player)
yield(battle_teams[player].Battler.move_battler($FightingPosition.position,"chosen_attack"),"completed")
	print("Battler is done moving ?")
	call_deferred("set_process",true)

So what I expect it to do is to go through my dictionary of players, increment their “initiative” and if someone reachs a limit, that’s is turn to move in the player_turn().
I am expecting the thing to go back to their natural states when the player has finished his turn and set the process back to true.

I also tried something like this :

battle_teams[player].Battler.call_deferred("move_battler",$FightingPosition.position,"chosen_attack")
	yield(battle_teams[player].Battler,"finished_moving")

with little success.

On the Battler side, I have a move_battler method that looks like this :

func move_battler(new_position,new_animation):
	var initial_position = Vector2()
	initial_position = self.rect_position
	$Character/Hp_bar.hide()
	print("Origin / Target: ", initial_position," / ", new_position)
	$Tween.interpolate_property(self,"rect_position", initial_position,new_position,0.5, Tween.TRANS_LINEAR, Tween.EASE_IN)
	$Tween.call_deferred("start")
	yield($Tween, "tween_completed")
	$Character/Skin/Battler.animation= "Blue"
	#some random animation
	$Tween.interpolate_property(self,"rect_position",new_position,initial_position,0.5, Tween.TRANS_LINEAR, Tween.EASE_OUT,2)
$Tween.call_deferred("start")
	yield($Tween, "tween_completed")
	$Character/Hp_bar.show()
	emit_signal("finished_moving")

So what it is supposed to do is to get the target position, hide some UI around the deisgnated player, have him to move, execute and animation, move back to his initial position and emit the signal that would call the end of his turn. Pretty basic, and probably badly written.

But here is the things, despites all the yields and call_deferred, all the players are moving while the tween is not finished for the first player. All in all, the players do not even have the time to go back to their initial position and end up barely moving around the target (temporary position) instead of moving one by one.

I tried many things (and my current code is not correct, I am still trying…) like pausing everything except the battler, or yield(Battler) for the “finished_moving” signal instead of “completed”, yield the $Tween for tween_completed or tween_all_completed etc.

Yield on tweens does not work for me (and it can wait for a signal that is never emitted and that I do not know how to force…) and I feel that my code is not executed as a sequence as my output looks like this :

  A player is ready : Player 1
Current player : 0
Origine / Target : (x,y) / (x',y')
A player is ready : Player 3
Current player : 2
Origine / Target : (x,y) / (x',y')
A player is ready : Player 4
Current player : 3
Origine / Target : (x,y) / (x',y')
A player is ready : Player 2
Current player : 1
Origine / Target : (x,y) / (x',y')
(...)

and then 
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?
Battler is done moving ?

I tried to make my problem as clear as possible. Do not hesitate to mention all the things I have done wrong (I am clearly a beginner) or ask for more detailed information.

I am a little bit stuck here so any help would be greatly appreciated :slight_smile:

Best regards !

Bk

Are you using the same $Tween for all animations on all players? I had a similar problem when one action was resetting the others since they were all connected to the same $Tween’s… Not sure if related, but looking at your code I would try to use different tweens for different actions so they can run simultaneously (even if its just one frame)

Your yield() use seems correct, tho, complicated. Yield just expects “what” and “signal”, the signal maybe be a built-in or created one, and since its a string, double-check or if possible use the auto-complete feature to avoid errors. Also, if it is indeed a created signal make sure it has the proper connection onready, passes the value or use a set_get.

If you dont need script communication for that I’d suggest a boolean toggle instead of a signal. Hope it helps.

Surtarso | 2021-03-27 18:19

Hi,

First of all, thank you for you reply and taking time to help me.

As you mentioned, my code was a little bit too complicated. I decided to cut it down to more elementary functions, even the tween part and did what you suggested : Adding one tween to the tree and splitting the effects, one waiting for the “end signal” of the other to be launched.

It turned out that the problem remained and seemed to be in fact linked to the _process still running while turned false… maybe the _process in the instantiated object was putting some mess into this. I tried to print the _process state all the time and even though it displayed “false” I had many instances of my fighters acting in the same time…

So I did the following : went out of the _process function (Out of despair I also tried the _physical_process with the same results than _process) and made a separated function I would call whenever I was turning _process to true instead.

And it kinda worked… the down side is that some characteristics where updating super nicely in the _process (like initiatives bar progress, very smooth and all) and are now updating roughly (when one turn triggers).

Anyway, at this point, the fighters are taking initiatives, one at a time, and all the tweens are completed before it is finished.

I can copy paste my code if you anyone is interested :slight_smile:

Thanks again @Surtarso for your help !

BakouKai | 2021-03-28 09:48

are you by any chance trying to collide physics while tweening? coz that wont work.

physics requires a “push” and will deliver consequences… tweening is fixed and if you try to push that body it will stick to the tween till its finished. makes sense?

maybe you can try to lerp() some of those tweens, even if just to debug

tween.interpolate_property(where,what,from,to,time,how,how)
change to
lerp(from,to,time)

time will be significantly smaller on lerp than on tween due to processes handling so adjust the number

also, even tho its pretty obvious, its easily missed… make sure your tween’s yield time are smaller than the other functions that depend on it… so it wont try to mess with the tween before it ends.

I like to use a variable to set my timer’s timer (lets say var t = timer.wait_time) and use that on my tween like tween.interpolate(bla,bla,bla,bla, t + 0.1 , blablabla) to be sure it will never overlap, hope it helps!

also very important, you cant change the size of a collision shape during runtime… it looks like it works if you set it to visible and you can see it growing etc… but in memory it is always the same size you added it… so you need multiple sizes overlaping to get a growing collision shape effect

on this line:

$Tween.interpolate_property(self,"rect_position",new_position,initial_position,0.5, Tween.TRANS_LINEAR, Tween.EASE_OUT,2)

what does that final 2 stand for? its start function also has an indent problem.
are you starting with call_deffered coz you queue mobs out? have you tried with a simple $Tween.start()?

Surtarso | 2021-03-28 10:45

“the down side is that some characteristics where updating super nicely in the _process (like initiatives bar progress, very smooth and all) and are now updating roughly (when one turn triggers).”

you dont need to interpolate in process with a tween, you do need with lerp() tho

for progress bars etc tweens are fine and great. for movement it can screw you up

I use my tweens after signals, this is my health bar, for instance, not connected to any progress function

onready var _health = PlayerData.connect("health_updated",self,"update_health")

func update_health(value):  ## THIS IS MY UI.GD
	var percent_hp = int((float(value) / PlayerData.MAX_HEALTH) * 100)
	$HealthBar/Tween.interpolate_property(
		$HealthBar, "value", #what
		$HealthBar.value, percent_hp, #from-to
		0.1, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) #duration-how
	$HealthBar/Tween.start()
	$HealthLabel.text = "Health: %s%%" % str(percent_hp)

value comes from a set_get that my player updates when hit with:

func _got_hit(damage):  ## THIS IS PLAYER.GD
			##-- GETS HIT:
			PlayerData.health -= clamp(damage,0,PlayerData.health)
			$AnimationPlayer.play("hit")
			$AnimationPlayer.queue("default")
			##-- DEATH CALL:
			if PlayerData.health == 0:
				_die(true)

with this set_get

var health: = MAX_HEALTH setget set_health
signal health_updated

func set_health(value:float):  ## THIS IS PLAYERDATA.GD AUTOLOAD
	health = value
	emit_signal("health_updated",value)

this is a tween I have connected to process on a boss character I have with limited movement so no problems:

func _aim_at_player():   ## THIS IS BOSS.GD
	if Globals.player != null:
		var aim_dir = (Globals.player.position - self.position).angle() - 1.57
		var current = self.rotation
		$AimPlayerTween.interpolate_property(
			self,"rotation",
			current,aim_dir,
			1.5,Tween.TRANS_LINEAR,Tween.EASE_OUT)
		$AimPlayerTween.start()
		yield($AimPlayerTween,"tween_completed")
		yield($AnimationPlayer,"animation_finished")

the animation finished is so the interpolate doesn’t start in the middle of a shot that uses animation player

now, for my regular enemies, who move a lot, I cant tween their movement or bumping into them has no effect, so for regular mobs to aim I switch to lerp (also on process)

func _aim_at_player():  ## THIS IS REGULAR ENEMY.GD
	if Globals.player != null:
		var aim_dir = (Globals.player.position - self.position).angle() - 1.57
		var current = self.rotation
		self.rotation = lerp_angle(current,aim_dir,0.01)

Surtarso | 2021-03-28 11:15

No collisions on this part. From my understanding, my problems comes from _process(delta) running as much threads as possible to go faster and launching multiples “characters_turns” almost at the same time (while _process() was supposed to be set to false after one player starts its turn).

I found abour lerp() and that was definitly an option if I could not fix it. My new methods, without any use of _process() seems to be satisfying with Tweens so I may keep them I think…

BakouKai | 2021-03-28 12:58

That is a very good point. I will definitely try to use tweens for updating health bar, thanks for pointing that out! Should solve my only remaining problem.

BakouKai | 2021-03-28 12:59