C#中使用Irrlicht Lime时Dictionary键不存在错误求助
问题
使用Irrlicht Lime引擎进行C#原型开发时遇到错误:刚向Dictionary<Vector3Di, Voxel>添加指定键值对,紧接着通过该键取值时,提示“字典中找不到键”。本人是该引擎新手,Irrlicht Lime无官方文档,只能参考原版Irrlicht文档开发。
报错代码段
Console.WriteLine("added dirt voxel at : " + new Vector3Di(x, y, z)); voxels.Add(new Vector3Di(x, y, z), new Voxel(VoxelType.Dirt, 2, 0, false, true)); Console.WriteLine(voxels[new Vector3Di(x, y, z)].VoxelType);
完整代码
using IrrlichtLime.Core; using IrrlichtLime.Scene; using IrrlichtLime.Video; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Krutesia { internal class Chunk { // 存储区块的位置与体素数据 public Dictionary<Vector3Di, Voxel> voxels = new Dictionary<Vector3Di, Voxel>(); public Vector3Di position; public Texture[] textures; public List<SceneNode> meshes = new List<SceneNode>(); public Chunk (Vector3Di pos) { textures = new Texture[7]; GetTextures(); position = pos; GenerateVoxels(); } public void GenerateVoxels() { // 生成区块内的体素 for (int x = 0; x < WorldData.chunkWidth; x++) { for (int y = 0; y < WorldData.chunkHeight; y++) { for (int z = 0; z < WorldData.chunkWidth; z++) { // 根据层级设置体素类型 if (y == WorldData.chunkHeight) { voxels.Add(new Vector3Di(x, y, z), new Voxel(VoxelType.BedRock, 0, 0, true, false)); Console.WriteLine("added bedrock voxel at : " + new Vector3Di(x, y, z)); } else if (y > WorldData.rockRange.X && y < WorldData.rockRange.Y) { Console.WriteLine("added stone voxel at : " + new Vector3Di(x, y, z)); voxels.Add(new Vector3Di(x, y, z), new Voxel(VoxelType.Stone, 1, 100, true, false)); } else if (y >= WorldData.rockRange.X && y < WorldData.terrainHeightMax) { Console.WriteLine("added dirt voxel at : " + new Vector3Di(x, y, z)); voxels.Add(new Vector3Di(x, y, z), new Voxel(VoxelType.Dirt, 2, 25, true, false)); } else { Console.WriteLine("added dirt voxel at : " + new Vector3Di(x, y, z)); voxels.Add(new Vector3Di(x, y, z), new Voxel(VoxelType.Dirt, 2, 0, false, true)); Console.WriteLine(voxels[new Vector3Di(x, y, z)].VoxelType); } } } } GenerateMeshes(); } public void DisplayMesh() { while (Program.device.Run()) { Program.device.VideoDriver.BeginScene(); Program.device.SceneManager.DrawAll(); Program.device.VideoDriver.EndScene(); } Program.device.Drop(); } public void GenerateMeshes() { for (int x = 0; x < voxels.Count; x++) { for (int y = 0; y < voxels.Count; y++) { for (int z = 0; z < voxels.Count; z++) { if(voxels.ContainsKey(new Vector3Di(x, y, z))) { if (voxels[new Vector3Di(x + 1, y + 1, z + 1)].VoxelType == VoxelType.BedRock) { Console.WriteLine(voxels[new Vector3Di(x, y, z)].VoxelType); SceneNode node = Program.device.SceneManager.AddCubeSceneNode(); node.SetMaterialTexture(0, textures[0]); node.Position = new Vector3Df(x, y, z); meshes.Add(node); } else if (voxels[new Vector3Di((int)x, (int)y, (int)z)].VoxelType == VoxelType.Stone) { SceneNode node = Program.device.SceneManager.AddCubeSceneNode(); node.SetMaterialTexture(0, textures[1]); node.Position = new Vector3Df(x, y, z); meshes.Add(node); } else if (voxels[new Vector3Di((int)x, (int)y, (int)z)].VoxelType == VoxelType.Dirt) { SceneNode node = Program.device.SceneManager.AddCubeSceneNode(); node.SetMaterialTexture(0, textures[2]); node.Position = new Vector3Df(x, y, z); meshes.Add(node); } } } } } //DisplayMesh(); } public void GetTextures() { textures[0] = Program.device.VideoDriver.GetTexture("content/textures/blocks/cobble_stone.png"); textures[1] = Program.device.VideoDriver.GetTexture("content/textures/blocks/stone.png"); textures[2] = Program.device.VideoDriver.GetTexture("content/textures/blocks/dirt.png"); textures[3] = Program.device.VideoDriver.GetTexture("content/textures/blocks/grass_side.png"); textures[4] = Program.device.VideoDriver.GetTexture("content/textures/blocks/grass_top.png"); textures[5] = Program.device.VideoDriver.GetTexture("content/textures/blocks/gravel.png"); textures[6] = Program.device.VideoDriver.GetTexture("content/textures/blocks/sand.png"); } } }
解决方案
核心原因
Vector3Di作为字典的键时,默认使用引用相等性判断而非值相等性判断。每次调用new Vector3Di(x,y,z)都会创建新的对象实例,即使x/y/z数值完全相同,这些实例的内存引用也不一样,字典会将它们判定为不同的键,导致添加后用新实例查找时找不到对应值。
解决办法
方法1:复用同一个Vector3Di实例
在添加和查找时使用同一个对象,避免重复创建新实例:
var pos = new Vector3Di(x, y, z); Console.WriteLine("added dirt voxel at : " + pos); voxels.Add(pos, new Voxel(VoxelType.Dirt, 2, 0, false, true)); Console.WriteLine(voxels[pos].VoxelType);
方法2:为Vector3Di实现自定义相等比较器
创建一个实现IEqualityComparer<Vector3Di>的类,让字典基于x/y/z的数值判断键的相等性:
public class Vector3DiEqualityComparer : IEqualityComparer<Vector3Di> { public bool Equals(Vector3Di a, Vector3Di b) { if (ReferenceEquals(a, b)) return true; if (a is null || b is null) return false; return a.X == b.X && a.Y == b.Y && a.Z == b.Z; } public int GetHashCode(Vector3Di vec) { return HashCode.Combine(vec.X, vec.Y, vec.Z); } }
初始化字典时传入该比较器:
public Dictionary<Vector3Di, Voxel> voxels = new Dictionary<Vector3Di, Voxel>(new Vector3DiEqualityComparer());
额外修复建议
你的GenerateMeshes方法存在多处问题,一并修复:
- 循环条件错误:用
voxels.Count作为x/y/z的上限不合理,应使用WorldData.chunkWidth和WorldData.chunkHeight - 多次创建
Vector3Di实例查找,会触发同样的键不存在问题 voxels[new Vector3Di(x + 1, y + 1, z + 1)]会触发越界,且新实例大概率找不到键
修改后的GenerateMeshes示例:
public void GenerateMeshes() { for (int x = 0; x < WorldData.chunkWidth; x++) { for (int y = 0; y < WorldData.chunkHeight; y++) { for (int z = 0; z < WorldData.chunkWidth; z++) { var pos = new Vector3Di(x, y, z); if(voxels.TryGetValue(pos, out var voxel)) { SceneNode node = Program.device.SceneManager.AddCubeSceneNode(); switch(voxel.VoxelType) { case VoxelType.BedRock: node.SetMaterialTexture(0, textures[0]); break; case VoxelType.Stone: node.SetMaterialTexture(0, textures[1]); break; case VoxelType.Dirt: node.SetMaterialTexture(0, textures[2]); break; } node.Position = new Vector3Df(x, y, z); meshes.Add(node); } } } } }
内容的提问来源于stack exchange,提问作者bard mask
相关产品推荐
相关产品推荐

