C# rewrite #1
25
Autoloads/ConfigFileHandler.cs
Normal file
25
Autoloads/ConfigFileHandler.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.Autoloads;
|
||||
|
||||
public partial class ConfigFileHandler : Node
|
||||
{
|
||||
private ConfigFile _settingsConfig = new();
|
||||
private const string SettingsPath = "user://settings.ini";
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (!FileAccess.FileExists(SettingsPath))
|
||||
{
|
||||
var err = _settingsConfig.Save(SettingsPath);
|
||||
if (err != Error.Ok)
|
||||
GD.PushError($"Failed to create settings file at {SettingsPath}: {err}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var err = _settingsConfig.Load(SettingsPath);
|
||||
if (err != Error.Ok)
|
||||
GD.PushError($"Failed to load settings file at {SettingsPath}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
195
Autoloads/GameManager.cs
Normal file
195
Autoloads/GameManager.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.Autoloads;
|
||||
|
||||
public partial class GameManager : Node
|
||||
{
|
||||
[Export] public Array<PackedScene> LevelScenes { get; set; } = new();
|
||||
|
||||
public Dictionary PlayerState { get; set; } = new()
|
||||
{
|
||||
{ "coins", 0 },
|
||||
{ "lives", 3 },
|
||||
{ "current_level", 0 },
|
||||
{ "completed_levels", new Array<int>() },
|
||||
{ "unlocked_levels", new Array<int>() {0}},
|
||||
{ "unlocked_skills", new Array<SkillData>() }
|
||||
};
|
||||
|
||||
private Dictionary _currentSessionState = new()
|
||||
{
|
||||
{ "coins_collected", 0 },
|
||||
{ "skills_unlocked", new Array<SkillData>() }
|
||||
};
|
||||
|
||||
public void AddCoins(int amount)
|
||||
{
|
||||
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] + amount);
|
||||
}
|
||||
|
||||
public void SetCoins(int amount) => PlayerState["coins"] = Mathf.Max(0, amount);
|
||||
|
||||
public int GetCoins() => (int)PlayerState["coins"] + (int)_currentSessionState["coins_collected"];
|
||||
|
||||
public void RemoveCoins(int amount)
|
||||
{
|
||||
var sessionCoins = (int)_currentSessionState["coins_collected"];
|
||||
if (amount <= sessionCoins)
|
||||
{
|
||||
_currentSessionState["coins_collected"] = sessionCoins - amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = amount - sessionCoins;
|
||||
_currentSessionState["coins_collected"] = 0;
|
||||
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] - remaining);
|
||||
}
|
||||
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"]);
|
||||
}
|
||||
|
||||
public void AddLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] + amount;
|
||||
public void RemoveLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] - amount;
|
||||
public void SetLives(int amount) => PlayerState["lives"] = amount;
|
||||
public int GetLives() => (int)PlayerState["lives"];
|
||||
|
||||
public bool IsSkillUnlocked(SkillData skill)
|
||||
{
|
||||
return ((Array)PlayerState["unlocked_skills"]).Contains(skill)
|
||||
|| ((Array)_currentSessionState["skills_unlocked"]).Contains(skill);
|
||||
}
|
||||
|
||||
public void UnlockSkill(SkillData skill)
|
||||
{
|
||||
if (!IsSkillUnlocked(skill))
|
||||
((Array)PlayerState["unlocked_skills"]).Add(skill);
|
||||
}
|
||||
|
||||
public void RemoveSkill(string skillName)
|
||||
{
|
||||
var arr = (Array)PlayerState["unlocked_skills"];
|
||||
foreach (SkillData s in arr)
|
||||
{
|
||||
if (s.Name != skillName) continue;
|
||||
|
||||
arr.Remove(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnlockSkills(Array<SkillData> skills)
|
||||
{
|
||||
foreach (var s in skills)
|
||||
UnlockSkill(s);
|
||||
}
|
||||
|
||||
public void ResetPlayerState()
|
||||
{
|
||||
PlayerState = new Dictionary
|
||||
{
|
||||
{ "coins", 0 },
|
||||
{ "lives", 3 },
|
||||
{ "current_level", 0 },
|
||||
{ "completed_levels", new Array<int>() },
|
||||
{ "unlocked_levels", new Array<int>() {0}},
|
||||
{ "unlocked_skills", new Array<SkillData>() }
|
||||
};
|
||||
}
|
||||
|
||||
public void UnlockLevel(int levelIndex)
|
||||
{
|
||||
var unlocked = (Array)PlayerState["unlocked_levels"];
|
||||
if (!unlocked.Contains(levelIndex)) unlocked.Add(levelIndex);
|
||||
}
|
||||
|
||||
public void TryToGoToNextLevel()
|
||||
{
|
||||
var next = (int)PlayerState["current_level"] + 1;
|
||||
var unlocked = (Array)PlayerState["unlocked_levels"];
|
||||
if (next < LevelScenes.Count && unlocked.Contains(next))
|
||||
{
|
||||
PlayerState["current_level"] = next;
|
||||
GetTree().ChangeSceneToPacked(LevelScenes[next]);
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkLevelComplete(int levelIndex)
|
||||
{
|
||||
UnlockLevel(levelIndex + 1);
|
||||
var completed = (Array)PlayerState["completed_levels"];
|
||||
if (!completed.Contains(levelIndex)) completed.Add(levelIndex);
|
||||
}
|
||||
|
||||
public void ResetCurrentSessionState()
|
||||
{
|
||||
_currentSessionState = new Dictionary
|
||||
{
|
||||
{ "coins_collected", 0 },
|
||||
{ "skills_unlocked", new Array<SkillData>() }
|
||||
};
|
||||
}
|
||||
|
||||
public void RestartGame()
|
||||
{
|
||||
ResetPlayerState();
|
||||
ResetCurrentSessionState();
|
||||
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
||||
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||
}
|
||||
|
||||
public void QuitGame() => GetTree().Quit();
|
||||
|
||||
public void PauseGame() => Engine.TimeScale = 0;
|
||||
public void ResumeGame() => Engine.TimeScale = 1;
|
||||
|
||||
public void StartNewGame()
|
||||
{
|
||||
ResetPlayerState();
|
||||
ResetCurrentSessionState();
|
||||
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
||||
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||
}
|
||||
|
||||
public void ContinueGame()
|
||||
{
|
||||
var save = GetNode<SaveSystem>("/root/SaveSystem");
|
||||
if (!save.LoadGame())
|
||||
{
|
||||
GD.PrintErr("Failed to load game. Starting a new game instead.");
|
||||
StartNewGame();
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = (int)PlayerState["current_level"];
|
||||
if (idx < LevelScenes.Count)
|
||||
GetTree().ChangeSceneToPacked(LevelScenes[idx]);
|
||||
else
|
||||
GD.PrintErr("No levels unlocked to continue.");
|
||||
}
|
||||
|
||||
public void OnLevelComplete()
|
||||
{
|
||||
var levelIndex = (int)PlayerState["current_level"];
|
||||
MarkLevelComplete(levelIndex);
|
||||
AddCoins((int)_currentSessionState["coins_collected"]);
|
||||
foreach (var s in (Array)_currentSessionState["skills_unlocked"])
|
||||
UnlockSkill((SkillData)s);
|
||||
|
||||
ResetCurrentSessionState();
|
||||
TryToGoToNextLevel();
|
||||
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||
}
|
||||
|
||||
public Array GetUnlockedSkills()
|
||||
{
|
||||
var unlocked = (Array<SkillData>)PlayerState["unlocked_skills"];
|
||||
var session = (Array<SkillData>)_currentSessionState["skills_unlocked"];
|
||||
if ((((Array)session)!).Count == 0) return (Array)unlocked;
|
||||
if ((((Array)unlocked)!).Count == 0) return (Array)session;
|
||||
var joined = new Array();
|
||||
joined.AddRange((Array)unlocked ?? new Array());
|
||||
joined.AddRange((Array)session ?? new Array());
|
||||
return joined;
|
||||
}
|
||||
}
|
46
Autoloads/SaveSystem.cs
Normal file
46
Autoloads/SaveSystem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Mr.BrickAdventures.Autoloads;
|
||||
|
||||
public partial class SaveSystem : Node
|
||||
{
|
||||
[Export] public string SavePath { get; set; } = "user://savegame.save";
|
||||
[Export] public int Version { get; set; } = 1;
|
||||
|
||||
//private GM _gm;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
//_gm = GetNode<GM>("/root/GameManager");
|
||||
}
|
||||
|
||||
public void SaveGame()
|
||||
{
|
||||
//TODO: Implement saving logic
|
||||
}
|
||||
|
||||
public bool LoadGame()
|
||||
{
|
||||
//TODO: Implement loading logic
|
||||
|
||||
if (!FileAccess.FileExists(SavePath))
|
||||
return false;
|
||||
|
||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
|
||||
var saveDataObj = (Dictionary)file.GetVar();
|
||||
|
||||
if (saveDataObj.ContainsKey("version") && (int)saveDataObj["version"] != Version)
|
||||
{
|
||||
GD.Print($"Save file version mismatch. Expected: {Version}, Found: {saveDataObj["version"]}");
|
||||
return false;
|
||||
}
|
||||
|
||||
GD.Print("Game state loaded from: ", SavePath);
|
||||
GD.Print("Player state: ", saveDataObj["player_state"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckSaveExists() => FileAccess.FileExists(SavePath);
|
||||
}
|
65
Autoloads/UIManager.cs
Normal file
65
Autoloads/UIManager.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Mr.BrickAdventures.Autoloads;
|
||||
|
||||
public partial class UIManager : Node
|
||||
{
|
||||
[Export] public Array<Control> UiStack { get; set; } = new();
|
||||
|
||||
[Signal] public delegate void ScreenPushedEventHandler(Control screen);
|
||||
[Signal] public delegate void ScreenPoppedEventHandler(Control screen);
|
||||
|
||||
public void PushScreen(Control screen)
|
||||
{
|
||||
if (screen == null)
|
||||
{
|
||||
GD.PushError($"Cannot push a null screen.");
|
||||
return;
|
||||
}
|
||||
|
||||
UiStack.Add(screen);
|
||||
screen.Show();
|
||||
screen.SetProcessInput(true);
|
||||
screen.SetFocusMode(Control.FocusModeEnum.All);
|
||||
screen.GrabFocus();
|
||||
EmitSignalScreenPushed(screen);
|
||||
}
|
||||
|
||||
public void PopScreen()
|
||||
{
|
||||
if (UiStack.Count == 0)
|
||||
{
|
||||
GD.PushError($"Cannot pop screen from an empty stack.");
|
||||
return;
|
||||
}
|
||||
|
||||
var top = (Control)UiStack[^1];
|
||||
UiStack.RemoveAt(UiStack.Count - 1);
|
||||
top.Hide();
|
||||
top.SetProcessInput(false);
|
||||
EmitSignalScreenPopped(top);
|
||||
top.AcceptEvent();
|
||||
|
||||
if (UiStack.Count > 0) ((Control)UiStack[^1]).GrabFocus();
|
||||
}
|
||||
|
||||
public Control TopScreen() => UiStack.Count > 0 ? (Control)UiStack[^1] : null;
|
||||
|
||||
public bool IsScreenOnTop(Control screen) => UiStack.Count > 0 && (Control)UiStack[^1] == screen;
|
||||
|
||||
public bool IsVisibleOnStack(Control screen) => UiStack.Contains(screen) && screen.Visible;
|
||||
|
||||
public void CloseAll()
|
||||
{
|
||||
while (UiStack.Count > 0)
|
||||
PopScreen();
|
||||
}
|
||||
|
||||
public static void HideAndDisable(Control screen)
|
||||
{
|
||||
screen.Hide();
|
||||
screen.SetProcessInput(false);
|
||||
}
|
||||
|
||||
}
|
@@ -4,4 +4,140 @@
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<RootNamespace>Mr.BrickAdventures</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="scripts\components\.idea\.gitignore" />
|
||||
<Content Include="scripts\components\.idea\encodings.xml" />
|
||||
<Content Include="scripts\components\.idea\indexLayout.xml" />
|
||||
<Content Include="scripts\components\.idea\inspectionProfiles\Project_Default.xml" />
|
||||
<Content Include="scripts\components\.idea\projectSettingsUpdater.xml" />
|
||||
<Content Include="scripts\components\.idea\vcs.xml" />
|
||||
<Content Include="scripts\components\.idea\workspace.xml" />
|
||||
<Content Include="scripts\components\BeamComponent.cs.uid" />
|
||||
<Content Include="scripts\components\beam_component.gd" />
|
||||
<Content Include="scripts\components\beam_component.gd.uid" />
|
||||
<Content Include="scripts\components\brick_throw.gd" />
|
||||
<Content Include="scripts\components\brick_throw.gd.uid" />
|
||||
<Content Include="scripts\components\bullet_component.gd" />
|
||||
<Content Include="scripts\components\bullet_component.gd.uid" />
|
||||
<Content Include="scripts\components\cage_component.gd" />
|
||||
<Content Include="scripts\components\cage_component.gd.uid" />
|
||||
<Content Include="scripts\components\cannot_stomp_component.gd" />
|
||||
<Content Include="scripts\components\cannot_stomp_component.gd.uid" />
|
||||
<Content Include="scripts\components\can_be_launched_component.gd" />
|
||||
<Content Include="scripts\components\can_be_launched_component.gd.uid" />
|
||||
<Content Include="scripts\components\can_pickup.gd" />
|
||||
<Content Include="scripts\components\can_pickup.gd.uid" />
|
||||
<Content Include="scripts\components\charge_throw_component.gd" />
|
||||
<Content Include="scripts\components\charge_throw_component.gd.uid" />
|
||||
<Content Include="scripts\components\chase_level_component.gd" />
|
||||
<Content Include="scripts\components\chase_level_component.gd.uid" />
|
||||
<Content Include="scripts\components\cleanup_component.gd" />
|
||||
<Content Include="scripts\components\cleanup_component.gd.uid" />
|
||||
<Content Include="scripts\components\collapsable.gd" />
|
||||
<Content Include="scripts\components\collapsable.gd.uid" />
|
||||
<Content Include="scripts\components\collectable.gd" />
|
||||
<Content Include="scripts\components\collectable.gd.uid" />
|
||||
<Content Include="scripts\components\damage_component.gd" />
|
||||
<Content Include="scripts\components\damage_component.gd.uid" />
|
||||
<Content Include="scripts\components\destroyable_component.gd" />
|
||||
<Content Include="scripts\components\destroyable_component.gd.uid" />
|
||||
<Content Include="scripts\components\effect_inflictor_component.gd" />
|
||||
<Content Include="scripts\components\effect_inflictor_component.gd.uid" />
|
||||
<Content Include="scripts\components\enemy_death.gd" />
|
||||
<Content Include="scripts\components\enemy_death.gd.uid" />
|
||||
<Content Include="scripts\components\enemy_wave_trigger.gd" />
|
||||
<Content Include="scripts\components\enemy_wave_trigger.gd.uid" />
|
||||
<Content Include="scripts\components\exit_door_component.gd" />
|
||||
<Content Include="scripts\components\exit_door_component.gd.uid" />
|
||||
<Content Include="scripts\components\explosive_component.gd" />
|
||||
<Content Include="scripts\components\explosive_component.gd.uid" />
|
||||
<Content Include="scripts\components\fade_away.gd" />
|
||||
<Content Include="scripts\components\fade_away.gd.uid" />
|
||||
<Content Include="scripts\components\fire_effect_component.gd" />
|
||||
<Content Include="scripts\components\fire_effect_component.gd.uid" />
|
||||
<Content Include="scripts\components\flashing_component.gd" />
|
||||
<Content Include="scripts\components\flashing_component.gd.uid" />
|
||||
<Content Include="scripts\components\flip_player.gd" />
|
||||
<Content Include="scripts\components\flip_player.gd.uid" />
|
||||
<Content Include="scripts\components\gravity_motion_component.gd" />
|
||||
<Content Include="scripts\components\gravity_motion_component.gd.uid" />
|
||||
<Content Include="scripts\components\health.gd" />
|
||||
<Content Include="scripts\components\health.gd.uid" />
|
||||
<Content Include="scripts\components\heal_component.gd" />
|
||||
<Content Include="scripts\components\heal_component.gd.uid" />
|
||||
<Content Include="scripts\components\hit_component.gd" />
|
||||
<Content Include="scripts\components\hit_component.gd.uid" />
|
||||
<Content Include="scripts\components\homing_missile_motion.gd" />
|
||||
<Content Include="scripts\components\homing_missile_motion.gd.uid" />
|
||||
<Content Include="scripts\components\ice_effect_component.gd" />
|
||||
<Content Include="scripts\components\ice_effect_component.gd.uid" />
|
||||
<Content Include="scripts\components\invulnerability_component.gd" />
|
||||
<Content Include="scripts\components\invulnerability_component.gd.uid" />
|
||||
<Content Include="scripts\components\jump_pad_component.gd" />
|
||||
<Content Include="scripts\components\jump_pad_component.gd.uid" />
|
||||
<Content Include="scripts\components\kill_player_out_of_screen.gd" />
|
||||
<Content Include="scripts\components\kill_player_out_of_screen.gd.uid" />
|
||||
<Content Include="scripts\components\knockback.gd" />
|
||||
<Content Include="scripts\components\knockback.gd.uid" />
|
||||
<Content Include="scripts\components\launch_component.gd" />
|
||||
<Content Include="scripts\components\launch_component.gd.uid" />
|
||||
<Content Include="scripts\components\lever_component.gd" />
|
||||
<Content Include="scripts\components\lever_component.gd.uid" />
|
||||
<Content Include="scripts\components\lifetime_component.gd" />
|
||||
<Content Include="scripts\components\lifetime_component.gd.uid" />
|
||||
<Content Include="scripts\components\magnetic_skill.gd" />
|
||||
<Content Include="scripts\components\magnetic_skill.gd.uid" />
|
||||
<Content Include="scripts\components\out_of_screen_component.gd" />
|
||||
<Content Include="scripts\components\out_of_screen_component.gd.uid" />
|
||||
<Content Include="scripts\components\periodic_shooting.gd" />
|
||||
<Content Include="scripts\components\periodic_shooting.gd.uid" />
|
||||
<Content Include="scripts\components\PlatformMovement.cs.uid" />
|
||||
<Content Include="scripts\components\platform_movement.gd" />
|
||||
<Content Include="scripts\components\platform_movement.gd.uid" />
|
||||
<Content Include="scripts\components\PlayerController.cs.uid" />
|
||||
<Content Include="scripts\components\player_death.gd" />
|
||||
<Content Include="scripts\components\player_death.gd.uid" />
|
||||
<Content Include="scripts\components\player_movement.gd" />
|
||||
<Content Include="scripts\components\player_movement.gd.uid" />
|
||||
<Content Include="scripts\components\progressive_damage_component.gd" />
|
||||
<Content Include="scripts\components\progressive_damage_component.gd.uid" />
|
||||
<Content Include="scripts\components\projectile_component.gd" />
|
||||
<Content Include="scripts\components\projectile_component.gd.uid" />
|
||||
<Content Include="scripts\components\projectile_init_component.gd" />
|
||||
<Content Include="scripts\components\projectile_init_component.gd.uid" />
|
||||
<Content Include="scripts\components\requirement_component.gd" />
|
||||
<Content Include="scripts\components\requirement_component.gd.uid" />
|
||||
<Content Include="scripts\components\score.gd" />
|
||||
<Content Include="scripts\components\score.gd.uid" />
|
||||
<Content Include="scripts\components\ship_movement.gd" />
|
||||
<Content Include="scripts\components\ship_movement.gd.uid" />
|
||||
<Content Include="scripts\components\ship_shooter.gd" />
|
||||
<Content Include="scripts\components\ship_shooter.gd.uid" />
|
||||
<Content Include="scripts\components\side_to_side_movement.gd" />
|
||||
<Content Include="scripts\components\side_to_side_movement.gd.uid" />
|
||||
<Content Include="scripts\components\skill_unlocker_component.gd" />
|
||||
<Content Include="scripts\components\skill_unlocker_component.gd.uid" />
|
||||
<Content Include="scripts\components\spaceship_enter_component.gd" />
|
||||
<Content Include="scripts\components\spaceship_enter_component.gd.uid" />
|
||||
<Content Include="scripts\components\spaceship_exit_component.gd" />
|
||||
<Content Include="scripts\components\spaceship_exit_component.gd.uid" />
|
||||
<Content Include="scripts\components\spin_component.gd" />
|
||||
<Content Include="scripts\components\spin_component.gd.uid" />
|
||||
<Content Include="scripts\components\status_effect_component.gd" />
|
||||
<Content Include="scripts\components\status_effect_component.gd.uid" />
|
||||
<Content Include="scripts\components\stomp_damage_component.gd" />
|
||||
<Content Include="scripts\components\stomp_damage_component.gd.uid" />
|
||||
<Content Include="scripts\components\straight_motion_component.gd" />
|
||||
<Content Include="scripts\components\straight_motion_component.gd.uid" />
|
||||
<Content Include="scripts\components\terrain_hit_fx.gd" />
|
||||
<Content Include="scripts\components\terrain_hit_fx.gd.uid" />
|
||||
<Content Include="scripts\components\tooltip_component.gd" />
|
||||
<Content Include="scripts\components\tooltip_component.gd.uid" />
|
||||
<Content Include="scripts\components\trail_component.gd" />
|
||||
<Content Include="scripts\components\trail_component.gd.uid" />
|
||||
<Content Include="scripts\components\trigger_lever_component.gd" />
|
||||
<Content Include="scripts\components\trigger_lever_component.gd.uid" />
|
||||
<Content Include="scripts\components\unlock_on_requirement_component.gd" />
|
||||
<Content Include="scripts\components\unlock_on_requirement_component.gd.uid" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -51,4 +51,4 @@ func close_all() -> void:
|
||||
|
||||
func hide_and_disable(screen: Control) -> void:
|
||||
screen.hide()
|
||||
screen.set_process_input(false)
|
||||
screen.set_process_input(false)
|
||||
|
@@ -19,7 +19,7 @@ config/version="in-dev"
|
||||
run/main_scene="uid://cl00e2ocomk3m"
|
||||
config/use_custom_user_dir=true
|
||||
config/custom_user_dir_name="MrBrickAdventures"
|
||||
config/features=PackedStringArray("4.4", "GL Compatibility")
|
||||
config/features=PackedStringArray("4.4", "C#", "GL Compatibility")
|
||||
run/max_fps=180
|
||||
boot_splash/bg_color=Color(0, 0, 0, 1)
|
||||
boot_splash/show_image=false
|
||||
|
9
scripts/Resources/CollectableResource.cs
Normal file
9
scripts/Resources/CollectableResource.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public partial class CollectableResource : Resource
|
||||
{
|
||||
[Export] public Variant Amount { get; set; } = 0.0;
|
||||
[Export] public CollectableType Type { get; set; }
|
||||
}
|
8
scripts/Resources/CollectableType.cs
Normal file
8
scripts/Resources/CollectableType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public enum CollectableType
|
||||
{
|
||||
Coin,
|
||||
Kid,
|
||||
Health,
|
||||
}
|
19
scripts/Resources/SkillData.cs
Normal file
19
scripts/Resources/SkillData.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public partial class SkillData : Resource
|
||||
{
|
||||
[Export] public String Name { get; set; } = "New Skill";
|
||||
[Export] public String Description { get; set; } = "New Skill";
|
||||
[Export] public Dictionary<String, Variant> Config { get; set; } = new();
|
||||
[Export] public int Cost { get; set; } = 0;
|
||||
[Export] public Texture2D Icon { get; set; }
|
||||
[Export] public bool IsActive { get; set; } = false;
|
||||
[Export] public int Level { get; set; } = 1;
|
||||
[Export] public int MaxLevel { get; set; } = 1;
|
||||
[Export] public SkillType Type { get; set; } = SkillType.Throw;
|
||||
[Export] public PackedScene Node { get; set; }
|
||||
}
|
8
scripts/Resources/SkillType.cs
Normal file
8
scripts/Resources/SkillType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public enum SkillType
|
||||
{
|
||||
Attack,
|
||||
Throw,
|
||||
Misc,
|
||||
}
|
8
scripts/Resources/StatusEffectDataResource.cs
Normal file
8
scripts/Resources/StatusEffectDataResource.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public partial class StatusEffectDataResource : Resource
|
||||
{
|
||||
[Export] public StatusEffectType Type { get; set; }
|
||||
}
|
8
scripts/Resources/StatusEffectType.cs
Normal file
8
scripts/Resources/StatusEffectType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
public enum StatusEffectType
|
||||
{
|
||||
None,
|
||||
Fire,
|
||||
Ice
|
||||
}
|
15
scripts/Screenshot.cs
Normal file
15
scripts/Screenshot.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts;
|
||||
|
||||
public partial class Screenshot : Node
|
||||
{
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!OS.IsDebugBuild() || !Input.IsActionJustPressed("screenshot")) return;
|
||||
var img = GetViewport().GetTexture().GetImage();
|
||||
var id = OS.GetUniqueId() + "_" + Time.GetDatetimeStringFromSystem();
|
||||
var path = "user://screenshots/screenshot_" + id + ".png";
|
||||
img.SavePng(path);
|
||||
}
|
||||
}
|
144
scripts/SkillManager.cs
Normal file
144
scripts/SkillManager.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.Autoloads;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts;
|
||||
|
||||
public partial class SkillManager : Node
|
||||
{
|
||||
private GameManager _gameManager;
|
||||
[Export] public Array<SkillData> AvailableSkills { get; set; } = [];
|
||||
|
||||
public Dictionary ActiveComponents { get; private set; } = new();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_gameManager = GetNode<GameManager>("/root/GameManager");
|
||||
ApplyUnlockedSkills();
|
||||
}
|
||||
|
||||
public void AddSkill(SkillData skillData)
|
||||
{
|
||||
if (ActiveComponents.ContainsKey(skillData.Name))
|
||||
return;
|
||||
|
||||
if (skillData.Type == SkillType.Throw)
|
||||
{
|
||||
var unlocked = _gameManager.GetUnlockedSkills();
|
||||
foreach (var skill in unlocked)
|
||||
{
|
||||
SkillData data = null;
|
||||
foreach (var s in AvailableSkills)
|
||||
{
|
||||
if (s == (SkillData)skill)
|
||||
{
|
||||
data = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data != null && data.Type == SkillType.Throw)
|
||||
RemoveSkill(data.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var instance = skillData.Node.Instantiate();
|
||||
foreach (var key in skillData.Config.Keys)
|
||||
{
|
||||
if (instance.HasMethod("get")) // rough presence check
|
||||
{
|
||||
var value = skillData.Config[key];
|
||||
var parent = GetParent();
|
||||
|
||||
if (value.VariantType == Variant.Type.NodePath)
|
||||
{
|
||||
var np = (NodePath)value;
|
||||
if (parent.HasNode(np))
|
||||
value = parent.GetNode(np);
|
||||
else if (instance.HasNode(np))
|
||||
value = instance.GetNode(np);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set via property if exists
|
||||
instance.Set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
Owner.AddChild(instance);
|
||||
ActiveComponents[skillData.Name] = instance;
|
||||
}
|
||||
|
||||
public void RemoveSkill(string skillName)
|
||||
{
|
||||
if (!ActiveComponents.TryGetValue(skillName, out var component))
|
||||
return;
|
||||
|
||||
var inst = (Node)component;
|
||||
if (IsInstanceValid(inst))
|
||||
inst.QueueFree();
|
||||
|
||||
var skills = _gameManager.GetUnlockedSkills();
|
||||
foreach (SkillData s in skills)
|
||||
{
|
||||
if (s.Name == skillName)
|
||||
{
|
||||
s.IsActive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ActiveComponents.Remove(skillName);
|
||||
}
|
||||
|
||||
public void ApplyUnlockedSkills()
|
||||
{
|
||||
foreach (var sd in AvailableSkills)
|
||||
{
|
||||
if (_gameManager.IsSkillUnlocked(sd))
|
||||
{
|
||||
GD.Print("Applying skill: ", sd.Name);
|
||||
CallDeferred(MethodName.AddSkill, sd);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveSkill(sd.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SkillData GetSkillByName(string skillName)
|
||||
{
|
||||
foreach (var sd in AvailableSkills)
|
||||
if (sd.Name == skillName) return sd;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ActivateSkill(SkillData skill)
|
||||
{
|
||||
if (!ActiveComponents.ContainsKey(skill.Name))
|
||||
{
|
||||
AddSkill(skill);
|
||||
skill.IsActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeactivateSkill(SkillData skill)
|
||||
{
|
||||
if (ActiveComponents.ContainsKey(skill.Name))
|
||||
{
|
||||
RemoveSkill(skill.Name);
|
||||
skill.IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleSkillActivation(SkillData skill)
|
||||
{
|
||||
if (skill == null) return;
|
||||
|
||||
if (ActiveComponents.ContainsKey(skill.Name))
|
||||
DeactivateSkill(skill);
|
||||
else
|
||||
ActivateSkill(skill);
|
||||
}
|
||||
}
|
13
scripts/components/.idea/.gitignore
generated
vendored
Normal file
13
scripts/components/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/.idea.components.iml
|
||||
/contentModel.xml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
4
scripts/components/.idea/encodings.xml
generated
Normal file
4
scripts/components/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
8
scripts/components/.idea/indexLayout.xml
generated
Normal file
8
scripts/components/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
6
scripts/components/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
scripts/components/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
6
scripts/components/.idea/vcs.xml
generated
Normal file
6
scripts/components/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
50
scripts/components/CollectableComponent.cs
Normal file
50
scripts/components/CollectableComponent.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CollectableComponent : Node
|
||||
{
|
||||
private bool _hasFadeAway = false;
|
||||
|
||||
[Export] public Area2D Area2D { get; set; }
|
||||
[Export] public CollisionShape2D CollisionShape { get; set; }
|
||||
[Export] public CollectableResource Data { get; set; }
|
||||
[Export] public AudioStreamPlayer2D Sfx {get; set; }
|
||||
|
||||
[Signal] public delegate void CollectedEventHandler(Variant amount, CollectableType type, Node2D body);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Area2D != null)
|
||||
Area2D.BodyEntered += OnArea2DBodyEntered;
|
||||
else
|
||||
GD.PushError("Collectable node missing Area2D node.");
|
||||
|
||||
if (Owner.HasNode("FadeAwayComponent"))
|
||||
_hasFadeAway = true;
|
||||
}
|
||||
|
||||
private async void OnArea2DBodyEntered(Node2D body)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!body.HasNode("CanPickUpComponent")) return;
|
||||
|
||||
EmitSignalCollected(Data.Amount, Data.Type, body);
|
||||
CollisionShape?.CallDeferred("set_disabled", true);
|
||||
Sfx?.Play();
|
||||
|
||||
if (_hasFadeAway) return;
|
||||
|
||||
if (Sfx != null)
|
||||
await ToSignal(Sfx, AudioStreamPlayer2D.SignalName.Finished);
|
||||
Owner.QueueFree();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GD.PushError($"Error in CollectableComponent.OnArea2DBodyEntered: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
105
scripts/components/DamageComponent.cs
Normal file
105
scripts/components/DamageComponent.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class DamageComponent : Node
|
||||
{
|
||||
[Export] public float Damage { get; set; } = 0.25f;
|
||||
[Export] public Area2D Area { get; set; }
|
||||
[Export] public StatusEffectDataResource StatusEffectData { get; set; }
|
||||
[Export] public Timer DamageTimer { get; set; }
|
||||
|
||||
private Node _currentTarget = null;
|
||||
|
||||
[Signal] public delegate void EffectInflictedEventHandler(Node2D target, StatusEffectDataResource effect);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Area == null)
|
||||
{
|
||||
GD.PushError($"DamageComponent: Area2D node is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
Area.BodyEntered += OnAreaBodyEntered;
|
||||
Area.BodyExited += OnAreaBodyExited;
|
||||
Area.AreaEntered += OnAreaAreaEntered;
|
||||
|
||||
if (DamageTimer != null)
|
||||
{
|
||||
DamageTimer.Timeout += OnDamageTimerTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_currentTarget == null) return;
|
||||
if (DamageTimer != null) return;
|
||||
|
||||
ProcessEntityAndApplyDamage(_currentTarget as Node2D);
|
||||
}
|
||||
|
||||
public void DealDamage(HealthComponent target) => target.DecreaseHealth(Damage);
|
||||
|
||||
private void OnAreaAreaEntered(Area2D area)
|
||||
{
|
||||
if (!CheckIfProcessingIsOn())
|
||||
return;
|
||||
if (area == Area) return;
|
||||
|
||||
var parent = area.GetParent();
|
||||
if (parent.HasNode("DamageComponent"))
|
||||
ProcessEntityAndApplyDamage(parent as Node2D);
|
||||
}
|
||||
|
||||
private void OnAreaBodyExited(Node2D body)
|
||||
{
|
||||
if (body != _currentTarget) return;
|
||||
_currentTarget = null;
|
||||
DamageTimer?.Stop();
|
||||
}
|
||||
|
||||
private void OnAreaBodyEntered(Node2D body)
|
||||
{
|
||||
_currentTarget = body;
|
||||
|
||||
if (!CheckIfProcessingIsOn())
|
||||
return;
|
||||
|
||||
DamageTimer?.Start();
|
||||
|
||||
ProcessEntityAndApplyDamage(body);
|
||||
}
|
||||
|
||||
private void OnDamageTimerTimeout()
|
||||
{
|
||||
if (_currentTarget == null) return;
|
||||
|
||||
ProcessEntityAndApplyDamage(_currentTarget as Node2D);
|
||||
}
|
||||
|
||||
private void ProcessEntityAndApplyDamage(Node2D body)
|
||||
{
|
||||
if (body == null) return;
|
||||
|
||||
if (!body.HasNode("HealthComponent")) return;
|
||||
var health = body.GetNode<HealthComponent>("HealthComponent");
|
||||
var inv = body.GetNodeOrNull<InvulnerabilityComponent>("InvulnerabilityComponent");
|
||||
|
||||
if (inv != null && inv.IsInvulnerable())
|
||||
return;
|
||||
|
||||
if (StatusEffectData != null && StatusEffectData.Type != StatusEffectType.None)
|
||||
EmitSignalEffectInflicted(body, StatusEffectData);
|
||||
|
||||
DealDamage(health);
|
||||
|
||||
inv?.Activate();
|
||||
}
|
||||
|
||||
private bool CheckIfProcessingIsOn()
|
||||
{
|
||||
return ProcessMode is ProcessModeEnum.Inherit or ProcessModeEnum.Always;
|
||||
}
|
||||
}
|
83
scripts/components/FlashingComponent.cs
Normal file
83
scripts/components/FlashingComponent.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class FlashingComponent : Node
|
||||
{
|
||||
[Export] public Node2D Sprite { get; set; }
|
||||
[Export] public float FlashDuration { get; set; } = 0.5f;
|
||||
[Export] public float FlashTime { get; set; } = 0.1f;
|
||||
[Export] public bool UseModulate { get; set; } = true;
|
||||
[Export] public HealthComponent HealthComponent { get; set; }
|
||||
|
||||
private Tween _tween;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (HealthComponent != null)
|
||||
{
|
||||
HealthComponent.HealthChanged += OnHealthChanged;
|
||||
HealthComponent.Death += OnDeath;
|
||||
}
|
||||
|
||||
if (Sprite == null)
|
||||
{
|
||||
GD.PushError("FlashingComponent: Sprite node is not set.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartFlashing()
|
||||
{
|
||||
if (Sprite == null) return;
|
||||
|
||||
_tween?.Kill();
|
||||
|
||||
_tween = CreateTween();
|
||||
_tween.SetParallel(true);
|
||||
|
||||
var flashes = (int)(FlashDuration / FlashTime);
|
||||
for (var i = 0; i < flashes; i++)
|
||||
{
|
||||
if (UseModulate)
|
||||
{
|
||||
var opacity = i % 2 == 0 ? 1.0f : 0.3f;
|
||||
_tween.TweenProperty(Sprite, "modulate:a", opacity, FlashTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
var visible = i % 2 == 0;
|
||||
_tween.TweenProperty(Sprite, "visible", visible, FlashTime);
|
||||
}
|
||||
}
|
||||
|
||||
_tween.TweenCallback(Callable.From(StopFlashing));
|
||||
}
|
||||
|
||||
public void StopFlashing()
|
||||
{
|
||||
if (UseModulate)
|
||||
{
|
||||
var modulateColor = Sprite.GetModulate();
|
||||
modulateColor.A = 1.0f;
|
||||
Sprite.SetModulate(modulateColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sprite.SetVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHealthChanged(float delta, float totalHealth)
|
||||
{
|
||||
if (delta < 0f)
|
||||
{
|
||||
StartFlashing();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeath()
|
||||
{
|
||||
StopFlashing();
|
||||
}
|
||||
}
|
65
scripts/components/HealthComponent.cs
Normal file
65
scripts/components/HealthComponent.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class HealthComponent : Node
|
||||
{
|
||||
[Export] public float Health { get; set; } = 1.0f;
|
||||
[Export] public float MaxHealth { get; set; } = 1.0f;
|
||||
[Export] public AudioStreamPlayer2D HurtSfx { get; set; }
|
||||
[Export] public AudioStreamPlayer2D HealSfx { get; set; }
|
||||
|
||||
[Signal] public delegate void HealthChangedEventHandler(float delta, float totalHealth);
|
||||
[Signal] public delegate void DeathEventHandler();
|
||||
|
||||
public void SetHealth(float newValue)
|
||||
{
|
||||
_ = ApplyHealthChange(newValue);
|
||||
}
|
||||
|
||||
public void IncreaseHealth(float delta)
|
||||
{
|
||||
_ = ApplyHealthChange(Health + delta);
|
||||
}
|
||||
|
||||
public void DecreaseHealth(float delta)
|
||||
{
|
||||
_ = ApplyHealthChange(Health - delta);
|
||||
}
|
||||
|
||||
public float GetDelta(float newValue) => newValue - Health;
|
||||
|
||||
private async Task ApplyHealthChange(float newHealth, bool playSfx = true)
|
||||
{
|
||||
newHealth = Mathf.Clamp(newHealth, 0.0f, MaxHealth);
|
||||
var delta = newHealth - Health;
|
||||
|
||||
if (delta == 0.0f)
|
||||
return;
|
||||
|
||||
if (playSfx)
|
||||
{
|
||||
if (delta > 0f && HealSfx != null)
|
||||
{
|
||||
HealSfx.Play();
|
||||
}
|
||||
else if (delta < 0f && HurtSfx != null)
|
||||
{
|
||||
HurtSfx.Play();
|
||||
await HurtSfx.ToSignal(HurtSfx, AudioStreamPlayer2D.SignalName.Finished);
|
||||
}
|
||||
}
|
||||
|
||||
Health = newHealth;
|
||||
|
||||
if (Health <= 0f)
|
||||
{
|
||||
EmitSignalDeath();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitSignalHealthChanged(delta, Health);
|
||||
}
|
||||
}
|
||||
}
|
31
scripts/components/InvulnerabilityComponent.cs
Normal file
31
scripts/components/InvulnerabilityComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class InvulnerabilityComponent : Node
|
||||
{
|
||||
[Export] public float Duration { get; set; } = 1f;
|
||||
[Export] public FlashingComponent FlashingComponent { get; set; }
|
||||
|
||||
private bool _isInvulnerable = false;
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if (_isInvulnerable)
|
||||
return;
|
||||
|
||||
_isInvulnerable = true;
|
||||
FlashingComponent?.StartFlashing();
|
||||
|
||||
var timer = GetTree().CreateTimer(Duration);
|
||||
timer.Timeout += Deactivate;
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
{
|
||||
_isInvulnerable = false;
|
||||
FlashingComponent?.StopFlashing();
|
||||
}
|
||||
|
||||
public bool IsInvulnerable() => _isInvulnerable;
|
||||
}
|
169
scripts/components/PlatformMovement.cs
Normal file
169
scripts/components/PlatformMovement.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class PlatformMovement : Node2D, IMovement
|
||||
{
|
||||
[Export]
|
||||
public float Speed { get; set; } = 300.0f;
|
||||
|
||||
[Export]
|
||||
public float JumpHeight { get; set; } = 100f;
|
||||
|
||||
[Export]
|
||||
public float JumpTimeToPeak { get; set; } = 0.5f;
|
||||
|
||||
[Export]
|
||||
public float JumpTimeToDescent { get; set; } = 0.4f;
|
||||
|
||||
[Export]
|
||||
public int CoyoteFrames { get; set; } = 6;
|
||||
|
||||
[Export]
|
||||
public AudioStreamPlayer2D JumpSfx { get; set; }
|
||||
|
||||
[Export]
|
||||
public Node2D RotationTarget { get; set; }
|
||||
|
||||
[Export]
|
||||
public CharacterBody2D Body { get; set; }
|
||||
|
||||
private float _gravity;
|
||||
private bool _wasLastFloor = false;
|
||||
private bool _coyoteMode = false;
|
||||
private Timer _coyoteTimer;
|
||||
private Vector2 _lastDirection = new Vector2(1, 0);
|
||||
|
||||
private float _jumpVelocity;
|
||||
private float _jumpGravity;
|
||||
private float _fallGravity;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
if (Body == null)
|
||||
return;
|
||||
|
||||
_gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity");
|
||||
_jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f;
|
||||
_jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f;
|
||||
_fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f;
|
||||
|
||||
_coyoteTimer = new Timer
|
||||
{
|
||||
OneShot = true,
|
||||
WaitTime = CoyoteFrames / 60.0f
|
||||
};
|
||||
_coyoteTimer.Timeout += OnCoyoteTimerTimeout;
|
||||
AddChild(_coyoteTimer);
|
||||
}
|
||||
|
||||
public string MovementType { get; } = "platform";
|
||||
public bool Enabled { get; set; }
|
||||
public Vector2 PreviousVelocity { get; set; }
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
|
||||
if (Body == null || !Enabled)
|
||||
return;
|
||||
|
||||
if (Body.Velocity.X > 0.0f)
|
||||
RotationTarget.Rotation = Mathf.DegToRad(-10);
|
||||
else if (Body.Velocity.X < 0.0f)
|
||||
RotationTarget.Rotation = Mathf.DegToRad(10);
|
||||
else
|
||||
RotationTarget.Rotation = 0;
|
||||
|
||||
CalculateJumpVars();
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
base._PhysicsProcess(delta);
|
||||
|
||||
if (Body == null || !Enabled)
|
||||
return;
|
||||
|
||||
if (Body.IsOnFloor())
|
||||
{
|
||||
_wasLastFloor = true;
|
||||
_coyoteMode = false; // Reset coyote mode when back on the floor
|
||||
_coyoteTimer.Stop(); // Stop timer when grounded
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_wasLastFloor) // Start coyote timer only once
|
||||
{
|
||||
_coyoteMode = true;
|
||||
_coyoteTimer.Start();
|
||||
}
|
||||
_wasLastFloor = false;
|
||||
}
|
||||
|
||||
if (!Body.IsOnFloor())
|
||||
Body.Velocity += new Vector2(0, CalculateGravity()) * (float)delta;
|
||||
|
||||
if (Input.IsActionPressed("jump") && (Body.IsOnFloor() || _coyoteMode))
|
||||
Jump();
|
||||
|
||||
if (Input.IsActionJustPressed("down"))
|
||||
Body.Position += new Vector2(0, 1);
|
||||
|
||||
float direction = Input.GetAxis("left", "right");
|
||||
if (direction != 0)
|
||||
_lastDirection = HandleDirection(direction);
|
||||
|
||||
if (direction != 0)
|
||||
Body.Velocity = new Vector2(direction * Speed, Body.Velocity.Y);
|
||||
else
|
||||
Body.Velocity = new Vector2(Mathf.MoveToward(Body.Velocity.X, 0, Speed), Body.Velocity.Y);
|
||||
|
||||
Body.MoveAndSlide();
|
||||
}
|
||||
|
||||
private void Jump()
|
||||
{
|
||||
if (Body == null)
|
||||
return;
|
||||
|
||||
Body.Velocity = new Vector2(Body.Velocity.X, _jumpVelocity);
|
||||
_coyoteMode = false;
|
||||
if (JumpSfx != null)
|
||||
JumpSfx.Play();
|
||||
}
|
||||
|
||||
private float CalculateGravity()
|
||||
{
|
||||
return Body.Velocity.Y < 0.0f ? _jumpGravity : _fallGravity;
|
||||
}
|
||||
|
||||
private void OnCoyoteTimerTimeout()
|
||||
{
|
||||
_coyoteMode = false;
|
||||
}
|
||||
|
||||
private Vector2 HandleDirection(float inputDir)
|
||||
{
|
||||
if (inputDir > 0)
|
||||
return new Vector2(1, 0);
|
||||
else if (inputDir < 0)
|
||||
return new Vector2(-1, 0);
|
||||
return _lastDirection;
|
||||
}
|
||||
|
||||
public void OnShipEntered()
|
||||
{
|
||||
RotationTarget.Rotation = 0;
|
||||
}
|
||||
|
||||
private void CalculateJumpVars()
|
||||
{
|
||||
_jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f;
|
||||
_jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f;
|
||||
_fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f;
|
||||
}
|
||||
}
|
1
scripts/components/PlatformMovement.cs.uid
Normal file
1
scripts/components/PlatformMovement.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://btlm1f3l70il
|
103
scripts/components/PlayerController.cs
Normal file
103
scripts/components/PlayerController.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class PlayerController : Node2D
|
||||
{
|
||||
[Export]
|
||||
public string DefaultMovementType { get; set; } = "platform";
|
||||
|
||||
[Export]
|
||||
public Godot.Collections.Dictionary<string, NodePath> MovementTypes { get; set; }
|
||||
|
||||
[Export]
|
||||
public Sprite2D ShipSprite { get; set; }
|
||||
|
||||
private IMovement _currentMovement = null;
|
||||
[Signal]
|
||||
public delegate void MovementSwitchedEventHandler(string movementType);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
foreach (var movementType in MovementTypes.Keys)
|
||||
{
|
||||
var movementNode = GetNodeOrNull(movementType);
|
||||
if (movementNode is IMovement playerMovement)
|
||||
{
|
||||
playerMovement.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
SwitchMovement(DefaultMovementType);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
base._UnhandledInput(@event);
|
||||
|
||||
if (@event is InputEventKey inputEventKey && inputEventKey.IsActionPressed("switch_movement"))
|
||||
{
|
||||
var nextMovementType = GetNextMovementType();
|
||||
SwitchMovement(nextMovementType);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchMovement(string movementType)
|
||||
{
|
||||
if (_currentMovement != null)
|
||||
{
|
||||
_currentMovement.Enabled = false;
|
||||
}
|
||||
|
||||
if (MovementTypes.TryGetValue(movementType, out var movement))
|
||||
{
|
||||
_currentMovement = GetNodeOrNull<IMovement>(movement);
|
||||
if (_currentMovement == null)
|
||||
{
|
||||
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
|
||||
return;
|
||||
}
|
||||
_currentMovement.Enabled = true;
|
||||
EmitSignalMovementSwitched(movementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
|
||||
}
|
||||
|
||||
if (_currentMovement == null)
|
||||
{
|
||||
GD.PushError("No current movement set after switching.");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetNextMovementType()
|
||||
{
|
||||
var keys = new List<string>(MovementTypes.Keys);
|
||||
var currentIndex = keys.IndexOf(_currentMovement?.MovementType);
|
||||
|
||||
if (currentIndex == -1)
|
||||
{
|
||||
return DefaultMovementType;
|
||||
}
|
||||
|
||||
currentIndex = (currentIndex + 1) % keys.Count;
|
||||
return keys[currentIndex];
|
||||
}
|
||||
|
||||
public void OnSpaceshipEntered()
|
||||
{
|
||||
SwitchMovement("ship");
|
||||
ShipSprite.Visible = true;
|
||||
}
|
||||
|
||||
public void OnSpaceshipExited()
|
||||
{
|
||||
SwitchMovement(DefaultMovementType);
|
||||
ShipSprite.Visible = false;
|
||||
}
|
||||
}
|
1
scripts/components/PlayerController.cs.uid
Normal file
1
scripts/components/PlayerController.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csel4s0e4g5uf
|
13
scripts/interfaces/IMovement.cs
Normal file
13
scripts/interfaces/IMovement.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
public interface IMovement
|
||||
{
|
||||
string MovementType { get; }
|
||||
bool Enabled { get; set; }
|
||||
Vector2 PreviousVelocity { get; set; }
|
||||
|
||||
void _Process(double delta);
|
||||
void _PhysicsProcess(double delta);
|
||||
}
|
1
scripts/interfaces/IMovement.cs.uid
Normal file
1
scripts/interfaces/IMovement.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bt2g2im63o8fh
|
Reference in New Issue
Block a user