我有很多types的实体,每种types都有很多, if instanceof Spider
(例如)然后在游戏代码中执行此操作…现在我到了要保存每个types的部分。
他们都是基于相同的类,实体,但实施一些自己的方法。 当我拯救他们,让我们现在说,我只是想要他们的位置和他们的健康。
所以我写的位置写健康,与所有的实体文件。
现在是这个问题,当我想加载它,我怎么知道这是一个蜘蛛,然后创建一个蜘蛛实例?
一个简单的解决办法是创建toString方法,然后写在位置和健康之前。 但是,这个问题是,当我加载时,我将不得不为每个types都有这个大的switch语句,我将不得不手动添加一个我创建的新types的情况。 这不是很友善。
什么样的方式来读取一个类的名称,然后创建一个该类的实例,而没有一个大开关的情况下?
我会避免使用像这样的reflection,并使用语言不可知的标记blob格式,或类似的东西(只是一种可能的方法实体序列化):
有一个ISaveable
接口的方法,产生一个Entity
给予大量的保存数据,并产生一个Entity
的保存数据块。
public interface ISaveable { public SaveHunk save(Entity e); public Entity load(SaveHunk sh); public GUID getClassID(); }
为每个实体types(即Spider
, Player
, Box
等)创建ISaveable
匿名实例,并让这些实例知道如何使用大量的保存数据(原始string,XML,JSON等)并创建适当的实体,如何将该实体转换回保存数据的大块。
ISaveable spiderSaveable = new ISaveable() { @Override public SaveHunk save(Entity e) { if (e == null) return null; if (!(e instanceof Spider)) { return null; } SaveHunk hunk = new SaveHunk(getClassID(), e.getEntityID()); Spider spider = (Spider)e; hunk.put("pos", spider.getPosition().toString()); hunk.put("vel", spider.getVelocity().toString()); // and so on return hunk; } @Override public Entity load(SaveHunk hunk) { if (hunk== null) return null; if (!hunk.getClassID().equals(getClassID())) { // The GUIDs don't match, may want to handle this in the manager return null; } Spider spider = new Spider(hunk.getEntityID()); spider.setPosition(Vector3.parse(hunk.get("pos"))); spider.setVelocity(Vector3.parse(hunk.get("vel"))); // and so on return spider; } @Override public GUID getClassID() { return Spider.UNIQUE_CLASS_ID; } };
有一个管理员类可以有一个ISaveable
实例附加到一个代表一个类的全局唯一ID(例如, Player
总是1, Spider
总是2,总是总是3,等等)。
saveManager.addSaveable(spiderSaveable);
当你保存的时候,把世界上所有的实体都传递给管理者,获取与他们的类ID相对应的ISaveable
,并把它们传递给ISaveable
的SaveHunk save(Entity e)
方法,该方法将实体向下转换并将其重要值转换为保存数据的大块,然后将该大块写入类ID后面的文件。
for (Entity e : world.getEntities()) { ISaveable saveable = saveManager.getSaveable(e.getClassID()); if (saveable != null) { saveFileWriter.writeHunk(saveable.save(e)); } else { // Log a warning } }
加载时,从类ID开始读取文件,获取该类的相应ISaveable
,然后将文件中的保存数据块传递给ISaveable
的Entity load(SaveHunk sh)
方法。
for (SaveHunk hunk : saveHunksFromFile) { ISaveable saveable = saveManager.getSaveable(hunk.getClassID()); if (saveable != null) { world.loadEntity(saveable.load(hunk)); } else { // Log a warning } }
请注意,这只是一个单一的方法,并有其((激烈))的局限性。 ISaveable
每个类types的所有ISaveable
实例是讨厌的,并且需要时间遍历所有实体类。 另外,在创造世界之前,必须在某个时刻完成,否则你将无法将资料加载到所说的世界中。 另外,如果你改变一个class级的ID(比如说你想移除一个敌人types),那么如果他们有这个敌人types(这可能是可取的或不可取的,取决于你有多好处理,我的代码在这种情况下没有什么特别的)。
这也限制了你如何使用实体的基类和子类; 例如,如果你将Spider
分解成CaveSpider
和FieldSpider
,那么你使用哪个ISaveable
? 这实际上意味着你不能/不应该在你的世界中实例化分支类,或者如果你想要一个Spider
,那么它必须有自己的叶类,如CommonSpider
,而不是使用更通用的Spider
,否则你最终会使你的装载机复杂化,并在它们之间复制代码。 你可以有一个多步的ISaveable
层次结构,其中的东西把剩下的大量的保存数据ISaveables
给基类的更一般的ISaveables
,但是它大大提高了系统的复杂性,因为实体现在必须知道他们自己的全部基类。
采用这种方法或类似的方法,与使用直线reflection相比,有许多优点。
ISaveable
实例决定。 SaveHunk
),您可以轻松切换实际保存文件的格式,并保留相同的代码库。 toString()部分本身并不是很糟糕。 我认为处理重新加载对象的一种方法是通过使用Reflection。
// an example of a forName argument: "java.util.Map" Class loadedObjectClass = Class.forName(xml.getElementValue("clazzName")); Object loadedObject = loadedObjectClass.newInstance();
我也会注意到一些人会担心过度使用types检查, 这最终会导致你想要避免的大规模交换语句。 例子:与其有一个玩家碰撞方法来检查某物是否是蜘蛛,并且损坏了玩家,可以尝试为每个物体设置一个onTouch()
方法,这对大多数物体不起作用,并且会损害Spider
类中的触摸器。 (然后,如果触摸器不能被破坏,你可以使他们的damage()
方法什么也不做)
最后,如果明智的用户在明文保存文件中看到类名,就要小心游戏。 没有必要强行让单人黑客成为不可能的事情,但是你可能只是想混淆一下保存文件,使之变得困难。
首先Java有很好的OO让它闪耀
每次你用instanceof
创建一个新的实例方法并在子类中重新实现
你也可以使用getter方法返回标准方法中使用的基本运动速度等参数
做同样的保存和加载(就像@shotgunninja一样,但每个实体是可以的)
和加载时有hashmap(或只是一个数组,如果你可以确保顺序将保持不变;))包含实体工厂创建一个空白的实体,你可以调用load(SaveChunk)
,将configuration它(作为奖金这样可以在GC暂停时变得太过分了)