Skip to content

Instantly share code, notes, and snippets.

@polygonalcube
Last active October 26, 2023 16:02
Show Gist options
  • Save polygonalcube/c615c443ae94a2f1640efe744a22bac0 to your computer and use it in GitHub Desktop.
Save polygonalcube/c615c443ae94a2f1640efe744a22bac0 to your computer and use it in GitHub Desktop.
Castle Conquerors (solo) | The standout scripts of the project (https://polygonalcube.itch.io/castle-conquerors).

Castle Conquerors

Castle Conquerors is a solo project made for Mini Jam 134: Islands(2). Over the course of 3 days, I designed the game, wrote the code, and created the graphics.

extends StaticBody3D
@onready var hp = get_node("HealthComponent") #for testing purposes
@onready var shoot = get_node("ShootComponent")
@export var player_side:bool = true
@onready var danger_zone = get_node("DetectionRadius")
var dangerous:Array
func _physics_process(_delta):
if player_side:
gm.game_stats[2] = hp.health
else:
gm.game_stats[5] = hp.health
for enemy in dangerous:
if null:
dangerous.erase(enemy)
else:
#print("A bullet should exist now.")
shoot.shoot_at_position(global_position + Vector3(0,1,0), enemy.global_position + Vector3(0,1,0))
if hp.health <= 0:
if player_side:
gm.winner = gm.win_state.opponent
else:
gm.winner = gm.win_state.player
func _on_detection_radius_body_entered(body):
dangerous.append(body)
func _on_detection_radius_body_exited(body):
if dangerous.has(body):
dangerous.erase(body)
extends Node
#Layer and Mask index
#Layers:
#1 = ground
#2 = player side
#3 = enemy side
#4 = misc
@onready var coin = preload("res://Scenes/Coin.tscn")
var coin_locs:Array = [
Vector3(0,0,-8),
Vector3(0,0,8),
Vector3(-8,0,-6),
Vector3(-8,0,6),
Vector3(8,0,6),
Vector3(8,0,-6),
Vector3(-14,0,0),
Vector3(14,0,0),
]
var coin_timer = 2
enum win_state{none, player, opponent}
var winner = win_state.none
var has_played:bool = false
var in_game:bool = false
var game_stats:Array = [1,1,1,1,1,1]
var play_sound:int = 0 #0 = no sound, 1 = coin
func _physics_process(delta):
if in_game:
coin_timer += delta
if coin_timer >= 2:
coin_locs.shuffle()
var new_coin = coin.instantiate()
new_coin.visible = false
get_tree().get_root().get_node("World").call_deferred("add_child", new_coin)
await get_tree().physics_frame
new_coin.global_position = coin_locs[0]
new_coin.visible = true
coin_timer = 0
extends Control
@onready var gui_stats:Array
@onready var castle_hp_0 = get_node("PlayerStats/CastleHP")
@onready var fighter_hp_0 = get_node("PlayerStats/FighterHP")
@onready var wealth_0 = get_node("PlayerStats/Wealth")
@onready var castle_hp_1 = get_node("OpponentStats/CastleHP")
@onready var fighter_hp_1 = get_node("OpponentStats/FighterHP")
@onready var wealth_1 = get_node("OpponentStats/Wealth")
@onready var results = get_node("Results")
@onready var tutor = preload("res://Scenes/TutorialPrompt.tscn")
func _ready():
results.visible = false
# if !gm.has_played:
# #get_tree().paused = true
# call_deferred("add_child", tutor)
func _process(_delta):
gui_stats = gm.game_stats
fighter_hp_0.text = str(gui_stats[0])
wealth_0.text = str(gui_stats[1])
castle_hp_0.text = str(gui_stats[2])
fighter_hp_1.text = str(gui_stats[3])
wealth_1.text = str(gui_stats[4])
castle_hp_1.text = str(gui_stats[5])
if gm.winner != gm.win_state.none:
get_tree().paused = true
if gm.winner == gm.win_state.player:
results.text = "You win!"
elif gm.winner == gm.win_state.opponent:
results.text = "You lost!"
results.visible = true
if Input.is_action_just_pressed("pause") && gm.winner == gm.win_state.none:
get_tree().paused = !get_tree().paused
func _input(event):
if gm.winner != gm.win_state.none:
if Input.is_action_just_pressed("pause") || Input.is_action_just_pressed("attack"):
gm.winner = gm.win_state.none
get_tree().paused = false
get_tree().change_scene_to_file("res://Scenes/World.tscn")
extends CharacterBody3D
@onready var hp = get_node("HealthComponent")
@onready var mny:Node3D = get_node("MoneyComponent")
@onready var mov:Node3D = get_node("MovementComponent")
@onready var nav:NavigationAgent3D = get_node("NavigationComponent")
@onready var char_graphic = get_node("Cloak_Guy_003_Opponent")
var look_dir:Vector2 = Vector2.ZERO
@onready var anim:AnimationPlayer = get_node("Cloak_Guy_003_Opponent/AnimationPlayer")
@onready var castle = get_node("/root/World/CastleBlue")
@onready var statue = preload("res://Scenes/Statue.tscn")
enum states {alive, dead}
var state = states.alive
var death_timer:float = 0
var spawn_point:Vector3
func _ready():
spawn_point = global_position
func _physics_process(delta):
gm.game_stats[3] = hp.health
gm.game_stats[4] = mny.amount
match state:
states.alive:
#var direction:Vector3 = Vector3.ZERO
if nav.target == null || nav.target == Vector3.ZERO || nav.target == global_position:
nav.target = find_closest_target_in_group("collectibles")
if mny.amount >= 3:
velocity = mov.nav_toward_target(castle.global_position, is_on_floor())
else:
velocity = mov.nav_toward_target(nav.target, is_on_floor())
#print("Opponent | velocity:", velocity)
move_and_slide()
if velocity:
look_dir = Vector2(nav.get_next_path_position().x, nav.get_next_path_position().z) - Vector2(global_position.x, global_position.z)
look_dir = look_dir.normalized()
char_graphic.rotation.y = lerp_angle(char_graphic.rotation.y, -look_dir.angle() + (PI/2), 0.4)
anim.speed_scale = 5
anim.play("move")
else:
anim.speed_scale = .5
anim.play("idle")
if global_position.y < -25:
global_position = spawn_point
hp.take_damage(1)
if mny.amount >= 3 && global_position.distance_to(castle.global_position) < 2:
var new_statue = statue.instantiate()
new_statue.visible = false
get_tree().get_root().get_node("World").call_deferred("add_child", new_statue)
await get_tree().physics_frame
new_statue.global_position = Vector3(16,0,0)
new_statue.player_side = false
set_layers(new_statue, "0010")
set_layers(new_statue.hurt, "0010")
set_masks(new_statue.hurt, "0100")
set_layers(new_statue.hit, "0010")
new_statue.visible = true
mny.remove_money(3)
if hp.health <= 0:
death_timer = 5
state = states.dead
anim.speed_scale = 1
anim.play("death")
states.dead:
death_timer -= delta
if death_timer <= 0:
hp.heal(99)
global_position = spawn_point
state = states.alive
func set_layers(object, layers:String):
var bit_array = layers.split("")
var i:int = 1
for bit in bit_array:
object.set_collision_layer_value(i, bool(bit.to_int()))
i += 1
func set_masks(object, layers:String):
var bit_array = layers.split("")
var i:int = 1
for bit in bit_array:
object.set_collision_mask_value(i, bool(bit.to_int()))
i += 1
func find_closest_target_in_group(group:String) -> Vector3:
#assemble refs to all nodes in a group
var all_nodes = get_tree().get_root().get_node("World").get_children()
var group_nodes:Array = []
for node in all_nodes:
if node.is_in_group(group):
group_nodes.append(node)
if group_nodes.size() >= 1:
return group_nodes[0].global_position
else:
return global_position
extends CharacterBody3D
#To-do:
@onready var hp = get_node("HealthComponent")
@onready var mny:Node3D = get_node("MoneyComponent")
@onready var mov:Node3D = get_node("MovementComponent")
@onready var char_graphic = get_node("Cloak_Guy_003")
var look_dir:Vector2 = Vector2.ZERO
@onready var anim:AnimationPlayer = get_node("Cloak_Guy_003/AnimationPlayer")
@onready var castle = get_node("/root/World/CastleRed")
@onready var statue = preload("res://Scenes/Statue.tscn")
var spawn_point:Vector3
enum states {alive, dead}
var state = states.alive
var death_timer:float = 0
func _ready():
spawn_point = global_position
gm.in_game = true
func _physics_process(delta):
gm.game_stats[0] = hp.health
gm.game_stats[1] = mny.amount
match state:
states.alive:
#var direction = Input.get_vector("move_left", "move_right", "move_down", "move_up")
var direction:Vector3 = Vector3.ZERO
direction.x = Input.get_axis("move_left", "move_right")
direction.z = Input.get_axis("move_up", "move_down")
#print("direction before normalization:", direction)
#print("direction after normalization:", direction)
#print("")
velocity = mov.move(direction, is_on_floor())
move_and_slide()
if velocity:
look_dir = Vector2(direction.x, direction.z).normalized()
char_graphic.rotation.y = lerp_angle(char_graphic.rotation.y, -look_dir.angle() + (PI/2), 0.4)
#print(char_graphic.rotation)
anim.speed_scale = 5
anim.play("move")
else:
anim.speed_scale = .5
anim.play("idle")
if global_position.y < -25:
global_position = spawn_point
hp.take_damage(1)
if mny.amount >= 3 && global_position.distance_to(castle.global_position) < 2:
if Input.is_action_just_pressed("attack"):
var new_statue = statue.instantiate()
new_statue.visible = false
get_tree().get_root().get_node("World").call_deferred("add_child", new_statue)
await get_tree().physics_frame
new_statue.global_position = Vector3(-16,0,0)
set_layers(new_statue, "0100")
set_layers(new_statue.hurt, "0100")
set_masks(new_statue.hurt, "0010")
set_layers(new_statue.hit, "0100")
new_statue.visible = true
mny.remove_money(3)
if hp.health <= 0:
death_timer = 5
state = states.dead
anim.speed_scale = 1
anim.play("death")
states.dead:
death_timer -= delta
if death_timer <= 0:
hp.heal(99)
global_position = spawn_point
state = states.alive
func set_layers(object, layers:String):
var bit_array = layers.split("")
var i:int = 1
for bit in bit_array:
object.set_collision_layer_value(i, bool(bit.to_int()))
i += 1
func set_masks(object, layers:String):
var bit_array = layers.split("")
var i:int = 1
for bit in bit_array:
object.set_collision_mask_value(i, bool(bit.to_int()))
i += 1
extends CharacterBody3D
@onready var hurt:Area3D = get_node("HurtboxComponent")
@onready var mov:Node3D = get_node("MovementComponent")
@onready var nav:NavigationAgent3D = get_node("NavigationComponent")
@onready var hit:Area3D = get_node("Statue_002/Armature/Skeleton3D/BoneAttachment3D/HitboxComponent")
enum states {idle, move, attack}
var state = states.idle
@onready var timer:float = 0
@onready var char_graphic = get_node("Statue_002")
@onready var anim:AnimationPlayer = get_node("Statue_002/AnimationPlayer")
var player_side:bool = true
@onready var castles:Array = [get_node("/root/World/CastleRed"), get_node("/root/World/CastleBlue")]
var death_timer:float = 3
func _ready():
await get_tree().physics_frame
if player_side:
if castles[1] != null:
nav.target = castles[1].global_position
else:
if castles[0] != null:
nav.target = castles[0].global_position
print(name, " collision_layer is:", collision_layer)
print(name, "'s HurtboxComponent", " collision_layer is:", hurt.collision_layer)
print(name, "'s HurtboxComponent", " collision_mask is:", hurt.collision_mask)
print(name, "'s HitboxComponent", " collision_layer is:", hit.collision_layer)
func _physics_process(delta):
timer -= delta
match state:
states.idle:
hit.power = 0
global_position.y = 0
if timer <= 0:
timer = 1
state = states.move
states.move:
hit.power = 0
velocity = mov.nav_toward_target(nav.target, is_on_floor())
move_and_slide()
if velocity:
var look_dir:Vector2
look_dir = Vector2(nav.get_next_path_position().x, nav.get_next_path_position().z) - Vector2(global_position.x, global_position.z)
look_dir = look_dir.normalized()
char_graphic.rotation.y = lerp_angle(char_graphic.rotation.y, -look_dir.angle() + (PI/2), 0.4)
if global_position.distance_to(nav.target) <= 2:
state = states.attack
states.attack:
hit.power = 1
anim.play("attack")
if !anim.is_playing():
state = states.move
"""
if global_position.distance_to(nav.target) <= 2:
death_timer -= delta
if death_timer <= 0:
queue_free()
"""
"""
#broken code to try to make the statues bounce
var direction:Vector3 = mov.nav_toward_target(Vector3(16,0,0), is_on_floor())
var tween = create_tween()
tween.tween_property(self, "global_position", global_position + direction.normalized() + Vector3(0,1,0), .5)
tween.tween_property(self, "global_position", global_position + Vector3(0,-1,0), .5)
timer = 1
state = states.idle
"""
"""
#old look_dir code
look_dir = Vector2(direction.x, direction.z).normalized()
char_graphic.rotation.y = lerp_angle(char_graphic.rotation.y, -look_dir.angle() + (PI/2), 0.4)
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment