I have an issue instantiating objects.
On the one hand, I have the following script for one projectile in one scene:
extends KinematicBody2D
var speed = 200
var life_time = 2
var life_spawn = 0
func _physics_process(delta):
var collision = move_and_collide(Vector2.UP * delta * speed)
life_spawn += delta
if life_spawn > life_time:
queue_free()
pass
and on the other hand I have the player in other scene with the following script:
extends KinematicBody2D
func _physics_process(delta):
if Input.is_action_pressed("ui_accept"):
createLaser()
pass
func createLaser():
var laser = preload("res://scenes/space_ship/laser/Laser.tscn")
var left_fired_laser = laser.instance()
var right_fired_laser = laser.instance()
var left_cannon = get_node("Cannons/left_cannon").get_global_position()
var right_cannon = get_node("Cannons/right_cannon").get_global_position()
left_fired_laser.position = Vector2(left_cannon.x, left_cannon.y)
get_parent().call_deferred("add_child", left_fired_laser)
right_fired_laser.position = Vector2(right_cannon.x, right_cannon.y)
get_parent().call_deferred("add_child", right_fired_laser)
pass
The problem is that the object is instantiated a lot of times. Even if I put a yield() function. If I put that function the object waits to be instantiated but it is instantiated a lot of times anyway.
How many and how often do you want to instance?
You say:
The problem is that the object is instantiated a lot of times.
But how many times is a lot?
I'll answer the cases that come to mind. Also, some of these can be combined.
If this answer does not cover what you want… You will need to be more specific. Baring that, hopefully you can figure it out considering these tools:
Picking between is_action_pressed vs is_action_just_pressed.
The use of Timers (the actual object or makeshift with delta).
Signals (including but not limited to the timeout signals of actual Timers).
Speaking of signals, all the approaches below require no special knowledge of the instance (except that it is some Node2D). You might add custom signals to the instances and connect to them to know when you can create more instances.
Another thing you can do is hold references to the instances to interrogate them (you may for example use is_instance_valid). However, I present below an approach that uses a tree_exited signal, which I consider more general.
You could also take advantage of AnimationPlayer with a call method track. For example if you need to add the instances on a particular frame of the animation.
Once per input
This code instances every physics frame as long as the input is pressed:
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept"):
createLaser()
If you only want to instance the first physic frame when the input was pressed, use is_action_just_pressed:
func _physics_process(delta:float) -> void:
if Input.is_action_just_pressed("ui_accept"):
createLaser()
Once every so often
We can use the same makeshift timer strategy you use for bullet life time:
var instance_period:float = 10.0
var instance_elapsed:float = 0.0
func _physics_process(delta:float) -> void:
instance_elapsed += delta
if Input.is_action_pressed("ui_accept") and instance_elapsed > instance_period:
createLaser()
instance_elapsed = 0.0
Only one time ever
If you only want one, we can hold a boolean variable to know if we already instanced:
var did_instance:bool = false
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and not did_instance:
createLaser()
did_instance = true
Only a fixed number of times ever
You can use an integer countdown:
export var yet_to_instance:int = 10
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
createLaser()
yet_to_instance -= 1
I made it an export variable so you can edit it from the Inspector panel.
*This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Also you can consider "Only one time ever" an special case when the number is 1.
Only a recharging number of times
This is a particular way to combines the makeshift timer with the idea of a fixed number of times to instance:
var recharge_period:float = 10.0
var recharge_elapsed:float = 0.0
export var max_to_instance:int = 10
onready var yet_to_instance:int = max_to_instance
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
createLaser()
yet_to_instance -= 1
# recharge_elapsed = 0.0
recharge_elapsed += delta
if recharge_elapsed > recharge_period:
if yet_to_instance < max_to_instance:
yet_to_instance += 1
recharge_elapsed = 0.0
This way the number of instances you can create increases overtime up to a maximum. If you can un-comment # instance_elapsed = 0.0 if you want to prevent that number to increase when there is input. You can think of it as autoreloading.
This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.
At most an amount alive
We are going to connect to the tree_exited signal to update our count:
const laser = preload("res://scenes/space_ship/laser/Laser.tscn")
export var max_to_instance:int = 20
func _physics_process(delta:float) -> void:
if Input.is_action_pressed("ui_accept") and max_to_instance > 0:
createLaser()
func createLaser() -> void:
createLaserFrom($Cannons/left_cannon)
createLaserFrom($Cannons/right_cannon)
func createLaserFrom(cannon:Node2D) -> void:
var fired_laser = laser.instance()
max_to_instance -= 1
fire_laser.connect("tree_exited", self, "laser_exited")
get_parent().add_child(fired_laser)
fired_laser.global_position = cannon.global_position
func laser_exited() -> void:
max_to_instance += 1
This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.
Related
I made a kinematic body2d for my boss battle in godot and made it so that when the bullet which is an area 2d has a body entered it checks for the name then does something. This is my code:
This is my boss code:
extends KinematicBody2D
const ATTACK_THRESHOLD = 1
const ATTACKS = ["movingL", "movingR", "plus", "cross", "horizon"]
var _idle_count = 0
var _attack_set = ATTACKS
signal startin
var health = 1
onready var _anim_tree = $boostree
func increase_idle_count():
_idle_count += 1
if health == 0:
death()
if _idle_count > ATTACK_THRESHOLD:
_idle_count = 0
attack()
func _on_bosstrigger_start():
_anim_tree.set_condition("starting", true)
emit_signal("startin")
$boossoo.play()
func _ready():
randomize()
func attack():
var attack = _attack_set[randi()%_attack_set.size()]
_anim_tree.set_condition(attack, true)
func death():
_anim_tree.set_condition("die", true)
func _on_area2DE_area_entered(area):
if "bullet2" in area.name:
health -= 1
I used "area2DE" which was an area2d that was a child of the boss.
the "increase_idle_count()" function was put at the end of my idle animation.
.
I would think that the boss would do the death animation after getting shot but absolutely nothing happens. all the other animations work normally and will happen except for the death animation. I had spent the entire day trying different solutions and nothing had worked. Please help.
I have a simple script like this:
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
func set_example_value(val):
print("Setting example_value=",val)
#time/memory consuming code segment here
example_value=val
and I set example_value to 3 and then exit the game engine
Now when I startup godot again the set_example_value() gets invoked to set the value,
Is there any way where the example_value will get set to 3 without the setter function being invoked?
why am I doing this?
because I have a time/memory consuming function which generates sprites when the value is changed,
so when I startup godot I don't want to recreate those sprites I only want the value to be changed to what it was before closing godot
Please read my answer for Play animation without invoking setget? first.
The difference is that we will tell Godot to only store one of them. We can do that with _get_property_list. Thus, we are not going to use export with it.
For example we can do this:
var example_value := 0
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
}
]
And the editor will show the variable because it has PROPERTY_USAGE_EDITOR, but it won't be stored because it does not have PROPERTY_USAGE_STORAGE.
If it is not stored, then when loading Godot won't find it, and won't set it (note that it might be already stored before you told Godot to not store it… Saving the resource again will fix it, or use an external editor).
Now the problem is that you are not saving the value at all. So we are going to have a two properties. One is only for the editor, and one is only for storage. And the storage one will not do the expensive process. Like this:
tool
extends Node
var example_value := 0 setget set_example_value
func set_example_value(mod_value:int) -> void:
print("HELLO")
example_value = mod_value
var example_value_storage:int setget set_example_value_storage, get_example_value_storage
func get_example_value_storage() -> int:
return example_value
func set_example_value_storage(mod_value:int) -> void:
example_value = mod_value
func _get_property_list() -> Array:
return [
{
name = "example_value",
type = TYPE_INT,
usage = PROPERTY_USAGE_EDITOR
},
{
name = "example_value_storage",
type = TYPE_INT,
usage = PROPERTY_USAGE_STORAGE
}
]
By the way, while we are at it, checking if the value is being modified in the setter might be a good idea:
func set_example_value(mod_value:int) -> void:
if example_value == mod_value:
return
print("HELLO")
example_value = mod_value
Just incase someone wanted a simpler (but hacky) workaround
tool
extends Node2D
export(int) var example_value=0 setget set_example_value
var startup=true
func set_example_value(val):
print("Setting example_value=",val)
if(startup):
example_value=val
return
#time/memory consuming code segment here
example_value=val
func _process(delta):
startup=false
set_process(false)
func _init():
set_process(true)
what is happening is that my "enemy" is not following the "player", when my player enters the detection area the "enemy" continues straight ahead (to the left).
thank you, if you understand something or need more information let me know
detection zone
enemy code
enemy code:
const EnemyDeathEffect = preload("res://Bots/EnemyDeathEffect.tscn")
export var MAX_SPEED = 60
export var ACCELERATION= 25
export var FRICTION = 700
enum {
IDLE,
WANDER,
CHASE
}
var velocity = Vector2.ZERO
var knockback = Vector2.ZERO
var state = CHASE
var path: PoolVector2Array
onready var sprite = $AnimatedSprite
onready var stats = $Stats
onready var playerDetectionZone = $PlayerDetectionZone
func _physics_process(delta):
knockback = knockback.move_toward(Vector2.ZERO, FRICTION * delta)
knockback = move_and_slide(knockback)
match state:
IDLE:
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
seek_player()
WANDER:
pass
CHASE:
var player = playerDetectionZone.player
if player != null:
var direction = (player.position - global_position).normalized()
velocity = velocity.move_toward(direction * MAX_SPEED , ACCELERATION * delta)
print(direction)
else:
state = IDLE
sprite.flip_h = velocity.x > 0
velocity = move_and_slide(velocity)
func seek_player():
if playerDetectionZone.can_see_player():
state = CHASE
func _on_Hurtbox_area_entered(area):
stats.health -= area.damage
knockback = area.knockback_vector * 100
func _on_Stats_no_health():
queue_free()
var enemyDeathEffect = EnemyDeathEffect.instance()
get_parent().add_child(enemyDeathEffect)
enemyDeathEffect.global_position = global_position
I only possible culprit I see is this line:
var direction = (player.position - global_position).normalized()
Here player.position is in its parent local coordinates, while global_position as the name says is in global coordinates. You want this instead:
var direction = (player.global_position - global_position).normalized()
I see this is the way you have on the linked image.
Or if you prefer:
var direction = global_position.direction_to(player.global_position)
Aside from that, it could be that it is changing direction too slowly. Which - given the code - is the same as saying that the ACCELERATION is low, but that is for you to tweak.
I guess it is worth debugging that playerDetectionZone is getting the player, and the CHASE is working correctly.
Common approaches include using a breakpoint, or print. You already have a print for direction. Try also printing player to check the enemy is chasing what you expect it to chase.
For this particular case I also suggest to go to the run the project from the editor, and then go to the Scene panel on the Remote tab, and select the enemy. That would allow you to the properties of the enemy on real time on the Inspector. You should see the state change to CHASE (which would have the value 2), and velocity should also steadily change.
Enabling "Visible Collision Shapes" form the debug menu may also help debugging.
If it is not working, double check the playerDetectionZone has monitoring enabled. Also check that the collision_mask of the playerDetectionZone and the collision_layer of the player are correct.
I have a "parent" player scene, and I inherit scenes for each player. The parent player scene has a camera. When the game switches between players, one player turns off its camera, and the other player turns its camera on:
if state != State.ACTIVE:
# If this player is becoming active, also
# set camera current
state = State.ACTIVE
camera.current = true
else:
# If player is not becoming active,
# disable this players camera
camera.current = false
But players can be in different positions, so the camera "jumps" from one to the other. Can we do something more sophisticated, like set the new camera to the current position so the smooth setting can be used to handle the transition?
One idea is to do get_viewport().get_camera() to find the current position of the camera to try and sync the position of the current camera with the new camera that is about to turn on, but appears to not work for 2D scenes. CF: https://github.com/godotengine/godot/pull/38317
Sadly, as you found out, there is no way to get the current Camera2D in Godot 3.x. And you found the pull request that adds the feature to Godot 4.0.
What I'm going to suggest is to have one sole Camera2D, so that one is always the current one. And you can define Position2D inside your scenes that can serve as interpolation targets to move the Camera2D.
I have an script that I think will be useful for you (I made it to be RemoteTransform2D but backwards, it does push a transform, it pulls it), I call it anchor_transform_2d.gd:
tool
class_name AnchorTransform2D
extends Node2D
export var anchor_path:NodePath setget set_anchor_path
export var reference_path:NodePath setget set_reference_path
export var set_local_transform:bool
export(int, FLAGS, "x", "y") var translation_mode:int
export(int, FLAGS, "x", "y") var scale_mode:int
export var rotation_mode:bool
var _anchor:Node2D
var _reference:Node2D
func _physics_process(_delta: float) -> void:
if not is_instance_valid(_anchor) or Engine.editor_hint:
set_physics_process(false)
return
#INPUT
var input := _anchor.global_transform
if is_instance_valid(_reference):
input = _reference.global_transform.affine_inverse() * input
#TRANSLATION
var origin := Vector2 (
input.origin.x if translation_mode & 1 else 0.0,
input.origin.y if translation_mode & 2 else 0.0
)
#ROTATION
var angle := 0.0
if rotation_mode:
angle = input.get_rotation()
#SCALE
var source_scale = input.get_scale()
var scaling := Vector2 (
source_scale.x if scale_mode & 16 else 1.0,
source_scale.y if scale_mode & 32 else 1.0
)
#RESULT
_set_target_transform(
Transform2D(angle, origin) * Transform2D.IDENTITY.scaled(scaling)
)
func set_anchor_path(new_value:NodePath) -> void:
anchor_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_anchor = get_node_or_null(anchor_path) as Node2D
set_physics_process(is_instance_valid(_anchor) and not Engine.editor_hint)
if Engine.editor_hint:
update_configuration_warning()
func set_reference_path(new_value:NodePath) -> void:
reference_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_reference = get_node_or_null(reference_path) as Node2D
func _set_target_transform(new_value:Transform2D) -> void:
if set_local_transform:
transform = new_value
return
global_transform = new_value
func _get_configuration_warning() -> String:
if _anchor == null:
return "Anchor not found"
return ""
Add this attached to a Node2D in anchor_path set the target from which you want to pull the transform (anchor_path is a NodePath, you can set to it something like $Position2D.get_path()). And set what do you want to copy (you can choose any combination of position x, position y, scaling x, scaling y, and rotation). Then put the Camera2D as a child of the AnchorTransform2D, and set smoothing_enabled to true.
Rundown of the properties:
anchor_path: A NodePath pointing to the Node2D you want to pull the transform from.
reference_path: A NodePath pointing to a Node2D used to make the transform relative (you will be taking the transform of what you put in anchor_path relative to what you put in reference_path).
set_local_transform: Set to true if you want to pull the transform as local (relative to the parent of AnchorTransform2D), leave to false to set the global transform instead.
translation_mode: Specifies if you are going to copy the x position, y position, both or neither.
scale_mode: Specifies if you are going to copy the x scale, y scale, both or neither.
rotation_mode: Specifies if you are going to copy the rotation or not.
The only reason the script is a tool script is to give you a warning in the editor if you forgot to set the anchor_path.
I am currently trying to implement some different actions instead of attack, but I want to focus on a party function, that looks like this, I can figure out the types of actions I want but I want to change the battle function to fit into a role that can accept multiple actions and not just attack
func change_party(inventory,index): # Inventory being if enemy or player
if inventory[index].fainted == false: # Checks if the monster is not dead
var hold = inventory[0] # Holds the first slot and then applys it later to the selected slot
inventory[0] = inventory[index]
inventory[index] = hold
This changes the index that was inputed by buttons and swaps it around with the first slot in the array this is because the first slot is what monster is shown first, I also have this battle function:
func _battle():
participants = [player_inventory[0],enemy_inventory[0]]
participants.sort_custom(self, "check_speed")
for attacker in participants:
var player_move = player_inventory[0].move_slot[action_index]
var random_move = randi() % 3 + 1
var enemy_move = attacker.move_slot[0] # Use random_move currently 0
var target = _choose_target(attacker, participants)
if attacker == player_inventory[0]:
yield(attack(attacker, target, player_move), "completed")
else:
yield(attack(attacker, target, enemy_move), "completed")
if player_inventory[0].current_hp <= 0:
player_inventory[0].current_hp = 0
player_inventory[0].fainted = true
Battle_Ui_Updater()
self.current_state = GameState.LOST
return
if enemy_inventory[0].current_hp <= 0:
enemy_inventory[0].current_hp = 0
enemy_inventory[0].fainted = true
Battle_Ui_Updater()
self.current_state = GameState.WON
return
self.current_state = GameState.ACTION
One solution that came to me was trying to have all the actions in a array and just call the action I want based on input but I have no clue how I would make that look readable or bug-free
In this function is how it decides who´s turn it is based on speed, but sometimes I want the player to go first for example when I want to change party members and when the player has changed I want the enemy to start attacking, But I am scratching my head on how would I make it change actions, I know the attack function should be changed if I want it to do something else but I also want to be able to control who´s turn based on what type of action is used, I am sorry if I didnt explain it well, hope you guys understand, I don't want to repeat my self by making another similar to battle function so how do I avoid being repetitive while also doing what I want?
BattleScript:
func attack(attacker, target, move):
print(str(attacker.name) + " used " + str(move.name) + " " + str((move)))
var new_text = (attacker.name + " attacked with " + move.name)
text_scroller(new_text)
var data = recieve_damage(move,attacker,target)
#var data = target.take_damage(move,attacker) # Calls the function to take damage
yield(get_tree().create_timer(2), "timeout") #Wait for 2 seconds
Battle_Ui_Updater()
if data:
yield(critical_hit(attacker),"completed")
func critical_hit(attacker):
var new_text = (attacker.name + " has landed a critical hit! ")
text_scroller(new_text)
print(attacker.name + " has landed a critical hit!")
yield(get_tree().create_timer(2.5), "timeout")
func Get_effectiveness(attack_type,defence_type):
if attack_type == TypeData.types.none or defence_type == TypeData.types.none:
return 1
print("row : " + str(attack_type))
print("col : " + str(defence_type))
return TypeData.chart[attack_type][defence_type]
func recieve_damage(action,attacker,defender):
var critical = 1
var critical_chance = randi() % 100 + 1
if critical_chance <= 6.25:
critical = 2
var attack_mode
var defence_mode
if action.is_special == true:
attack_mode = attacker.special_attack
defence_mode = defender.special_defence
else:
attack_mode = attacker.attack
defence_mode = defender.defence
var type : float = Get_effectiveness(action.type,defender.type_1) * Get_effectiveness(action.type,defender.type_2)
var modifier : float = rand_range(0.85,1.0) * type * critical
var a : float = (2.0 * attacker.level / 5.0 + 2.0)
var b : float = (a * attack_mode * action.power / defence_mode) / 50.0
var c : float = (b + 2.0) * modifier
var damage = int(c)
defender.current_hp -= damage
print(str(attacker.name) + " = " + str(damage))
return critical > 1
Swap Party member with the current one:
func change_party(inventory,index):
if inventory[index].fainted == false:
var hold = inventory[0]
inventory[0] = inventory[index]
inventory[index] = hold
print(inventory[0].hp)
Battle_Ui_Updater()
move_ui_updater(player_inventory[0])
yield(get_tree().create_timer(2),"timeout")
I came up with this:
class_name Task
class Skipper:
signal skip
func emit() -> void:
emit_signal("skip")
var _instance:Object
var _method:String
var _parameters:Array
var _result = null
func _init(instance:Object, method:String, parameters:Array = []) -> void:
_instance = instance
_method = method
_parameters = parameters
func execute():
var instance = _instance
var parameters = _parameters
if instance != null and instance.has_method(_method):
_instance = null
_parameters = []
_result = instance.callv(_method, parameters)
if _result is GDScriptFunctionState && _result.is_valid():
_result = yield(_result, "completed")
return _result
var skipper = Skipper.new()
skipper.call_deferred("emit")
yield(skipper, "skip")
return _result
And this is how you initialize it:
var parameters = [player_inventory[0], enemy_inventory[0], player_inventory[0].move_slot[action_index]]
player_action = Task.new(self, "attack", parameters)
And this is how you use it:
yield(player_action.execute(), "completed")
The new thing about this class is that it will be asynchronous regardless of whether the method it calls is asynchronous or not (so you don't have to worry if what it calls yields or not). And it will complete after the method it calls completes, either way. And it even returns (null if what it calls does not return)!
Note: This code will memoize the result, and get rid of the parameters and instance it was linked to. Subsequent calls to execute will simply return the memoized result. This way it does not hold references unecesarily.
How do we do that?
Well, first of all, we are using callv to call a method by name with an array of parameters. There is a class FuncRef in Godot that can be used in similar fashion. However, not using it resulted more convinient.
Second, calling something asynchronous would be yield(..., "completed") but we don't know if what we are calling is asynchronous. So we check. How? If it is asynchronous, it actually returns the state it left the execution as a GDScriptFunctionState, so we check that.
If it is GDScriptFunctionState, then we can use yield(..., "completed") on it.
If it isn't. We need to somehow make the function asynchronous. We are going to do that by emitting a signal with call_deferred. I decided in making it an inner class so that it is not exposed outside of the script.
Also note that the code checks if the instance is not null and has the method passed. But if it does not, it will simply return the result it had stored. This is part of the memoization mechanism, however, it also means that you have no feedback if you passed the wrong instance of wrote the method name wrong.
Finally, it should work with this version of _battle:
func _battle():
participants = [player_inventory[0],enemy_inventory[0]]
participants.sort_custom(self, "check_speed")
for current_participant in participants:
if current_participant == player_inventory[0]:
yield(player_action.execute(), "completed")
else:
yield(enemy_action.execute(), "completed")
var state = decide_battle_state()
if state != GameState.BATTLE:
self.current_state = state
return
self.current_state = GameState.ACTION
func decide_battle_state():
if check_fainted(player_inventory[0]):
return GameState.LOST
if check_fainted(enemy_inventory[0]):
return GameState.WON
return GameState.BATTLE
func check_fainted(participant) -> bool:
if participant.current_hp > 0:
return false
participant.current_hp = 0
participant.fainted = true
Battle_Ui_Updater()
return true
You could make an Task with speed:
class_name BattleAction extends Task
# warning-ignore:unused_class_variable
var speed
func _init(instance:Object, method:String, parameters:Array = []).(instance, method, parameters) -> void:
pass
Then use like this:
var parameters = [player_inventory[0], enemy_inventory[0], player_inventory[0].move_slot[action_index]]
player_action = BattleAction.new(self, "attack", parameters)
player_action.speed = player_inventory[0].speed
And finally _battle can look like this:
With your BattleAction which has speed, you can do this:
func _battle():
actions = [player_action, enemy_action]
actions.sort_custom(self, "check_speed")
for action in actions:
yield(action.execute(), "completed")
var state = decide_battle_state()
if state != GameState.BATTLE:
self.current_state = state
return
self.current_state = GameState.ACTION