The first thing I wanted to do when I started this project is build some kind of skeleton of what Cocos2d is. That means getting to know the big picture and then making something functional, extremely simplified, but that works the same way.
After the break, I'll present a diagram showing a general view of Zissou2d based on how CC2d works and I'll give more details as I put it into code.
So here's that diagram:
(I put layer in parentheses because I'm not going to implement it at first. Even though layers can be really useful, a scene can still contain sprites directly, as if there was only one layer. Also, from this diagram, scenes contain layers and layers contain sprites, but there's really nothing stopping you from making a sprite that contains a scene, at least not in CC2d.)
If you read the Basic Cube series I wrote a couple of months back, I have a template I use for building OpenGL ES 2.0 applications almost from scratch. The project is close to being empty because it uses the GLKView/GLKViewController combo. All I have to do is create a drawing context and give it to the view and then it's pretty much ready to go to start drawing OpenGL stuff.
That template was the starting point for Zissou2d. That GLKViewController can act as the director since it has access to the view and also because it is that controller that decides what is being drawn (being the view's delegate). Now there's a couple of additional things I wanted to be able to do with my director Steve:
- Access it from anywhere
- Push scenes to be displayed with it
- Start/Pause the animation
Now, since this is a GLKViewController, you can already use the paused property and set it to true or false to pause or resume the animation, so we really have nothing to do here. To be able to access it from anywhere and push my scenes, I had to define my public interface like this:
The sharedDirector method is implemented like
(This is just the top of the file, and there is more code below this that I didn't include from the template I mentioned before, but later in this article, there is the full implementation)
awakeFromNib is called only once after the class has been allocated and initialized from the storyboard, so it's a good place to initialize our sharedDirector. As you can see though, we already need a scene to continue with the implementation of the Director, so we're going to go ahead and implement the Node, Scene and Sprite objects before coming back to this class.
The Node class (I called it Entity)
For this class, we're going to provide four things in the public interface:
- a build class method that returns an allocated and initialized node object. This is the same pattern as if you called [NSArray array]
- a constructor
- an addChild method to be able to add sprites to a scene for example
- a visit method which I'll talk about in a bit
Since the class is fairly simple (for now), I'm just going to provide the implementation and then talk about it:
- we have a mutable array containing the children
- draw has nothing in it since a node is an "abtract" class
- I added lazy initialization of the children array
- visit first checks if a child has been added (if there's none it's nil) and then loops over each child, calling visit on them (recursive). At the bottom of the hierarchy (possibly a sprite), there are no more children, and that object draws itself. This basically means that we need to override the draw method for objects that actually have something to draw. For example, a sprite might contain a shape, or an image that needs to be drawn.
The Scene class
This class, just as in CC2d, is completely empty. Really. It's just a concept to make it easier for people to use it, but it's exactly the same as the Node class. So, for this one, you just need to create a class with nothing in it.
The Sprite class
The Sprite class is going to be very simple for now, but it's going to get more complex as we start drawing actual shapes and images. All we need for our proof of concept though, is to "override" the draw method.
Back to the Director
Now that we have everything in place, we can implement the pushScene method and use the scene that's being pushed in the drawInRect method:
- In the main loop (drawInRect), if the nextScene is not nil, we set it as the current scene, and then reset nextScene to nil.
- In CC2d, there is actually a stack of scenes and you can push multiple scenes on that stack.
- Right after that, visit is called on the current scene.
Everything is in place. What now?
Well, now that we have every basic element, we need to create a scene and push it for the director to display it. How do we do that? By subclassing the Scene object. Here's an intro scene:
Alright, now where do we push that scene? Well, this can be done in multiple places, but I'm going to do it in the AppDelegate's didFinishLaunchingWithOptions with the following call:
[[Steve sharedDirector] pushScene:[IntroScene build]];
And that's it! That's a pretty great game if you ask me...
Next up, drawing real stuff
PS. I just want to reiterate that I'm not inventing anything here, all this stuff comes mostly from the devs that worked on Cocos2d.