Add item generator.

加入物品生成器。
This commit is contained in:
Cold-Mint 2024-06-22 23:29:24 +08:00
parent 18d9ccb8ab
commit b7f72c0456
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
10 changed files with 181 additions and 82 deletions

View File

@ -2,14 +2,14 @@
importer="csv_translation" importer="csv_translation"
type="Translation" type="Translation"
uid="uid://cc0k86apkvut7" uid="uid://dl2r1rydlm7pa"
[deps] [deps]
files=["res://locals/slogan.zh.translation", "res://locals/slogan.en.translation", "res://locals/slogan.ja.translation"] files=["res://locals/Slogan.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation"]
source_file="res://locals/slogan.csv" source_file="res://locals/Slogan.csv"
dest_files=["res://locals/slogan.zh.translation", "res://locals/slogan.en.translation", "res://locals/slogan.ja.translation"] dest_files=["res://locals/Slogan.zh.translation", "res://locals/Slogan.en.translation", "res://locals/Slogan.ja.translation"]
[params] [params]

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=6 format=3 uid="uid://du5ldsp613fei"] [gd_scene load_steps=7 format=3 uid="uid://du5ldsp613fei"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"] [ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Script" path="res://scripts/map/PlayerSpawn.cs" id="2_6p8mv"] [ext_resource type="Script" path="res://scripts/map/PlayerSpawn.cs" id="2_6p8mv"]
[ext_resource type="PackedScene" uid="uid://dnnn2xyayiehk" path="res://prefab/weapons/staffOfTheUndead.tscn" id="3_ud0w8"] [ext_resource type="Script" path="res://scripts/map/ItemSpawn.cs" id="3_v1tlc"]
[ext_resource type="Texture2D" uid="uid://b2blj0yf4ohx3" path="res://icon.svg" id="4_psvpu"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_kiih8"]
size = Vector2(450, 191) size = Vector2(450, 191)
@ -40,8 +41,14 @@ shape = SubResource("RectangleShape2D_jxmys")
debug_color = Color(0, 0.6, 0.701961, 0.419608) debug_color = Color(0, 0.6, 0.701961, 0.419608)
[node name="Marker2D" type="Marker2D" parent="."] [node name="Marker2D" type="Marker2D" parent="."]
position = Vector2(216, 113) position = Vector2(221, 134)
script = ExtResource("2_6p8mv") script = ExtResource("2_6p8mv")
[node name="StaffOfTheUndead6" parent="." instance=ExtResource("3_ud0w8")] [node name="ItemMarker2D" type="Marker2D" parent="."]
position = Vector2(290, 167) position = Vector2(142, 84)
script = ExtResource("3_v1tlc")
ItemId = "staff_of_the_undead"
[node name="Icon" type="Sprite2D" parent="ItemMarker2D"]
scale = Vector2(0.3, 0.3)
texture = ExtResource("4_psvpu")

View File

@ -492,7 +492,7 @@ public partial class CharacterTemplate : CharacterBody2D
foreach (var lootDatum in lootData) foreach (var lootDatum in lootData)
{ {
var (id, amount) = lootDatum.Value; var (id, amount) = lootDatum.Value;
ItemTypeManager.CreateItems(id, amount, parentNode, position); ItemTypeManager.CreateItems(id, amount, position, parentNode);
} }
} }
@ -644,7 +644,7 @@ public partial class CharacterTemplate : CharacterBody2D
} }
else else
{ {
for (int i = 0; i < number && !itemSlotNode.IsEmpty(); i++) for (var i = 0; i < number && !itemSlotNode.IsEmpty(); i++)
{ {
ThrowOneItem(itemSlotNode, velocity); ThrowOneItem(itemSlotNode, velocity);
} }

View File

@ -101,6 +101,43 @@ public partial class ItemSlotNode : MarginContainer
return _item; return _item;
} }
/// <summary>
/// <para>CreateItemInstance</para>
/// <para>创建物品槽内的物品实例</para>
/// </summary>
/// <param name="number">
///<para>number</para>
///<para>数量</para>
/// </param>
/// <returns>
///<para>Newly created item</para>
///<para>新创建的物品</para>
/// </returns>
public IItem? CreateItemInstance(int number)
{
if (_item is not Node2D node2D)
{
return null;
}
var duplicate = node2D.Duplicate();
if (duplicate is not IItem newItem)
{
return null;
}
if (number > _item.Quantity)
{
//The number of item instances created exceeds the current number of items
//创建的物品实例数量,超过了当前物品的数量
duplicate.QueueFree();
return null;
}
newItem.Quantity = number;
return newItem;
}
public override void _DropData(Vector2 atPosition, Variant data) public override void _DropData(Vector2 atPosition, Variant data)
{ {
if (_iconTextureRect == null) if (_iconTextureRect == null)
@ -311,6 +348,11 @@ public partial class ItemSlotNode : MarginContainer
var newQuantity = item.Quantity + _item.Quantity; var newQuantity = item.Quantity + _item.Quantity;
_item.Quantity = Math.Min(newQuantity, _item.MaxQuantity); _item.Quantity = Math.Min(newQuantity, _item.MaxQuantity);
if (item is Node2D node2D)
{
node2D.QueueFree();
}
UpdateQuantityLabel();
return true; return true;
} }
} }

View File

@ -3,23 +3,30 @@ using Godot;
namespace ColdMint.scripts.inventory; namespace ColdMint.scripts.inventory;
public readonly struct ItemType(string id, Func<IItem?> newItemFunc, Texture2D? icon, int maxStackQuantity) public readonly struct ItemType(
string id,
Func<Node?, bool, IItem?> createItemFunc,
Texture2D? icon,
int maxStackQuantity)
{ {
/// <summary> /// <summary>
/// <para>Item id of this type</para> /// <para>Item id of this type</para>
/// <para>该类型物品的id</para> /// <para>该类型物品的id</para>
/// </summary> /// </summary>
public string Id { get; init; } = id; public string Id { get; init; } = id;
/// <summary> /// <summary>
/// <para>A function returns a new item instance of this type</para> /// <para>A function returns a new item instance of this type</para>
/// <para>用于创建该类型的物品实例的函数</para> /// <para>用于创建该类型的物品实例的函数</para>
/// </summary> /// </summary>
public Func<IItem?> NewItemFunc { get; init; } = newItemFunc; public Func<Node?, bool, IItem?> CreateItemFunc { get; init; } = createItemFunc;
/// <summary> /// <summary>
/// <para>Default icon of items of this type</para> /// <para>Default icon of items of this type</para>
/// <para>该类型物品的默认图标</para> /// <para>该类型物品的默认图标</para>
/// </summary> /// </summary>
public Texture2D? Icon { get; init; } = icon; public Texture2D? Icon { get; init; } = icon;
/// <summary> /// <summary>
/// <para>Max number in item stack of this type</para> /// <para>Max number in item stack of this type</para>
/// <para>该类型物品的最大堆叠数量</para> /// <para>该类型物品的最大堆叠数量</para>

View File

@ -33,69 +33,62 @@ public static class ItemTypeManager
/// <para>Returns null when the id is not registered.</para> /// <para>Returns null when the id is not registered.</para>
/// <para>当物品id没有注册时返回null</para> /// <para>当物品id没有注册时返回null</para>
/// </returns> /// </returns>
/// <seealso cref="NewItems"/><seealso cref="CreateItem"/> /// <param name="id">
public static IItem? NewItem(string id) => ///<para>Item Id</para>
Registry.TryGetValue(id, out var itemType) ? itemType.NewItemFunc() : null; ///<para>物品Id</para>
/// </param>
/// <param name="defaultParentNode">
///<para>Default parent</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>
/// <seealso cref="CreateItems"/>
public static IItem? CreateItem(string id, Node? defaultParentNode = null, bool assignedByRootNodeType = true) =>
Registry.TryGetValue(id, out var itemType)
? itemType.CreateItemFunc(defaultParentNode, assignedByRootNodeType)
: null;
/// <summary> /// <summary>
/// <para>Creates new instances in given amount of the item registered to the given id.</para> /// <para>Create multiple new item instances for the given item Id</para>
/// <para>创建给定数量的注册到给定 id 的物品的新实例。</para> /// <para>创建多个给定物品Id的新物品实例</para>
/// </summary> /// </summary>
/// <param name="id"></param>
/// <param name="number"></param>
/// <param name="defaultParentNode"></param>
/// <param name="assignedByRootNodeType"></param>
/// <param name="globalPosition"></param>
/// <returns></returns> /// <returns></returns>
/// <seealso cref="NewItem"/><seealso cref="CreateItems"/> /// <seealso cref="CreateItem"/>
public static IList<IItem> NewItems(string id, int amount) public static IItem?[]? CreateItems(string id, int number, Vector2 globalPosition, Node? defaultParentNode = null,
bool assignedByRootNodeType = true)
{ {
IList<IItem> result = []; if (number <= 0)
for (int i = 0; i < amount; i++)
{ {
if (NewItem(id) is { } item) result.Add(item); return null;
} }
return result; var items = new List<IItem?>();
} for (var i = 0; i < number; i++)
/// <summary>
/// <para>Creates new instance of the item registered to the given id, and put it into given position in both node tree and 2D space</para>
/// <para>创建以给定 id 注册的物品的新实例,并将其放到节点树和二维空间中的给定位置</para>
/// </summary>
/// <param name="id"></param>
/// <param name="parent"></param>
/// <param name="position">
/// <para>Position in global coordinate</para>
/// <para>全局坐标中的位置</para>
/// </param>
/// <seealso cref="NewItem"/><seealso cref="CreateItems"/>
public static IItem? CreateItem(string id, Node? parent = null, Vector2? position = null)
{
var item = NewItem(id);
parent?.CallDeferred(GodotStringNameUtils.AddChild, (item as Node)!);
if (item is not Node2D node) return item;
if (position is { } pos) node.GlobalPosition = pos;
return item;
}
/// <summary>
/// <para>Creates new instances in given amount of the item registered to the given id, and put them into given position in both node tree and 2D space</para>
/// <para>创建以给定 id 注册的物品的给定数量的新实例,并将其放到节点树和二维空间中的给定位置</para>
/// </summary>
/// <param name="id"></param>
/// <param name="amount"></param>
/// <param name="parent"></param>
/// <param name="position">
/// <para>Position in global coordinate</para>
/// <para>全局坐标中的位置</para>
/// </param>
/// <seealso cref="NewItems"/><seealso cref="CreateItem"/>
public static IList<IItem> CreateItems(string id, int amount, Node? parent = null, Vector2? position = null)
{
IList<IItem> result = [];
for (int i = 0; i < amount; i++)
{ {
if (CreateItem(id, parent, position) is { } item) var singleItem = CreateItem(id, defaultParentNode, assignedByRootNodeType);
result.Add(item); if (singleItem == null)
{
continue;
}
if (singleItem is Node2D node2D)
{
node2D.GlobalPosition = globalPosition;
}
items.Add(singleItem);
} }
return result; return items.ToArray();
} }
/// <summary> /// <summary>

View File

@ -50,7 +50,7 @@ public static class ItemTypeRegister
//将文件解析为项目类型信息 //将文件解析为项目类型信息
//parse files to item type infos //parse files to item type infos
IEnumerable<ItemTypeInfo> typeInfos = IEnumerable<ItemTypeInfo> typeInfos =
files.SelectMany(file => ParseFile( $"{itemRegsDirPath}/{file}")).ToList(); files.SelectMany(file => ParseFile($"{itemRegsDirPath}/{file}")).ToList();
LogCat.LogWithFormat("found_item_types", typeInfos.Count()); LogCat.LogWithFormat("found_item_types", typeInfos.Count());
//遍历类型信息并注册它们。 //遍历类型信息并注册它们。
@ -70,8 +70,9 @@ public static class ItemTypeRegister
private static IList<ItemTypeInfo> ParseFile(string filePath) private static IList<ItemTypeInfo> ParseFile(string filePath)
{ {
var yamlFile = FileAccess.Open(filePath, FileAccess.ModeFlags.Read); var yamlFile = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
//阅读和反序列化
//Read & deserialize //Read & deserialize
//阅读和反序列化
var yamlString = yamlFile.GetAsText(); var yamlString = yamlFile.GetAsText();
var typeInfos = YamlSerialization.Deserialize<IList<ItemTypeInfo>>(yamlString); var typeInfos = YamlSerialization.Deserialize<IList<ItemTypeInfo>>(yamlString);
yamlFile.Close(); yamlFile.Close();
@ -81,35 +82,32 @@ public static class ItemTypeRegister
private static void RegisterTypeInfo(ItemTypeInfo typeInfo) private static void RegisterTypeInfo(ItemTypeInfo typeInfo)
{ {
//Load scene and icon //Load scene and icon
//加载场景和图标
var scene = ResourceLoader.Load<PackedScene>(typeInfo.ScenePath); var scene = ResourceLoader.Load<PackedScene>(typeInfo.ScenePath);
var icon = ResourceLoader.Load<Texture2D>(typeInfo.IconPath); var icon = ResourceLoader.Load<Texture2D>(typeInfo.IconPath);
//Create init delegate //Create init delegate
Func<IItem?> newItemFunc; //创建初始化委托
if (typeInfo.CustomArgs is null or []) Action<Node?>? setArgs = null;
if (typeInfo.CustomArgs != null && typeInfo.CustomArgs.Count > 0)
{ {
newItemFunc = () => NodeUtils.InstantiatePackedScene<IItem>(scene);
}
else
{
Action<Node?>? setArgs = null;
foreach (var arg in typeInfo.CustomArgs) foreach (var arg in typeInfo.CustomArgs)
{ {
setArgs += setArgs +=
node => node?.SetDeferred(arg.Name, arg.ParseValue()); node => node?.SetDeferred(arg.Name, arg.ParseValue());
} }
newItemFunc = () =>
{
var newItem = NodeUtils.InstantiatePackedScene<IItem>(scene);
setArgs?.Invoke(newItem as Node);
return newItem;
};
} }
//构造项目类型,寄存器
//construct item type, register //construct item type, register
var itemType = new ItemType(typeInfo.Id, var itemType = new ItemType(typeInfo.Id,
newItemFunc, (defaultParentNode, assignedByRootNodeType) =>
{
var newItem = NodeUtils.InstantiatePackedScene<IItem>(scene, defaultParentNode, assignedByRootNodeType);
setArgs?.Invoke(newItem as Node);
return newItem;
},
icon, typeInfo.MaxStackValue); icon, typeInfo.MaxStackValue);
var succeed = ItemTypeManager.Register(itemType); var succeed = ItemTypeManager.Register(itemType);
LogCat.LogWithFormat("register_item", itemType.Id, succeed); LogCat.LogWithFormat("register_item", itemType.Id, succeed);

View File

@ -1,5 +1,11 @@
namespace ColdMint.scripts.loot; namespace ColdMint.scripts.loot;
/// <summary>
/// <para>LootDatum</para>
/// <para>战利品数据</para>
/// </summary>
/// <param name="ItemId"></param>
/// <param name="Quantity"></param>
public readonly record struct LootDatum(string ItemId, int Quantity) public readonly record struct LootDatum(string ItemId, int Quantity)
{ {
public (string id, int quantity) Value => (ItemId, Quantity); public (string id, int quantity) Value => (ItemId, Quantity);

44
scripts/map/ItemSpawn.cs Normal file
View File

@ -0,0 +1,44 @@
using ColdMint.scripts.debug;
using ColdMint.scripts.inventory;
using ColdMint.scripts.map.events;
using Godot;
namespace ColdMint.scripts.map;
/// <summary>
/// <para>ItemSpawn</para>
/// <para>物品出生点</para>
/// </summary>
public partial class ItemSpawn : Marker2D
{
[Export] public string? ItemId { get; private set; }
public override void _Ready()
{
base._Ready();
EventManager.MapGenerationCompleteEvent += MapGenerationCompleteEvent;
}
private void MapGenerationCompleteEvent(MapGenerationCompleteEvent mapGenerationCompleteEvent)
{
//After the map is generated, create the item instance.
//当地图生成完成后,创建物品实例。
if (ItemId == null)
{
return;
}
var item = ItemTypeManager.CreateItem(ItemId,this);
if (item is Node2D node2D)
{
node2D.GlobalPosition = GlobalPosition;
}
}
public override void _ExitTree()
{
base._ExitTree();
EventManager.MapGenerationCompleteEvent -= MapGenerationCompleteEvent;
}
}

View File

@ -250,8 +250,10 @@ public static class NodeUtils
where T : class where T : class
{ {
var node = InstantiatePackedScene(packedScene, defaultParentNode, assignedByRootNodeType); var node = InstantiatePackedScene(packedScene, defaultParentNode, assignedByRootNodeType);
// Check the type conversion and return the result successfully
// 检查类型转化,成功返回结果 // 检查类型转化,成功返回结果
if (node is T result) return result; if (node is T result) return result;
// If the transformation fails, release the created node
//如果转型失败,释放所创建的节点 //如果转型失败,释放所创建的节点
LogCat.LogWarningWithFormat("warning_node_cannot_cast_to", node, nameof(T)); LogCat.LogWarningWithFormat("warning_node_cannot_cast_to", node, nameof(T));
node.QueueFree(); node.QueueFree();