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.
Last active
October 26, 2023 16:02
Castle Conquerors (solo) | The standout scripts of the project (https://polygonalcube.itch.io/castle-conquerors).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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