2D tile map resource format issue

As my project is a custom map, all map resource tiles are packaged in Cocos2d plist. I want to obtain the corresponding image texture through the key and then use TDrawableImage for rendering. After testing, TDrawableImage meets my project’s performance requirements. Upon reviewing the source code, I found that TCastleScene only provides animation and cannot obtain the corresponding image file through the key
objects.zip (936.4 KB)

First of all: if you want to render sprites yourself, using TDrawableImage, then

  1. you can,

  2. but I really don’t recommend it :slight_smile:

    Doing sprite rendering manually, you will need to deal with a lot of things yourself – like order of sprites (what is behind what), camera management (how to move each sprite), managing animations (how to play/observe them, loop, stop notification), efficient rendering with batching. And you will not be able to use some features, like physics, comfortably – unless you will set physics colliders on empty scenes. Please make sure to read How to render 2D games with images and sprites | Manual | Castle Game Engine to be sure that by “doing it manually, using TDrawableImage” you really gain more than you lose versus “doing it using TCastleScene”.

    We have developed “sprites in TCastleScene”, and even our own sprite sheet editor + format ( Sprite Sheets | Manual | Castle Game Engine ) after really a lot of conversations on forums with people who struggled to implement sprites “on their own”. It starts easy (rendering sprite is just like rendering a specific image part) but then there’s more work with more features :slight_smile:

    Moreover, it seems you want to display original images (not using sprite sheet atlas) – this will have a performance hit, due to changing images. Using sprite sheet atlas is more work, but also more efficient.

After this warning, if you are sure that this is what you want :slight_smile:, I will go ahead with the answer how to do what you ask :slight_smile:

It is true, we don’t expose the information you point out above from TCastleScene.

We actually don’t even read the key text (like obj1_003900.png you point) you look for in our Cocos2d importer, from what I can see we never needed them – the text inside “key” doesn’t really matter for CGE (as the sprite sheet has an associated atlas which is used as a texture, we don’t need to know original frame png names, which seem to be recorded there).

If you really want to do this, then open the PLIST file yourself, and parse it, to get any information you want. You can of course still use Castle Game Engine utilities to load XML file from URL and use our CastleXmlUtils with some helpers to parse XML files.

Our full Cocos2d reader is here, you may want to look at it for info how to read everything that we do: castle-engine/src/scene/load/x3dloadinternalcocos2d.pas at master · castle-engine/castle-engine · GitHub .

For this particular task, this is likely a good start (this is a subset / rework of code from TCocos2dLoader.Load – I just quickly hacked it now; no guarantees, it most likely doesn’t compile and requires work from your side to finish):

procedure LoadCocos2dFrames(const Url: String);
var
  Doc: TXMLDocument;
  Node: TDOMNode;
  PlistNode: TDOMElement;
  PlistDictNode: TDOMElement;

  KeyNode: TDOMElement;
  DictNode: TDOMElement;

  I: TXMLElementIterator;
begin
  Doc := UrlReadXml(Url);
  try

    { Doc.FindNode('plist') can fail here because of plist Apple DOCTYPE
      above plist element. }
    {$ifdef FPC}
    Node := Doc.FirstChild;
    while Node <> nil do
    begin
      if (Node.NodeName = 'plist') and (Node.NodeType = ELEMENT_NODE) then
      begin
        PlistNode := Node as TDOMElement;
        break;
      end;
      Node := Node.NextSibling;
    end;
    {$else}
      // Should work in delphi because current we have only one element in document
      Node := Doc.DocumentElement;
      if (Node.NodeName = 'plist') and (Node.NodeType = ELEMENT_NODE) then
        PlistNode := Node as TDOMElement;
    {$endif}

    if PlistNode = nil then
      raise EInvalidCocos2dPlist.CreateFmt('Invalid Cocos2d plist file "%s" - plist node not found.', [FDisplayUrl]);

    PlistDictNode := PlistNode.Child('dict', false);
    if (PlistDictNode = nil) then
      raise EInvalidCocos2dPlist.CreateFmt('Invalid Cocos2d plist file "%s" - plist dictionary node not found.', [FDisplayUrl]);

    { Iterate file to find frames, metadata, textures nodes }
    I := PlistDictNode.ChildrenIterator;
    try
      while I.GetNext do
      begin
        { Should be key node. }
        KeyNode := I.Current;
        if KeyNode.NodeName <> 'key' then
          raise EInvalidCocos2dPlist.CreateFmt('Invalid Cocos2d plist file "%s" - key node expected.', [FDisplayUrl]);

        if not I.GetNext then
          raise EInvalidCocos2dPlist.CreateFmt('Invalid Cocos2d plist file "%s" - dict node expected.', [FDisplayUrl]);

        DictNode := I.Current;

        if DictNode.NodeName <> 'dict' then
          raise EInvalidCocos2dPlist.CreateFmt('Invalid Cocos2d plist file "%s" - dict node expected.', [FDisplayUrl]);

        { Check KeyNode.TextData to decide what this is metadata, texture or frames}
        if KeyNode.TextData = 'frames' then
          ReadFrames(DictNode);
        // ignore other keys
      end;
    finally
      FreeAndNil(I);
    end;
  finally
    FreeAndNil(Doc);
  end;
end;

Next you will want to implement ReadFrames. Similar to TCocos2dLoader.ReadFrames in castle-engine/src/scene/load/x3dloadinternalcocos2d.pas at master · castle-engine/castle-engine · GitHub , but you will be able to remove lots of code you don’t need. You will need to find the animation you’re looking for, or list all animations, depending on your need.

Next you will want to implement something like TCocos2dLoader.TCocosFrame.ParseFrameDictionaryFormat2 . Iterate over each frame. For each KeyNode, the information you’re looking for is in KeyNode.TextData.

This quite a lot of work, I know. Ultimately it’s because our Cocos2d loader doesn’t even read the information you want, we don’t need it to render sprite sheet using atlas. So you have to write your own Cocos2d loader if you are sure you want to do things this way. I hope this is a helpful start, if you decide to follow this way :slight_smile:

1 Like

Using TCastleScene to render images normally, thank you very much. I will start map rendering and test if the performance meets my requirements