Class inside array to achieve protocol conformance?

I am unsure if my implementation of protocol conformance with an array to accept different properties is good code, I suspect not as I am unable to loop over the elements as I would like:

protocol HUDMenuProtocol {
  
    var itemTitleLabel: SKLabelNode { get }
    var itemSprite: SKSpriteNode { get }
}


private let menuElements: [HUDMenuProtocol] = [HUDMenu()]


class HUDMenu: SKNode, HUDMenuProtocol {

    
    let itemTitleLabel: SKLabelNode = {

        let label = SKLabelNode(fontNamed: "font")
        label.position = CGPoint(x: 0.5, y: 0.5)
        return label
    }()
    
    let itemSprite: SKSpriteNode = {
        
        let sprite = SKSpriteNode(imageNamed: "itemSprite")
        sprite.position = CGPoint(x: 0.3, y: 0.3)
        return sprite
    }()
    
    
    func addHUDNodesToScene() {
        
        for node in menuElements {
            addChild(node.itemTitleLabel)
            addChild(node.itemSprite)
        }
    }
}

I have had to place the entire class inside the array, rather than each element, which means I have to manually add each node to the scene, rather than just the temporary "node" in the loop.

I did try to add the nodes, but I could not get the code to work. The code does work however, only I don't think I should have put the entire class inside the array?

I think we have a concept problem here. I understand what your HUDMenuProtocol is. I'm having trouble with your idea of HUDMenu. Is a HUDMenu supposed to the whole set of menu entries that conform to HUDMenuProtocol, or is it the class of the menu item that comprise the menu? Is menuElements supposed to be the HUDMenu? Here is what I think you might be trying to do.

protocol HUDMenuItemProtocol {
    var itemTitleLabel: SKLabelNode { get }
    var itemSprite: SKSpriteNode { get }
}

protocol HUDMenuProtocol { } // If you need it for the whole menu

class HUDMenuItem: SKNode, HUDMenuItemProtocol {

    let itemTitleLabel: SKLabelNode = {
        let label = SKLabelNode(fontNamed: "font")
        label.position = CGPoint(x: 0.5, y: 0.5)
        return label
    }()
    
    let itemSprite: SKSpriteNode = {
        let sprite = SKSpriteNode(imageNamed: "itemSprite")
        sprite.position = CGPoint(x: 0.3, y: 0.3)
        return sprite
    }()
}

class HUDMenu : HUDMenuProtocol {
    private var menuElements : [HUDMenuItemProtocol] = []

   func addElement(_ item: HUDMenuItem) {
       menuElements.append(item)
   }

   func addHUDNodesToScene() {
        for node in menuElements {
            addChild(node.itemTitleLabel)
            addChild(node.itemSprite)
        }
   }
}

If I understand your code, this is what I think you trying to do. Unless you want a 1-element array, in which case I'm not clear what you are trying to do.

Hi johnPrescott, thank you for your help.

I am trying to make an HUD menu so that it appears and disappears on command (also I think that I will need to use a delegate to execute a function in another class), and wanted to simply put all the items into an array to be hidden, unhidden, added to the scene etc...

At present, even with your example, I have to run through each one manually, which is a lot of work as with my implementation I have to first removeFromParent because the navigation system I have trips over if a Node already exists and then are added to the scene again, then add them to the scene, then immediately hide them until needed, which all rather invalidates they DRY concept.

I found the addChild function not working outside where the values were declared inside the HUDMenuItem class, therefore I had to remove the array outside of both classes so it would be available to each:

protocol HUDMenuItemProtocol {
    var itemTitleLabel: SKLabelNode { get }
    var itemSprite: SKSpriteNode { get }
}

protocol HUDMenuProtocol { } // If you need it for the whole menu

private var menuElements : [HUDMenuItemProtocol] = []



class HUDMenuItem: SKNode, HUDMenuItemProtocol {

    let itemTitleLabel: SKLabelNode = {
        let label = SKLabelNode(fontNamed: "font")
        label.position = CGPoint(x: 0.5, y: 0.5)
        return label
    }()
    
    let itemSprite: SKSpriteNode = {
        let sprite = SKSpriteNode(imageNamed: "itemSprite")
        sprite.position = CGPoint(x: 0.3, y: 0.3)
        return sprite
    }()

    func addHUDNodesToScene() {
         for node in menuElements {
             addChild(node.itemTitleLabel)
             addChild(node.itemSprite)
         }
    }

}

class HUDMenu : HUDMenuProtocol {

   func addElement(_ item: HUDMenuItem) {
       menuElements.append(item)
   }

}


let test1 = HUDMenu()
test1.addElement(HUDMenuItem())

I would like to simply apply any SKNode action to the nodes in the array with the temporary term "node" that conforms to the protocol:

            for node in menuElements {
                node.removeFromParent()  //stop crashes when navigating back to page
                addChild(node)  //add to scene (again)
                node.isHidden = true  //hide until needed (on press event)
            }

I don't understand why I simply cannot iterate over the elements in the array, which conforms to a protocol, in order to apply SKNode action, otherwise I could have used AnyObject.

Thanks.

This is another attempt to get what I think you want. If I understand it, you want a menu consisting of a list of menu items that can be manipulated in the context of a SKNode. You want to be able to have a variety of processing algorithms perform a variety of transforms on the menu and SKNode.

protocol HUDMenuItemProtocol 
{
    var itemTitleLabel: SKLabelNode { get }
    var itemSprite: SKSpriteNode { get }
}

protocol HUDMenuProtocol { } // If you need it for the whole menu

class HUDMenuItem: HUDMenuItemProtocol 
{
    let itemTitleLabel: SKLabelNode = {
        let label = SKLabelNode(fontNamed: "font")
        label.position = CGPoint(x: 0.5, y: 0.5)
        return label
    }()
    
    let itemSprite: SKSpriteNode = {
        let sprite = SKSpriteNode(imageNamed: "itemSprite")
        sprite.position = CGPoint(x: 0.3, y: 0.3)
        return sprite
    }()
}

class HUDMenu :  HUDMenuProtocol 
{
    public var menuElements : [HUDMenuItemProtocol] = []

    func addElement(_ item: HUDMenuItemProtocol) {
        menuElements.append(item)
    }
}

/// A function to add nodes to SKNode
func addHUDMenuToScene(_ skNode : SKNode, HUD: HUDMenu)
{
    for node in HUD.menuElements {
        skNode.addChild(node.itemTitleLabel)
        skNode.addChild(node.itemSprite)
    }
}

/// Here's your other function
func ComplexNodeProcess(_ skNode : SKNode, HUD: HUDMenu)
{
     for node in HUD.menuElements {
        node.removeFromParent()  // stop crashes when navigating back to page
        skNode.addChild(node)     // add to scene (again)
        node.isHidden = true       // hide until needed (on press event)
    }
}

You can write manipulation functions using the element list in the HUDMenu to do what you want. I think this is closer to what you want to do, but, code is still unclear.

If you want, you could make HUDMenu inherit from SKNode, and put the processing algorithms as member functions, or you could add accessors to the menuElement array for outside funcs to manipulate, etc..

The menuItem array does not conform to the HUBMenuItemProtocol, it contains elements that conform to the protocol.

I'm sorry jhonPrescott, but I am so confused.

My original example suited my needs: I needed to display a temporary HUD menu on top of an existing SKScene via a tap event, and dismiss it to go back to the SKScene. The major issue with my code was that I would have to manipulate all the SKNodes in that menu individually several times via loop, where as I wanted to just iterate over a single temporary constant and apply instance members.

Here is the order of what I need to do to make the menu work:

Initial background loading within the main SKScene (or re-entering SKScene):

  1. Remove any HUD menu SKNodes existing in the scene (prevents crashing)
  2. Add all HUD menu SKNodes to the scene
  3. Hide all HUD menu SKNodes from the main SKScene

HUD menu in use (on tap event):

  1. Unhide all HUD menu SKNodes from main SKScene

HUD menu dismissed (on tap event):

  1. Hide all HUD menu SKNodes from main SKScene

That is five lines of code to perform on each SKNnode, and the HUD menu will have fifteen or more SKNodes, so naturally I wanted to loop over the elements in an array:

for node in menuElements {
                node.removeFromParent()  //stop crashes when navigating back to page
                addChild(node)  //add to scene (again)
                node.isHidden = true  //hide until needed (on press event)
            }

Rather than something like the following, which could work out as forty five lines of code if applied to fifteen elements individually:

    func addHUDMenuToScene(_ skNode : SKNode, HUD: HUDMenu)
    {
        for node in HUD.overlayHUDElements {
            node.itemTitleLabel.removeFromParent()
            skNode.addChild(node.itemTitleLabel)
            node.itemTitleLabel.isHidden = true
        }
    }

Aside from the necessity of adding elements individually to the array, afterwards I just expected to iterate over them to apply a member instance as needed, which was the issue with AnyObject, so I turned to a protocol to try to infer the class and thus the instance members that could be applied to a temporary "node" in a loop.

I hope that clears things up. I know that you have dedicated a fair amount of effort on this matter and if need be I will do it the hard way and return to the matter later on.

I need to push on and get the some functionality to the buttons in the HUD menu, but I find that they can't access existing data in the main SKScene, with data returning as nil, even though it's present inside the SKScene. I was looking into delegates on the matter, but you mentioned "manipulation functions" and "accessors" but I cannot seem to find out more on this, what system would be best e.g. from within the temporary HUD menu if I wanted to change something on the SKScene (which would result in the HUD menu disappearing) like array having a value purged or an SKSpriteNode being rotated, what would be the best approach please?

Many thanks for your time once again.