Compare commits

...

2 Commits

Author SHA1 Message Date
63a56cbb2c
Unified management scenario instantiation, loot generation in the right place, and allow to specify the probability and quantity.
统一管理场景实例化,战利品生成在正确的位置,且允许指定概率和数量了。
2024-06-10 21:05:18 +08:00
f7ad69440c
Add a loot table.
添加战利品表。
2024-06-09 22:05:49 +08:00
27 changed files with 491 additions and 63 deletions

View File

@ -23,3 +23,6 @@ player_packed_scene_not_exist,玩家预制场景不存在。,Player packed scene
exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。 exit_the_room_debug,节点{0}退出房间{1}。,"Node {0} exits room {1}.",ノード{0}が部屋{1}を退出します。
enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。 enter_the_room_debug,节点{0}进入房间{1}。,"Node {0} enters room {1}.",ノード{0}が部屋{1}に入ります。
death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。 death_info,生物{0}被{1}击败。,"Creature {0} was defeated by {1}.",生物{0}が{1}によって打ち負かされました。
loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Loot list with ID {0}, no entry specified.",ID{0}の戦利品テーブルは、エントリ指定されていません。
not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。
1 id zh en ja
23 exit_the_room_debug 节点{0}退出房间{1}。 Node {0} exits room {1}. ノード{0}が部屋{1}を退出します。
24 enter_the_room_debug 节点{0}进入房间{1}。 Node {0} enters room {1}. ノード{0}が部屋{1}に入ります。
25 death_info 生物{0}被{1}击败。 Creature {0} was defeated by {1}. 生物{0}が{1}によって打ち負かされました。
26 loot_list_has_no_entries ID为{0}的战利品表,没有指定条目。 Loot list with ID {0}, no entry specified. ID{0}の戦利品テーブルは、エントリ指定されていません。
27 not_within_the_loot_spawn_range 给定的数值{0}没有在战利品{1}的生成范围{2}内。 The given value {0} is not within the spawn range {2} of loot {1}. 与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。
28 loot_data_quantity 有{0}个战利品数据被返回。 {0} loot data was returned. {0}個の戦利品データが返されます。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,7 @@
id,zh,en,ja id,zh,en,ja
slogan_0,游戏属于每一个人。,The game belongs to everyone.,ゲームはすべての人のものです。 slogan_0,游戏属于每一个人。,The game belongs to everyone.,ゲームはすべての人のものです。
slogan_1,如果你想要得到爱,你就播种爱。,"If you want love, you sow love.",愛を手に入れたければ、愛の種をまきます。 slogan_1,Kawaii!,Kawaii!,Kawaii!
slogan_2,快乐?伤心?痛苦?,Happy? Sad? Pain?,楽しいですか?悲しいですか?苦しいですか? slogan_2,魔法是想象的世界。,Magic is an imaginary world.,魔法は想像の世界です。
slogan_3,Kawaii!,Kawaii!,Kawaii! slogan_3,也试试《Minecraft》,Also try 'Minecraft'!,「Minecraft」もやってみて
slogan_4,魔法是想象的世界。,Magic is an imaginary world.,魔法は想像の世界です。 slogan_4,也试试《Terraria》,Also try 'Terraria'!,「Terraria」もやってみて
slogan_5,也试试Minecraft,Also try Minecraft!,Minecraftもやってみて slogan_5,你好,Hello,こんにちは
slogan_6,也试试Terraria,Also try Terraria!,Terrariaもやってみて
1 id zh en ja
2 slogan_0 游戏属于每一个人。 The game belongs to everyone. ゲームはすべての人のものです。
3 slogan_1 如果你想要得到爱,你就播种爱。 Kawaii! If you want love, you sow love. Kawaii! 愛を手に入れたければ、愛の種をまきます。 Kawaii!
4 slogan_2 快乐?伤心?痛苦? 魔法是想象的世界。 Happy? Sad? Pain? Magic is an imaginary world. 楽しいですか?悲しいですか?苦しいですか? 魔法は想像の世界です。
5 slogan_3 Kawaii! 也试试《Minecraft》! Kawaii! Also try 'Minecraft'! Kawaii! 「Minecraft」もやってみて!
6 slogan_4 魔法是想象的世界。 也试试《Terraria》! Magic is an imaginary world. Also try 'Terraria'! 魔法は想像の世界です。 「Terraria」もやってみて!
7 slogan_5 也试试Minecraft! 你好 Also try Minecraft! Hello Minecraftもやってみて! こんにちは
slogan_6 也试试Terraria! Also try Terraria! Terrariaもやってみて!

Binary file not shown.

Binary file not shown.

View File

@ -34,6 +34,7 @@ script = ExtResource("1_ubaid")
metadata/CampId = "Mazoku" metadata/CampId = "Mazoku"
metadata/MaxHp = 50 metadata/MaxHp = 50
metadata/Name = "死灵法师" metadata/Name = "死灵法师"
metadata/LootListId = "Test"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4) position = Vector2(0, 4)

View File

@ -45,6 +45,3 @@ position = Vector2(65, 0)
position = Vector2(26, -8) position = Vector2(26, -8)
target_position = Vector2(0, 20) target_position = Vector2(0, 20)
collision_mask = 34 collision_mask = 34
[node name="RayCast2D2" type="RayCast2D" parent="."]
target_position = Vector2(0, -29)

View File

@ -147,7 +147,6 @@ hotbar_previous={
[internationalization] [internationalization]
locale/translations=PackedStringArray("res://locals/DeathInfo.en.translation", "res://locals/DeathInfo.ja.translation", "res://locals/DeathInfo.zh.translation", "res://locals/InputMapping.en.translation", "res://locals/InputMapping.ja.translation", "res://locals/InputMapping.zh.translation", "res://locals/Log.en.translation", "res://locals/Log.ja.translation", "res://locals/Log.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation", "res://locals/Slogan.zh.translation", "res://locals/UI.en.translation", "res://locals/UI.ja.translation", "res://locals/UI.zh.translation", "res://locals/Weapon.en.translation", "res://locals/Weapon.ja.translation", "res://locals/Weapon.zh.translation") locale/translations=PackedStringArray("res://locals/DeathInfo.en.translation", "res://locals/DeathInfo.ja.translation", "res://locals/DeathInfo.zh.translation", "res://locals/InputMapping.en.translation", "res://locals/InputMapping.ja.translation", "res://locals/InputMapping.zh.translation", "res://locals/Log.en.translation", "res://locals/Log.ja.translation", "res://locals/Log.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation", "res://locals/Slogan.zh.translation", "res://locals/UI.en.translation", "res://locals/UI.ja.translation", "res://locals/UI.zh.translation", "res://locals/Weapon.en.translation", "res://locals/Weapon.ja.translation", "res://locals/Weapon.zh.translation")
locale/test="ja"
[layer_names] [layer_names]

View File

@ -18,7 +18,20 @@ public static class Config
/// <para>巡逻</para> /// <para>巡逻</para>
/// <para>Patrol</para> /// <para>Patrol</para>
/// </summary> /// </summary>
public const string? Patrol = "Patrol"; public const string Patrol = "Patrol";
}
/// <summary>
/// <para>Loot table ID</para>
/// <para>战利品表ID</para>
/// </summary>
public static class LootListId
{
/// <summary>
/// <para>A trophy table for testing</para>
/// <para>测试用的战利品表</para>
/// </summary>
public const string Test = "Test";
} }
/// <summary> /// <summary>

View File

@ -113,6 +113,12 @@ public partial class CharacterTemplate : CharacterBody2D
private DamageNumberNodeSpawn? _damageNumber; private DamageNumberNodeSpawn? _damageNumber;
/// <summary>
/// <para>Character referenced loot table</para>
/// <para>角色引用的战利品表</para>
/// </summary>
private LootList? _lootList;
private HealthBar? _healthBar; private HealthBar? _healthBar;
private DateTime _lastDamageTime; private DateTime _lastDamageTime;
@ -207,6 +213,14 @@ public partial class CharacterTemplate : CharacterBody2D
CharacterName = GetMeta("Name", Name).AsString(); CharacterName = GetMeta("Name", Name).AsString();
CampId = GetMeta("CampId", Config.CampId.Default).AsString(); CampId = GetMeta("CampId", Config.CampId.Default).AsString();
MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32(); MaxHp = GetMeta("MaxHp", Config.DefaultMaxHp).AsInt32();
var lootListId = GetMeta("LootListId", string.Empty).AsString();
if (!string.IsNullOrEmpty(lootListId))
{
//If a loot table is specified, get the loot table.
//如果指定了战利品表,那么获取战利品表。
_lootList = LootListManager.GetLootList(lootListId);
}
if (MaxHp <= 0) if (MaxHp <= 0)
{ {
//If Max blood volume is 0 or less, set Max blood volume to 10 //If Max blood volume is 0 or less, set Max blood volume to 10
@ -438,6 +452,7 @@ public partial class CharacterTemplate : CharacterBody2D
//角色死亡 //角色死亡
OnDie(damageTemplate); OnDie(damageTemplate);
ThrowAllItemOnDie(); ThrowAllItemOnDie();
return true; return true;
} }
@ -445,6 +460,42 @@ public partial class CharacterTemplate : CharacterBody2D
return true; return true;
} }
/// <summary>
/// <para>Create Loot Object</para>
/// <para>创建战利品对象</para>
/// </summary>
protected void CreateLootObject()
{
if (_lootList == null)
{
return;
}
var lootDataArray = _lootList.GenerateLootData();
if (lootDataArray.Length == 0)
{
return;
}
var finalGlobalPosition = GlobalPosition;
CallDeferred("GenerateLootObjects", this,lootDataArray, finalGlobalPosition);
}
/// <summary>
/// <para>GenerateLootObjects</para>
/// <para>生成战利品对象</para>
/// </summary>
/// <param name="parentNode"></param>
/// <param name="lootDataArray"></param>
/// <param name="position"></param>
public void GenerateLootObjects(Node parentNode,
LootData[] lootDataArray,
Vector2 position)
{
LootListManager.GenerateLootObjects(parentNode, lootDataArray, position);
}
/// <summary> /// <summary>
/// <para>Add power to the character</para> /// <para>Add power to the character</para>
/// <para>在角色身上添加力</para> /// <para>在角色身上添加力</para>
@ -481,6 +532,7 @@ public partial class CharacterTemplate : CharacterBody2D
} }
} }
CreateLootObject();
QueueFree(); QueueFree();
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -396,28 +396,29 @@ public partial class Player : CharacterTemplate
//If there is a scene of floating text, then we generate floating text. //If there is a scene of floating text, then we generate floating text.
//如果有悬浮文本的场景,那么我们生成悬浮文本。 //如果有悬浮文本的场景,那么我们生成悬浮文本。
_floatLabel?.QueueFree(); _floatLabel?.QueueFree();
_floatLabel = (Control)_floatLabelPackedScene.Instantiate(); _floatLabel = NodeUtils.InstantiatePackedScene<Control>(_floatLabelPackedScene, node);
var rotationDegreesNode2D = node2D.RotationDegrees; if (_floatLabel != null)
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
{ {
var stringBuilder = new StringBuilder(); var rotationDegreesNode2D = node2D.RotationDegrees;
if (weapon.Owner is CharacterTemplate characterTemplate) var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
if (node is WeaponTemplate weapon)
{ {
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName); var stringBuilder = new StringBuilder();
stringBuilder.Append(TranslationServerUtils.Translate("de")); if (weapon.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
}
stringBuilder.Append(TranslationServerUtils.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
} }
stringBuilder.Append(TranslationServerUtils.Translate(weapon.Name));
label.Text = stringBuilder.ToString();
} }
node.AddChild(_floatLabel);
} }
UpdateOperationTip(); UpdateOperationTip();

View File

@ -1,3 +1,4 @@
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.damage; namespace ColdMint.scripts.damage;
@ -99,7 +100,8 @@ public partial class DamageNumberNodeSpawn : Marker2D
return; return;
} }
if (_damageNumberPackedScene.Instantiate() is not DamageNumber damageNumber) var damageNumber = NodeUtils.InstantiatePackedScene<DamageNumber>(_damageNumberPackedScene);
if (damageNumber == null)
{ {
return; return;
} }

View File

@ -339,6 +339,7 @@ public partial class HotBar : HBoxContainer, IItemContainer
{ {
return false; return false;
} }
return itemSlotNode.SetItem(item); return itemSlotNode.SetItem(item);
} }
@ -396,7 +397,7 @@ public partial class HotBar : HBoxContainer, IItemContainer
return; return;
} }
var itemSlotNode = _itemSlotPackedScene.Instantiate() as ItemSlotNode; var itemSlotNode = NodeUtils.InstantiatePackedScene<ItemSlotNode>(_itemSlotPackedScene);
if (itemSlotNode == null) if (itemSlotNode == null)
{ {
return; return;

View File

@ -0,0 +1,9 @@
using Godot;
namespace ColdMint.scripts.inventory;
public partial class LootData : GodotObject
{
public string? ResPath { get; set; }
public int Quantity { get; set; }
}

View File

@ -0,0 +1,32 @@
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Loot entry</para>
/// <para>战利品条目</para>
/// </summary>
public class LootEntry
{
/// <summary>
/// <para>generation probability</para>
/// <para>生成概率</para>
/// </summary>
public double? Chance { get; set; }
/// <summary>
/// <para>Minimum number of generated</para>
/// <para>最小生成多少个</para>
/// </summary>
public int MinQuantity { get; set; }
/// <summary>
/// <para>The maximum number of files to be generated</para>
/// <para>最多生成多少个</para>
/// </summary>
public int MaxQuantity { get; set; }
/// <summary>
/// <para>resources path</para>
/// <para>资源路径</para>
/// </summary>
public string? ResPath { get; set; }
}

View File

@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>Loot list</para>
/// <para>战利品表</para>
/// </summary>
public class LootList
{
/// <summary>
/// <para>Id</para>
/// <para>战利品表的Id</para>
/// </summary>
public string? Id { get; set; }
private List<LootEntry>? _lootEntrieList;
/// <summary>
/// <para>Add loot entry</para>
/// <para>添加战利品条目</para>
/// </summary>
/// <param name="lootEntry"></param>
public void AddLootEntry(LootEntry lootEntry)
{
if (_lootEntrieList == null)
{
_lootEntrieList = new List<LootEntry>();
}
_lootEntrieList.Add(lootEntry);
}
/// <summary>
/// <para>GenerateLootData</para>
/// <para>生成战利品数据</para>
/// </summary>
/// <returns></returns>
public LootData[] GenerateLootData()
{
var lootDataList = new List<LootData>();
if (_lootEntrieList == null)
{
LogCat.LogWithFormat("loot_list_has_no_entries", Id);
return lootDataList.ToArray();
}
foreach (var lootEntry in _lootEntrieList)
{
var chance = GD.Randf();
if (chance > lootEntry.Chance)
{
//If the random number is greater than the generation probability, skip the current loop.
//如果随机数大于生成概率,则跳过当前循环。
LogCat.LogWithFormat("not_within_the_loot_spawn_range", chance, lootEntry.ResPath, lootEntry.Chance);
continue;
}
//We generate a loot data for each loot entry.
//我们为每个战利品条目生成一个战利品数据。
var quantity = GD.RandRange(lootEntry.MinQuantity, lootEntry.MaxQuantity);
var lootData = new LootData
{
ResPath = lootEntry.ResPath,
Quantity = quantity
};
lootDataList.Add(lootData);
}
LogCat.LogWithFormat("loot_data_quantity", lootDataList.Count);
return lootDataList.ToArray();
}
/// <summary>
/// <para>Remove loot entry</para>
/// <para>移除战利品条目</para>
/// </summary>
/// <param name="lootEntry"></param>
/// <returns></returns>
public bool RemoveLootEntry(LootEntry lootEntry)
{
if (_lootEntrieList == null)
{
return false;
}
return _lootEntrieList.Remove(lootEntry);
}
}

View File

@ -0,0 +1,118 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.inventory;
/// <summary>
/// <para>LootListManager</para>
/// <para>战利品表管理器</para>
/// </summary>
public static class LootListManager
{
private static Dictionary<string, LootList>? _lootListDictionary;
/// <summary>
/// <para>Register loot table</para>
/// <para>注册战利品表</para>
/// </summary>
/// <param name="lootList"></param>
public static bool RegisterLootList(LootList lootList)
{
var id = lootList.Id;
if (id == null)
{
return false;
}
if (_lootListDictionary != null) return _lootListDictionary.TryAdd(id, lootList);
_lootListDictionary = new Dictionary<string, LootList> { { id, lootList } };
return true;
}
/// <summary>
/// <para>Get Loot List</para>
/// <para>获取战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static LootList? GetLootList(string id)
{
return _lootListDictionary?.GetValueOrDefault(id);
}
/// <summary>
/// <para>Generate loot objects</para>
/// <para>生成战利品对象</para>
/// </summary>
public static void GenerateLootObjects(Node parentNode, LootData[] lootDataArray, Vector2 position)
{
if (lootDataArray.Length == 0)
{
return;
}
//Cache the loaded PackedScene object.
//缓存已加载的PackedScene对象。
Dictionary<string, PackedScene> packedSceneDictionary = new();
foreach (var lootData in lootDataArray)
{
if (string.IsNullOrEmpty(lootData.ResPath) || lootData.Quantity == 0)
{
continue;
}
if (!packedSceneDictionary.TryGetValue(lootData.ResPath, out var packedScene))
{
packedScene = GD.Load<PackedScene>(lootData.ResPath);
packedSceneDictionary.TryAdd(lootData.ResPath, packedScene);
}
for (var i = 0; i < lootData.Quantity; i++)
{
//Generate as many loot instance objects as there are loot.
//有多少个战利品就生成多少个战利品实例对象。
CreateLootInstanceObject(parentNode, packedScene, position);
}
}
}
/// <summary>
/// <para>Create a loot instance object</para>
/// <para>创建战利品实例对象</para>
/// </summary>
private static void CreateLootInstanceObject(Node parent, PackedScene? packedScene, Vector2 position)
{
if (packedScene == null)
{
return;
}
var lootObject = NodeUtils.InstantiatePackedScene<Node2D>(packedScene, parent);
if (lootObject == null)
{
return;
}
lootObject.Position = position;
}
/// <summary>
/// <para>Remove loot list</para>
/// <para>移除战利品表</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool UnregisterLootList(string id)
{
if (_lootListDictionary == null)
{
return false;
}
return _lootListDictionary.Remove(id);
}
}

View File

@ -19,7 +19,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
{ {
private string? _defaultRoomName; private string? _defaultRoomName;
private readonly LevelGraphEditorBinding _nodeBinding = new LevelGraphEditorBinding(); private readonly LevelGraphEditorBinding _nodeBinding = new();
/// <summary> /// <summary>
/// <para>Index of the room</para> /// <para>Index of the room</para>
@ -35,7 +35,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
private PackedScene? _roomNodeScene; private PackedScene? _roomNodeScene;
private readonly List<Node> _selectedNodes = new List<Node>(); private readonly List<Node> _selectedNodes = new();
/// <summary> /// <summary>
/// <para>Displays the time to enter the suggestion</para> /// <para>Displays the time to enter the suggestion</para>
@ -47,7 +47,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
/// <para>Offset to append when a new node is created.</para> /// <para>Offset to append when a new node is created.</para>
/// <para>创建新节点时追加的偏移量。</para> /// <para>创建新节点时追加的偏移量。</para>
/// </summary> /// </summary>
private Vector2 _positionOffset = new Vector2(100, 100); private Vector2 _positionOffset = new(100, 100);
/// <summary> /// <summary>
/// <para>Is the press event of an active button saved?</para> /// <para>Is the press event of an active button saved?</para>
@ -99,7 +99,7 @@ public partial class LevelGraphEditorLoader : UiLoaderTemplate
return null; return null;
} }
var node = _roomNodeScene.Instantiate(); var node = NodeUtils.InstantiatePackedScene<Node>(_roomNodeScene);
if (node == null) if (node == null)
{ {
return null; return null;

View File

@ -4,6 +4,7 @@ using System.Text;
using ColdMint.scripts.camp; using ColdMint.scripts.camp;
using ColdMint.scripts.deathInfo; using ColdMint.scripts.deathInfo;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.map; using ColdMint.scripts.map;
using ColdMint.scripts.map.roomInjectionProcessor; using ColdMint.scripts.map.roomInjectionProcessor;
using Godot; using Godot;
@ -39,6 +40,23 @@ public partial class MainMenuLoader : UiLoaderTemplate
LogCat.MinLogLevel = LogCat.DisableAllLogLevel; LogCat.MinLogLevel = LogCat.DisableAllLogLevel;
} }
//注册测试使用的战利品表
if (Config.IsDebug())
{
var testLootList = new LootList
{
Id = Config.LootListId.Test
};
var lootEntry = new LootEntry
{
Chance = 1f,
MaxQuantity = 5,
MinQuantity = 1,
ResPath = "res://prefab/weapons/staffOfTheUndead.tscn"
};
testLootList.AddLootEntry(lootEntry);
LootListManager.RegisterLootList(testLootList);
}
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler()); DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor()); MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor()); MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor());

View File

@ -1,6 +1,7 @@
using ColdMint.scripts.character; using ColdMint.scripts.character;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.map.events; using ColdMint.scripts.map.events;
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.map; namespace ColdMint.scripts.map;
@ -32,18 +33,16 @@ public partial class AiCharacterSpawn : Marker2D
/// <param name="aiCharacterGenerateEvent"></param> /// <param name="aiCharacterGenerateEvent"></param>
public void OnAiCharacterGenerateEvent(AiCharacterGenerateEvent aiCharacterGenerateEvent) public void OnAiCharacterGenerateEvent(AiCharacterGenerateEvent aiCharacterGenerateEvent)
{ {
var node = _packedScene?.Instantiate(); if (_packedScene == null)
if (node is not AiCharacter aiCharacter)
{ {
return; return;
} }
if (GameSceneNodeHolder.AiCharacterContainer == null) var aiCharacter = NodeUtils.InstantiatePackedScene<AiCharacter>(_packedScene,GameSceneNodeHolder.AiCharacterContainer);
if (aiCharacter == null)
{ {
return; return;
} }
GameSceneNodeHolder.AiCharacterContainer.AddChild(aiCharacter);
aiCharacter.Position = GlobalPosition; aiCharacter.Position = GlobalPosition;
} }

View File

@ -1,6 +1,7 @@
using ColdMint.scripts.character; using ColdMint.scripts.character;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.map.events; using ColdMint.scripts.map.events;
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.map; namespace ColdMint.scripts.map;
@ -30,6 +31,7 @@ public partial class PlayerSpawn : Marker2D
GameSceneNodeHolder.Player.Revive(GameSceneNodeHolder.Player.MaxHp); GameSceneNodeHolder.Player.Revive(GameSceneNodeHolder.Player.MaxHp);
return; return;
} }
SpawnPlayer(); SpawnPlayer();
} }
@ -50,17 +52,16 @@ public partial class PlayerSpawn : Marker2D
return; return;
} }
var playerNode = _playerPackedScene.Instantiate(); var playerNode =
if (playerNode is not Player player) NodeUtils.InstantiatePackedScene<Player>(_playerPackedScene, GameSceneNodeHolder.PlayerContainer);
if (playerNode == null)
{ {
return; return;
} }
playerNode.ItemContainer = GameSceneNodeHolder.HotBar;
player.ItemContainer = GameSceneNodeHolder.HotBar; GameSceneNodeHolder.Player = playerNode;
GameSceneNodeHolder.PlayerContainer.AddChild(player); playerNode.Position = GlobalPosition;
GameSceneNodeHolder.Player = player; LogCat.LogWithFormat("player_spawn_debug", playerNode.ReadOnlyCharacterName, playerNode.Position);
player.Position = GlobalPosition;
LogCat.LogWithFormat("player_spawn_debug", player.ReadOnlyCharacterName, player.Position);
} }
private void MapGenerationCompleteEvent(MapGenerationCompleteEvent mapGenerationCompleteEvent) private void MapGenerationCompleteEvent(MapGenerationCompleteEvent mapGenerationCompleteEvent)

View File

@ -104,9 +104,14 @@ public class Room
/// <param name="packedScene"></param> /// <param name="packedScene"></param>
private void AnalyzeRoomData(PackedScene? packedScene) private void AnalyzeRoomData(PackedScene? packedScene)
{ {
var node = packedScene?.Instantiate(); if (packedScene == null)
if (node is not Node2D node2D)
{ {
return;
}
var node2D = NodeUtils.InstantiatePackedScene<Node2D>(packedScene);
if (node2D == null)
{
//The room node is not of Node2D type. An exception is thrown
//房间节点不是Node2D类型抛出异常 //房间节点不是Node2D类型抛出异常
LogCat.LogError("room_root_node_must_be_node2d"); LogCat.LogError("room_root_node_must_be_node2d");
return; return;

View File

@ -1,5 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ColdMint.scripts.debug;
using ColdMint.scripts.weapon;
using Godot; using Godot;
namespace ColdMint.scripts.utils; namespace ColdMint.scripts.utils;
@ -95,4 +98,85 @@ public static class NodeUtils
return closestNode; return closestNode;
} }
/// <summary>
/// <para>Find the corresponding container node based on the child node</para>
/// <para>根据子节点查找对应的容器节点</para>
/// </summary>
/// <remarks>
///<para>We want child nodes to be placed under a specific parent node to facilitate the same management. For example, the weapon node should be placed inside the Weapon container node. We call a parent node of the same type as a child node a "container". This method is used to find the corresponding node container based on the type of the child node.</para>
///<para>我们希望子节点被放置在特定的父节点下,方便同一管理。例如:武器节点应该被放置在“武器容器”节点内。我们将子节点的类型相同的父节点叫做“容器”。此方法用于根据子节点的类型查找对应的节点容器。</para>
/// </remarks>
/// <param name="childNode">
///<para>childNode</para>
///<para>子节点</para>
/// </param>
/// <param name="defaultParentNode">
///<para>Default parent, which returns the default node if it cannot be matched by type.</para>
///<para>默认父节点,当按照类型无法匹配时,将返回默认节点。</para>
/// </param>
/// <returns></returns>
public static Node FindContainerNode(Node childNode, Node defaultParentNode)
{
if (GameSceneNodeHolder.WeaponContainer != null && childNode is WeaponTemplate)
{
return GameSceneNodeHolder.WeaponContainer;
}
return defaultParentNode;
}
/// <summary>
/// <para>Instantiate Packed Scene</para>
/// <para>实例化场景</para>
/// </summary>
/// <remarks>
///<para>This method is recommended in place of all packedScene.Instantiate() calls within a project, using it to instantiate a scene, optionally assigned to a container that matches the type of the root node.</para>
///<para>推荐使用此方法代替项目内所有的packedScene.Instantiate()调用,使用此方法实例化场景,可选择将其分配到与根节点类型相匹配的容器内。</para>
/// </remarks>
/// <param name="packedScene">
///<para>packedScene</para>
///<para>打包的场景</para>
/// </param>
/// <param name="assignedByRootNodeType">
///<para>Enabled by default, whether to place a node into a container node that matches the type of the root node after it is instantiated. If the assignment fails by type, it is placed under the default parent node.</para>
///<para>默认启用,实例化节点后,是否将其放置到与根节点类型相匹配的容器节点内。如果按照类型分配失败,则放置在默认父节点下。</para>
/// </param>
/// <param name="defaultParentNode">
/// <para>Default parent. If null is passed, the instantiated node is not put into the parent. You need to place it manually, which is quite useful for delaying setting the parent node.</para>
/// <para>默认父节点如果传入null则不会将实例化后的节点放入父节点内。您需要手动放置这对于延迟设置父节点相当有用。</para>
/// </param>
/// <returns></returns>
public static T? InstantiatePackedScene<T>(PackedScene packedScene, Node? defaultParentNode = null,
bool assignedByRootNodeType = true) where T : Node
{
try
{
var instantiateNode = packedScene.Instantiate<T>();
//An attempt is made to place an instantiated node under a husband node only after the default parent node is set.
//只有设定了默认父节点后才会尝试将实例化的节点放置到夫节点下。
if (defaultParentNode != null)
{
if (assignedByRootNodeType)
{
var containerNode = FindContainerNode(instantiateNode, defaultParentNode);
containerNode.AddChild(instantiateNode);
}
else
{
//If you do not need to assign by type, place it under the default parent node.
//如果不需要按照类型分配,那么将其放到默认父节点下。
defaultParentNode.AddChild(instantiateNode);
}
}
return instantiateNode;
}
catch (InvalidCastException e)
{
//null is returned if the type conversion fails.
//在类型转换失败时返回null。
LogCat.WhenCaughtException(e);
return null;
}
}
} }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.projectile; using ColdMint.scripts.projectile;
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.weapon; namespace ColdMint.scripts.weapon;
@ -26,6 +27,7 @@ public partial class ProjectileWeapon : WeaponTemplate
/// <para>抛射体列表</para> /// <para>抛射体列表</para>
/// </summary> /// </summary>
private string[]? _projectiles; private string[]? _projectiles;
private Dictionary<string, PackedScene>? _projectileCache; private Dictionary<string, PackedScene>? _projectileCache;
private Node2D? _projectileContainer; private Node2D? _projectileContainer;
@ -42,6 +44,7 @@ public partial class ProjectileWeapon : WeaponTemplate
{ {
continue; continue;
} }
_projectileCache.Add(projectileItem, packedScene); _projectileCache.Add(projectileItem, packedScene);
} }
@ -62,17 +65,14 @@ public partial class ProjectileWeapon : WeaponTemplate
LogCat.LogError("projectiles_is_empty"); LogCat.LogError("projectiles_is_empty");
return; return;
} }
//Get the first projectile //Get the first projectile
//获取第一个抛射体 //获取第一个抛射体
var projectileScene = _projectileCache[_projectiles[0]]; var projectileScene = _projectileCache[_projectiles[0]];
var projectile = projectileScene.Instantiate() as ProjectileTemplate; var projectile = NodeUtils.InstantiatePackedScene<ProjectileTemplate>(projectileScene, _projectileContainer);
if (projectile != null) if (projectile == null) return;
{ projectile.Owner = owner;
projectile.Owner = owner; projectile.Velocity = (enemyGlobalPosition - _marker2D.GlobalPosition).Normalized() * projectile.Speed;
projectile.Velocity = (enemyGlobalPosition - _marker2D.GlobalPosition).Normalized() * projectile.Speed; projectile.Position = _marker2D.GlobalPosition;
projectile.Position = _marker2D.GlobalPosition;
}
_projectileContainer.AddChild(projectile);
} }
} }