Godot: audio stutter issue - audio

One of my playtesters reported an audio stutter issue that seems to happen when the player character is walking.
Video here for the normal game audio vs. my playtester's recording:
https://www.dropbox.com/sh/2ssc5hgn6caz2ao/AAAOurXJ_8Zh0Jm_RxMQn7VEa?dl=0
Game prototype is playable online here (on desktop browser) if you want to test it yourself:
https://mellowminx.itch.io/reincarnated-as-an-adorable-walking-rock-in-a-peaceful-gardening-sim-game?secret=DXXjBoYCoM6aKdqTM8rc1L6wjU
Any idea on how I can fix it? I haven't been able to replicate the issue on my own and it hasn't been reported by other playtesters. In my code for the footstep audio, the audio plays whenever the animation frame changes while the player character is walking:
func _ready():
connect("frame_changed", self, "_on_frame_changed")
func footstep_grass_audio():
var footstep_grass_audio_tracks = [
$"%audio_footstep_grass_0",
$"%audio_footstep_grass_1",
$"%audio_footstep_grass_2",
$"%audio_footstep_grass_3",
$"%audio_footstep_grass_4",
]
footstep_grass_audio_tracks.shuffle()
var footstep_grass_audio_track = footstep_grass_audio_tracks[0]
footstep_grass_audio_track.play()
func footstep_puddle_audio():
var footstep_puddle_audio_tracks = [
$"%audio_footstep_puddle_0",
$"%audio_footstep_puddle_1",
$"%audio_footstep_puddle_2",
$"%audio_footstep_puddle_3",
$"%audio_footstep_puddle_4",
]
footstep_puddle_audio_tracks.shuffle()
var footstep_puddle_audio_track = footstep_puddle_audio_tracks[0]
footstep_puddle_audio_track.play()
func _on_frame_changed():
if Global.current_movement != "bounce" && Global.terrain_type == "land":
match frame:
0, 1:
footstep_grass_audio()
match frame:
1, 1:
footstep_grass_audio()
if Global.current_movement == "bounce" && Global.terrain_type == "land":
match frame:
1, 1:
footstep_grass_audio()
if Global.current_movement != "bounce" && Global.terrain_type == "water":
match frame:
0, 1:
footstep_puddle_audio()
match frame:
1, 1:
footstep_puddle_audio()
if Global.current_movement == "bounce" && Global.terrain_type == "water":
match frame:
1, 1:
footstep_puddle_audio()

Related

Switch between 2D layers programmatically

NOTE this is about Godot 4.0
In a particular scene, I've a set of layers, of which only one is visible at a time. My code is able to decide how a switch from one layer to other occurs.
I've implemented a class for solutioning this: ViewSwitch. That class has a set of ViewSwitchItem. The layers have to be a subtype of ViewSwitchItem.
The problem I'm facing is that, only once, from a layer first_menu, after I click the "Start Game" button in my first layer and switch to another layer start_game_menu, the game switches back to first_menu even if the player didn't meant so. Like I said, it happens only once. After you click like, say, the second time, you'll be transitioned to start_game_menu without being redirected to first_menu again. All this is done using GDScript. Something is wrong in my logic.
gd_util/ViewSwitch.gd
class_name ViewSwitch
var current_item: ViewSwitchItem = null
var items: Array[ViewSwitchItem] = []
func initialize() -> void:
swap(null)
for sw in items:
sw.parent_switch = self
sw.initialize()
func swap(swap: ViewSwitchItem) -> void:
if current_item == swap:
return
if current_item != null:
current_item.end(swap)
else:
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem) -> void:
for sw in items:
sw.node.visible = false
if swap == null:
return
current_item = swap
swap.node.visible = true
swap.start()
gd_util/ViewSwitchItem.gd
class_name ViewSwitchItem
var parent_switch: ViewSwitch = null
var node: Node = null
func initialize():
pass
func start():
pass
func end(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
immediate_swap(swap)
func immediate_swap(swap: ViewSwitchItem):
if parent_switch.current_item == swap:
return
parent_switch.immediate_swap(swap)
scenes/mainMenuScene/MainMenuScene.gd
extends Node2D
var view_switch: ViewSwitch = ViewSwitch.new()
var first_menu := MainMenuScene_firstMenu.new()
var start_game_menu := MainMenuScene_startGameMenu.new()
# Called when the node enters the scene tree for the first time.
func _ready():
view_switch.items = [
first_menu,
start_game_menu,
]
first_menu.node = $root/animation/container1
start_game_menu.node = $root/animation/container2_startGame
view_switch.initialize()
view_switch.swap(first_menu)
# first_menu
$root/animation/container1/startGameBtn.pressed.connect(func():
view_switch.swap(start_game_menu))
$root/animation/container1/exitBtn.pressed.connect(func():
get_tree().quit())
# start_game_menu
$root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn.pressed.connect(func():
view_switch.swap(first_menu))
$root/animation.animation_finished.connect(func(animation_name):
if animation_name == "Anim":
view_switch.swap(first_menu))
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# first_menu
if view_switch.current_item == first_menu:
pass
# start_game_menu
elif view_switch.current_item == start_game_menu:
if Input.is_action_just_released("back"):
view_switch.swap(first_menu)
scenes/mainMenuScene/switches/MainMenuScene_firstMenu.gd
class_name MainMenuScene_firstMenu
extends ViewSwitchItem
var start_game_button = null
func initialize():
start_game_button = self.node.get_tree().root.get_child(0).get_node("root/animation/container1/startGameBtn")
func start():
start_game_button.grab_focus()
func end(swap: ViewSwitchItem):
immediate_swap(swap)
scenes/mainMenuScene/switches/MainMenuScene_startGameMenu.gd
class_name MainMenuScene_startGameMenu
extends ViewSwitchItem
var panel1 = null
func initialize():
panel1 = self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel")
panel1.after_popup.connect(func():
self.node.get_tree().root.get_child(0).get_node("root/animation/container2_startGame/panel/PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/returnBtn").grab_focus())
panel1.after_collapse.connect(func():
# switch to first_menu
immediate_swap(parent_switch.items[0]))
func start():
panel1.popup()
func end(swap: ViewSwitchItem):
panel1.collapse()
Thanks!

Meele attack animation freezing alternate solution

I had a script somewhat similar like in this video:
extends KinematicBody2D
var movement=Vector2();
var up= Vector2(0, -1);
var speed=200;
var isAttacking=false;
func _process(delta):
if Input.is_action_pressed("ui_right") && isAttacking == false:
movement.x = speed;
$AnimatedSprite.play("walk");
elif Input.is_action_pressed("ui_left") && isAttacking == false:
movement.x= -speed;
$AnimatedSprite.play("Walk");
else:
movement.x = 0;
if isAttacking == false:
$AnimatedSprite.play("Idle");
if Input.is_action_just_pressed("Attack"):
$AnimatedSprite.play("Slash");
isAttacking=true;
movement = move_and_slide(movement, up * delta);
func _on_AnimatedSprite_animation_finished():
if $AnimatedSprite.animation == "Slash":
isAttacking= false;
but the problem was when I was rapidly pressing attack & movement on my keyboard
sometimes the isAttacking did not get set back to false after the animation was completed and hence froze my character animation
Maybe it was a bug in invoking the connected signal function when pressed rapidly? but anyhow it gave me a nightmare
so I came up with this workaround for rapid key pressing attack and movements (check the solutions) so no one else has to go through what I did :)
Instead of checking for attack in _process() I used _unhandled_key_input() and this seemed to get rid of the problem
Hope this helps! :D
...
func _process(delta):
if Input.is_action_pressed("ui_right") && isAttacking == false:
movement.x = speed;
$AnimatedSprite.play("walk");
elif Input.is_action_pressed("ui_left") && isAttacking == false:
movement.x= -speed;
$AnimatedSprite.play("Walk");
else:
movement.x = 0;
if isAttacking == false:
$AnimatedSprite.play("Idle");
if Input.is_action_just_pressed("Attack"):
$AnimatedSprite.play("Slash");
isAttacking=true;
movement = move_and_slide(movement, up * delta);
func attack_animation_finished(var _extra):
isAttacking=false
func _unhandled_key_input(event):
if(event.echo):
return
if(!event.pressed):
return
if(event.scancode==KEY_X and !isAttacking): # x key to attack (you can use whatever you like)
isAttacking=true
if!($AnimatedSprite.is_connected("animation_finished",self,"attack_animation_finished")):
$AnimatedSprite.connect("animation_finished", self, "attack_animation_finished", [], CONNECT_ONESHOT) # warning-ignore:return_value_discarded
$AnimatedSprite.play("Slash");
Note: I haven't ran this particular code segment but I have used this logic/approach in a working larger script which would be too long to share here

Trying to make a 2D destructable box in godot

i suck at coding. I am currently trying to make a 2D box that can be destroyed when the player attacks, however (like i said before) i suck at coding. I managed to get it working somewhat (and by somewhat i mean not at all) the box has an animation that plays when the player attacks when in range, but the animation almost never plays (sometimes it does but idk why)
code for box
extends Area2D
var inside = false
var attacking = false
func _physics_process(delta):
pass
func _on_Area2D_body_entered(body):
if Input.is_action_just_pressed("Attack"):
$AnimationPlayer.play("box_hit")
$boxdeathtimer.set_wait_time(0.5)
$boxdeathtimer.start()
func _on_boxdeathtimer_timeout():
queue_free()
code for weapon (if needed)
extends RigidBody2D
var picked = false
func _ready():
Global.weapon = self
func _exit_tree():
Global.weapon = null
var attacking = false
func _physics_process(delta):
if picked == true:
self.position = get_node("../player/Position2D").global_position
func _input(event):
if Input.is_action_just_pressed("e"): #picks up weapon when in range
var bodies = $detector.get_overlapping_bodies()
for b in bodies:
if (b.get_name() == "player"):
picked = true
sleeping = true
rotation_degrees = 90
if Input.is_action_just_pressed("Attack"):
if picked == true && Global.player.facing_right == true:
$AnimationPlayer.play("attack")
attacking = true
if picked == true && Global.player.facing_right == false:
$AnimationPlayer.play("attack2")
attacking = true
The body_entered signal notifies you when a physics body enters the area. The method is_action_just_pressed tells you if the (key associated with the) action were pressed the same (graphics, by default) frame.
So, in your code, everything inside here (Presuming the signal is properly connected):
func _on_Area2D_body_entered(body):
if Input.is_action_just_pressed("Attack"):
# CODE HERE
Can only run if the player pressed (the keys associated with the) action the same (graphics, by default) frame that a body entered the area, which is very hard to manage.
My suggestion is to give a "range of attack" area to the weapon. Then when the player attacks, you can use the signals of that area as it moves.
By the way, Avoid using is_action_just_pressed in _input. It is not intended for that. See Godot - Input.is_action_just_pressed() runs twice. In fact, I would argue to just use Input in _physics_process, unless you really need _input. See the link for what to replace is_action_just_pressed with, if you are working in _input.
So, it can look like this:
On the player side:
func _physics_process(delta):
# … some other code …
if Input.is_action_just_pressed("Attack"):
if picked == true && Global.player.facing_right == true:
$AnimationPlayer.play("attack")
attacking = true
if picked == true && Global.player.facing_right == false:
$AnimationPlayer.play("attack2")
attacking = true
if attacking:
$RangeOfAttack.monitoring = true
yield($AnimationPlayer, "animation_finished")
$RangeOfAttack.monitoring = false
# attacking = false # ?
func _on_RangeOfAttack_area_entered(area):
if area.has_method("attacked"):
area.attacked()
Where:
yield($AnimationPlayer, "animation_finished") is telling Godot to continue the execution after the $AnimationPlayer emits the "animation_finished"signal.
$RangeOfAttack refers to te range of attack area. Child node of the weapon.
_on_RangeOfAttack_area_entered is connected to the "area_entered" signal of the range of attack area. The reason I'm using "area_entered" instead of "body_entered" is that you made the box an area. If box weren't an area but some physics body, you could use the "body_entered" signal of the weapon (which is a RigidBody2D), and you would have no need for $RangeOfAttack.
On the target side:
func attacked():
$AnimationPlayer.play("box_hit")
yield($AnimationPlayer, "animation_finished")
queue_free()

Trying to find what is wrong with my code

Here I am trying to devolope a game.
Anyone please explain why boolean value of the variable win is not updating if Board value changes
Here is the code..
Board = {'1':'1','2':'6','3':'7',
'4':'2','5':'5','6':'8',
'7':'3','8':'4','9':'9'}
Win = Board['1'] == Board['2']== Board['3']=='X' or Board['4'] == Board['5'] == Board['6'] or Board['7'] == Board['8'] == Board['9'] or Board['1'] == Board['5'] == Board['9'] or Board['3'] == Board['5'] == Board['7'] or Board['1'] == Board['4'] == Board['7'] or Board['2'] == Board['5'] == Board['8'] or Board['3'] == Board['6'] == Board['9']
turn = 'X'
print(Win)
for i in range(9):
a = str(input('Enter choice {}: '.format(turn)))
Board[a] = turn
turn = 'Y' if turn == 'X' else 'X'
if Win: #Here I am expecting Win to be True but it is actually False
print('Win')
Please help me out with this.
You have one static boolean value that is unmodified as you "play the game"
Instead, define a function that inspects the state of the board
def isWin(board):
return board['1'] == board['2']== board['3']=='X' or ...
Then use that within the game loop
Board[a] = turn
turn = 'Y' if turn == 'X' else 'X'
if isWin(Board):
...
break # stop the game

Python 3: How to exit loop without stopping function

I am a complete newbie and have tried solving this problem (with my own head and with online research) for the last 5 hours.
Below is a snippet of a function we have written to simulate a game. We want to offer the ooportunity to start a new round - meaning if a player hits "b", the game should start again at the beginning of the range (0, players). But right now it just goes onto the next player in the range (if player 1 enters "b", the program calls player 2)
players = input(4)
if players in range(3, 9):
for player in range(0, players):
sum_points = 0
throw_per_player_counter = 0
print("\nIt is player no.", player+1, "'s turn!\n")
print("\nPress 'return' to roll the dice.\n"
"To start a new round press 'b'.\n"
"Player", player+1)
roll_dice = input(">>> ")
if roll_dice == "b":
player = 0
throw_per_player_counter = 0
sum_points = 0
print("\n * A new round was started. * \n")
I have tried return and break, also tried to put it all in another while-loop... failed. Break and return just ended the function.
Any hints highly appreciated!
you could change the for loop to a while loop. instead of using a range, make player a counter
players = 4
if 3 <= players < 9:
player = 0 # here's where you make your counter
while player < players:
sum_points = 0
throw_per_player_counter = 0
print("\nIt is player no.", player+1, "'s turn!\n")
print("\nPress 'return' to roll the dice.\n"
"To start a new round press 'b'.\n"
"Player", player+1)
roll_dice = input(">>> ")
player += 1 # increment it
if roll_dice == "b":
player = 0 # now your reset should work
throw_per_player_counter = 0
sum_points = 0
print("\n * A new round was started. * \n")

Resources