├── .gitignore ├── scripts ├── finite_state_machine │ ├── node_finite_state_machine.gd │ └── node_state.gd └── state_machine_v1 │ ├── node_state.gd │ └── node_state_machine.gd ├── sprites └── chronobot │ └── player_sprites.png └── tutorials ├── chronobot ├── chronobot-v1 │ └── scripts │ │ ├── collectibles │ │ ├── blue_coin │ │ │ └── blue_coin.gd │ │ ├── green_diamond │ │ │ └── green_diamond.gd │ │ └── pink_gem │ │ │ └── pink_gem.gd │ │ ├── enemies │ │ ├── crab │ │ │ └── enemy_crab.gd │ │ ├── dino │ │ │ ├── attack_state.gd │ │ │ ├── gravity.gd │ │ │ ├── idle_state.gd │ │ │ └── state_machine_controller.gd │ │ └── enemy_death_effect.gd │ │ ├── levels │ │ ├── door │ │ │ └── door.gd │ │ └── keys │ │ │ └── key.gd │ │ ├── pickups │ │ └── health_pickup │ │ │ └── health_pickup.gd │ │ ├── player │ │ ├── BulletImpactEffect.gd │ │ ├── bullet.gd │ │ ├── game_input_events.gd │ │ ├── player.gd │ │ ├── player_camera.gd │ │ ├── player_death_effect │ │ │ └── player_death_effect.gd │ │ └── player_states │ │ │ ├── fall_state.gd │ │ │ ├── idle_state.gd │ │ │ ├── jump_state.gd │ │ │ ├── run_state.gd │ │ │ ├── shoot_crouch_state.gd │ │ │ ├── shoot_run_state.gd │ │ │ ├── shoot_stand_state.gd │ │ │ ├── shoot_up_state.gd │ │ │ └── shoot_wall_cling_state.gd │ │ ├── scripts │ │ ├── collectible_manager.gd │ │ ├── game_manager.gd │ │ ├── health_manager.gd │ │ ├── inventory_manager.gd │ │ ├── scene_manager.gd │ │ ├── settings_manager.gd │ │ └── state_machine │ │ │ ├── node_finite_state_machine.gd │ │ │ └── node_state.gd │ │ └── ui │ │ ├── game_screen.gd │ │ ├── health_bar │ │ └── health_bar.gd │ │ ├── main_menu_screen.gd │ │ ├── pause_menu_screen.gd │ │ ├── screen_transition │ │ └── scene_transition_screen.gdshader │ │ ├── settings_data_resource.gd │ │ └── settings_menu_screen.gd └── chronobot-v2-player-state-machine │ └── scripts │ ├── collectibles │ ├── blue_coin │ │ └── blue_coin.gd │ ├── green_diamond │ │ └── green_diamond.gd │ └── pink_gem │ │ └── pink_gem.gd │ ├── enemies │ ├── crab │ │ └── enemy_crab.gd │ ├── dino │ │ ├── attack_state.gd │ │ ├── gravity.gd │ │ ├── idle_state.gd │ │ └── state_machine_controller.gd │ └── enemy_death_effect.gd │ ├── levels │ ├── door │ │ └── door.gd │ └── keys │ │ └── key.gd │ ├── pickups │ └── health_pickup │ │ └── health_pickup.gd │ ├── player │ ├── BulletImpactEffect.gd │ ├── bullet.gd │ ├── game_input_events.gd │ ├── player.gd │ ├── player_camera.gd │ ├── player_death_effect │ │ └── player_death_effect.gd │ └── player_states │ │ ├── fall_state.gd │ │ ├── idle_state.gd │ │ ├── jump_state.gd │ │ ├── run_state.gd │ │ ├── shoot_crouch_state.gd │ │ ├── shoot_run_state.gd │ │ ├── shoot_stand_state.gd │ │ ├── shoot_up_state.gd │ │ └── shoot_wall_cling_state.gd │ ├── scripts │ ├── collectible_manager.gd │ ├── game_manager.gd │ ├── health_manager.gd │ ├── inventory_manager.gd │ ├── scene_manager.gd │ ├── settings_manager.gd │ └── state_machine │ │ ├── node_finite_state_machine.gd │ │ └── node_state.gd │ └── ui │ ├── game_screen.gd │ ├── health_bar │ └── health_bar.gd │ ├── main_menu_screen.gd │ ├── pause_menu_screen.gd │ ├── screen_transition │ └── scene_transition_screen.gdshader │ ├── settings_data_resource.gd │ └── settings_menu_screen.gd ├── croptails ├── audio │ └── sfx │ │ ├── chicken-cluck-1.ogg │ │ ├── chicken-cluck-2.ogg │ │ ├── chicken-cluck-3.ogg │ │ └── cow-moo.ogg └── scripts │ ├── dialogue │ ├── base_game_dialogue_balloon.gd │ └── game_dialogue_balloon.gd │ ├── resources │ ├── node_data_resource.gd │ ├── save_game_data_resource.gd │ ├── scene_data_resource.gd │ └── tilemap_layer_data_resource.gd │ ├── scenes │ ├── characters │ │ ├── chicken │ │ │ └── chicken.gd │ │ ├── cow │ │ │ └── cow.gd │ │ ├── guide │ │ │ └── guide.gd │ │ ├── non_playable_character.gd │ │ ├── npc_states │ │ │ ├── idle_state.gd │ │ │ └── walk_state.gd │ │ └── player │ │ │ ├── chopping_state.gd │ │ │ ├── idle_state.gd │ │ │ ├── player.gd │ │ │ ├── tilling_state.gd │ │ │ ├── walk_state.gd │ │ │ └── watering_state.gd │ ├── components │ │ ├── collectable_component.gd │ │ ├── crops_cursor_component.gd │ │ ├── damage_component.gd │ │ ├── day_night_cycle_component.gd │ │ ├── feed_component.gd │ │ ├── field_cursor_component.gd │ │ ├── hit_component.gd │ │ ├── hurt_component.gd │ │ ├── interactable_component.gd │ │ ├── mouse_cursor_component.gd │ │ ├── save_data_component.gd │ │ ├── save_level_data_component.gd │ │ ├── test_scene_enable_tool_buttons_component.gd │ │ └── test_scene_save_data_manager_component.gd │ ├── houses │ │ └── door.gd │ ├── objects │ │ ├── chest │ │ │ └── chest.gd │ │ ├── plants │ │ │ ├── corn.gd │ │ │ ├── growth_cycle_component.gd │ │ │ └── tomato.gd │ │ ├── rocks │ │ │ └── rock.gd │ │ └── trees │ │ │ ├── large_tree.gd │ │ │ └── small_tree.gd │ └── ui │ │ ├── day_and_night_panel.gd │ │ ├── emotes_panel.gd │ │ ├── game_menu_screen.gd │ │ ├── game_menu_screen_background.gd │ │ ├── inventory_panel.gd │ │ └── tools_panel.gd │ └── scripts │ ├── game_input_events.gd │ ├── globals │ ├── data_types.gd │ ├── day_and_night_cycle_manager.gd │ ├── game_dialogue_manager.gd │ ├── game_manager.gd │ ├── inventory_manager.gd │ ├── save_game_manager.gd │ ├── scene_manager.gd │ └── tool_manager.gd │ └── state_machine │ ├── node_state.gd │ └── node_state_machine.gd └── prototype_3d └── scripts ├── scenes └── player │ ├── camera_pivot.gd │ ├── player.gd │ ├── player_animation_tree_transitions.gd │ ├── player_transitions.gd │ └── states │ ├── fall_state.gd │ ├── idle_state.gd │ ├── jump_state.gd │ ├── run_state.gd │ └── walk_state.gd ├── script_templates └── StateNode │ └── state_node_script_template.gd └── scripts └── game_input_events.gd /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | -------------------------------------------------------------------------------- /scripts/finite_state_machine/node_finite_state_machine.gd: -------------------------------------------------------------------------------- 1 | class_name NodeFiniteStateMachine 2 | extends Node 3 | 4 | @export var initial_node_state : NodeState 5 | 6 | var node_states : Dictionary = {} 7 | var current_node_state : NodeState 8 | var current_node_state_name : String 9 | 10 | 11 | func _ready(): 12 | for child in get_children(): 13 | if child is NodeState: 14 | node_states[child.name.to_lower()] = child 15 | child.transition.connect(on_state_transition) 16 | 17 | if initial_node_state: 18 | initial_node_state.on_enter() 19 | current_node_state = initial_node_state 20 | 21 | 22 | func _process(delta : float): 23 | if current_node_state: 24 | current_node_state.on_process(delta) 25 | 26 | 27 | func _physics_process(delta: float): 28 | if current_node_state: 29 | current_node_state.on_physics_process(delta) 30 | 31 | #print("Current State: ", current_node_state.name.to_lower()) 32 | 33 | 34 | func on_state_transition(node_state_name : String): 35 | if node_state_name == current_node_state.name.to_lower(): 36 | return 37 | 38 | var new_node_state = node_states.get(node_state_name.to_lower()) 39 | 40 | if !new_node_state: 41 | return 42 | 43 | if current_node_state: 44 | current_node_state.on_exit() 45 | 46 | new_node_state.on_enter() 47 | 48 | current_node_state = new_node_state 49 | current_node_state_name = current_node_state.name.to_lower() 50 | -------------------------------------------------------------------------------- /scripts/finite_state_machine/node_state.gd: -------------------------------------------------------------------------------- 1 | class_name NodeState 2 | extends Node 3 | 4 | signal transition 5 | 6 | func on_process(_delta : float): 7 | pass 8 | 9 | 10 | func on_physics_process(_delta : float): 11 | pass 12 | 13 | 14 | func on_enter(): 15 | pass 16 | 17 | 18 | func on_exit(): 19 | pass 20 | -------------------------------------------------------------------------------- /scripts/state_machine_v1/node_state.gd: -------------------------------------------------------------------------------- 1 | class_name NodeState 2 | extends Node 3 | 4 | @warning_ignore("unused_signal") 5 | signal transition 6 | 7 | 8 | func _on_process(_delta : float) -> void: 9 | pass 10 | 11 | 12 | func _on_physics_process(_delta : float) -> void: 13 | pass 14 | 15 | 16 | func _on_next_transitions() -> void: 17 | pass 18 | 19 | 20 | func _on_enter() -> void: 21 | pass 22 | 23 | 24 | func _on_exit() -> void: 25 | pass 26 | -------------------------------------------------------------------------------- /scripts/state_machine_v1/node_state_machine.gd: -------------------------------------------------------------------------------- 1 | class_name NodeStateMachine 2 | extends Node 3 | 4 | @export var initial_node_state : NodeState 5 | 6 | var node_states : Dictionary = {} 7 | var current_node_state : NodeState 8 | var current_node_state_name : String 9 | 10 | func _ready() -> void: 11 | for child in get_children(): 12 | if child is NodeState: 13 | node_states[child.name.to_lower()] = child 14 | child.transition.connect(transition_to) 15 | 16 | if initial_node_state: 17 | initial_node_state._on_enter() 18 | current_node_state = initial_node_state 19 | 20 | 21 | func _process(delta : float) -> void: 22 | if current_node_state: 23 | current_node_state._on_process(delta) 24 | 25 | 26 | func _physics_process(delta: float) -> void: 27 | if current_node_state: 28 | current_node_state._on_physics_process(delta) 29 | current_node_state._on_next_transitions() 30 | 31 | 32 | func transition_to(node_state_name : String) -> void: 33 | if node_state_name == current_node_state.name.to_lower(): 34 | return 35 | 36 | var new_node_state = node_states.get(node_state_name.to_lower()) 37 | 38 | if !new_node_state: 39 | return 40 | 41 | if current_node_state: 42 | current_node_state._on_exit() 43 | 44 | new_node_state._on_enter() 45 | 46 | current_node_state = new_node_state 47 | current_node_state_name = current_node_state.name.to_lower() 48 | print("Current State: ", current_node_state_name) 49 | -------------------------------------------------------------------------------- /sprites/chronobot/player_sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapidvectors/tutorial-components-and-scripts/430563b18cbd9d368f834e983a36d28e5448afe8/sprites/chronobot/player_sprites.png -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/collectibles/blue_coin/blue_coin.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 1 4 | 5 | @onready var animated_sprite_2d = $AnimatedSprite2D 6 | @onready var label = $Label 7 | 8 | 9 | func _ready(): 10 | label.hide() 11 | 12 | 13 | func _on_area_2d_body_entered(body): 14 | if body.is_in_group("Player"): 15 | print(award_amount) 16 | 17 | animated_sprite_2d.hide() 18 | 19 | label.text = "%s" % award_amount 20 | CollectibleManager.give_pickup_award(award_amount) 21 | 22 | label.show() 23 | 24 | var tween = get_tree().create_tween() 25 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 26 | tween.tween_callback(queue_free) 27 | 28 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/collectibles/green_diamond/green_diamond.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 5 4 | 5 | @onready var label = $Label 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | 8 | func _ready(): 9 | label.hide() 10 | 11 | 12 | func _on_area_2d_body_entered(body : Node2D): 13 | if body == null: 14 | return 15 | 16 | if body.is_in_group("Player"): 17 | print(award_amount) 18 | 19 | animated_sprite_2d.hide() 20 | 21 | label.text = "%s" % award_amount 22 | CollectibleManager.give_pickup_award(award_amount) 23 | 24 | label.show() 25 | 26 | var tween = get_tree().create_tween() 27 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 28 | tween.tween_callback(queue_free) 29 | 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/collectibles/pink_gem/pink_gem.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 3 4 | 5 | @onready var label = $Label 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | 8 | func _ready(): 9 | label.hide() 10 | 11 | 12 | func _on_area_2d_body_entered(body : Node2D): 13 | if body == null: 14 | return 15 | 16 | if body.is_in_group("Player"): 17 | print(award_amount) 18 | 19 | animated_sprite_2d.hide() 20 | 21 | label.text = "%s" % award_amount 22 | CollectibleManager.give_pickup_award(award_amount) 23 | 24 | label.show() 25 | 26 | var tween = get_tree().create_tween() 27 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 28 | tween.tween_callback(queue_free) 29 | 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/crab/enemy_crab.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | var enemy_death_effect = preload("res://enemies/enemy_death_effect.tscn") 4 | 5 | @export var patrol_points : Node 6 | @export var speed : int = 1500 7 | @export var wait_time : int = 3 8 | @export var health_amount : int = 3 9 | @export var damage_amount : int = 1 10 | 11 | @onready var animated_sprite_2d = $AnimatedSprite2D 12 | @onready var timer = $Timer 13 | 14 | const GRAVITY = 1000 15 | 16 | enum State { Idle, Walk } 17 | var current_state : State 18 | var direction : Vector2 = Vector2.LEFT 19 | var number_of_points : int 20 | var point_positions : Array[Vector2] 21 | var current_point : Vector2 22 | var current_point_position : int 23 | var can_walk : bool 24 | 25 | 26 | func _ready(): 27 | if patrol_points != null: 28 | number_of_points = patrol_points.get_children().size() 29 | for point in patrol_points.get_children(): 30 | point_positions.append(point.global_position) 31 | current_point = point_positions[current_point_position] 32 | else: 33 | print("No patrol points") 34 | 35 | timer.wait_time = wait_time 36 | 37 | current_state = State.Idle 38 | 39 | 40 | func _physics_process(delta : float): 41 | enemy_gravity(delta) 42 | enemy_idle(delta) 43 | enemy_walk(delta) 44 | 45 | move_and_slide() 46 | 47 | enemy_animations() 48 | 49 | 50 | func enemy_gravity(delta : float): 51 | velocity.y += GRAVITY * delta 52 | 53 | 54 | func enemy_idle(delta : float): 55 | if !can_walk: 56 | velocity.x = move_toward(velocity.x, 0, speed * delta) 57 | current_state = State.Idle 58 | 59 | 60 | func enemy_walk(delta : float): 61 | if !can_walk: 62 | return 63 | 64 | if abs(position.x - current_point.x) > 0.5: 65 | velocity.x = direction.x * speed * delta 66 | current_state = State.Walk 67 | else: 68 | current_point_position += 1 69 | 70 | if current_point_position >= number_of_points: 71 | current_point_position = 0 72 | 73 | current_point = point_positions[current_point_position]; 74 | 75 | if current_point.x > position.x: 76 | direction = Vector2.RIGHT 77 | else: 78 | direction = Vector2.LEFT 79 | 80 | can_walk = false 81 | timer.start() 82 | 83 | animated_sprite_2d.flip_h = direction.x > 0 84 | 85 | 86 | func enemy_animations(): 87 | if current_state == State.Idle && !can_walk: 88 | animated_sprite_2d.play("idle") 89 | elif current_state == State.Walk && can_walk: 90 | animated_sprite_2d.play("walk") 91 | 92 | func _on_timer_timeout(): 93 | can_walk = true 94 | 95 | 96 | func _on_hurtbox_area_entered(area : Area2D): 97 | print("Hurtbox area entered") 98 | if area.get_parent().has_method("get_damage_amount"): 99 | var node = area.get_parent() as Node 100 | health_amount -= node.damage_amount 101 | print("Health amount: ", health_amount) 102 | 103 | if health_amount <= 0: 104 | var enemy_death_effect_instance = enemy_death_effect.instantiate() as Node2D 105 | enemy_death_effect_instance.global_position = global_position 106 | get_parent().add_child(enemy_death_effect_instance) 107 | queue_free() 108 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/dino/attack_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | @export var speed : int 6 | 7 | var player : CharacterBody2D 8 | var max_speed : int 9 | 10 | func on_process(delta : float): 11 | pass 12 | 13 | 14 | func on_physics_process(delta : float): 15 | var direction : int 16 | 17 | if character_body_2d.global_position > player.global_position: 18 | animated_sprite_2d.flip_h = false 19 | direction = -1 20 | elif character_body_2d.global_position < player.global_position: 21 | animated_sprite_2d.flip_h = true 22 | direction = 1 23 | 24 | animated_sprite_2d.play("attack") 25 | 26 | character_body_2d.velocity.x += direction * speed * delta 27 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_speed, max_speed) 28 | character_body_2d.move_and_slide() 29 | 30 | 31 | func enter(): 32 | player = get_tree().get_nodes_in_group("Player")[0] as CharacterBody2D 33 | max_speed = speed + 20 34 | 35 | 36 | func exit(): 37 | pass 38 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/dino/gravity.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | const GRAVITY : int = 1000 7 | 8 | 9 | func _physics_process(delta): 10 | if !character_body_2d.is_on_floor(): 11 | character_body_2d.velocity.y += GRAVITY * delta 12 | 13 | character_body_2d.move_and_slide() 14 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/dino/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var slow_down_speed : int = 50 6 | 7 | func on_process(delta : float): 8 | pass 9 | 10 | 11 | func on_physics_process(delta : float): 12 | character_body_2d.velocity.x = move_toward(character_body_2d.velocity.x, 0, slow_down_speed * delta) 13 | animated_sprite_2d.play("idle") 14 | character_body_2d.move_and_slide() 15 | 16 | 17 | 18 | func enter(): 19 | pass 20 | 21 | 22 | func exit(): 23 | pass 24 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/dino/state_machine_controller.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var node_finite_state_machine : NodeFiniteStateMachine 4 | 5 | 6 | func _on_attack_area_2d_body_entered(body : Node2D): 7 | if body.is_in_group("Player"): 8 | node_finite_state_machine.transition_to("attack") 9 | 10 | 11 | func _on_attack_area_2d_body_exited(body): 12 | if body.is_in_group("Player"): 13 | node_finite_state_machine.transition_to("idle") 14 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/enemies/enemy_death_effect.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/levels/door/door.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var animated_sprite_2d = $AnimatedSprite2D 4 | @onready var collision_shape_2d = $CollisionShape2D 5 | 6 | @export var next_scene :String 7 | @export var key_id : String 8 | 9 | 10 | var door_open : bool 11 | 12 | 13 | func _on_exit_area_2d_body_entered(body): 14 | if body.is_in_group("Player"): 15 | var player = body as CharacterBody2D 16 | player.queue_free() 17 | 18 | await get_tree().create_timer(3.0).timeout 19 | SceneManager.transition_to_scene(next_scene) 20 | 21 | 22 | func _on_activate_door_area_2d_body_entered(body): 23 | if body.is_in_group("Player"): 24 | var has_item : bool = InventoryManager.has_inventory_item(key_id) 25 | 26 | if !has_item: 27 | return 28 | 29 | if !door_open: 30 | animated_sprite_2d.play("open") 31 | door_open = true 32 | collision_shape_2d.set_deferred("disabled", true) 33 | 34 | 35 | func _on_activate_door_area_2d_body_exited(body): 36 | if body.is_in_group("Player"): 37 | if door_open: 38 | animated_sprite_2d.play("close") 39 | door_open = false 40 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/levels/keys/key.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var key_id : String 4 | 5 | 6 | func _on_area_2d_body_entered(body): 7 | if body.is_in_group("Player"): 8 | InventoryManager.add_to_inventory("Key1", key_id) 9 | queue_free() 10 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/pickups/health_pickup/health_pickup.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var pickup_amount : int = 1 4 | 5 | 6 | func _on_health_pickup_box_body_entered(body): 7 | if body.is_in_group("Player"): 8 | HealthManager.increase_health(pickup_amount) 9 | queue_free() 10 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/BulletImpactEffect.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/bullet.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | var bullet_impact_effect = preload("res://player/bullet_impact_effect.tscn") 4 | 5 | var speed : int = 600 6 | var direction : int 7 | var damage_amount : int = 1 8 | var move_x_direction : bool 9 | 10 | 11 | func _physics_process(delta): 12 | if move_x_direction: 13 | move_local_x(direction * speed * delta) 14 | else: 15 | move_local_y(direction * speed * delta) 16 | 17 | 18 | func _on_timer_timeout(): 19 | queue_free() 20 | 21 | 22 | func _on_hitbox_area_entered(area): 23 | print("Bullet area entered") 24 | bullet_impact() 25 | 26 | 27 | func _on_hitbox_body_entered(body): 28 | print("Bullet body entered") 29 | bullet_impact() 30 | 31 | 32 | func get_damage_amount() -> int: 33 | return damage_amount 34 | 35 | 36 | func bullet_impact(): 37 | var bullet_impact_effect_instance = bullet_impact_effect.instantiate() as Node2D 38 | bullet_impact_effect_instance.global_position = global_position 39 | get_parent().add_child(bullet_impact_effect_instance) 40 | queue_free() 41 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/game_input_events.gd: -------------------------------------------------------------------------------- 1 | class_name GameInputEvents 2 | extends Node 3 | 4 | 5 | static func movement_input() -> float: 6 | var direction : float = Input.get_axis("move_left", "move_right") 7 | return direction 8 | 9 | 10 | static func jump_input() -> bool: 11 | var jump_input : bool = Input.is_action_just_pressed("jump") 12 | return jump_input 13 | 14 | 15 | static func shoot_input() -> bool: 16 | var shoot_input : bool = Input.is_action_just_pressed("shoot") 17 | return shoot_input 18 | 19 | 20 | static func shoot_up_input() -> bool: 21 | var shoot_input : bool = Input.is_action_just_pressed("shoot") 22 | var up_input : bool = Input.is_action_pressed("look_up") 23 | return up_input and shoot_input 24 | 25 | 26 | static func crouch_input() -> bool: 27 | var crouch_input : bool = Input.is_action_just_pressed("crouch") 28 | return crouch_input 29 | 30 | 31 | static func fall_input() -> bool: 32 | var fall_input : bool = Input.is_action_just_pressed("force_fall") 33 | return fall_input 34 | 35 | 36 | static func wall_cling_input() -> bool: 37 | var wall_cling_input : bool = Input.is_action_pressed("wall_cling") 38 | return wall_cling_input 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | var player_death_effect = preload("res://player/player_death_effect/player_death_effect.tscn") 5 | 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | @onready var muzzle : Marker2D = $Muzzle 8 | 9 | 10 | func player_death(): 11 | var player_death_effect_instance = player_death_effect.instantiate() as Node2D 12 | player_death_effect_instance.global_position = global_position 13 | get_parent().add_child(player_death_effect_instance) 14 | queue_free() 15 | 16 | 17 | func _on_hurtbox_body_entered(body : Node2D): 18 | if body.is_in_group("Enemy"): 19 | print("Enemy entered ", body.damage_amount) 20 | 21 | var tween = get_tree().create_tween() 22 | tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", true, 0) 23 | tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", false, 0.2) 24 | 25 | HealthManager.decrease_health(body.damage_amount) 26 | 27 | if HealthManager.current_health == 0: 28 | player_death() 29 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_camera.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | @export_category("Follow Character") 4 | @export var player : CharacterBody2D 5 | 6 | @export_category("Camera Smoothing") 7 | @export var smoothing_enabled : bool 8 | @export_range(1, 10) var smoothing_distance : int = 8 9 | 10 | var weight : float 11 | 12 | func _ready(): 13 | weight = float(11 - smoothing_distance) / 100 14 | 15 | 16 | func _physics_process(delta): 17 | if player != null: 18 | var camera_position : Vector2 19 | 20 | if smoothing_enabled: 21 | camera_position = lerp(global_position, player.global_position, weight) 22 | else: 23 | camera_position = player.global_position 24 | 25 | global_position = camera_position.floor() 26 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_death_effect/player_death_effect.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/fall_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Fall State") 7 | @export var coyote_time : float = 0.1 8 | 9 | const GRAVITY : int = 700 10 | var coyote_jump : bool 11 | 12 | 13 | func on_process(delta : float): 14 | pass 15 | 16 | 17 | func on_physics_process(delta : float): 18 | if !character_body_2d.is_on_floor(): 19 | get_coyote_time() 20 | character_body_2d.velocity.y += GRAVITY * delta 21 | 22 | character_body_2d.move_and_slide() 23 | 24 | # transitioning states 25 | 26 | # idle state 27 | if character_body_2d.is_on_floor(): 28 | transition.emit("Idle") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input() and coyote_jump: 32 | transition.emit("Jump") 33 | 34 | 35 | func enter(): 36 | coyote_jump = true 37 | animated_sprite_2d.play("fall") 38 | 39 | 40 | func exit(): 41 | animated_sprite_2d.stop() 42 | 43 | 44 | func get_coyote_time(): 45 | await get_tree().create_timer(coyote_time).timeout 46 | coyote_jump = false 47 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Physics Friction") 7 | @export var slow_down_speed : int = 1700 8 | 9 | func on_process(delta : float): 10 | pass 11 | 12 | 13 | func on_physics_process(delta : float): 14 | character_body_2d.velocity.x = move_toward(character_body_2d.velocity.x, 0, slow_down_speed) 15 | 16 | character_body_2d.move_and_slide() 17 | 18 | # transitioning states 19 | 20 | # fall state 21 | if !character_body_2d.is_on_floor(): 22 | transition.emit("Fall") 23 | 24 | # run state 25 | var direction : float = GameInputEvents.movement_input() 26 | 27 | if direction and character_body_2d.is_on_floor(): 28 | transition.emit("Run") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input(): 32 | transition.emit("Jump") 33 | 34 | # shoot stand state 35 | if GameInputEvents.shoot_input(): 36 | transition.emit("ShootStand") 37 | 38 | # shoot up state 39 | if GameInputEvents.shoot_up_input(): 40 | transition.emit("ShootUp") 41 | 42 | # shoot crouch state 43 | if GameInputEvents.crouch_input(): 44 | transition.emit("ShootCrouch") 45 | 46 | 47 | func enter(): 48 | animated_sprite_2d.play("idle") 49 | 50 | 51 | func exit(): 52 | animated_sprite_2d.stop() 53 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/jump_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Jump State") 7 | @export var jump_height : float = -250 8 | @export var jump_horizontal_speed : int = 500 9 | @export var max_jump_horizontal_speed : int = 300 10 | @export var max_jump_count : int = 1 11 | @export var jump_gravity : int = 1000 12 | 13 | var current_jump_count : int 14 | var coyote_jump : bool 15 | 16 | func on_process(delta : float): 17 | pass 18 | 19 | 20 | func on_physics_process(delta : float): 21 | 22 | character_body_2d.velocity.y += jump_gravity * delta 23 | 24 | if character_body_2d.is_on_floor(): 25 | current_jump_count = 0 26 | character_body_2d.velocity.y = jump_height 27 | coyote_jump = false 28 | current_jump_count += 1 29 | 30 | if coyote_jump: 31 | character_body_2d.velocity.y = jump_height 32 | coyote_jump = false 33 | current_jump_count += 1 34 | 35 | # multiple jumps 36 | if !character_body_2d.is_on_floor() and GameInputEvents.jump_input() and current_jump_count != max_jump_count: 37 | character_body_2d.velocity.y = jump_height 38 | current_jump_count += 1 39 | 40 | var direction : float = GameInputEvents.movement_input() 41 | 42 | if !character_body_2d.is_on_floor(): 43 | character_body_2d.velocity.x += direction * jump_horizontal_speed 44 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_jump_horizontal_speed, max_jump_horizontal_speed) 45 | 46 | character_body_2d.move_and_slide() 47 | 48 | # transitioning states 49 | 50 | # idle state 51 | if character_body_2d.is_on_floor(): 52 | transition.emit("Idle") 53 | 54 | # wall cling state 55 | if GameInputEvents.wall_cling_input() and character_body_2d.is_on_wall(): 56 | transition.emit("ShootWallCling") 57 | 58 | func enter(): 59 | coyote_jump = true 60 | animated_sprite_2d.play("jump") 61 | 62 | 63 | func exit(): 64 | coyote_jump = false 65 | animated_sprite_2d.stop() 66 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/run_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Run State") 7 | @export var speed : int = 1000 8 | @export var max_horizontal_speed: int = 300 9 | 10 | const GRAVITY : int = 1000 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | var direction : float = GameInputEvents.movement_input() 18 | 19 | if direction: 20 | character_body_2d.velocity.x += direction * speed 21 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_horizontal_speed, max_horizontal_speed) 22 | 23 | if direction != 0: 24 | animated_sprite_2d.flip_h = false if direction > 0 else true 25 | 26 | character_body_2d.velocity.y += GRAVITY * delta 27 | 28 | character_body_2d.move_and_slide() 29 | 30 | # transitioning states 31 | 32 | # idle state 33 | if direction == 0: 34 | transition.emit("Idle") 35 | 36 | # jump state 37 | if GameInputEvents.jump_input(): 38 | transition.emit("Jump") 39 | 40 | #shoot run state 41 | if direction != 0 and GameInputEvents.shoot_input(): 42 | transition.emit("ShootRun") 43 | 44 | # fall state 45 | if !character_body_2d.is_on_floor(): 46 | transition.emit("Fall") 47 | 48 | 49 | func enter(): 50 | animated_sprite_2d.play("run") 51 | 52 | 53 | func exit(): 54 | animated_sprite_2d.stop() 55 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/shoot_crouch_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | var muzzle_position : Vector2 10 | 11 | func on_process(delta : float): 12 | pass 13 | 14 | 15 | func on_physics_process(delta : float): 16 | 17 | gun_muzzle_position() 18 | 19 | if GameInputEvents.shoot_input(): 20 | gun_shooting() 21 | 22 | # transitioning states 23 | 24 | # run state 25 | var direction : float = GameInputEvents.movement_input() 26 | 27 | if direction and character_body_2d.is_on_floor(): 28 | transition.emit("Run") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input(): 32 | transition.emit("Jump") 33 | 34 | 35 | func enter(): 36 | muzzle.position = Vector2(17, -14) 37 | muzzle_position = muzzle.position 38 | 39 | animated_sprite_2d.play("shoot_crouch") 40 | 41 | 42 | func exit(): 43 | animated_sprite_2d.stop() 44 | 45 | 46 | func gun_muzzle_position(): 47 | if !animated_sprite_2d.flip_h: 48 | muzzle.position.x = muzzle_position.x 49 | elif animated_sprite_2d.flip_h: 50 | muzzle.position.x = -muzzle_position.x 51 | 52 | 53 | func gun_shooting(): 54 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 55 | 56 | var bullet_instance = bullet.instantiate() as Node2D 57 | bullet_instance.direction = direction 58 | bullet_instance.move_x_direction = true 59 | bullet_instance.global_position = muzzle.global_position 60 | get_parent().add_child(bullet_instance) 61 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/shoot_run_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | @export_category("Run State") 10 | @export var speed : int = 1000 11 | @export var max_horizontal_speed: int = 300 12 | 13 | const GRAVITY : int = 1000 14 | var muzzle_position : Vector2 15 | 16 | func on_process(delta : float): 17 | pass 18 | 19 | 20 | func on_physics_process(delta : float): 21 | var direction : float = GameInputEvents.movement_input() 22 | 23 | gun_muzzle_position(direction) 24 | 25 | if direction: 26 | character_body_2d.velocity.x += direction * speed 27 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_horizontal_speed, max_horizontal_speed) 28 | 29 | if direction != 0: 30 | animated_sprite_2d.flip_h = false if direction > 0 else true 31 | 32 | character_body_2d.velocity.y += GRAVITY * delta 33 | 34 | if GameInputEvents.shoot_input(): 35 | gun_shooting(direction) 36 | 37 | character_body_2d.move_and_slide() 38 | 39 | # transitioning states 40 | 41 | # fall state 42 | if !character_body_2d.is_on_floor(): 43 | transition.emit("Fall") 44 | 45 | # idle state 46 | if direction == 0: 47 | transition.emit("Idle") 48 | 49 | # jump state 50 | if GameInputEvents.jump_input(): 51 | transition.emit("jump") 52 | 53 | 54 | func enter(): 55 | muzzle.position = Vector2(18, -26) 56 | muzzle_position = muzzle.position 57 | 58 | animated_sprite_2d.play("shoot_run") 59 | 60 | 61 | func exit(): 62 | animated_sprite_2d.stop() 63 | 64 | 65 | func gun_muzzle_position(direction : float): 66 | if direction > 0: 67 | muzzle.position.x = muzzle_position.x 68 | elif direction < 0: 69 | muzzle.position.x = -muzzle_position.x 70 | 71 | 72 | func gun_shooting(direction : float): 73 | var bullet_instance = bullet.instantiate() as Node2D 74 | bullet_instance.direction = direction 75 | bullet_instance.move_x_direction = true 76 | bullet_instance.global_position = muzzle.global_position 77 | get_parent().add_child(bullet_instance) 78 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/shoot_stand_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | @export var hold_gun_time : float = 2.0 9 | 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | 18 | gun_muzzle_position() 19 | 20 | if GameInputEvents.shoot_input(): 21 | gun_shooting() 22 | 23 | # transitioning states 24 | 25 | # run state 26 | var direction : float = GameInputEvents.movement_input() 27 | 28 | if direction and character_body_2d.is_on_floor(): 29 | transition.emit("Run") 30 | 31 | # jump state 32 | if GameInputEvents.jump_input(): 33 | transition.emit("Jump") 34 | 35 | 36 | func enter(): 37 | muzzle.position = Vector2(18, -26) 38 | muzzle_position = muzzle.position 39 | 40 | get_tree().create_timer(hold_gun_time).timeout.connect(on_hold_gun_timout) 41 | animated_sprite_2d.play("shoot_stand") 42 | 43 | 44 | func exit(): 45 | animated_sprite_2d.stop() 46 | 47 | 48 | func on_hold_gun_timout(): 49 | transition.emit("Idle") 50 | 51 | 52 | func gun_muzzle_position(): 53 | if !animated_sprite_2d.flip_h: 54 | muzzle.position.x = muzzle_position.x 55 | elif animated_sprite_2d.flip_h: 56 | muzzle.position.x = -muzzle_position.x 57 | 58 | 59 | func gun_shooting(): 60 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 61 | 62 | var bullet_instance = bullet.instantiate() as Node2D 63 | bullet_instance.direction = direction 64 | bullet_instance.move_x_direction = true 65 | bullet_instance.global_position = muzzle.global_position 66 | get_parent().add_child(bullet_instance) 67 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/shoot_up_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | @export var hold_gun_time : float = 2.0 9 | 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | 18 | gun_muzzle_position() 19 | 20 | if GameInputEvents.shoot_input(): 21 | gun_shooting() 22 | 23 | # transitioning states 24 | 25 | # run state 26 | var direction : float = GameInputEvents.movement_input() 27 | 28 | if direction and character_body_2d.is_on_floor(): 29 | transition.emit("Run") 30 | 31 | # jump state 32 | if GameInputEvents.jump_input(): 33 | transition.emit("Jump") 34 | 35 | 36 | func enter(): 37 | muzzle.position = Vector2(4, -42) 38 | muzzle_position = muzzle.position 39 | 40 | get_tree().create_timer(hold_gun_time).timeout.connect(on_hold_gun_timout) 41 | animated_sprite_2d.play("shoot_up") 42 | 43 | 44 | func exit(): 45 | animated_sprite_2d.stop() 46 | 47 | 48 | func on_hold_gun_timout(): 49 | transition.emit("Idle") 50 | 51 | 52 | func gun_muzzle_position(): 53 | if !animated_sprite_2d.flip_h: 54 | muzzle.position.x = muzzle_position.x 55 | elif animated_sprite_2d.flip_h: 56 | muzzle.position.x = -muzzle_position.x 57 | 58 | 59 | func gun_shooting(): 60 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 61 | 62 | var bullet_instance = bullet.instantiate() as Node2D 63 | bullet_instance.direction = -1 64 | bullet_instance.move_x_direction = false 65 | bullet_instance.global_position = muzzle.global_position 66 | get_parent().add_child(bullet_instance) 67 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/player/player_states/shoot_wall_cling_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | var wall_cling_direction : Vector2 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | character_body_2d.velocity.y = 0 18 | 19 | var direction : float = GameInputEvents.movement_input() 20 | 21 | if direction > 0 and wall_cling_direction == Vector2.ZERO: 22 | animated_sprite_2d.flip_h = true 23 | wall_cling_direction = Vector2.RIGHT 24 | 25 | if direction < 0 and wall_cling_direction == Vector2.ZERO: 26 | animated_sprite_2d.flip_h = false 27 | wall_cling_direction = Vector2.LEFT 28 | 29 | gun_muzzle_position() 30 | 31 | if GameInputEvents.shoot_input(): 32 | gun_shooting() 33 | 34 | character_body_2d.move_and_slide() 35 | 36 | # transitioning states 37 | 38 | # jump state 39 | if GameInputEvents.jump_input(): 40 | transition.emit("Jump") 41 | 42 | # forced fall state 43 | if GameInputEvents.fall_input(): 44 | transition.emit("Fall") 45 | 46 | 47 | func enter(): 48 | muzzle.position = Vector2(21, -26) 49 | muzzle_position = muzzle.position 50 | 51 | animated_sprite_2d.play("shoot_wall_cling") 52 | 53 | 54 | func exit(): 55 | wall_cling_direction = Vector2.ZERO 56 | animated_sprite_2d.stop() 57 | 58 | 59 | func gun_muzzle_position(): 60 | if !animated_sprite_2d.flip_h: 61 | muzzle.position.x = muzzle_position.x 62 | elif animated_sprite_2d.flip_h: 63 | muzzle.position.x = -muzzle_position.x 64 | 65 | 66 | func gun_shooting(): 67 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 68 | 69 | var bullet_instance = bullet.instantiate() as Node2D 70 | bullet_instance.direction = direction 71 | bullet_instance.move_x_direction = true 72 | bullet_instance.global_position = muzzle.global_position 73 | get_parent().add_child(bullet_instance) 74 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/collectible_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | static var total_award_amount : int 4 | 5 | signal on_collectible_award_received 6 | 7 | 8 | func give_pickup_award(collectible_award : int): 9 | total_award_amount += collectible_award 10 | 11 | on_collectible_award_received.emit(total_award_amount) 12 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/game_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var main_menu_screen = preload("res://ui/main_menu_screen.tscn") 4 | var pause_menu_screen = preload("res://ui/pause_menu_screen.tscn") 5 | 6 | func _ready(): 7 | RenderingServer.set_default_clear_color(Color(0.44,0.12,0.53,1.00)) 8 | 9 | SettingsManager.load_settings() 10 | 11 | 12 | func start_game(): 13 | if get_tree().paused: 14 | continue_game() 15 | return 16 | 17 | SceneManager.transition_to_scene("Level1") 18 | 19 | 20 | func exit_game(): 21 | get_tree().quit() 22 | 23 | 24 | func pause_game(): 25 | get_tree().paused = true 26 | 27 | var pause_menu_screen_instance = pause_menu_screen.instantiate() 28 | get_tree().get_root().add_child(pause_menu_screen_instance) 29 | 30 | 31 | func continue_game(): 32 | get_tree().paused = false 33 | 34 | 35 | func main_menu(): 36 | var main_menu_screen_instance = main_menu_screen.instantiate() 37 | get_tree().get_root().add_child(main_menu_screen_instance) 38 | 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/health_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var max_health : int = 3 4 | var current_health : int 5 | 6 | signal on_health_changed 7 | 8 | 9 | func _ready(): 10 | current_health = max_health 11 | 12 | 13 | func decrease_health(health_amount : int): 14 | current_health -= health_amount 15 | 16 | if current_health < 0: 17 | current_health = 0 18 | 19 | print("decrease_health called") 20 | on_health_changed.emit(current_health) 21 | 22 | 23 | func increase_health(health_amount : int): 24 | current_health += health_amount 25 | 26 | if current_health > max_health: 27 | current_health = max_health 28 | 29 | print("increase_health called") 30 | on_health_changed.emit(current_health) 31 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/inventory_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var inventory : Dictionary 4 | 5 | 6 | func add_to_inventory(type : String, value : String): 7 | inventory[type] = value 8 | 9 | 10 | func has_inventory_item(value : String) -> bool: 11 | if value == null: 12 | return false 13 | 14 | var item = inventory.find_key(value) 15 | 16 | if item: 17 | return true 18 | 19 | return false 20 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/scene_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var scene_transition_screen = preload("res://ui/screen_transition/scene_transition_screen.tscn") 4 | 5 | var scenes : Dictionary = { "Level1": "res://levels/level_1.tscn", 6 | "Level2": "res://levels/level_2.tscn" } 7 | 8 | 9 | func transition_to_scene(level : String): 10 | var scene_path : String = scenes.get(level) 11 | 12 | if scene_path != null: 13 | var scene_transition_screen_instance = scene_transition_screen.instantiate() 14 | get_tree().get_root().add_child(scene_transition_screen_instance) 15 | await get_tree().create_timer(5.0).timeout 16 | get_tree().change_scene_to_file(scene_path) 17 | scene_transition_screen_instance.queue_free() 18 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/settings_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var settings_data : SettingsDataResource 4 | 5 | # %APPDATA%\Godot\app_userdata\Chronobot\game_data 6 | var save_settings_path = "user://game_data/" 7 | var save_file_name = "settings_data.tres" 8 | 9 | 10 | func load_settings(): 11 | if !DirAccess.dir_exists_absolute(save_settings_path): 12 | DirAccess.make_dir_absolute(save_settings_path) 13 | 14 | if ResourceLoader.exists(save_settings_path + save_file_name): 15 | settings_data = ResourceLoader.load(save_settings_path + save_file_name) 16 | 17 | if settings_data == null: 18 | settings_data = SettingsDataResource.new() 19 | 20 | if settings_data != null: 21 | set_window_mode(settings_data.window_mode, settings_data.window_mode_index) 22 | set_resolution(settings_data.resolution, settings_data.resolution_index) 23 | 24 | 25 | func set_window_mode(window_mode : int, window_mode_index : int): 26 | match window_mode: 27 | DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN: 28 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN) 29 | DisplayServer.WINDOW_MODE_WINDOWED: 30 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) 31 | DisplayServer.WINDOW_MODE_MAXIMIZED: 32 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED) 33 | _: 34 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) 35 | 36 | settings_data.window_mode = window_mode 37 | settings_data.window_mode_index = window_mode_index 38 | 39 | 40 | func set_resolution(resolution : Vector2i, resolution_index : int): 41 | get_tree().root.content_scale_size = resolution 42 | settings_data.resolution = resolution 43 | settings_data.resolution_index = resolution_index 44 | 45 | 46 | func get_settings() -> SettingsDataResource: 47 | return settings_data 48 | 49 | 50 | func save_settings(): 51 | ResourceSaver.save(settings_data, save_settings_path + save_file_name) 52 | 53 | 54 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/state_machine/node_finite_state_machine.gd: -------------------------------------------------------------------------------- 1 | class_name NodeFiniteStateMachine 2 | extends Node 3 | 4 | @export var initial_node_state : NodeState 5 | 6 | var node_states : Dictionary = {} 7 | var current_node_state : NodeState 8 | var current_node_state_name : String 9 | 10 | func _ready(): 11 | for child in get_children(): 12 | if child is NodeState: 13 | node_states[child.name.to_lower()] = child 14 | child.transition.connect(transition_to) 15 | 16 | if initial_node_state: 17 | initial_node_state.enter() 18 | current_node_state = initial_node_state 19 | 20 | 21 | func _process(delta : float): 22 | if current_node_state: 23 | current_node_state.on_process(delta) 24 | 25 | 26 | func _physics_process(delta: float): 27 | if current_node_state: 28 | current_node_state.on_physics_process(delta) 29 | 30 | print("Current State: ", current_node_state.name.to_lower()) 31 | 32 | 33 | func transition_to(node_state_name : String): 34 | if node_state_name == current_node_state.name.to_lower(): 35 | return 36 | 37 | var new_node_state = node_states.get(node_state_name.to_lower()) 38 | 39 | if !new_node_state: 40 | return 41 | 42 | if current_node_state: 43 | current_node_state.exit() 44 | 45 | new_node_state.enter() 46 | 47 | current_node_state = new_node_state 48 | current_node_state_name = current_node_state.name.to_lower() 49 | 50 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/scripts/state_machine/node_state.gd: -------------------------------------------------------------------------------- 1 | class_name NodeState 2 | extends Node 3 | 4 | signal transition 5 | 6 | func on_process(delta : float): 7 | pass 8 | 9 | 10 | func on_physics_process(delta : float): 11 | pass 12 | 13 | 14 | func enter(): 15 | pass 16 | 17 | 18 | func exit(): 19 | pass 20 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/game_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @onready var collectible_label = $MarginContainer/VBoxContainer/HBoxContainer/CollectibleLabel 4 | 5 | 6 | func _ready(): 7 | CollectibleManager.on_collectible_award_received.connect(on_collectible_award_received) 8 | 9 | 10 | func on_collectible_award_received(total_award : int): 11 | collectible_label.text = str(total_award) 12 | 13 | 14 | func _on_pause_texture_button_pressed(): 15 | GameManager.pause_game() 16 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/health_bar/health_bar.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var heart1 : Texture2D 4 | @export var heart0 : Texture2D 5 | 6 | @onready var heart_1 = $Heart1 7 | @onready var heart_2 = $Heart2 8 | @onready var heart_3 = $Heart3 9 | 10 | 11 | func _ready(): 12 | HealthManager.on_health_changed.connect(on_player_health_changed) 13 | 14 | 15 | func on_player_health_changed(player_current_health : int): 16 | if player_current_health == 3: 17 | heart_3.texture = heart1 18 | elif player_current_health < 3: 19 | heart_3.texture = heart0 20 | 21 | if player_current_health == 2: 22 | heart_2.texture = heart1 23 | elif player_current_health < 2: 24 | heart_2.texture = heart0 25 | 26 | if player_current_health == 1: 27 | heart_1.texture = heart1 28 | elif player_current_health < 1: 29 | heart_1.texture = heart0 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/main_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | var settings_menu_screen = preload("res://ui/settings_menu_screen.tscn") 4 | 5 | func _on_play_button_pressed(): 6 | GameManager.start_game() 7 | queue_free() 8 | 9 | 10 | func _on_exit_button_pressed(): 11 | GameManager.exit_game() 12 | 13 | 14 | func _on_settings_button_pressed(): 15 | var settings_menu_screen_instance = settings_menu_screen.instantiate() 16 | get_tree().get_root().add_child(settings_menu_screen_instance) 17 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/pause_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | 4 | func _on_continue_button_pressed(): 5 | GameManager.continue_game() 6 | queue_free() 7 | 8 | 9 | func _on_main_menu_button_pressed(): 10 | GameManager.main_menu() 11 | queue_free() 12 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/screen_transition/scene_transition_screen.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform sampler2D noise_texture; 4 | uniform sampler2D dissolve_noise_texture; 5 | uniform vec4 colour_1 : source_color = vec4(0.0, 0.0, 0.0, 1.0); 6 | uniform vec4 colour_2 : source_color = vec4(34.0, 32.0, 12.0, 1.0); 7 | uniform float tiling_factor : hint_range(1.0, 10.0) = 1.0; 8 | uniform float move_speed : hint_range(0.1, 5.0) = 0.1; 9 | uniform float subtraction_speed : hint_range(0.1, 5.0) = 1.0; 10 | 11 | void fragment() { 12 | // Calculate UV coordinates and apply tiling 13 | vec2 uv = UV * tiling_factor; 14 | 15 | // Offset UV coordinates to move the noise texture up or down 16 | uv.y += TIME * -move_speed; 17 | 18 | // Adjust UV coordinates for seamless tiling 19 | uv = fract(uv); 20 | 21 | // Sample the noise texture using the modified UV coordinates 22 | float noise_value = texture(noise_texture, uv).r; 23 | 24 | // Sample the dissolve noise texture using the modified UV coordinates 25 | float dissolve_value = texture(dissolve_noise_texture, uv).r; 26 | 27 | // Calculate the time-dependent factor for subtraction 28 | float subtraction_factor = sin(TIME * subtraction_speed); 29 | 30 | // Subtract the values of the dissolve noise texture from the noise texture 31 | float final_value = noise_value - (dissolve_value * subtraction_factor); 32 | 33 | // Interpolate between the 2 colours based on the final value 34 | vec4 final_colour = mix(colour_1, colour_2, final_value); 35 | 36 | // Output the final color 37 | COLOR = final_colour; 38 | } 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/settings_data_resource.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name SettingsDataResource 3 | 4 | @export var window_mode : int = 0 5 | @export var window_mode_index : int = 1 6 | @export var resolution : Vector2i = Vector2i(480, 270) 7 | @export var resolution_index : int = 1 8 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v1/scripts/ui/settings_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @onready var window_mode_option_button = $MarginContainer/PanelContainer/MarginContainer/VBoxContainer/WindowModeOptionButton 4 | @onready var resolution_option_button = $MarginContainer/PanelContainer/MarginContainer/VBoxContainer/ResolutionOptionButton 5 | 6 | var window_modes : Dictionary = {"Fullscreen" : DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN, 7 | "Window" : DisplayServer.WINDOW_MODE_WINDOWED, 8 | "Window Maximized" : DisplayServer.WINDOW_MODE_MAXIMIZED } 9 | 10 | var resolutions : Dictionary = {"320x180" : Vector2i(320, 180), 11 | "480x270" : Vector2i(480, 270), 12 | "640x360" : Vector2i(640, 360), 13 | "854x480" : Vector2i(640, 360), 14 | "1280x720" : Vector2i(1280, 720)} 15 | 16 | 17 | func _ready(): 18 | for window_mode in window_modes: 19 | window_mode_option_button.add_item(window_mode) 20 | 21 | for resolution in resolutions: 22 | resolution_option_button.add_item(resolution) 23 | 24 | initialise_controls() 25 | 26 | 27 | func initialise_controls(): 28 | SettingsManager.load_settings() 29 | var settings_data : SettingsDataResource = SettingsManager.get_settings() 30 | window_mode_option_button.selected = settings_data.window_mode_index 31 | resolution_option_button.selected = settings_data.resolution_index 32 | 33 | 34 | func _on_window_mode_option_button_item_selected(index): 35 | var window_mode = window_modes.get(window_mode_option_button.get_item_text(index)) as int 36 | SettingsManager.set_window_mode(window_mode, index) 37 | 38 | 39 | func _on_resolution_option_button_item_selected(index): 40 | var resolution = resolutions.get(resolution_option_button.get_item_text(index)) as Vector2i 41 | SettingsManager.set_resolution(resolution, index) 42 | 43 | 44 | func _on_main_menu_button_pressed(): 45 | SettingsManager.save_settings() 46 | queue_free() 47 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/collectibles/blue_coin/blue_coin.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 1 4 | 5 | @onready var animated_sprite_2d = $AnimatedSprite2D 6 | @onready var label = $Label 7 | 8 | 9 | func _ready(): 10 | label.hide() 11 | 12 | 13 | func _on_area_2d_body_entered(body): 14 | if body.is_in_group("Player"): 15 | print(award_amount) 16 | 17 | animated_sprite_2d.hide() 18 | 19 | label.text = "%s" % award_amount 20 | CollectibleManager.give_pickup_award(award_amount) 21 | 22 | label.show() 23 | 24 | var tween = get_tree().create_tween() 25 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 26 | tween.tween_callback(queue_free) 27 | 28 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/collectibles/green_diamond/green_diamond.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 5 4 | 5 | @onready var label = $Label 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | 8 | func _ready(): 9 | label.hide() 10 | 11 | 12 | func _on_area_2d_body_entered(body : Node2D): 13 | if body == null: 14 | return 15 | 16 | if body.is_in_group("Player"): 17 | print(award_amount) 18 | 19 | animated_sprite_2d.hide() 20 | 21 | label.text = "%s" % award_amount 22 | CollectibleManager.give_pickup_award(award_amount) 23 | 24 | label.show() 25 | 26 | var tween = get_tree().create_tween() 27 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 28 | tween.tween_callback(queue_free) 29 | 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/collectibles/pink_gem/pink_gem.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var award_amount : int = 3 4 | 5 | @onready var label = $Label 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | 8 | func _ready(): 9 | label.hide() 10 | 11 | 12 | func _on_area_2d_body_entered(body : Node2D): 13 | if body == null: 14 | return 15 | 16 | if body.is_in_group("Player"): 17 | print(award_amount) 18 | 19 | animated_sprite_2d.hide() 20 | 21 | label.text = "%s" % award_amount 22 | CollectibleManager.give_pickup_award(award_amount) 23 | 24 | label.show() 25 | 26 | var tween = get_tree().create_tween() 27 | tween.tween_property(label, "position", Vector2(label.position.x, label.position.y + -10), 0.5).from_current() 28 | tween.tween_callback(queue_free) 29 | 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/crab/enemy_crab.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | var enemy_death_effect = preload("res://enemies/enemy_death_effect.tscn") 4 | 5 | @export var patrol_points : Node 6 | @export var speed : int = 1500 7 | @export var wait_time : int = 3 8 | @export var health_amount : int = 3 9 | @export var damage_amount : int = 1 10 | 11 | @onready var animated_sprite_2d = $AnimatedSprite2D 12 | @onready var timer = $Timer 13 | 14 | const GRAVITY = 1000 15 | 16 | enum State { Idle, Walk } 17 | var current_state : State 18 | var direction : Vector2 = Vector2.LEFT 19 | var number_of_points : int 20 | var point_positions : Array[Vector2] 21 | var current_point : Vector2 22 | var current_point_position : int 23 | var can_walk : bool 24 | 25 | 26 | func _ready(): 27 | if patrol_points != null: 28 | number_of_points = patrol_points.get_children().size() 29 | for point in patrol_points.get_children(): 30 | point_positions.append(point.global_position) 31 | current_point = point_positions[current_point_position] 32 | else: 33 | print("No patrol points") 34 | 35 | timer.wait_time = wait_time 36 | 37 | current_state = State.Idle 38 | 39 | 40 | func _physics_process(delta : float): 41 | enemy_gravity(delta) 42 | enemy_idle(delta) 43 | enemy_walk(delta) 44 | 45 | move_and_slide() 46 | 47 | enemy_animations() 48 | 49 | 50 | func enemy_gravity(delta : float): 51 | velocity.y += GRAVITY * delta 52 | 53 | 54 | func enemy_idle(delta : float): 55 | if !can_walk: 56 | velocity.x = move_toward(velocity.x, 0, speed * delta) 57 | current_state = State.Idle 58 | 59 | 60 | func enemy_walk(delta : float): 61 | if !can_walk: 62 | return 63 | 64 | if abs(position.x - current_point.x) > 0.5: 65 | velocity.x = direction.x * speed * delta 66 | current_state = State.Walk 67 | else: 68 | current_point_position += 1 69 | 70 | if current_point_position >= number_of_points: 71 | current_point_position = 0 72 | 73 | current_point = point_positions[current_point_position]; 74 | 75 | if current_point.x > position.x: 76 | direction = Vector2.RIGHT 77 | else: 78 | direction = Vector2.LEFT 79 | 80 | can_walk = false 81 | timer.start() 82 | 83 | animated_sprite_2d.flip_h = direction.x > 0 84 | 85 | 86 | func enemy_animations(): 87 | if current_state == State.Idle && !can_walk: 88 | animated_sprite_2d.play("idle") 89 | elif current_state == State.Walk && can_walk: 90 | animated_sprite_2d.play("walk") 91 | 92 | func _on_timer_timeout(): 93 | can_walk = true 94 | 95 | 96 | func _on_hurtbox_area_entered(area : Area2D): 97 | print("Hurtbox area entered") 98 | if area.get_parent().has_method("get_damage_amount"): 99 | var node = area.get_parent() as Node 100 | health_amount -= node.damage_amount 101 | print("Health amount: ", health_amount) 102 | 103 | if health_amount <= 0: 104 | var enemy_death_effect_instance = enemy_death_effect.instantiate() as Node2D 105 | enemy_death_effect_instance.global_position = global_position 106 | get_parent().add_child(enemy_death_effect_instance) 107 | queue_free() 108 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/dino/attack_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | @export var speed : int 6 | 7 | var player : CharacterBody2D 8 | var max_speed : int 9 | 10 | func on_process(delta : float): 11 | pass 12 | 13 | 14 | func on_physics_process(delta : float): 15 | var direction : int 16 | 17 | if character_body_2d.global_position > player.global_position: 18 | animated_sprite_2d.flip_h = false 19 | direction = -1 20 | elif character_body_2d.global_position < player.global_position: 21 | animated_sprite_2d.flip_h = true 22 | direction = 1 23 | 24 | animated_sprite_2d.play("attack") 25 | 26 | character_body_2d.velocity.x += direction * speed * delta 27 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_speed, max_speed) 28 | character_body_2d.move_and_slide() 29 | 30 | 31 | func enter(): 32 | player = get_tree().get_nodes_in_group("Player")[0] as CharacterBody2D 33 | max_speed = speed + 20 34 | 35 | 36 | func exit(): 37 | pass 38 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/dino/gravity.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | const GRAVITY : int = 1000 7 | 8 | 9 | func _physics_process(delta): 10 | if !character_body_2d.is_on_floor(): 11 | character_body_2d.velocity.y += GRAVITY * delta 12 | 13 | character_body_2d.move_and_slide() 14 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/dino/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var slow_down_speed : int = 50 6 | 7 | func on_process(delta : float): 8 | pass 9 | 10 | 11 | func on_physics_process(delta : float): 12 | character_body_2d.velocity.x = move_toward(character_body_2d.velocity.x, 0, slow_down_speed * delta) 13 | animated_sprite_2d.play("idle") 14 | character_body_2d.move_and_slide() 15 | 16 | 17 | 18 | func enter(): 19 | pass 20 | 21 | 22 | func exit(): 23 | pass 24 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/dino/state_machine_controller.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var node_finite_state_machine : NodeFiniteStateMachine 4 | 5 | 6 | func _on_attack_area_2d_body_entered(body : Node2D): 7 | if body.is_in_group("Player"): 8 | node_finite_state_machine.transition_to("attack") 9 | 10 | 11 | func _on_attack_area_2d_body_exited(body): 12 | if body.is_in_group("Player"): 13 | node_finite_state_machine.transition_to("idle") 14 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/enemies/enemy_death_effect.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/levels/door/door.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var animated_sprite_2d = $AnimatedSprite2D 4 | @onready var collision_shape_2d = $CollisionShape2D 5 | 6 | @export var next_scene :String 7 | @export var key_id : String 8 | 9 | 10 | var door_open : bool 11 | 12 | 13 | func _on_exit_area_2d_body_entered(body): 14 | if body.is_in_group("Player"): 15 | var player = body as CharacterBody2D 16 | player.queue_free() 17 | 18 | await get_tree().create_timer(3.0).timeout 19 | SceneManager.transition_to_scene(next_scene) 20 | 21 | 22 | func _on_activate_door_area_2d_body_entered(body): 23 | if body.is_in_group("Player"): 24 | var has_item : bool = InventoryManager.has_inventory_item(key_id) 25 | 26 | if !has_item: 27 | return 28 | 29 | if !door_open: 30 | animated_sprite_2d.play("open") 31 | door_open = true 32 | collision_shape_2d.set_deferred("disabled", true) 33 | 34 | 35 | func _on_activate_door_area_2d_body_exited(body): 36 | if body.is_in_group("Player"): 37 | if door_open: 38 | animated_sprite_2d.play("close") 39 | door_open = false 40 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/levels/keys/key.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var key_id : String 4 | 5 | 6 | func _on_area_2d_body_entered(body): 7 | if body.is_in_group("Player"): 8 | InventoryManager.add_to_inventory("Key1", key_id) 9 | queue_free() 10 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/pickups/health_pickup/health_pickup.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var pickup_amount : int = 1 4 | 5 | 6 | func _on_health_pickup_box_body_entered(body): 7 | if body.is_in_group("Player"): 8 | HealthManager.increase_health(pickup_amount) 9 | queue_free() 10 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/BulletImpactEffect.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/bullet.gd: -------------------------------------------------------------------------------- 1 | extends AnimatedSprite2D 2 | 3 | var bullet_impact_effect = preload("res://player/bullet_impact_effect.tscn") 4 | 5 | var speed : int = 600 6 | var direction : int 7 | var damage_amount : int = 1 8 | var move_x_direction : bool 9 | 10 | 11 | func _physics_process(delta): 12 | if move_x_direction: 13 | move_local_x(direction * speed * delta) 14 | else: 15 | move_local_y(direction * speed * delta) 16 | 17 | 18 | func _on_timer_timeout(): 19 | queue_free() 20 | 21 | 22 | func _on_hitbox_area_entered(area): 23 | print("Bullet area entered") 24 | bullet_impact() 25 | 26 | 27 | func _on_hitbox_body_entered(body): 28 | print("Bullet body entered") 29 | bullet_impact() 30 | 31 | 32 | func get_damage_amount() -> int: 33 | return damage_amount 34 | 35 | 36 | func bullet_impact(): 37 | var bullet_impact_effect_instance = bullet_impact_effect.instantiate() as Node2D 38 | bullet_impact_effect_instance.global_position = global_position 39 | get_parent().add_child(bullet_impact_effect_instance) 40 | queue_free() 41 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/game_input_events.gd: -------------------------------------------------------------------------------- 1 | class_name GameInputEvents 2 | extends Node 3 | 4 | 5 | static func movement_input() -> float: 6 | var direction : float = Input.get_axis("move_left", "move_right") 7 | return direction 8 | 9 | 10 | static func jump_input() -> bool: 11 | var jump_input : bool = Input.is_action_just_pressed("jump") 12 | return jump_input 13 | 14 | 15 | static func shoot_input() -> bool: 16 | var shoot_input : bool = Input.is_action_just_pressed("shoot") 17 | return shoot_input 18 | 19 | 20 | static func shoot_up_input() -> bool: 21 | var shoot_input : bool = Input.is_action_just_pressed("shoot") 22 | var up_input : bool = Input.is_action_pressed("look_up") 23 | return up_input and shoot_input 24 | 25 | 26 | static func crouch_input() -> bool: 27 | var crouch_input : bool = Input.is_action_just_pressed("crouch") 28 | return crouch_input 29 | 30 | 31 | static func fall_input() -> bool: 32 | var fall_input : bool = Input.is_action_just_pressed("force_fall") 33 | return fall_input 34 | 35 | 36 | static func wall_cling_input() -> bool: 37 | var wall_cling_input : bool = Input.is_action_pressed("wall_cling") 38 | return wall_cling_input 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | var player_death_effect = preload("res://player/player_death_effect/player_death_effect.tscn") 5 | 6 | @onready var animated_sprite_2d = $AnimatedSprite2D 7 | @onready var muzzle : Marker2D = $Muzzle 8 | 9 | 10 | func player_death(): 11 | var player_death_effect_instance = player_death_effect.instantiate() as Node2D 12 | player_death_effect_instance.global_position = global_position 13 | get_parent().add_child(player_death_effect_instance) 14 | queue_free() 15 | 16 | 17 | func _on_hurtbox_body_entered(body : Node2D): 18 | if body.is_in_group("Enemy"): 19 | print("Enemy entered ", body.damage_amount) 20 | 21 | var tween = get_tree().create_tween() 22 | tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", true, 0) 23 | tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", false, 0.2) 24 | 25 | HealthManager.decrease_health(body.damage_amount) 26 | 27 | if HealthManager.current_health == 0: 28 | player_death() 29 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_camera.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | @export_category("Follow Character") 4 | @export var player : CharacterBody2D 5 | 6 | @export_category("Camera Smoothing") 7 | @export var smoothing_enabled : bool 8 | @export_range(1, 10) var smoothing_distance : int = 8 9 | 10 | var weight : float 11 | 12 | func _ready(): 13 | weight = float(11 - smoothing_distance) / 100 14 | 15 | 16 | func _physics_process(delta): 17 | if player != null: 18 | var camera_position : Vector2 19 | 20 | if smoothing_enabled: 21 | camera_position = lerp(global_position, player.global_position, weight) 22 | else: 23 | camera_position = player.global_position 24 | 25 | global_position = camera_position.floor() 26 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_death_effect/player_death_effect.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | 4 | func _on_timer_timeout(): 5 | queue_free() 6 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/fall_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Fall State") 7 | @export var coyote_time : float = 0.1 8 | 9 | const GRAVITY : int = 700 10 | var coyote_jump : bool 11 | 12 | 13 | func on_process(delta : float): 14 | pass 15 | 16 | 17 | func on_physics_process(delta : float): 18 | if !character_body_2d.is_on_floor(): 19 | get_coyote_time() 20 | character_body_2d.velocity.y += GRAVITY * delta 21 | 22 | character_body_2d.move_and_slide() 23 | 24 | # transitioning states 25 | 26 | # idle state 27 | if character_body_2d.is_on_floor(): 28 | transition.emit("Idle") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input() and coyote_jump: 32 | transition.emit("Jump") 33 | 34 | 35 | func enter(): 36 | coyote_jump = true 37 | animated_sprite_2d.play("fall") 38 | 39 | 40 | func exit(): 41 | animated_sprite_2d.stop() 42 | 43 | 44 | func get_coyote_time(): 45 | await get_tree().create_timer(coyote_time).timeout 46 | coyote_jump = false 47 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Physics Friction") 7 | @export var slow_down_speed : int = 1700 8 | 9 | func on_process(delta : float): 10 | pass 11 | 12 | 13 | func on_physics_process(delta : float): 14 | character_body_2d.velocity.x = move_toward(character_body_2d.velocity.x, 0, slow_down_speed) 15 | 16 | character_body_2d.move_and_slide() 17 | 18 | # transitioning states 19 | 20 | # fall state 21 | if !character_body_2d.is_on_floor(): 22 | transition.emit("Fall") 23 | 24 | # run state 25 | var direction : float = GameInputEvents.movement_input() 26 | 27 | if direction and character_body_2d.is_on_floor(): 28 | transition.emit("Run") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input(): 32 | transition.emit("Jump") 33 | 34 | # shoot stand state 35 | if GameInputEvents.shoot_input(): 36 | transition.emit("ShootStand") 37 | 38 | # shoot up state 39 | if GameInputEvents.shoot_up_input(): 40 | transition.emit("ShootUp") 41 | 42 | # shoot crouch state 43 | if GameInputEvents.crouch_input(): 44 | transition.emit("ShootCrouch") 45 | 46 | 47 | func enter(): 48 | animated_sprite_2d.play("idle") 49 | 50 | 51 | func exit(): 52 | animated_sprite_2d.stop() 53 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/jump_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Jump State") 7 | @export var jump_height : float = -250 8 | @export var jump_horizontal_speed : int = 500 9 | @export var max_jump_horizontal_speed : int = 300 10 | @export var max_jump_count : int = 1 11 | @export var jump_gravity : int = 1000 12 | 13 | var current_jump_count : int 14 | var coyote_jump : bool 15 | 16 | func on_process(delta : float): 17 | pass 18 | 19 | 20 | func on_physics_process(delta : float): 21 | 22 | character_body_2d.velocity.y += jump_gravity * delta 23 | 24 | if character_body_2d.is_on_floor(): 25 | current_jump_count = 0 26 | character_body_2d.velocity.y = jump_height 27 | coyote_jump = false 28 | current_jump_count += 1 29 | 30 | if coyote_jump: 31 | character_body_2d.velocity.y = jump_height 32 | coyote_jump = false 33 | current_jump_count += 1 34 | 35 | # multiple jumps 36 | if !character_body_2d.is_on_floor() and GameInputEvents.jump_input() and current_jump_count != max_jump_count: 37 | character_body_2d.velocity.y = jump_height 38 | current_jump_count += 1 39 | 40 | var direction : float = GameInputEvents.movement_input() 41 | 42 | if !character_body_2d.is_on_floor(): 43 | character_body_2d.velocity.x += direction * jump_horizontal_speed 44 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_jump_horizontal_speed, max_jump_horizontal_speed) 45 | 46 | character_body_2d.move_and_slide() 47 | 48 | # transitioning states 49 | 50 | # idle state 51 | if character_body_2d.is_on_floor(): 52 | transition.emit("Idle") 53 | 54 | # wall cling state 55 | if GameInputEvents.wall_cling_input() and character_body_2d.is_on_wall(): 56 | transition.emit("ShootWallCling") 57 | 58 | func enter(): 59 | coyote_jump = true 60 | animated_sprite_2d.play("jump") 61 | 62 | 63 | func exit(): 64 | coyote_jump = false 65 | animated_sprite_2d.stop() 66 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/run_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character_body_2d : CharacterBody2D 4 | @export var animated_sprite_2d : AnimatedSprite2D 5 | 6 | @export_category("Run State") 7 | @export var speed : int = 1000 8 | @export var max_horizontal_speed: int = 300 9 | 10 | const GRAVITY : int = 1000 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | var direction : float = GameInputEvents.movement_input() 18 | 19 | if direction: 20 | character_body_2d.velocity.x += direction * speed 21 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_horizontal_speed, max_horizontal_speed) 22 | 23 | if direction != 0: 24 | animated_sprite_2d.flip_h = false if direction > 0 else true 25 | 26 | character_body_2d.velocity.y += GRAVITY * delta 27 | 28 | character_body_2d.move_and_slide() 29 | 30 | # transitioning states 31 | 32 | # idle state 33 | if direction == 0: 34 | transition.emit("Idle") 35 | 36 | # jump state 37 | if GameInputEvents.jump_input(): 38 | transition.emit("Jump") 39 | 40 | #shoot run state 41 | if direction != 0 and GameInputEvents.shoot_input(): 42 | transition.emit("ShootRun") 43 | 44 | # fall state 45 | if !character_body_2d.is_on_floor(): 46 | transition.emit("Fall") 47 | 48 | 49 | func enter(): 50 | animated_sprite_2d.play("run") 51 | 52 | 53 | func exit(): 54 | animated_sprite_2d.stop() 55 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/shoot_crouch_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | var muzzle_position : Vector2 10 | 11 | func on_process(delta : float): 12 | pass 13 | 14 | 15 | func on_physics_process(delta : float): 16 | 17 | gun_muzzle_position() 18 | 19 | if GameInputEvents.shoot_input(): 20 | gun_shooting() 21 | 22 | # transitioning states 23 | 24 | # run state 25 | var direction : float = GameInputEvents.movement_input() 26 | 27 | if direction and character_body_2d.is_on_floor(): 28 | transition.emit("Run") 29 | 30 | # jump state 31 | if GameInputEvents.jump_input(): 32 | transition.emit("Jump") 33 | 34 | 35 | func enter(): 36 | muzzle.position = Vector2(17, -14) 37 | muzzle_position = muzzle.position 38 | 39 | animated_sprite_2d.play("shoot_crouch") 40 | 41 | 42 | func exit(): 43 | animated_sprite_2d.stop() 44 | 45 | 46 | func gun_muzzle_position(): 47 | if !animated_sprite_2d.flip_h: 48 | muzzle.position.x = muzzle_position.x 49 | elif animated_sprite_2d.flip_h: 50 | muzzle.position.x = -muzzle_position.x 51 | 52 | 53 | func gun_shooting(): 54 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 55 | 56 | var bullet_instance = bullet.instantiate() as Node2D 57 | bullet_instance.direction = direction 58 | bullet_instance.move_x_direction = true 59 | bullet_instance.global_position = muzzle.global_position 60 | get_parent().add_child(bullet_instance) 61 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/shoot_run_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | @export_category("Run State") 10 | @export var speed : int = 1000 11 | @export var max_horizontal_speed: int = 300 12 | 13 | const GRAVITY : int = 1000 14 | var muzzle_position : Vector2 15 | 16 | func on_process(delta : float): 17 | pass 18 | 19 | 20 | func on_physics_process(delta : float): 21 | var direction : float = GameInputEvents.movement_input() 22 | 23 | gun_muzzle_position(direction) 24 | 25 | if direction: 26 | character_body_2d.velocity.x += direction * speed 27 | character_body_2d.velocity.x = clamp(character_body_2d.velocity.x, -max_horizontal_speed, max_horizontal_speed) 28 | 29 | if direction != 0: 30 | animated_sprite_2d.flip_h = false if direction > 0 else true 31 | 32 | character_body_2d.velocity.y += GRAVITY * delta 33 | 34 | if GameInputEvents.shoot_input(): 35 | gun_shooting(direction) 36 | 37 | character_body_2d.move_and_slide() 38 | 39 | # transitioning states 40 | 41 | # fall state 42 | if !character_body_2d.is_on_floor(): 43 | transition.emit("Fall") 44 | 45 | # idle state 46 | if direction == 0: 47 | transition.emit("Idle") 48 | 49 | # jump state 50 | if GameInputEvents.jump_input(): 51 | transition.emit("jump") 52 | 53 | 54 | func enter(): 55 | muzzle.position = Vector2(18, -26) 56 | muzzle_position = muzzle.position 57 | 58 | animated_sprite_2d.play("shoot_run") 59 | 60 | 61 | func exit(): 62 | animated_sprite_2d.stop() 63 | 64 | 65 | func gun_muzzle_position(direction : float): 66 | if direction > 0: 67 | muzzle.position.x = muzzle_position.x 68 | elif direction < 0: 69 | muzzle.position.x = -muzzle_position.x 70 | 71 | 72 | func gun_shooting(direction : float): 73 | var bullet_instance = bullet.instantiate() as Node2D 74 | bullet_instance.direction = direction 75 | bullet_instance.move_x_direction = true 76 | bullet_instance.global_position = muzzle.global_position 77 | get_parent().add_child(bullet_instance) 78 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/shoot_stand_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | @export var hold_gun_time : float = 2.0 9 | 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | 18 | gun_muzzle_position() 19 | 20 | if GameInputEvents.shoot_input(): 21 | gun_shooting() 22 | 23 | # transitioning states 24 | 25 | # run state 26 | var direction : float = GameInputEvents.movement_input() 27 | 28 | if direction and character_body_2d.is_on_floor(): 29 | transition.emit("Run") 30 | 31 | # jump state 32 | if GameInputEvents.jump_input(): 33 | transition.emit("Jump") 34 | 35 | 36 | func enter(): 37 | muzzle.position = Vector2(18, -26) 38 | muzzle_position = muzzle.position 39 | 40 | get_tree().create_timer(hold_gun_time).timeout.connect(on_hold_gun_timout) 41 | animated_sprite_2d.play("shoot_stand") 42 | 43 | 44 | func exit(): 45 | animated_sprite_2d.stop() 46 | 47 | 48 | func on_hold_gun_timout(): 49 | transition.emit("Idle") 50 | 51 | 52 | func gun_muzzle_position(): 53 | if !animated_sprite_2d.flip_h: 54 | muzzle.position.x = muzzle_position.x 55 | elif animated_sprite_2d.flip_h: 56 | muzzle.position.x = -muzzle_position.x 57 | 58 | 59 | func gun_shooting(): 60 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 61 | 62 | var bullet_instance = bullet.instantiate() as Node2D 63 | bullet_instance.direction = direction 64 | bullet_instance.move_x_direction = true 65 | bullet_instance.global_position = muzzle.global_position 66 | get_parent().add_child(bullet_instance) 67 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/shoot_up_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | @export var hold_gun_time : float = 2.0 9 | 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | 18 | gun_muzzle_position() 19 | 20 | if GameInputEvents.shoot_input(): 21 | gun_shooting() 22 | 23 | # transitioning states 24 | 25 | # run state 26 | var direction : float = GameInputEvents.movement_input() 27 | 28 | if direction and character_body_2d.is_on_floor(): 29 | transition.emit("Run") 30 | 31 | # jump state 32 | if GameInputEvents.jump_input(): 33 | transition.emit("Jump") 34 | 35 | 36 | func enter(): 37 | muzzle.position = Vector2(4, -42) 38 | muzzle_position = muzzle.position 39 | 40 | get_tree().create_timer(hold_gun_time).timeout.connect(on_hold_gun_timout) 41 | animated_sprite_2d.play("shoot_up") 42 | 43 | 44 | func exit(): 45 | animated_sprite_2d.stop() 46 | 47 | 48 | func on_hold_gun_timout(): 49 | transition.emit("Idle") 50 | 51 | 52 | func gun_muzzle_position(): 53 | if !animated_sprite_2d.flip_h: 54 | muzzle.position.x = muzzle_position.x 55 | elif animated_sprite_2d.flip_h: 56 | muzzle.position.x = -muzzle_position.x 57 | 58 | 59 | func gun_shooting(): 60 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 61 | 62 | var bullet_instance = bullet.instantiate() as Node2D 63 | bullet_instance.direction = -1 64 | bullet_instance.move_x_direction = false 65 | bullet_instance.global_position = muzzle.global_position 66 | get_parent().add_child(bullet_instance) 67 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/player/player_states/shoot_wall_cling_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | var bullet = preload("res://player/bullet.tscn") 4 | 5 | @export var character_body_2d : CharacterBody2D 6 | @export var animated_sprite_2d : AnimatedSprite2D 7 | @export var muzzle : Marker2D 8 | 9 | var wall_cling_direction : Vector2 10 | var muzzle_position : Vector2 11 | 12 | func on_process(delta : float): 13 | pass 14 | 15 | 16 | func on_physics_process(delta : float): 17 | character_body_2d.velocity.y = 0 18 | 19 | var direction : float = GameInputEvents.movement_input() 20 | 21 | if direction > 0 and wall_cling_direction == Vector2.ZERO: 22 | animated_sprite_2d.flip_h = true 23 | wall_cling_direction = Vector2.RIGHT 24 | 25 | if direction < 0 and wall_cling_direction == Vector2.ZERO: 26 | animated_sprite_2d.flip_h = false 27 | wall_cling_direction = Vector2.LEFT 28 | 29 | gun_muzzle_position() 30 | 31 | if GameInputEvents.shoot_input(): 32 | gun_shooting() 33 | 34 | character_body_2d.move_and_slide() 35 | 36 | # transitioning states 37 | 38 | # jump state 39 | if GameInputEvents.jump_input(): 40 | transition.emit("Jump") 41 | 42 | # forced fall state 43 | if GameInputEvents.fall_input(): 44 | transition.emit("Fall") 45 | 46 | 47 | func enter(): 48 | muzzle.position = Vector2(21, -26) 49 | muzzle_position = muzzle.position 50 | 51 | animated_sprite_2d.play("shoot_wall_cling") 52 | 53 | 54 | func exit(): 55 | wall_cling_direction = Vector2.ZERO 56 | animated_sprite_2d.stop() 57 | 58 | 59 | func gun_muzzle_position(): 60 | if !animated_sprite_2d.flip_h: 61 | muzzle.position.x = muzzle_position.x 62 | elif animated_sprite_2d.flip_h: 63 | muzzle.position.x = -muzzle_position.x 64 | 65 | 66 | func gun_shooting(): 67 | var direction : float = -1 if animated_sprite_2d.flip_h == true else 1 68 | 69 | var bullet_instance = bullet.instantiate() as Node2D 70 | bullet_instance.direction = direction 71 | bullet_instance.move_x_direction = true 72 | bullet_instance.global_position = muzzle.global_position 73 | get_parent().add_child(bullet_instance) 74 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/collectible_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | static var total_award_amount : int 4 | 5 | signal on_collectible_award_received 6 | 7 | 8 | func give_pickup_award(collectible_award : int): 9 | total_award_amount += collectible_award 10 | 11 | on_collectible_award_received.emit(total_award_amount) 12 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/game_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var main_menu_screen = preload("res://ui/main_menu_screen.tscn") 4 | var pause_menu_screen = preload("res://ui/pause_menu_screen.tscn") 5 | 6 | func _ready(): 7 | RenderingServer.set_default_clear_color(Color(0.44,0.12,0.53,1.00)) 8 | 9 | SettingsManager.load_settings() 10 | 11 | 12 | func start_game(): 13 | if get_tree().paused: 14 | continue_game() 15 | return 16 | 17 | SceneManager.transition_to_scene("Level1") 18 | 19 | 20 | func exit_game(): 21 | get_tree().quit() 22 | 23 | 24 | func pause_game(): 25 | get_tree().paused = true 26 | 27 | var pause_menu_screen_instance = pause_menu_screen.instantiate() 28 | get_tree().get_root().add_child(pause_menu_screen_instance) 29 | 30 | 31 | func continue_game(): 32 | get_tree().paused = false 33 | 34 | 35 | func main_menu(): 36 | var main_menu_screen_instance = main_menu_screen.instantiate() 37 | get_tree().get_root().add_child(main_menu_screen_instance) 38 | 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/health_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var max_health : int = 3 4 | var current_health : int 5 | 6 | signal on_health_changed 7 | 8 | 9 | func _ready(): 10 | current_health = max_health 11 | 12 | 13 | func decrease_health(health_amount : int): 14 | current_health -= health_amount 15 | 16 | if current_health < 0: 17 | current_health = 0 18 | 19 | print("decrease_health called") 20 | on_health_changed.emit(current_health) 21 | 22 | 23 | func increase_health(health_amount : int): 24 | current_health += health_amount 25 | 26 | if current_health > max_health: 27 | current_health = max_health 28 | 29 | print("increase_health called") 30 | on_health_changed.emit(current_health) 31 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/inventory_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var inventory : Dictionary 4 | 5 | 6 | func add_to_inventory(type : String, value : String): 7 | inventory[type] = value 8 | 9 | 10 | func has_inventory_item(value : String) -> bool: 11 | if value == null: 12 | return false 13 | 14 | var item = inventory.find_key(value) 15 | 16 | if item: 17 | return true 18 | 19 | return false 20 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/scene_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var scene_transition_screen = preload("res://ui/screen_transition/scene_transition_screen.tscn") 4 | 5 | var scenes : Dictionary = { "Level1": "res://levels/level_1.tscn", 6 | "Level2": "res://levels/level_2.tscn" } 7 | 8 | 9 | func transition_to_scene(level : String): 10 | var scene_path : String = scenes.get(level) 11 | 12 | if scene_path != null: 13 | var scene_transition_screen_instance = scene_transition_screen.instantiate() 14 | get_tree().get_root().add_child(scene_transition_screen_instance) 15 | await get_tree().create_timer(5.0).timeout 16 | get_tree().change_scene_to_file(scene_path) 17 | scene_transition_screen_instance.queue_free() 18 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/settings_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var settings_data : SettingsDataResource 4 | 5 | # %APPDATA%\Godot\app_userdata\Chronobot\game_data 6 | var save_settings_path = "user://game_data/" 7 | var save_file_name = "settings_data.tres" 8 | 9 | 10 | func load_settings(): 11 | if !DirAccess.dir_exists_absolute(save_settings_path): 12 | DirAccess.make_dir_absolute(save_settings_path) 13 | 14 | if ResourceLoader.exists(save_settings_path + save_file_name): 15 | settings_data = ResourceLoader.load(save_settings_path + save_file_name) 16 | 17 | if settings_data == null: 18 | settings_data = SettingsDataResource.new() 19 | 20 | if settings_data != null: 21 | set_window_mode(settings_data.window_mode, settings_data.window_mode_index) 22 | set_resolution(settings_data.resolution, settings_data.resolution_index) 23 | 24 | 25 | func set_window_mode(window_mode : int, window_mode_index : int): 26 | match window_mode: 27 | DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN: 28 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN) 29 | DisplayServer.WINDOW_MODE_WINDOWED: 30 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) 31 | DisplayServer.WINDOW_MODE_MAXIMIZED: 32 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED) 33 | _: 34 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) 35 | 36 | settings_data.window_mode = window_mode 37 | settings_data.window_mode_index = window_mode_index 38 | 39 | 40 | func set_resolution(resolution : Vector2i, resolution_index : int): 41 | get_tree().root.content_scale_size = resolution 42 | settings_data.resolution = resolution 43 | settings_data.resolution_index = resolution_index 44 | 45 | 46 | func get_settings() -> SettingsDataResource: 47 | return settings_data 48 | 49 | 50 | func save_settings(): 51 | ResourceSaver.save(settings_data, save_settings_path + save_file_name) 52 | 53 | 54 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/state_machine/node_finite_state_machine.gd: -------------------------------------------------------------------------------- 1 | class_name NodeFiniteStateMachine 2 | extends Node 3 | 4 | @export var initial_node_state : NodeState 5 | 6 | var node_states : Dictionary = {} 7 | var current_node_state : NodeState 8 | var current_node_state_name : String 9 | 10 | func _ready(): 11 | for child in get_children(): 12 | if child is NodeState: 13 | node_states[child.name.to_lower()] = child 14 | child.transition.connect(transition_to) 15 | 16 | if initial_node_state: 17 | initial_node_state.enter() 18 | current_node_state = initial_node_state 19 | 20 | 21 | func _process(delta : float): 22 | if current_node_state: 23 | current_node_state.on_process(delta) 24 | 25 | 26 | func _physics_process(delta: float): 27 | if current_node_state: 28 | current_node_state.on_physics_process(delta) 29 | 30 | print("Current State: ", current_node_state.name.to_lower()) 31 | 32 | 33 | func transition_to(node_state_name : String): 34 | if node_state_name == current_node_state.name.to_lower(): 35 | return 36 | 37 | var new_node_state = node_states.get(node_state_name.to_lower()) 38 | 39 | if !new_node_state: 40 | return 41 | 42 | if current_node_state: 43 | current_node_state.exit() 44 | 45 | new_node_state.enter() 46 | 47 | current_node_state = new_node_state 48 | current_node_state_name = current_node_state.name.to_lower() 49 | 50 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/scripts/state_machine/node_state.gd: -------------------------------------------------------------------------------- 1 | class_name NodeState 2 | extends Node 3 | 4 | signal transition 5 | 6 | func on_process(delta : float): 7 | pass 8 | 9 | 10 | func on_physics_process(delta : float): 11 | pass 12 | 13 | 14 | func enter(): 15 | pass 16 | 17 | 18 | func exit(): 19 | pass 20 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/game_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @onready var collectible_label = $MarginContainer/VBoxContainer/HBoxContainer/CollectibleLabel 4 | 5 | 6 | func _ready(): 7 | CollectibleManager.on_collectible_award_received.connect(on_collectible_award_received) 8 | 9 | 10 | func on_collectible_award_received(total_award : int): 11 | collectible_label.text = str(total_award) 12 | 13 | 14 | func _on_pause_texture_button_pressed(): 15 | GameManager.pause_game() 16 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/health_bar/health_bar.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var heart1 : Texture2D 4 | @export var heart0 : Texture2D 5 | 6 | @onready var heart_1 = $Heart1 7 | @onready var heart_2 = $Heart2 8 | @onready var heart_3 = $Heart3 9 | 10 | 11 | func _ready(): 12 | HealthManager.on_health_changed.connect(on_player_health_changed) 13 | 14 | 15 | func on_player_health_changed(player_current_health : int): 16 | if player_current_health == 3: 17 | heart_3.texture = heart1 18 | elif player_current_health < 3: 19 | heart_3.texture = heart0 20 | 21 | if player_current_health == 2: 22 | heart_2.texture = heart1 23 | elif player_current_health < 2: 24 | heart_2.texture = heart0 25 | 26 | if player_current_health == 1: 27 | heart_1.texture = heart1 28 | elif player_current_health < 1: 29 | heart_1.texture = heart0 30 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/main_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | var settings_menu_screen = preload("res://ui/settings_menu_screen.tscn") 4 | 5 | func _on_play_button_pressed(): 6 | GameManager.start_game() 7 | queue_free() 8 | 9 | 10 | func _on_exit_button_pressed(): 11 | GameManager.exit_game() 12 | 13 | 14 | func _on_settings_button_pressed(): 15 | var settings_menu_screen_instance = settings_menu_screen.instantiate() 16 | get_tree().get_root().add_child(settings_menu_screen_instance) 17 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/pause_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | 4 | func _on_continue_button_pressed(): 5 | GameManager.continue_game() 6 | queue_free() 7 | 8 | 9 | func _on_main_menu_button_pressed(): 10 | GameManager.main_menu() 11 | queue_free() 12 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/screen_transition/scene_transition_screen.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform sampler2D noise_texture; 4 | uniform sampler2D dissolve_noise_texture; 5 | uniform vec4 colour_1 : source_color = vec4(0.0, 0.0, 0.0, 1.0); 6 | uniform vec4 colour_2 : source_color = vec4(34.0, 32.0, 12.0, 1.0); 7 | uniform float tiling_factor : hint_range(1.0, 10.0) = 1.0; 8 | uniform float move_speed : hint_range(0.1, 5.0) = 0.1; 9 | uniform float subtraction_speed : hint_range(0.1, 5.0) = 1.0; 10 | 11 | void fragment() { 12 | // Calculate UV coordinates and apply tiling 13 | vec2 uv = UV * tiling_factor; 14 | 15 | // Offset UV coordinates to move the noise texture up or down 16 | uv.y += TIME * -move_speed; 17 | 18 | // Adjust UV coordinates for seamless tiling 19 | uv = fract(uv); 20 | 21 | // Sample the noise texture using the modified UV coordinates 22 | float noise_value = texture(noise_texture, uv).r; 23 | 24 | // Sample the dissolve noise texture using the modified UV coordinates 25 | float dissolve_value = texture(dissolve_noise_texture, uv).r; 26 | 27 | // Calculate the time-dependent factor for subtraction 28 | float subtraction_factor = sin(TIME * subtraction_speed); 29 | 30 | // Subtract the values of the dissolve noise texture from the noise texture 31 | float final_value = noise_value - (dissolve_value * subtraction_factor); 32 | 33 | // Interpolate between the 2 colours based on the final value 34 | vec4 final_colour = mix(colour_1, colour_2, final_value); 35 | 36 | // Output the final color 37 | COLOR = final_colour; 38 | } 39 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/settings_data_resource.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name SettingsDataResource 3 | 4 | @export var window_mode : int = 0 5 | @export var window_mode_index : int = 1 6 | @export var resolution : Vector2i = Vector2i(480, 270) 7 | @export var resolution_index : int = 1 8 | -------------------------------------------------------------------------------- /tutorials/chronobot/chronobot-v2-player-state-machine/scripts/ui/settings_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @onready var window_mode_option_button = $MarginContainer/PanelContainer/MarginContainer/VBoxContainer/WindowModeOptionButton 4 | @onready var resolution_option_button = $MarginContainer/PanelContainer/MarginContainer/VBoxContainer/ResolutionOptionButton 5 | 6 | var window_modes : Dictionary = {"Fullscreen" : DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN, 7 | "Window" : DisplayServer.WINDOW_MODE_WINDOWED, 8 | "Window Maximized" : DisplayServer.WINDOW_MODE_MAXIMIZED } 9 | 10 | var resolutions : Dictionary = {"320x180" : Vector2i(320, 180), 11 | "480x270" : Vector2i(480, 270), 12 | "640x360" : Vector2i(640, 360), 13 | "854x480" : Vector2i(640, 360), 14 | "1280x720" : Vector2i(1280, 720)} 15 | 16 | 17 | func _ready(): 18 | for window_mode in window_modes: 19 | window_mode_option_button.add_item(window_mode) 20 | 21 | for resolution in resolutions: 22 | resolution_option_button.add_item(resolution) 23 | 24 | initialise_controls() 25 | 26 | 27 | func initialise_controls(): 28 | SettingsManager.load_settings() 29 | var settings_data : SettingsDataResource = SettingsManager.get_settings() 30 | window_mode_option_button.selected = settings_data.window_mode_index 31 | resolution_option_button.selected = settings_data.resolution_index 32 | 33 | 34 | func _on_window_mode_option_button_item_selected(index): 35 | var window_mode = window_modes.get(window_mode_option_button.get_item_text(index)) as int 36 | SettingsManager.set_window_mode(window_mode, index) 37 | 38 | 39 | func _on_resolution_option_button_item_selected(index): 40 | var resolution = resolutions.get(resolution_option_button.get_item_text(index)) as Vector2i 41 | SettingsManager.set_resolution(resolution, index) 42 | 43 | 44 | func _on_main_menu_button_pressed(): 45 | SettingsManager.save_settings() 46 | queue_free() 47 | -------------------------------------------------------------------------------- /tutorials/croptails/audio/sfx/chicken-cluck-1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapidvectors/tutorial-components-and-scripts/430563b18cbd9d368f834e983a36d28e5448afe8/tutorials/croptails/audio/sfx/chicken-cluck-1.ogg -------------------------------------------------------------------------------- /tutorials/croptails/audio/sfx/chicken-cluck-2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapidvectors/tutorial-components-and-scripts/430563b18cbd9d368f834e983a36d28e5448afe8/tutorials/croptails/audio/sfx/chicken-cluck-2.ogg -------------------------------------------------------------------------------- /tutorials/croptails/audio/sfx/chicken-cluck-3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapidvectors/tutorial-components-and-scripts/430563b18cbd9d368f834e983a36d28e5448afe8/tutorials/croptails/audio/sfx/chicken-cluck-3.ogg -------------------------------------------------------------------------------- /tutorials/croptails/audio/sfx/cow-moo.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapidvectors/tutorial-components-and-scripts/430563b18cbd9d368f834e983a36d28e5448afe8/tutorials/croptails/audio/sfx/cow-moo.ogg -------------------------------------------------------------------------------- /tutorials/croptails/scripts/dialogue/base_game_dialogue_balloon.gd: -------------------------------------------------------------------------------- 1 | class_name BaseGameDialogueBalloon 2 | extends CanvasLayer 3 | 4 | ## A basic dialogue balloon for use with Dialogue Manager. 5 | 6 | ## The action to use for advancing the dialogue 7 | @export var next_action: StringName = &"ui_accept" 8 | 9 | ## The action to use to skip typing the dialogue 10 | @export var skip_action: StringName = &"ui_cancel" 11 | 12 | ## The dialogue resource 13 | var resource: DialogueResource 14 | 15 | ## Temporary game states 16 | var temporary_game_states: Array = [] 17 | 18 | ## See if we are waiting for the player 19 | var is_waiting_for_input: bool = false 20 | 21 | ## See if we are running a long mutation and should hide the balloon 22 | var will_hide_balloon: bool = false 23 | 24 | var _locale: String = TranslationServer.get_locale() 25 | 26 | ## The current line 27 | var dialogue_line: DialogueLine: 28 | set(next_dialogue_line): 29 | is_waiting_for_input = false 30 | balloon.focus_mode = Control.FOCUS_ALL 31 | balloon.grab_focus() 32 | 33 | # The dialogue has finished so close the balloon 34 | if not next_dialogue_line: 35 | queue_free() 36 | return 37 | 38 | # If the node isn't ready yet then none of the labels will be ready yet either 39 | if not is_node_ready(): 40 | await ready 41 | 42 | dialogue_line = next_dialogue_line 43 | 44 | character_label.visible = not dialogue_line.character.is_empty() 45 | character_label.text = tr(dialogue_line.character, "dialogue") 46 | 47 | dialogue_label.hide() 48 | dialogue_label.dialogue_line = dialogue_line 49 | 50 | responses_menu.hide() 51 | responses_menu.set_responses(dialogue_line.responses) 52 | 53 | # Show our balloon 54 | balloon.show() 55 | will_hide_balloon = false 56 | 57 | dialogue_label.show() 58 | if not dialogue_line.text.is_empty(): 59 | dialogue_label.type_out() 60 | await dialogue_label.finished_typing 61 | 62 | # Wait for input 63 | if dialogue_line.responses.size() > 0: 64 | balloon.focus_mode = Control.FOCUS_NONE 65 | responses_menu.show() 66 | elif dialogue_line.time != "": 67 | var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float() 68 | await get_tree().create_timer(time).timeout 69 | next(dialogue_line.next_id) 70 | else: 71 | is_waiting_for_input = true 72 | balloon.focus_mode = Control.FOCUS_ALL 73 | balloon.grab_focus() 74 | get: 75 | return dialogue_line 76 | 77 | ## The base balloon anchor 78 | @onready var balloon: Control = %Balloon 79 | 80 | ## The label showing the name of the currently speaking character 81 | @onready var character_label: RichTextLabel = %CharacterLabel 82 | 83 | ## The label showing the currently spoken dialogue 84 | @onready var dialogue_label: DialogueLabel = %DialogueLabel 85 | 86 | ## The menu of responses 87 | @onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu 88 | 89 | 90 | func _ready() -> void: 91 | balloon.hide() 92 | Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated) 93 | 94 | # If the responses menu doesn't have a next action set, use this one 95 | if responses_menu.next_action.is_empty(): 96 | responses_menu.next_action = next_action 97 | 98 | 99 | func _unhandled_input(_event: InputEvent) -> void: 100 | # Only the balloon is allowed to handle input while it's showing 101 | get_viewport().set_input_as_handled() 102 | 103 | 104 | func _notification(what: int) -> void: 105 | ## Detect a change of locale and update the current dialogue line to show the new language 106 | if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label): 107 | _locale = TranslationServer.get_locale() 108 | var visible_ratio = dialogue_label.visible_ratio 109 | self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id) 110 | if visible_ratio < 1: 111 | dialogue_label.skip_typing() 112 | 113 | 114 | ## Start some dialogue 115 | func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void: 116 | temporary_game_states = [self] + extra_game_states 117 | is_waiting_for_input = false 118 | resource = dialogue_resource 119 | self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states) 120 | 121 | 122 | ## Go to the next line 123 | func next(next_id: String) -> void: 124 | self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states) 125 | 126 | 127 | #region Signals 128 | 129 | 130 | func _on_mutated(_mutation: Dictionary) -> void: 131 | is_waiting_for_input = false 132 | will_hide_balloon = true 133 | get_tree().create_timer(0.1).timeout.connect(func(): 134 | if will_hide_balloon: 135 | will_hide_balloon = false 136 | balloon.hide() 137 | ) 138 | 139 | 140 | func _on_balloon_gui_input(event: InputEvent) -> void: 141 | # See if we need to skip typing of the dialogue 142 | if dialogue_label.is_typing: 143 | var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() 144 | var skip_button_was_pressed: bool = event.is_action_pressed(skip_action) 145 | if mouse_was_clicked or skip_button_was_pressed: 146 | get_viewport().set_input_as_handled() 147 | dialogue_label.skip_typing() 148 | return 149 | 150 | if not is_waiting_for_input: return 151 | if dialogue_line.responses.size() > 0: return 152 | 153 | # When there are no response options the balloon itself is the clickable thing 154 | get_viewport().set_input_as_handled() 155 | 156 | if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: 157 | next(dialogue_line.next_id) 158 | elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon: 159 | next(dialogue_line.next_id) 160 | 161 | 162 | func _on_responses_menu_response_selected(response: DialogueResponse) -> void: 163 | next(response.next_id) 164 | 165 | 166 | #endregion 167 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/dialogue/game_dialogue_balloon.gd: -------------------------------------------------------------------------------- 1 | extends BaseGameDialogueBalloon 2 | 3 | @onready var emotes_panel: Panel = $Balloon/Panel/Dialogue/BoxContainer/EmotesPanel 4 | 5 | func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void: 6 | super.start(dialogue_resource, title, extra_game_states) 7 | emotes_panel.play_emote("emote_12_talking") 8 | 9 | func next(next_id: String) -> void: 10 | super.next(next_id) 11 | emotes_panel.play_emote("emote_12_talking") 12 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/resources/node_data_resource.gd: -------------------------------------------------------------------------------- 1 | class_name NodeDataResource 2 | extends Resource 3 | 4 | @export var global_position: Vector2 5 | @export var node_path: NodePath 6 | @export var parent_node_path: NodePath 7 | 8 | 9 | func _save_data(node: Node2D) -> void: 10 | global_position = node.global_position 11 | node_path = node.get_path() 12 | 13 | var parent_node = node.get_parent() 14 | 15 | if parent_node != null: 16 | parent_node_path = parent_node.get_path() 17 | 18 | 19 | func _load_data(_window: Window) -> void: 20 | pass 21 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/resources/save_game_data_resource.gd: -------------------------------------------------------------------------------- 1 | class_name SaveGameDataResource 2 | extends Resource 3 | 4 | @export var save_data_nodes: Array[NodeDataResource] 5 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/resources/scene_data_resource.gd: -------------------------------------------------------------------------------- 1 | class_name SceneDataResource 2 | extends NodeDataResource 3 | 4 | @export var node_name: String 5 | @export var scene_file_path: String 6 | 7 | func _save_data(node: Node2D) -> void: 8 | super._save_data(node) 9 | 10 | node_name = node.name 11 | scene_file_path = node.scene_file_path 12 | 13 | 14 | func _load_data(window: Window) -> void: 15 | var parent_node: Node2D 16 | var scene_node: Node2D 17 | 18 | if parent_node_path != null: 19 | parent_node = window.get_node_or_null(parent_node_path) 20 | 21 | if node_path != null: 22 | var scene_file_resource: Resource = load(scene_file_path) 23 | scene_node = scene_file_resource.instantiate() as Node2D 24 | 25 | if parent_node != null and scene_node != null: 26 | scene_node.global_position = global_position 27 | parent_node.add_child(scene_node) 28 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/resources/tilemap_layer_data_resource.gd: -------------------------------------------------------------------------------- 1 | class_name TileMapLayerDataResource 2 | extends NodeDataResource 3 | 4 | @export var tilemap_layer_used_cells: Array[Vector2i] 5 | @export var terrain_set: int = 0 6 | @export var terrain: int = 3 7 | 8 | 9 | func _save_data(node: Node2D) -> void: 10 | super._save_data(node) 11 | 12 | var tilemap_layer: TileMapLayer = node as TileMapLayer 13 | var cells: Array[Vector2i] = tilemap_layer.get_used_cells() 14 | 15 | tilemap_layer_used_cells = cells 16 | 17 | 18 | func _load_data(window: Window) -> void: 19 | var scene_node = window.get_node_or_null(node_path) 20 | 21 | if scene_node != null: 22 | var tilemap_layer: TileMapLayer = scene_node as TileMapLayer 23 | tilemap_layer.set_cells_terrain_connect(tilemap_layer_used_cells, terrain_set, terrain, true) 24 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/chicken/chicken.gd: -------------------------------------------------------------------------------- 1 | extends NonPlayableCharacter 2 | 3 | 4 | func _ready() -> void: 5 | walk_cycles = randi_range(min_walk_cycle, max_walk_cycle) 6 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/cow/cow.gd: -------------------------------------------------------------------------------- 1 | extends NonPlayableCharacter 2 | 3 | 4 | func _ready() -> void: 5 | walk_cycles = randi_range(min_walk_cycle, max_walk_cycle) 6 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/guide/guide.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var balloon_scene = preload("res://dialogue/game_dialogue_balloon.tscn") 4 | 5 | @onready var interactable_component: InteractableComponent = $InteractableComponent 6 | @onready var interactable_label_component: Control = $InteractableLabelComponent 7 | 8 | var in_range: bool 9 | 10 | 11 | func _ready() -> void: 12 | interactable_component.interactable_activated.connect(on_interactable_activated) 13 | interactable_component.interactable_deactivated.connect(on_interactable_deactivated) 14 | interactable_label_component.hide() 15 | 16 | GameDialogueManager.give_crop_seeds.connect(on_give_crop_seeds) 17 | 18 | 19 | func on_interactable_activated() -> void: 20 | interactable_label_component.show() 21 | in_range = true 22 | 23 | 24 | func on_interactable_deactivated() -> void: 25 | interactable_label_component.hide() 26 | in_range = false 27 | 28 | 29 | func _unhandled_input(event: InputEvent) -> void: 30 | if in_range: 31 | if event.is_action_pressed("show_dialogue"): 32 | var balloon: BaseGameDialogueBalloon = balloon_scene.instantiate() 33 | get_tree().root.add_child(balloon) 34 | balloon.start(load("res://dialogue/conversations/guide.dialogue"), "start") 35 | 36 | 37 | func on_give_crop_seeds() -> void: 38 | ToolManager.enable_tool_button(DataTypes.Tools.TillGround) 39 | ToolManager.enable_tool_button(DataTypes.Tools.WaterCrops) 40 | ToolManager.enable_tool_button(DataTypes.Tools.PlantCorn) 41 | ToolManager.enable_tool_button(DataTypes.Tools.PlantTomato) 42 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/non_playable_character.gd: -------------------------------------------------------------------------------- 1 | class_name NonPlayableCharacter 2 | extends CharacterBody2D 3 | 4 | @export var min_walk_cycle: int = 2 5 | @export var max_walk_cycle: int = 6 6 | 7 | var walk_cycles: int 8 | var current_walk_cycle: int 9 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/npc_states/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character: CharacterBody2D 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var idle_state_time_interval: float = 5.0 6 | 7 | @onready var idle_state_timer: Timer = Timer.new() 8 | 9 | var idle_state_timeout: bool = false 10 | 11 | func _ready() -> void: 12 | idle_state_timer.wait_time = idle_state_time_interval 13 | idle_state_timer.timeout.connect(on_idle_state_timout) 14 | add_child(idle_state_timer) 15 | 16 | func _on_process(_delta : float) -> void: 17 | pass 18 | 19 | 20 | func _on_physics_process(_delta : float) -> void: 21 | pass 22 | 23 | 24 | func _on_next_transitions() -> void: 25 | if idle_state_timeout: 26 | transition.emit("Walk") 27 | 28 | 29 | func _on_enter() -> void: 30 | animated_sprite_2d.play("idle") 31 | 32 | idle_state_timeout = false 33 | idle_state_timer.start() 34 | 35 | 36 | func _on_exit() -> void: 37 | animated_sprite_2d.stop() 38 | idle_state_timer.stop() 39 | 40 | 41 | func on_idle_state_timout() -> void: 42 | idle_state_timeout = true 43 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/npc_states/walk_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var character: NonPlayableCharacter 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var navigation_agent_2d: NavigationAgent2D 6 | @export var min_speed: float = 5.0 7 | @export var max_speed: float = 10.0 8 | 9 | var speed: float 10 | 11 | func _ready() -> void: 12 | navigation_agent_2d.velocity_computed.connect(on_safe_velocity_computed) 13 | 14 | call_deferred("character_setup") 15 | 16 | func character_setup() -> void: 17 | await get_tree().physics_frame 18 | 19 | set_movement_target() 20 | 21 | func set_movement_target() -> void: 22 | var target_position: Vector2 = NavigationServer2D.map_get_random_point(navigation_agent_2d.get_navigation_map(), navigation_agent_2d.navigation_layers, false) 23 | navigation_agent_2d.target_position = target_position 24 | speed = randf_range(min_speed, max_speed) 25 | 26 | 27 | func _on_process(_delta : float) -> void: 28 | pass 29 | 30 | 31 | func _on_physics_process(_delta : float) -> void: 32 | if navigation_agent_2d.is_navigation_finished(): 33 | character.current_walk_cycle += 1 34 | set_movement_target() 35 | return 36 | 37 | var target_position: Vector2 = navigation_agent_2d.get_next_path_position() 38 | var target_direction: Vector2 = character.global_position.direction_to(target_position) 39 | 40 | var velocity: Vector2 = target_direction * speed 41 | 42 | if navigation_agent_2d.avoidance_enabled: 43 | animated_sprite_2d.flip_h = velocity.x < 0 44 | navigation_agent_2d.velocity = velocity 45 | else: 46 | character.velocity = velocity 47 | character.move_and_slide() 48 | 49 | func on_safe_velocity_computed(safe_velocity: Vector2) -> void: 50 | animated_sprite_2d.flip_h = safe_velocity.x < 0 51 | character.velocity = safe_velocity 52 | character.move_and_slide() 53 | 54 | func _on_next_transitions() -> void: 55 | if character.current_walk_cycle == character.walk_cycles: 56 | character.velocity = Vector2.ZERO 57 | transition.emit("Idle") 58 | 59 | 60 | func _on_enter() -> void: 61 | animated_sprite_2d.play("walk") 62 | character.current_walk_cycle = 0 63 | 64 | 65 | func _on_exit() -> void: 66 | animated_sprite_2d.stop() 67 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/chopping_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var player: Player 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var hit_component_collision_shape: CollisionShape2D 6 | 7 | 8 | func _ready() -> void: 9 | hit_component_collision_shape.disabled = true 10 | hit_component_collision_shape.position = Vector2(0, 0); 11 | 12 | func _on_process(_delta : float) -> void: 13 | pass 14 | 15 | 16 | func _on_physics_process(_delta : float) -> void: 17 | pass 18 | 19 | 20 | func _on_next_transitions() -> void: 21 | if !animated_sprite_2d.is_playing(): 22 | transition.emit("Idle") 23 | 24 | 25 | func _on_enter() -> void: 26 | if player.player_direction == Vector2.UP: 27 | animated_sprite_2d.play("chopping_back") 28 | hit_component_collision_shape.position = Vector2(0, -18) 29 | elif player.player_direction == Vector2.RIGHT: 30 | animated_sprite_2d.play("chopping_right") 31 | hit_component_collision_shape.position = Vector2(9, 0) 32 | elif player.player_direction == Vector2.DOWN: 33 | animated_sprite_2d.play("chopping_front") 34 | hit_component_collision_shape.position = Vector2(0, 3) 35 | elif player.player_direction == Vector2.LEFT: 36 | animated_sprite_2d.play("chopping_left") 37 | hit_component_collision_shape.position = Vector2(-9, 0) 38 | else: 39 | animated_sprite_2d.play("chopping_front") 40 | hit_component_collision_shape.position = Vector2(0, 3) 41 | 42 | hit_component_collision_shape.disabled = false 43 | 44 | func _on_exit() -> void: 45 | animated_sprite_2d.stop() 46 | hit_component_collision_shape.disabled = true 47 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var player: Player 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | 6 | 7 | func _on_process(_delta : float) -> void: 8 | pass 9 | 10 | 11 | func _on_physics_process(_delta : float) -> void: 12 | if player.player_direction == Vector2.UP: 13 | animated_sprite_2d.play("idle_back") 14 | elif player.player_direction == Vector2.RIGHT: 15 | animated_sprite_2d.play("idle_right") 16 | elif player.player_direction == Vector2.DOWN: 17 | animated_sprite_2d.play("idle_front") 18 | elif player.player_direction == Vector2.LEFT: 19 | animated_sprite_2d.play("idle_left") 20 | else: 21 | animated_sprite_2d.play("idle_front") 22 | 23 | 24 | func _on_next_transitions() -> void: 25 | GameInputEvents.movement_input() 26 | 27 | if GameInputEvents.is_movement_input(): 28 | transition.emit("Walk") 29 | 30 | if player.current_tool == DataTypes.Tools.AxeWood && GameInputEvents.use_tool(): 31 | transition.emit("Chopping") 32 | 33 | if player.current_tool == DataTypes.Tools.TillGround && GameInputEvents.use_tool(): 34 | transition.emit("Tilling") 35 | 36 | if player.current_tool == DataTypes.Tools.WaterCrops && GameInputEvents.use_tool(): 37 | transition.emit("Watering") 38 | 39 | 40 | func _on_enter() -> void: 41 | pass 42 | 43 | 44 | func _on_exit() -> void: 45 | animated_sprite_2d.stop() 46 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/player.gd: -------------------------------------------------------------------------------- 1 | class_name Player 2 | extends CharacterBody2D 3 | 4 | @onready var hit_component: HitComponent = $HitComponent 5 | 6 | @export var current_tool: DataTypes.Tools = DataTypes.Tools.None 7 | 8 | var player_direction: Vector2 9 | 10 | func _ready() -> void: 11 | ToolManager.tool_selected.connect(on_tool_selected) 12 | 13 | func on_tool_selected(tool: DataTypes.Tools) -> void: 14 | current_tool = tool 15 | hit_component.current_tool = tool 16 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/tilling_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var player: Player 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | 6 | 7 | func _on_process(_delta : float) -> void: 8 | pass 9 | 10 | 11 | func _on_physics_process(_delta : float) -> void: 12 | pass 13 | 14 | 15 | func _on_next_transitions() -> void: 16 | if !animated_sprite_2d.is_playing(): 17 | transition.emit("Idle") 18 | 19 | 20 | func _on_enter() -> void: 21 | if player.player_direction == Vector2.UP: 22 | animated_sprite_2d.play("tilling_back") 23 | elif player.player_direction == Vector2.RIGHT: 24 | animated_sprite_2d.play("tilling_right") 25 | elif player.player_direction == Vector2.DOWN: 26 | animated_sprite_2d.play("tilling_front") 27 | elif player.player_direction == Vector2.LEFT: 28 | animated_sprite_2d.play("tilling_left") 29 | else: 30 | animated_sprite_2d.play("tilling_front") 31 | 32 | func _on_exit() -> void: 33 | animated_sprite_2d.stop() 34 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/walk_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var player: Player 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var speed: int = 50 6 | 7 | 8 | func _on_process(_delta : float) -> void: 9 | pass 10 | 11 | 12 | func _on_physics_process(_delta : float) -> void: 13 | var direction: Vector2 = GameInputEvents.movement_input() 14 | 15 | if direction == Vector2.UP: 16 | animated_sprite_2d.play("walk_back") 17 | elif direction == Vector2.RIGHT: 18 | animated_sprite_2d.play("walk_right") 19 | elif direction == Vector2.DOWN: 20 | animated_sprite_2d.play("walk_front") 21 | elif direction == Vector2.LEFT: 22 | animated_sprite_2d.play("walk_left") 23 | 24 | if direction != Vector2.ZERO: 25 | player.player_direction = direction 26 | 27 | player.velocity = direction * speed 28 | player.move_and_slide() 29 | 30 | 31 | func _on_next_transitions() -> void: 32 | if !GameInputEvents.is_movement_input(): 33 | transition.emit("Idle") 34 | 35 | 36 | func _on_enter() -> void: 37 | pass 38 | 39 | 40 | func _on_exit() -> void: 41 | animated_sprite_2d.stop() 42 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/characters/player/watering_state.gd: -------------------------------------------------------------------------------- 1 | extends NodeState 2 | 3 | @export var player: Player 4 | @export var animated_sprite_2d: AnimatedSprite2D 5 | @export var hit_component_collision_shape: CollisionShape2D 6 | 7 | func _ready() -> void: 8 | hit_component_collision_shape.disabled = true 9 | hit_component_collision_shape.position = Vector2(0, 0); 10 | 11 | 12 | func _on_process(_delta : float) -> void: 13 | pass 14 | 15 | 16 | func _on_physics_process(_delta : float) -> void: 17 | pass 18 | 19 | 20 | func _on_next_transitions() -> void: 21 | if !animated_sprite_2d.is_playing(): 22 | transition.emit("Idle") 23 | 24 | 25 | func _on_enter() -> void: 26 | if player.player_direction == Vector2.UP: 27 | animated_sprite_2d.play("watering_back") 28 | hit_component_collision_shape.position = Vector2(0, -18) 29 | elif player.player_direction == Vector2.RIGHT: 30 | animated_sprite_2d.play("watering_right") 31 | hit_component_collision_shape.position = Vector2(9, 0) 32 | elif player.player_direction == Vector2.DOWN: 33 | animated_sprite_2d.play("watering_front") 34 | hit_component_collision_shape.position = Vector2(0, 3) 35 | elif player.player_direction == Vector2.LEFT: 36 | animated_sprite_2d.play("watering_left") 37 | hit_component_collision_shape.position = Vector2(-9, 0) 38 | else: 39 | animated_sprite_2d.play("watering_front") 40 | hit_component_collision_shape.position = Vector2(0, 3) 41 | 42 | hit_component_collision_shape.disabled = false 43 | 44 | func _on_exit() -> void: 45 | animated_sprite_2d.stop() 46 | hit_component_collision_shape.disabled = true 47 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/collectable_component.gd: -------------------------------------------------------------------------------- 1 | class_name CollectableComponent 2 | extends Area2D 3 | 4 | @export var collectable_name: String 5 | 6 | func _on_body_entered(body: Node2D) -> void: 7 | if body is Player: 8 | InventoryManager.add_collectable(collectable_name) 9 | print("Collected:", collectable_name) 10 | get_parent().queue_free() 11 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/crops_cursor_component.gd: -------------------------------------------------------------------------------- 1 | class_name CropsCursorComponent 2 | extends Node 3 | 4 | @export var tilled_soil_tilemap_layer: TileMapLayer 5 | 6 | var player: Player 7 | 8 | var corn_plant_scene = preload("res://scenes/objects/plants/corn.tscn") 9 | var tomato_plant_scene = preload("res://scenes/objects/plants/tomato.tscn") 10 | 11 | var mouse_position: Vector2 12 | var cell_position: Vector2i 13 | var cell_source_id: int 14 | var local_cell_position : Vector2 15 | var distance: float 16 | 17 | func _ready() -> void: 18 | await get_tree().process_frame 19 | player = get_tree().get_first_node_in_group("player") 20 | 21 | func _unhandled_input(event: InputEvent) -> void: 22 | if event.is_action_pressed("remove_dirt"): 23 | if ToolManager.selected_tool == DataTypes.Tools.TillGround: 24 | get_cell_under_mouse() 25 | remove_crop() 26 | elif event.is_action_pressed("hit"): 27 | if ToolManager.selected_tool == DataTypes.Tools.PlantCorn or ToolManager.selected_tool == DataTypes.Tools.PlantTomato: 28 | get_cell_under_mouse() 29 | add_crop() 30 | 31 | 32 | func get_cell_under_mouse() -> void: 33 | mouse_position = tilled_soil_tilemap_layer.get_local_mouse_position() 34 | cell_position = tilled_soil_tilemap_layer.local_to_map(mouse_position) 35 | cell_source_id = tilled_soil_tilemap_layer.get_cell_source_id(cell_position) 36 | local_cell_position = tilled_soil_tilemap_layer.map_to_local(cell_position) 37 | distance = player.global_position.distance_to(local_cell_position) 38 | 39 | func add_crop() -> void: 40 | if distance < 20.0: 41 | if ToolManager.selected_tool == DataTypes.Tools.PlantCorn: 42 | var corn_instance = corn_plant_scene.instantiate() as Node2D 43 | corn_instance.global_position = local_cell_position 44 | get_parent().find_child("CropFields").add_child(corn_instance) 45 | 46 | if ToolManager.selected_tool == DataTypes.Tools.PlantTomato: 47 | var tomato_instance = tomato_plant_scene.instantiate() as Node2D 48 | tomato_instance.global_position = local_cell_position 49 | get_parent().find_child("CropFields").add_child(tomato_instance) 50 | 51 | 52 | func remove_crop() -> void: 53 | if distance < 20.0: 54 | var crop_nodes = get_parent().find_child("CropFields").get_children() 55 | 56 | for node: Node2D in crop_nodes: 57 | if node.global_position == local_cell_position: 58 | node.queue_free() 59 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/damage_component.gd: -------------------------------------------------------------------------------- 1 | class_name DamageComponent 2 | extends Node2D 3 | 4 | @export var max_damage = 1 5 | @export var current_damage = 0 6 | 7 | signal max_damaged_reached 8 | 9 | func apply_damage(damage: int) -> void: 10 | current_damage = clamp(current_damage + damage, 0, max_damage) 11 | 12 | if current_damage == max_damage: 13 | max_damaged_reached.emit() 14 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/day_night_cycle_component.gd: -------------------------------------------------------------------------------- 1 | class_name DayNightCycleComponent 2 | extends CanvasModulate 3 | 4 | @export var initial_day: int = 1: 5 | set(id): 6 | initial_day = id 7 | DayAndNightCycleManager.initial_day = id 8 | DayAndNightCycleManager.set_initial_time() 9 | 10 | @export var initial_hour: int = 12: 11 | set(ih): 12 | initial_hour = ih 13 | DayAndNightCycleManager.initial_hour = ih 14 | DayAndNightCycleManager.set_initial_time() 15 | 16 | @export var initial_minute: int = 30: 17 | set(im): 18 | initial_minute = im 19 | DayAndNightCycleManager.initial_minute = im 20 | DayAndNightCycleManager.set_initial_time() 21 | 22 | @export var day_night_gradient_texture: GradientTexture1D 23 | 24 | func _ready() -> void: 25 | DayAndNightCycleManager.initial_day = initial_day 26 | DayAndNightCycleManager.initial_hour = initial_hour 27 | DayAndNightCycleManager.initial_minute = initial_minute 28 | DayAndNightCycleManager.set_initial_time() 29 | 30 | DayAndNightCycleManager.game_time.connect(on_game_time) 31 | 32 | func on_game_time(time: float) -> void: 33 | var sample_value = 0.5 * (sin(time - PI * 0.5) + 1.0) 34 | color = day_night_gradient_texture.gradient.sample(sample_value) 35 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/feed_component.gd: -------------------------------------------------------------------------------- 1 | class_name FeedComponent 2 | extends Area2D 3 | 4 | signal food_received(area: Area2D) 5 | 6 | 7 | func _on_area_entered(area: Area2D) -> void: 8 | food_received.emit(area) 9 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/field_cursor_component.gd: -------------------------------------------------------------------------------- 1 | class_name FieldCursorComponent 2 | extends Node 3 | 4 | @export var grass_tilemap_layer: TileMapLayer 5 | @export var tilled_soil_tilemap_layer: TileMapLayer 6 | @export var terrain_set: int = 0 7 | @export var terrain: int = 3 8 | 9 | var player: Player 10 | var mouse_position: Vector2 11 | var cell_position: Vector2i 12 | var cell_source_id: int 13 | var local_cell_position: Vector2 14 | var distance: float 15 | 16 | func _ready() -> void: 17 | await get_tree().process_frame 18 | player = get_tree().get_first_node_in_group("player") 19 | 20 | 21 | func _unhandled_input(event: InputEvent) -> void: 22 | if event.is_action_pressed("remove_dirt"): 23 | if ToolManager.selected_tool == DataTypes.Tools.TillGround: 24 | get_cell_under_mouse() 25 | remove_tilled_soil_cell() 26 | elif event.is_action_pressed("hit"): 27 | if ToolManager.selected_tool == DataTypes.Tools.TillGround: 28 | get_cell_under_mouse() 29 | add_tilled_soil_cell() 30 | 31 | func get_cell_under_mouse() -> void: 32 | mouse_position = grass_tilemap_layer.get_local_mouse_position() 33 | cell_position = grass_tilemap_layer.local_to_map(mouse_position) 34 | cell_source_id = grass_tilemap_layer.get_cell_source_id(cell_position) 35 | local_cell_position = grass_tilemap_layer.map_to_local(cell_position) 36 | distance = player.global_position.distance_to(local_cell_position) 37 | 38 | 39 | func add_tilled_soil_cell() -> void: 40 | if distance < 20.0 && cell_source_id != -1: 41 | tilled_soil_tilemap_layer.set_cells_terrain_connect([cell_position], terrain_set, terrain, true) 42 | 43 | func remove_tilled_soil_cell() -> void: 44 | if distance < 20.0: 45 | tilled_soil_tilemap_layer.set_cells_terrain_connect([cell_position], 0, -1, true) 46 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/hit_component.gd: -------------------------------------------------------------------------------- 1 | class_name HitComponent 2 | extends Area2D 3 | 4 | @export var current_tool : DataTypes.Tools = DataTypes.Tools.None 5 | @export var hit_damage : int = 1 6 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/hurt_component.gd: -------------------------------------------------------------------------------- 1 | class_name HurtComponent 2 | extends Area2D 3 | 4 | @export var tool : DataTypes.Tools = DataTypes.Tools.None 5 | 6 | signal hurt 7 | 8 | 9 | func _on_area_entered(area: Area2D) -> void: 10 | var hit_component = area as HitComponent 11 | 12 | if tool == hit_component.current_tool: 13 | hurt.emit(hit_component.hit_damage) 14 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/interactable_component.gd: -------------------------------------------------------------------------------- 1 | class_name InteractableComponent 2 | extends Area2D 3 | 4 | signal interactable_activated 5 | signal interactable_deactivated 6 | 7 | 8 | func _on_body_entered(_body: Node2D) -> void: 9 | interactable_activated.emit() 10 | 11 | 12 | func _on_body_exited(_body: Node2D) -> void: 13 | interactable_deactivated.emit() 14 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/mouse_cursor_component.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @export var cursor_component_texture: Texture2D 4 | 5 | func _ready() -> void: 6 | Input.set_custom_mouse_cursor(cursor_component_texture, Input.CURSOR_ARROW) 7 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/save_data_component.gd: -------------------------------------------------------------------------------- 1 | class_name SaveDataComponent 2 | extends Node 3 | 4 | @onready var parent_node: Node2D = get_parent() as Node2D 5 | 6 | @export var save_data_resource: Resource 7 | 8 | func _ready() -> void: 9 | add_to_group("save_data_component") 10 | 11 | 12 | func _save_data() -> Resource: 13 | if parent_node == null: 14 | return null 15 | 16 | if save_data_resource == null: 17 | push_error("save_data_resource:", save_data_resource, parent_node.name) 18 | return null 19 | 20 | save_data_resource._save_data(parent_node) 21 | 22 | return save_data_resource 23 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/save_level_data_component.gd: -------------------------------------------------------------------------------- 1 | class_name SaveLevelDataComponent 2 | extends Node 3 | 4 | var level_scene_name: String 5 | var save_game_data_path: String = "user://game_data/" 6 | var save_file_name: String = "save_%s_game_data.tres" 7 | var game_data_resource: SaveGameDataResource 8 | 9 | 10 | func _ready() -> void: 11 | add_to_group("save_level_data_component") 12 | level_scene_name = get_parent().name 13 | 14 | 15 | func save_node_data() -> void: 16 | var nodes = get_tree().get_nodes_in_group("save_data_component") 17 | 18 | game_data_resource = SaveGameDataResource.new() 19 | 20 | if nodes != null: 21 | for node: SaveDataComponent in nodes: 22 | if node is SaveDataComponent: 23 | var save_data_resource: NodeDataResource = node._save_data() 24 | var save_final_resource = save_data_resource.duplicate() 25 | game_data_resource.save_data_nodes.append(save_final_resource) 26 | 27 | 28 | func save_game() -> void: 29 | if !DirAccess.dir_exists_absolute(save_game_data_path): 30 | DirAccess.make_dir_absolute(save_game_data_path) 31 | 32 | var level_save_file_name: String = save_file_name % level_scene_name 33 | 34 | save_node_data() 35 | 36 | var result: int = ResourceSaver.save(game_data_resource, save_game_data_path + level_save_file_name) 37 | print("save result:", result) 38 | 39 | 40 | func load_game() -> void: 41 | var level_save_file_name: String = save_file_name % level_scene_name 42 | var save_game_path: String = save_game_data_path + level_save_file_name 43 | 44 | if !FileAccess.file_exists(save_game_path): 45 | return 46 | 47 | game_data_resource = ResourceLoader.load(save_game_path) 48 | 49 | if game_data_resource == null: 50 | return 51 | 52 | var root_node: Window = get_tree().root 53 | 54 | for resource in game_data_resource.save_data_nodes: 55 | if resource is Resource: 56 | if resource is NodeDataResource: 57 | resource._load_data(root_node) 58 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/test_scene_enable_tool_buttons_component.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func _ready() -> void: 4 | call_deferred("enable_tool_buttons") 5 | 6 | 7 | func enable_tool_buttons() -> void: 8 | ToolManager.enable_tool_button(DataTypes.Tools.TillGround) 9 | ToolManager.enable_tool_button(DataTypes.Tools.WaterCrops) 10 | ToolManager.enable_tool_button(DataTypes.Tools.PlantCorn) 11 | ToolManager.enable_tool_button(DataTypes.Tools.PlantTomato) 12 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/components/test_scene_save_data_manager_component.gd: -------------------------------------------------------------------------------- 1 | class_name TestSceneSaveDataManagerComponent 2 | extends Node 3 | 4 | 5 | func _ready() -> void: 6 | call_deferred("load_test_scene") 7 | 8 | 9 | func load_test_scene(): 10 | SaveGameManager.load_game() 11 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/houses/door.gd: -------------------------------------------------------------------------------- 1 | extends StaticBody2D 2 | 3 | @onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D 4 | @onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D 5 | @onready var interactable_component: InteractableComponent = $InteractableComponent 6 | 7 | func _ready() -> void: 8 | interactable_component.interactable_activated.connect(on_interactable_activated) 9 | interactable_component.interactable_deactivated.connect(on_interactable_deactivated) 10 | collision_layer = 1 11 | 12 | func on_interactable_activated() -> void: 13 | animated_sprite_2d.play("open_door") 14 | collision_layer = 2 15 | print("activated") 16 | 17 | 18 | func on_interactable_deactivated() -> void: 19 | animated_sprite_2d.play("close_door") 20 | collision_layer = 1 21 | print("deactivated") 22 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/chest/chest.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var balloon_scene = preload("res://dialogue/game_dialogue_balloon.tscn") 4 | 5 | var corn_harvest_scene = preload("res://scenes/objects/plants/corn_harvest.tscn") 6 | var tomato_harvest_scene = preload("res://scenes/objects/plants/tomato_harvest.tscn") 7 | 8 | @export var dialogue_start_command: String 9 | @export var food_drop_height: int = 40 10 | @export var reward_output_radius: int = 20 11 | @export var output_reward_scenes: Array[PackedScene] = [] 12 | 13 | @onready var interactable_component: InteractableComponent = $InteractableComponent 14 | @onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D 15 | @onready var feed_component: FeedComponent = $FeedComponent 16 | @onready var reward_marker: Marker2D = $RewardMarker 17 | @onready var interactable_label_component: Control = $InteractableLabelComponent 18 | 19 | var in_range: bool 20 | var is_chest_open: bool 21 | 22 | func _ready() -> void: 23 | interactable_component.interactable_activated.connect(on_interactable_activated) 24 | interactable_component.interactable_deactivated.connect(on_interactable_deactivated) 25 | interactable_label_component.hide() 26 | 27 | GameDialogueManager.feed_the_animals.connect(on_feed_the_animals) 28 | feed_component.food_received.connect(on_food_received) 29 | 30 | func on_interactable_activated() -> void: 31 | interactable_label_component.show() 32 | in_range = true 33 | 34 | func on_interactable_deactivated() -> void: 35 | if is_chest_open: 36 | animated_sprite_2d.play("chest_close") 37 | 38 | is_chest_open = false 39 | interactable_label_component.hide() 40 | in_range = false 41 | 42 | func _unhandled_input(event: InputEvent) -> void: 43 | if in_range: 44 | if event.is_action_pressed("show_dialogue"): 45 | interactable_label_component.hide() 46 | animated_sprite_2d.play("chest_open") 47 | is_chest_open = true 48 | 49 | # create some dialogue 50 | var balloon: BaseGameDialogueBalloon = balloon_scene.instantiate() 51 | get_tree().root.add_child(balloon) 52 | balloon.start(load("res://dialogue/conversations/chest.dialogue"), dialogue_start_command) 53 | 54 | func on_feed_the_animals() -> void: 55 | if in_range: 56 | trigger_feed_harvest("corn", corn_harvest_scene) 57 | trigger_feed_harvest("tomato", tomato_harvest_scene) 58 | 59 | 60 | func trigger_feed_harvest(inventory_item: String, scene: Resource) -> void: 61 | var inventory: Dictionary = InventoryManager.inventory 62 | 63 | if !inventory.has(inventory_item): 64 | return 65 | 66 | var inventory_item_count = inventory[inventory_item] 67 | 68 | for index in inventory_item_count: 69 | var harvest_instance = scene.instantiate() as Node2D 70 | harvest_instance.global_position = Vector2(global_position.x, global_position.y - food_drop_height) 71 | get_tree().root.add_child(harvest_instance) 72 | var target_position = global_position 73 | 74 | var time_delay = randf_range(0.5, 2.0) 75 | await get_tree().create_timer(time_delay).timeout 76 | 77 | var tween = get_tree().create_tween() 78 | tween.tween_property(harvest_instance, "position", target_position, 1.0) 79 | tween.tween_property(harvest_instance, "scale", Vector2(0.5, 0.5), 1.0) 80 | tween.tween_callback(harvest_instance.queue_free) 81 | 82 | InventoryManager.remove_collectable(inventory_item) 83 | 84 | func on_food_received(area: Area2D) -> void: 85 | call_deferred("add_reward_scene") 86 | 87 | 88 | func add_reward_scene() -> void: 89 | for scene in output_reward_scenes: 90 | var reward_scene: Node2D = scene.instantiate() 91 | var reward_position: Vector2 = get_random_position_in_circle(reward_marker.global_position, reward_output_radius) 92 | reward_scene.global_position = reward_position 93 | get_tree().root.add_child(reward_scene) 94 | 95 | 96 | func get_random_position_in_circle(center: Vector2, radius: int) -> Vector2i: 97 | var angle = randf() * TAU 98 | 99 | var distance_from_center = sqrt(randf()) * radius 100 | 101 | var x: int = center.x + distance_from_center * cos(angle) 102 | var y: int = center.y + distance_from_center * cos(angle) 103 | 104 | return Vector2i(x, y) 105 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/plants/corn.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var corn_harvest_scene = preload("res://scenes/objects/plants/corn_harvest.tscn") 4 | 5 | @onready var sprite_2d: Sprite2D = $Sprite2D 6 | @onready var watering_particles: GPUParticles2D = $WateringParticles 7 | @onready var flowering_particles: GPUParticles2D = $FloweringParticles 8 | @onready var growth_cycle_component: GrowthCycleComponent = $GrowthCycleComponent 9 | @onready var hurt_component: HurtComponent = $HurtComponent 10 | 11 | var growth_state: DataTypes.GrowthStates = DataTypes.GrowthStates.Seed 12 | 13 | 14 | func _ready() -> void: 15 | watering_particles.emitting = false 16 | flowering_particles.emitting = false 17 | 18 | hurt_component.hurt.connect(on_hurt) 19 | growth_cycle_component.crop_maturity.connect(on_crop_maturity) 20 | growth_cycle_component.crop_harvesting.connect(on_crop_harvesting) 21 | 22 | 23 | func _process(delta: float) -> void: 24 | growth_state = growth_cycle_component.get_current_growth_state() 25 | sprite_2d.frame = growth_state 26 | 27 | if growth_state == DataTypes.GrowthStates.Maturity: 28 | flowering_particles.emitting = true 29 | 30 | 31 | func on_hurt(hit_damage: int) -> void: 32 | if !growth_cycle_component.is_watered: 33 | watering_particles.emitting = true 34 | await get_tree().create_timer(5.0).timeout 35 | watering_particles.emitting = false 36 | growth_cycle_component.is_watered = true 37 | 38 | 39 | func on_crop_maturity() -> void: 40 | flowering_particles.emitting = true 41 | 42 | 43 | func on_crop_harvesting() -> void: 44 | var corn_harvest_instance = corn_harvest_scene.instantiate() as Node2D 45 | corn_harvest_instance.global_position = global_position 46 | get_parent().add_child(corn_harvest_instance) 47 | queue_free() 48 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/plants/growth_cycle_component.gd: -------------------------------------------------------------------------------- 1 | class_name GrowthCycleComponent 2 | extends Node 3 | 4 | @export var current_growth_state: DataTypes.GrowthStates = DataTypes.GrowthStates.Germination 5 | @export_range(5, 365) var days_until_harvest: int = 7 6 | 7 | signal crop_maturity 8 | signal crop_harvesting 9 | 10 | var is_watered: bool 11 | var starting_day: int 12 | var current_day: int 13 | 14 | func _ready() -> void: 15 | DayAndNightCycleManager.time_tick_day.connect(on_time_tick_day) 16 | 17 | 18 | func on_time_tick_day(day: int) -> void: 19 | if is_watered: 20 | if starting_day == 0: 21 | starting_day = day 22 | 23 | growth_states(starting_day, day) 24 | harvest_state(starting_day, day) 25 | 26 | 27 | func growth_states(starting_day: int, current_day: int) -> void: 28 | if current_growth_state == DataTypes.GrowthStates.Maturity: 29 | return 30 | 31 | var num_states = 5 32 | 33 | var growth_days_passed = (current_day - starting_day) % num_states 34 | var state_index = growth_days_passed % num_states + 1 35 | 36 | current_growth_state = state_index 37 | 38 | var name = DataTypes.GrowthStates.keys()[current_growth_state] 39 | 40 | if current_growth_state == DataTypes.GrowthStates.Maturity: 41 | crop_maturity.emit() 42 | 43 | 44 | func harvest_state(starting_day: int, current_day: int) -> void: 45 | if current_growth_state == DataTypes.GrowthStates.Harvesting: 46 | return 47 | 48 | var days_passed = (current_day - starting_day) % days_until_harvest 49 | 50 | if days_passed == days_until_harvest - 1: 51 | current_growth_state = DataTypes.GrowthStates.Harvesting 52 | crop_harvesting.emit() 53 | 54 | 55 | func get_current_growth_state() -> DataTypes.GrowthStates: 56 | return current_growth_state 57 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/plants/tomato.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var tomato_harvest_scene = preload("res://scenes/objects/plants/tomato_harvest.tscn") 4 | 5 | @onready var sprite_2d: Sprite2D = $Sprite2D 6 | @onready var watering_particles: GPUParticles2D = $WateringParticles 7 | @onready var flowering_particles: GPUParticles2D = $FloweringParticles 8 | @onready var growth_cycle_component: GrowthCycleComponent = $GrowthCycleComponent 9 | @onready var hurt_component: HurtComponent = $HurtComponent 10 | 11 | var growth_state: DataTypes.GrowthStates = DataTypes.GrowthStates.Seed 12 | var start_tomato_frame_offset: int = 6 13 | 14 | func _ready() -> void: 15 | watering_particles.emitting = false 16 | flowering_particles.emitting = false 17 | 18 | hurt_component.hurt.connect(on_hurt) 19 | growth_cycle_component.crop_maturity.connect(on_crop_maturity) 20 | growth_cycle_component.crop_harvesting.connect(on_crop_harvesting) 21 | 22 | 23 | func _process(delta: float) -> void: 24 | growth_state = growth_cycle_component.get_current_growth_state() 25 | sprite_2d.frame = growth_state + start_tomato_frame_offset 26 | 27 | if growth_state == DataTypes.GrowthStates.Maturity: 28 | flowering_particles.emitting = true 29 | 30 | 31 | func on_hurt(hit_damage: int) -> void: 32 | if !growth_cycle_component.is_watered: 33 | watering_particles.emitting = true 34 | await get_tree().create_timer(5.0).timeout 35 | watering_particles.emitting = false 36 | growth_cycle_component.is_watered = true 37 | 38 | 39 | func on_crop_maturity() -> void: 40 | flowering_particles.emitting = true 41 | 42 | 43 | func on_crop_harvesting() -> void: 44 | var tomato_harvest_instance = tomato_harvest_scene.instantiate() as Node2D 45 | tomato_harvest_instance.global_position = global_position 46 | get_parent().add_child(tomato_harvest_instance) 47 | queue_free() 48 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/rocks/rock.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | @onready var hurt_component: HurtComponent = $HurtComponent 4 | @onready var damage_component: DamageComponent = $DamageComponent 5 | 6 | var stone_scene = preload("res://scenes/objects/rocks/stone.tscn") 7 | 8 | func _ready() -> void: 9 | hurt_component.hurt.connect(on_hurt) 10 | damage_component.max_damaged_reached.connect(on_max_damage_reached) 11 | 12 | 13 | func on_hurt(hit_damage: int) -> void: 14 | damage_component.apply_damage(hit_damage) 15 | material.set_shader_parameter("shake_intensity", 0.3) 16 | await get_tree().create_timer(0.5).timeout 17 | material.set_shader_parameter("shake_intensity", 0.0) 18 | 19 | func on_max_damage_reached() -> void: 20 | call_deferred("add_stone_scene") 21 | queue_free() 22 | 23 | func add_stone_scene() -> void: 24 | var stone_instance = stone_scene.instantiate() as Node2D 25 | stone_instance.global_position = global_position 26 | get_parent().add_child(stone_instance) 27 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/trees/large_tree.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | @onready var hurt_component: HurtComponent = $HurtComponent 4 | @onready var damage_component: DamageComponent = $DamageComponent 5 | 6 | var log_scene = preload("res://scenes/objects/trees/log.tscn") 7 | 8 | func _ready() -> void: 9 | hurt_component.hurt.connect(on_hurt) 10 | damage_component.max_damaged_reached.connect(on_max_damage_reached) 11 | 12 | 13 | func on_hurt(hit_damage: int) -> void: 14 | damage_component.apply_damage(hit_damage) 15 | material.set_shader_parameter("shake_intensity", 0.5) 16 | await get_tree().create_timer(1.0).timeout 17 | material.set_shader_parameter("shake_intensity", 0.0) 18 | 19 | func on_max_damage_reached() -> void: 20 | call_deferred("add_log_scene") 21 | print("max damaged reached") 22 | queue_free() 23 | 24 | func add_log_scene() -> void: 25 | var log_instance = log_scene.instantiate() as Node2D 26 | log_instance.global_position = global_position 27 | get_parent().add_child(log_instance) 28 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/objects/trees/small_tree.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | @onready var hurt_component: HurtComponent = $HurtComponent 4 | @onready var damage_component: DamageComponent = $DamageComponent 5 | 6 | var log_scene = preload("res://scenes/objects/trees/log.tscn") 7 | 8 | func _ready() -> void: 9 | hurt_component.hurt.connect(on_hurt) 10 | damage_component.max_damaged_reached.connect(on_max_damage_reached) 11 | 12 | 13 | func on_hurt(hit_damage: int) -> void: 14 | damage_component.apply_damage(hit_damage) 15 | material.set_shader_parameter("shake_intensity", 0.5) 16 | await get_tree().create_timer(1.0).timeout 17 | material.set_shader_parameter("shake_intensity", 0.0) 18 | 19 | func on_max_damage_reached() -> void: 20 | call_deferred("add_log_scene") 21 | print("max damaged reached") 22 | queue_free() 23 | 24 | func add_log_scene() -> void: 25 | var log_instance = log_scene.instantiate() as Node2D 26 | log_instance.global_position = global_position 27 | get_parent().add_child(log_instance) 28 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/day_and_night_panel.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | @onready var day_label: Label = $DayPanel/MarginContainer/DayLabel 4 | @onready var time_label: Label = $TimePanel/MarginContainer/TimeLabel 5 | 6 | @export var normal_speed: int = 5 7 | @export var fast_speed: int = 100 8 | @export var cheetah_speed: int = 200 9 | 10 | func _ready() -> void: 11 | DayAndNightCycleManager.time_tick.connect(on_time_tick) 12 | 13 | 14 | func on_time_tick(day: int, hour: int, minute: int) -> void: 15 | day_label.text = "Day " + str(day) 16 | time_label.text = "%02d:%02d" % [hour, minute] 17 | 18 | 19 | func _on_normal_speed_button_pressed() -> void: 20 | DayAndNightCycleManager.game_speed = normal_speed 21 | 22 | 23 | func _on_fast_speed_button_pressed() -> void: 24 | DayAndNightCycleManager.game_speed = fast_speed 25 | 26 | func _on_cheetah_speed_button_pressed() -> void: 27 | DayAndNightCycleManager.game_speed = cheetah_speed 28 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/emotes_panel.gd: -------------------------------------------------------------------------------- 1 | extends Panel 2 | 3 | @onready var animated_sprite_2d: AnimatedSprite2D = $Emote/AnimatedSprite2D 4 | @onready var emote_idle_timer: Timer = $EmoteIdleTimer 5 | 6 | var idle_emotes: Array = ["emote_1_idle", "emote_2_smile", "emote_3_ear_wave", "emote_4_blink"] 7 | 8 | 9 | func _ready() -> void: 10 | animated_sprite_2d.play("emote_1_idle") 11 | 12 | InventoryManager.inventory_changed.connect(on_inventory_changed) 13 | GameDialogueManager.feed_the_animals.connect(on_feed_the_animals) 14 | 15 | 16 | func play_emote(animation: String) -> void: 17 | animated_sprite_2d.play(animation) 18 | 19 | 20 | func _on_emote_idle_timer_timeout() -> void: 21 | var index = randi_range(0,3) 22 | var emote = idle_emotes[index] 23 | 24 | animated_sprite_2d.play(emote) 25 | 26 | func on_inventory_changed() -> void: 27 | animated_sprite_2d.play("emote_7_excited") 28 | 29 | func on_feed_the_animals() -> void: 30 | animated_sprite_2d.play("emote_6_love_kiss") 31 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/game_menu_screen.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @onready var save_game_button: Button = $MarginContainer/VBoxContainer/SaveGameButton 4 | 5 | func _ready() -> void: 6 | save_game_button.disabled = !SaveGameManager.allow_save_game 7 | save_game_button.focus_mode = SaveGameManager.allow_save_game if Control.FOCUS_ALL else Control.FOCUS_NONE 8 | 9 | func _on_start_game_button_pressed() -> void: 10 | GameManager.start_game() 11 | queue_free() 12 | 13 | 14 | func _on_save_game_button_pressed() -> void: 15 | SaveGameManager.save_game() 16 | 17 | 18 | func _on_exit_game_button_pressed() -> void: 19 | GameManager.exit_game() 20 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/game_menu_screen_background.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | func _ready() -> void: 4 | call_deferred("set_scene_process_mode") 5 | 6 | func set_scene_process_mode() -> void: 7 | process_mode = PROCESS_MODE_DISABLED 8 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/inventory_panel.gd: -------------------------------------------------------------------------------- 1 | extends PanelContainer 2 | 3 | @onready var log_label: Label = $MarginContainer/VBoxContainer/Logs/LogLabel 4 | @onready var stone_label: Label = $MarginContainer/VBoxContainer/Stone/StoneLabel 5 | @onready var corn_label: Label = $MarginContainer/VBoxContainer/Corn/CornLabel 6 | @onready var tomato_label: Label = $MarginContainer/VBoxContainer/Tomato/TomatoLabel 7 | @onready var egg_label: Label = $MarginContainer/VBoxContainer/Egg/EggLabel 8 | @onready var milk_label: Label = $MarginContainer/VBoxContainer/Milk/MilkLabel 9 | 10 | 11 | func _ready() -> void: 12 | InventoryManager.inventory_changed.connect(on_inventory_changed) 13 | 14 | func on_inventory_changed() -> void: 15 | var inventory: Dictionary = InventoryManager.inventory 16 | 17 | if inventory.has("log"): 18 | log_label.text = str(inventory["log"]) 19 | 20 | if inventory.has("stone"): 21 | stone_label.text = str(inventory["stone"]) 22 | 23 | if inventory.has("corn"): 24 | corn_label.text = str(inventory["corn"]) 25 | 26 | if inventory.has("tomato"): 27 | tomato_label.text = str(inventory["tomato"]) 28 | 29 | if inventory.has("egg"): 30 | egg_label.text = str(inventory["egg"]) 31 | 32 | if inventory.has("milk"): 33 | milk_label.text = str(inventory["milk"]) 34 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scenes/ui/tools_panel.gd: -------------------------------------------------------------------------------- 1 | extends PanelContainer 2 | 3 | @onready var tool_axe: Button = $MarginContainer/HBoxContainer/ToolAxe 4 | @onready var tool_tilling: Button = $MarginContainer/HBoxContainer/ToolTilling 5 | @onready var tool_watering_can: Button = $MarginContainer/HBoxContainer/ToolWateringCan 6 | @onready var tool_corn: Button = $MarginContainer/HBoxContainer/ToolCorn 7 | @onready var tool_tomato: Button = $MarginContainer/HBoxContainer/ToolTomato 8 | 9 | func _ready() -> void: 10 | ToolManager.enable_tool.connect(on_enable_tool_button) 11 | 12 | tool_tilling.disabled = true 13 | tool_tilling.focus_mode = Control.FOCUS_NONE 14 | 15 | tool_watering_can.disabled = true 16 | tool_watering_can.focus_mode = Control.FOCUS_NONE 17 | 18 | tool_corn.disabled = true 19 | tool_corn.focus_mode = Control.FOCUS_NONE 20 | 21 | tool_tomato.disabled = true 22 | tool_tomato.focus_mode = Control.FOCUS_NONE 23 | 24 | 25 | func _on_tool_axe_pressed() -> void: 26 | ToolManager.select_tool(DataTypes.Tools.AxeWood) 27 | 28 | func _on_tool_tilling_pressed() -> void: 29 | ToolManager.select_tool(DataTypes.Tools.TillGround) 30 | 31 | func _on_tool_watering_can_pressed() -> void: 32 | ToolManager.select_tool(DataTypes.Tools.WaterCrops) 33 | 34 | func _on_tool_corn_pressed() -> void: 35 | ToolManager.select_tool(DataTypes.Tools.PlantCorn) 36 | 37 | func _on_tool_tomato_pressed() -> void: 38 | ToolManager.select_tool(DataTypes.Tools.PlantTomato) 39 | 40 | func _unhandled_input(event: InputEvent) -> void: 41 | if event.is_action_pressed("release_tool"): 42 | ToolManager.select_tool(DataTypes.Tools.None) 43 | tool_axe.release_focus() 44 | tool_tilling.release_focus() 45 | tool_watering_can.release_focus() 46 | tool_corn.release_focus() 47 | tool_tomato.release_focus() 48 | 49 | 50 | func on_enable_tool_button(tool: DataTypes.Tools) -> void: 51 | if tool == DataTypes.Tools.TillGround: 52 | tool_tilling.disabled = false 53 | tool_tilling.focus_mode = Control.FOCUS_ALL 54 | elif tool == DataTypes.Tools.WaterCrops: 55 | tool_watering_can.disabled = false 56 | tool_watering_can.focus_mode = Control.FOCUS_ALL 57 | elif tool == DataTypes.Tools.PlantCorn: 58 | tool_corn.disabled = false 59 | tool_corn.focus_mode = Control.FOCUS_ALL 60 | elif tool == DataTypes.Tools.PlantTomato: 61 | tool_tomato.disabled = false 62 | tool_tomato.focus_mode = Control.FOCUS_ALL 63 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/game_input_events.gd: -------------------------------------------------------------------------------- 1 | class_name GameInputEvents 2 | 3 | static var direction: Vector2 4 | 5 | static func movement_input() -> Vector2: 6 | if Input.is_action_pressed("walk_left"): 7 | direction = Vector2.LEFT 8 | elif Input.is_action_pressed("walk_right"): 9 | direction = Vector2.RIGHT 10 | elif Input.is_action_pressed("walk_up"): 11 | direction = Vector2.UP 12 | elif Input.is_action_pressed("walk_down"): 13 | direction = Vector2.DOWN 14 | else: 15 | direction = Vector2.ZERO 16 | 17 | return direction 18 | 19 | 20 | static func is_movement_input() -> bool: 21 | if direction == Vector2.ZERO: 22 | return false 23 | else: 24 | return true 25 | 26 | static func use_tool() -> bool: 27 | var use_tool_value: bool = Input.is_action_just_pressed("hit") 28 | 29 | return use_tool_value 30 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/data_types.gd: -------------------------------------------------------------------------------- 1 | class_name DataTypes 2 | 3 | enum Tools { 4 | None, 5 | AxeWood, 6 | TillGround, 7 | WaterCrops, 8 | PlantCorn, 9 | PlantTomato 10 | } 11 | 12 | enum GrowthStates { 13 | Seed, 14 | Germination, 15 | Vegetative, 16 | Reproduction, 17 | Maturity, 18 | Harvesting 19 | } 20 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/day_and_night_cycle_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | const MINUTES_PER_DAY: int = 24 * 60 4 | const MINUTES_PER_HOUR: int = 60 5 | const GAME_MINUTE_DURATION: float = TAU / MINUTES_PER_DAY 6 | 7 | var game_speed: float = 5.0 8 | 9 | var initial_day: int = 1 10 | var initial_hour: int = 12 11 | var initial_minute: int = 30 12 | 13 | var time: float = 0.0 14 | var current_minute: int = -1 15 | var current_day: int = 0 16 | 17 | signal game_time(time: float) 18 | signal time_tick(day: int, hour: int, minute: int) 19 | signal time_tick_day(day: int) 20 | 21 | func _ready() -> void: 22 | set_initial_time() 23 | 24 | func _process(delta: float) -> void: 25 | time += delta * game_speed * GAME_MINUTE_DURATION 26 | game_time.emit(time) 27 | 28 | recalculate_time() 29 | 30 | 31 | func set_initial_time() -> void: 32 | var initial_total_minutes = initial_day * MINUTES_PER_DAY + (initial_hour * MINUTES_PER_HOUR) + initial_minute 33 | 34 | time = initial_total_minutes * GAME_MINUTE_DURATION 35 | 36 | func recalculate_time() -> void: 37 | var total_minutes: int = int(time / GAME_MINUTE_DURATION) 38 | @warning_ignore("integer_division") 39 | var day: int = int(total_minutes / MINUTES_PER_DAY) 40 | var current_day_minutes: int = total_minutes % MINUTES_PER_DAY 41 | @warning_ignore("integer_division") 42 | var hour: int = int(current_day_minutes / MINUTES_PER_HOUR) 43 | var minute: int = int(current_day_minutes % MINUTES_PER_HOUR) 44 | 45 | if current_minute != minute: 46 | current_minute = minute 47 | time_tick.emit(day, hour, minute) 48 | 49 | if current_day != day: 50 | current_day = day 51 | time_tick_day.emit(day) 52 | 53 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/game_dialogue_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal give_crop_seeds 4 | signal feed_the_animals 5 | 6 | 7 | func action_give_crop_seeds() -> void: 8 | give_crop_seeds.emit() 9 | 10 | func action_feed_animals() -> void: 11 | feed_the_animals.emit() 12 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/game_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var game_menu_screen = preload("res://scenes/ui/game_menu_screen.tscn") 4 | 5 | 6 | func _unhandled_input(event: InputEvent) -> void: 7 | if event.is_action_pressed("game_menu"): 8 | show_game_menu_screen() 9 | 10 | 11 | func start_game() -> void: 12 | SceneManager.load_main_scene_container() 13 | SceneManager.load_level("Level1") 14 | SaveGameManager.load_game() 15 | SaveGameManager.allow_save_game = true 16 | 17 | func exit_game() -> void: 18 | get_tree().quit() 19 | 20 | func show_game_menu_screen() -> void: 21 | var game_menu_screen_instance = game_menu_screen.instantiate() 22 | get_tree().root.add_child(game_menu_screen_instance) 23 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/inventory_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var inventory: Dictionary = Dictionary() 4 | 5 | signal inventory_changed 6 | 7 | func add_collectable(collectable_name: String) -> void: 8 | inventory.get_or_add(collectable_name) 9 | 10 | if inventory[collectable_name] == null: 11 | inventory[collectable_name] = 1 12 | else: 13 | inventory[collectable_name] += 1 14 | 15 | inventory_changed.emit() 16 | 17 | 18 | func remove_collectable(collectable_name: String) -> void: 19 | if inventory[collectable_name] == null: 20 | inventory[collectable_name] = 0 21 | else: 22 | if inventory[collectable_name] > 0: 23 | inventory[collectable_name] -= 1 24 | 25 | inventory_changed.emit() 26 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/save_game_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var allow_save_game: bool 4 | 5 | func _unhandled_input(event: InputEvent) -> void: 6 | if event.is_action_pressed("save_game"): 7 | save_game() 8 | 9 | 10 | func save_game() -> void: 11 | var save_level_data_component: SaveLevelDataComponent = get_tree().get_first_node_in_group("save_level_data_component") 12 | 13 | if save_level_data_component != null: 14 | save_level_data_component.save_game() 15 | 16 | 17 | func load_game() -> void: 18 | await get_tree().process_frame 19 | 20 | var save_level_data_component: SaveLevelDataComponent = get_tree().get_first_node_in_group("save_level_data_component") 21 | 22 | if save_level_data_component != null: 23 | save_level_data_component.load_game() 24 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/scene_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var main_scene_path: String = "res://scenes/main_scene.tscn" 4 | var main_scene_root_path: String = "/root/MainScene" 5 | var main_scene_level_root_path: String = "/root/MainScene/GameRoot/LevelRoot" 6 | 7 | var level_scenes : Dictionary = { 8 | "Level1" : "res://scenes/levels/level_1.tscn" 9 | } 10 | 11 | func load_main_scene_container() -> void: 12 | if get_tree().root.has_node(main_scene_root_path): 13 | return 14 | 15 | var node: Node = load(main_scene_path).instantiate() 16 | 17 | if node != null: 18 | get_tree().root.add_child(node) 19 | 20 | 21 | func load_level(level: String) -> void: 22 | var scene_path: String = level_scenes.get(level) 23 | 24 | if scene_path == null: 25 | return 26 | 27 | var level_scene: Node = load(scene_path).instantiate() 28 | var level_root: Node = get_node(main_scene_level_root_path) 29 | 30 | if level_root != null: 31 | var nodes = level_root.get_children() 32 | 33 | if nodes != null: 34 | for node: Node in nodes: 35 | node.queue_free() 36 | 37 | await get_tree().process_frame 38 | 39 | level_root.add_child(level_scene) 40 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/globals/tool_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var selected_tool: DataTypes.Tools = DataTypes.Tools.None 4 | 5 | signal tool_selected(tool: DataTypes.Tools) 6 | signal enable_tool(tool : DataTypes.Tools) 7 | 8 | 9 | func select_tool(tool: DataTypes.Tools) -> void: 10 | tool_selected.emit(tool) 11 | selected_tool = tool 12 | 13 | func enable_tool_button(tool : DataTypes.Tools) -> void: 14 | enable_tool.emit(tool) 15 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/state_machine/node_state.gd: -------------------------------------------------------------------------------- 1 | class_name NodeState 2 | extends Node 3 | 4 | @warning_ignore("unused_signal") 5 | signal transition 6 | 7 | 8 | func _on_process(_delta : float) -> void: 9 | pass 10 | 11 | 12 | func _on_physics_process(_delta : float) -> void: 13 | pass 14 | 15 | 16 | func _on_next_transitions() -> void: 17 | pass 18 | 19 | 20 | func _on_enter() -> void: 21 | pass 22 | 23 | 24 | func _on_exit() -> void: 25 | pass 26 | -------------------------------------------------------------------------------- /tutorials/croptails/scripts/scripts/state_machine/node_state_machine.gd: -------------------------------------------------------------------------------- 1 | class_name NodeStateMachine 2 | extends Node 3 | 4 | @export var initial_node_state : NodeState 5 | 6 | var node_states : Dictionary = {} 7 | var current_node_state : NodeState 8 | var current_node_state_name : String 9 | var parent_node_name: String 10 | 11 | 12 | func _ready() -> void: 13 | parent_node_name = get_parent().name 14 | 15 | for child in get_children(): 16 | if child is NodeState: 17 | node_states[child.name.to_lower()] = child 18 | child.transition.connect(transition_to) 19 | 20 | if initial_node_state: 21 | initial_node_state._on_enter() 22 | current_node_state = initial_node_state 23 | current_node_state_name = current_node_state.name.to_lower() 24 | 25 | 26 | func _process(delta : float) -> void: 27 | if current_node_state: 28 | current_node_state._on_process(delta) 29 | 30 | 31 | func _physics_process(delta: float) -> void: 32 | if current_node_state: 33 | current_node_state._on_physics_process(delta) 34 | current_node_state._on_next_transitions() 35 | #print(parent_node_name, " Current State: ", current_node_state_name) 36 | 37 | 38 | func transition_to(node_state_name : String) -> void: 39 | if node_state_name == current_node_state.name.to_lower(): 40 | return 41 | 42 | var new_node_state = node_states.get(node_state_name.to_lower()) 43 | 44 | if !new_node_state: 45 | return 46 | 47 | if current_node_state: 48 | current_node_state._on_exit() 49 | 50 | new_node_state._on_enter() 51 | 52 | current_node_state = new_node_state 53 | current_node_state_name = current_node_state.name.to_lower() 54 | #print("Current State: ", current_node_state_name) 55 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/camera_pivot.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | @export var player : Player 4 | @export var camera_spring_arm : SpringArm3D 5 | @export var camera : Camera3D 6 | 7 | @export_category("Camera Field Of View") 8 | @export var enable_fov_on_run : bool 9 | @export var normal_fov : float = 70.0 10 | @export var run_fov : float = 85.0 11 | 12 | @export_category("Camera Settings") 13 | @export var camera_blend : float = 0.05 14 | @export_range(-45, 0, 1, "degrees") var min_rotation_x : float = rad_to_deg(-PI/4) 15 | @export_range(0, 45, 1, "degrees") var max_rotation_x : float = rad_to_deg(PI/10) 16 | 17 | 18 | func _unhandled_input(event: InputEvent) -> void: 19 | if event.is_action_pressed("capture_mouse"): 20 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 21 | 22 | if event.is_action_pressed("release_mouse"): 23 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) 24 | 25 | if event is InputEventMouseMotion: 26 | rotate_y(-event.relative.x * 0.005) 27 | camera_spring_arm.rotate_x(-event.relative.y * 0.005) 28 | camera_spring_arm.rotation.x = clamp(camera_spring_arm.rotation.x, deg_to_rad(min_rotation_x), deg_to_rad(max_rotation_x)) 29 | 30 | func _physics_process(_delta: float) -> void: 31 | if enable_fov_on_run: 32 | #if player.is_on_floor(): 33 | if GameInputEvents.run_input(): 34 | camera.fov = lerp(camera.fov, run_fov, camera_blend) 35 | else: 36 | camera.fov = lerp(camera.fov, normal_fov, camera_blend) 37 | #else: 38 | # camera.fov = lerp(camera.fov, normal_fov, camera_blend) 39 | 40 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/player.gd: -------------------------------------------------------------------------------- 1 | class_name Player 2 | extends CharacterBody3D 3 | 4 | @export var head_collision_shape : CollisionShape3D 5 | 6 | var coyote_jump : bool 7 | 8 | func _ready() -> void: 9 | head_collision_shape.disabled = true 10 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/player_animation_tree_transitions.gd: -------------------------------------------------------------------------------- 1 | class_name PlayerAnimationTreeTransitions 2 | 3 | 4 | static func play_idle_animation(animation_tree : AnimationTree) -> void: 5 | animation_tree["parameters/player_transition_states/transition_request"] = "state_idle" 6 | 7 | static func play_walk_animation(animation_tree : AnimationTree) -> void: 8 | animation_tree["parameters/player_transition_states/transition_request"] = "state_walk" 9 | 10 | static func play_run_animation(animation_tree : AnimationTree) -> void: 11 | animation_tree["parameters/player_transition_states/transition_request"] = "state_run" 12 | 13 | static func play_fall_animation(animation_tree : AnimationTree) -> void: 14 | animation_tree["parameters/player_transition_states/transition_request"] = "state_fall" 15 | 16 | static func play_jump_animation(animation_tree : AnimationTree) -> void: 17 | animation_tree["parameters/player_transition_states/transition_request"] = "state_jump" 18 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/player_transitions.gd: -------------------------------------------------------------------------------- 1 | class_name PlayerTransitions 2 | 3 | static func is_idle() -> bool: 4 | if !GameInputEvents.is_movement_input(): 5 | return true 6 | else: 7 | return false 8 | 9 | static func is_walking() -> bool: 10 | if !GameInputEvents.run_input() and GameInputEvents.is_movement_input(): 11 | return true 12 | else: 13 | return false 14 | 15 | static func is_running() -> bool: 16 | if GameInputEvents.run_input() and GameInputEvents.is_movement_input(): 17 | return true 18 | else: 19 | return false 20 | 21 | static func is_falling(character : CharacterBody3D) -> bool: 22 | if !character.is_on_floor(): 23 | return true 24 | else: 25 | return false 26 | 27 | static func is_jumping() -> bool: 28 | if GameInputEvents.jump_input(): 29 | return true 30 | else: 31 | return false 32 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/states/fall_state.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | @export var player : Player 4 | @export var animation_tree : AnimationTree 5 | 6 | @export_category("Fall State") 7 | @export var coyote_time : float = 0.1 8 | 9 | var gravity : int 10 | 11 | func _ready() -> void: 12 | gravity = ProjectSettings.get("physics/3d/default_gravity") 13 | 14 | 15 | func _on_process(_delta : float) -> void: 16 | pass 17 | 18 | 19 | func _on_physics_process(delta : float) -> void: 20 | if !player.is_on_floor(): 21 | get_coyote_time() 22 | player.velocity.y -= gravity * delta 23 | 24 | player.move_and_slide() 25 | 26 | 27 | func _on_unhandled_input(_event: InputEvent) -> void: 28 | pass 29 | 30 | 31 | func _on_next_transitions() -> void: 32 | if !PlayerTransitions.is_falling(player): 33 | transition.emit("Idle") 34 | 35 | 36 | func _on_enter() -> void: 37 | PlayerAnimationTreeTransitions.play_fall_animation(animation_tree) 38 | player.coyote_jump = true 39 | 40 | 41 | func _on_exit() -> void: 42 | pass 43 | 44 | func get_coyote_time() -> void: 45 | await get_tree().create_timer(coyote_time).timeout 46 | player.coyote_jump = false 47 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/states/idle_state.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | @export var player : Player 4 | @export var animation_tree : AnimationTree 5 | 6 | var gravity : int 7 | 8 | func _ready() -> void: 9 | gravity = ProjectSettings.get("physics/3d/default_gravity") 10 | 11 | func _on_process(_delta : float) -> void: 12 | pass 13 | 14 | 15 | func _on_physics_process(delta : float) -> void: 16 | GameInputEvents.movement_input(player.transform) 17 | 18 | if !player.is_on_floor(): 19 | player.velocity.y -= gravity * delta 20 | 21 | player.move_and_slide() 22 | 23 | 24 | func _on_unhandled_input(_event: InputEvent) -> void: 25 | pass 26 | 27 | 28 | func _on_next_transitions() -> void: 29 | if PlayerTransitions.is_falling(player): 30 | transition.emit("Fall") 31 | 32 | if PlayerTransitions.is_walking(): 33 | transition.emit("Walk") 34 | 35 | if PlayerTransitions.is_running(): 36 | transition.emit("Run") 37 | 38 | if PlayerTransitions.is_jumping(): 39 | transition.emit("Jump") 40 | 41 | 42 | func _on_enter() -> void: 43 | PlayerAnimationTreeTransitions.play_idle_animation(animation_tree) 44 | 45 | 46 | func _on_exit() -> void: 47 | pass 48 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/states/jump_state.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | @export var player : Player 4 | @export var animation_tree : AnimationTree 5 | 6 | @export_category("Jump Settings") 7 | @export var jump_height : float = 1.5 8 | @export var jump_time_to_peak : float = 0.5 9 | @export var jump_time_to_descent : float = 0.5 10 | 11 | @export var velocity_multiplier : float = 2.0 12 | @export var gravity_multiplier : float = 3.5 13 | 14 | @onready var jump_velocity : float = (velocity_multiplier * jump_height) / jump_time_to_peak 15 | @onready var jump_gravity : float = (gravity_multiplier * jump_height) / (jump_time_to_peak * jump_time_to_peak) 16 | @onready var fall_gravity : float = (gravity_multiplier * jump_height) / (jump_time_to_descent * jump_time_to_descent) 17 | 18 | 19 | func _on_process(_delta : float) -> void: 20 | pass 21 | 22 | 23 | func _on_physics_process(delta : float) -> void: 24 | if player.is_on_floor(): 25 | print("on floor jump") 26 | player.velocity.y = jump_velocity 27 | player.coyote_jump = false 28 | 29 | if player.coyote_jump: 30 | print("coyote jump") 31 | player.velocity.y = jump_velocity 32 | player.coyote_jump = false 33 | 34 | if !player.is_on_floor(): 35 | player.velocity.y -= get_gravity() * delta 36 | 37 | player.move_and_slide() 38 | 39 | 40 | func _on_unhandled_input(_event: InputEvent) -> void: 41 | pass 42 | 43 | 44 | func _on_next_transitions() -> void: 45 | if !PlayerTransitions.is_falling(player): 46 | transition.emit("Idle") 47 | 48 | if PlayerTransitions.is_walking() and !PlayerTransitions.is_falling(player): 49 | transition.emit("Walk") 50 | 51 | if PlayerTransitions.is_running() and !PlayerTransitions.is_falling(player): 52 | transition.emit("Run") 53 | 54 | 55 | func _on_enter() -> void: 56 | PlayerAnimationTreeTransitions.play_jump_animation(animation_tree) 57 | player.coyote_jump = true 58 | 59 | func _on_exit() -> void: 60 | player.coyote_jump = false 61 | 62 | func get_gravity() -> float: 63 | if player.velocity.y < 0.0: 64 | return jump_gravity 65 | else: 66 | return fall_gravity 67 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/states/run_state.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | const ROTATION_LERP_VALUE : float = 0.15 4 | 5 | @export var player : Player 6 | @export var player_skeleton : Skeleton3D 7 | @export var head_collision_shape : CollisionShape3D 8 | @export var animation_tree : AnimationTree 9 | @export var camera_pivot : Node3D 10 | @export var run_speed : float = 4.0 11 | @export var head_offset : float = 0.25 12 | 13 | var gravity : int 14 | 15 | func _ready() -> void: 16 | gravity = ProjectSettings.get("physics/3d/default_gravity") 17 | 18 | func _on_process(_delta : float) -> void: 19 | pass 20 | 21 | 22 | func _on_physics_process(delta : float) -> void: 23 | var move_direction : Vector3 = GameInputEvents.movement_input(player.transform) 24 | move_direction = move_direction.rotated(Vector3.UP, camera_pivot.rotation.y) 25 | 26 | var horizontal_velocity :Vector3 = move_direction * run_speed 27 | player.velocity.x = horizontal_velocity.x 28 | player.velocity.z = horizontal_velocity.z 29 | 30 | player.velocity.y -= gravity * delta 31 | 32 | if move_direction.length() > 0: 33 | player_skeleton.rotation.y = lerp_angle(player_skeleton.rotation.y, atan2(player.velocity.x, player.velocity.z), ROTATION_LERP_VALUE) 34 | head_collision_shape.position.x = player_skeleton.position.x + move_direction.x * head_offset 35 | head_collision_shape.position.z = player_skeleton.position.z + move_direction.z * head_offset 36 | 37 | player.apply_floor_snap() 38 | player.move_and_slide() 39 | 40 | 41 | func _on_unhandled_input(_event: InputEvent) -> void: 42 | pass 43 | 44 | 45 | func _on_next_transitions() -> void: 46 | if PlayerTransitions.is_idle(): 47 | transition.emit("Idle") 48 | 49 | if PlayerTransitions.is_falling(player): 50 | transition.emit("Fall") 51 | 52 | if PlayerTransitions.is_walking(): 53 | transition.emit("Walk") 54 | 55 | if PlayerTransitions.is_jumping(): 56 | transition.emit("Jump") 57 | 58 | 59 | func _on_enter() -> void: 60 | PlayerAnimationTreeTransitions.play_run_animation(animation_tree) 61 | head_collision_shape.disabled = false 62 | 63 | 64 | func _on_exit() -> void: 65 | head_collision_shape.disabled = true 66 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scenes/player/states/walk_state.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | const ROTATION_LERP_VALUE : float = 0.15 4 | 5 | @export var player : Player 6 | @export var player_skeleton : Skeleton3D 7 | @export var animation_tree : AnimationTree 8 | @export var camera_pivot : Node3D 9 | @export var walk_speed : float = 2.0 10 | 11 | var gravity : int 12 | 13 | func _ready() -> void: 14 | gravity = ProjectSettings.get("physics/3d/default_gravity") 15 | 16 | func _on_process(_delta : float) -> void: 17 | pass 18 | 19 | 20 | func _on_physics_process(delta : float) -> void: 21 | var move_direction : Vector3 = GameInputEvents.movement_input(player.transform) 22 | move_direction = move_direction.rotated(Vector3.UP, camera_pivot.rotation.y) 23 | 24 | var horizontal_velocity :Vector3 = move_direction * walk_speed 25 | player.velocity.x = horizontal_velocity.x 26 | player.velocity.z = horizontal_velocity.z 27 | 28 | player.velocity.y -= gravity * delta 29 | 30 | if move_direction.length() > 0: 31 | player_skeleton.rotation.y = lerp_angle(player_skeleton.rotation.y, atan2(player.velocity.x, player.velocity.z), ROTATION_LERP_VALUE) 32 | 33 | player.apply_floor_snap() 34 | player.move_and_slide() 35 | 36 | 37 | func _on_unhandled_input(_event: InputEvent) -> void: 38 | pass 39 | 40 | 41 | func _on_next_transitions() -> void: 42 | if PlayerTransitions.is_idle(): 43 | transition.emit("Idle") 44 | 45 | if PlayerTransitions.is_falling(player): 46 | transition.emit("Fall") 47 | 48 | if PlayerTransitions.is_running(): 49 | transition.emit("Run") 50 | 51 | if PlayerTransitions.is_jumping(): 52 | transition.emit("Jump") 53 | 54 | 55 | func _on_enter() -> void: 56 | PlayerAnimationTreeTransitions.play_walk_animation(animation_tree) 57 | 58 | 59 | func _on_exit() -> void: 60 | pass 61 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/script_templates/StateNode/state_node_script_template.gd: -------------------------------------------------------------------------------- 1 | extends StateNode 2 | 3 | 4 | func _on_process(_delta : float) -> void: 5 | pass 6 | 7 | 8 | func _on_physics_process(_delta : float) -> void: 9 | pass 10 | 11 | 12 | func _on_unhandled_input(_event: InputEvent) -> void: 13 | pass 14 | 15 | 16 | func _on_next_transitions() -> void: 17 | pass 18 | 19 | 20 | func _on_enter() -> void: 21 | pass 22 | 23 | 24 | func _on_exit() -> void: 25 | pass 26 | -------------------------------------------------------------------------------- /tutorials/prototype_3d/scripts/scripts/game_input_events.gd: -------------------------------------------------------------------------------- 1 | class_name GameInputEvents 2 | extends Node 3 | 4 | static var direction : Vector3 5 | 6 | 7 | static func movement_input(transform : Transform3D) -> Vector3: 8 | var move_direction : Vector2 = Input.get_vector("move_right", "move_left", "move_backward", "move_forward") 9 | direction = (transform.basis * Vector3(move_direction.x, 0, move_direction.y)).normalized() 10 | return direction 11 | 12 | static func is_movement_input() -> bool: 13 | if direction == Vector3.ZERO: 14 | return false 15 | else: 16 | return true 17 | 18 | static func run_input() -> bool: 19 | if Input.is_action_pressed("run"): 20 | return true 21 | else: 22 | return false 23 | 24 | static func jump_input() -> bool: 25 | var jump_value : bool = Input.is_action_just_pressed("jump") 26 | return jump_value 27 | --------------------------------------------------------------------------------