This post is part of my Mobile Game Development series. Start at the beginning to catch up. This series was inspired by the things I learned developing a new game for Android and iOS called Mirror Maze.
Positioning in CocosSharp can get pretty mind bending at times. This is because CCNode has Position as a property and everything your user will see in your app will derive from CCNode, which means everything has a position. Couple that with the fact that there will likely be varying degrees of nesting with these nodes, each containing their own Position, and things can get quite hairy. So, let’s talk about the main components that are involved with positioning: Position, Anchor Point, Rotation, and ContentSize.
CCNode’s Position property is of type CCPoint. CCPoint is a simple struct with x and y properties, each of type float. Now remember, your game’s dimensions are logical dimensions that don’t necessarily map to pixels. Therefore, the positioning used in CocosSharp uses floats for coordinates so that you can have much more precision than integers would provide.
In CocosSharp, the origin, coordinate (0, 0), starts in the lower-left corner of the screen. Increasing the x coordinate value moves the node to the right and decreasing the x coordinate value moves the node to the left. Increasing the y coordinate value moves the node up and decreasing the y coordinate value moves the node down.
You may have noticed the phrasing that I used for the origin, that it “starts in the lower-left corner”. CocosSharp has the concept of cameras which can be moved around, changing where in the world space the user is able to see. So the origin starts in the lower-left corner, but cameras may move around, making it seem as though the origin moved. I’ll talk about the concept of cameras in a future post in this series.
CCNode’s AnchorPoint defines where on the node the position actually refers to. By default, the anchor point is at the center of the node. The AnchorPoint property is also of type CCPoint, but uses it differently than Position does. The values of the x and y properties are treated as percentage values, rather than explicit values. Therefore, the AnchorPoint default value is (0.5, 0.5), which, again, is the center of the node. Going around the horn we have:
Lower-Left: (0.0, 0.0)
Upper-Left: (0.0, 1.0)
Upper-Right: (1.0, 1.0)
Lower-Right: (1.0, 0.0)
Fortunately, there are predefined static properties for you to use. These static properties are all part of the CCPoint class and are named CCPoint.Anchor*.
CCNode’s Rotation property is pretty straight-forward. The type is float and defines how much the node is rotated in degrees. A positive value rotates the node clockwise and a negative value rotates the node counter-clockwise, all around the AnchorPoint.
CCNode’s ContentSize property is what tells CocosSharp how big your node is. ContentSize is of type CCSize, which has a Width and Height property. When you start getting into custom nodes, making sure this ContentSize property is accurate is key to keeping positioning correct in your game.
When applying changes to any of these properties, it’s important to remember that the changes are always relative to the node’s parent. If the parent is the root node, then all the property values can be taken at face value. However, let’s say the node’s parent is another node, and the parent node has changes applied to any of its properties. The parent’s changes are applied first. Then, the child node’s changes are applied to the child, relative to the newly re-positioned parent. To help make this more clear, let’s look at an example.
NodeA is added to the root layer. NodeB is added as a child to NodeA. The following code sets this up:
public class RootLayer : CCLayer
var nodeA = new CCNode();
var nodeB = new CCNode();
Now, let’s say that NodeA has a position of (10, 20) applied. Then NodeB has a position of (25, 35) applied.
nodeA.Position = new CCPoint(10, 20);
nodeB.Position = new CCPoint(25, 35);
Now, as far as NodeB is concerned, it’s position is (25, 35). However, what the user actually sees, due to the parent node’s changes being applied first, the user sees NodeB with a position in the world space of (35, 55). The concept of a node’s world space is conceptually pretty simple: Where in the world space of your game is the node, after all positioning is applied to the node and all parent nodes.
This example is simple on purpose, to ease into the concept of world space. It doesn’t take long for things to get much more difficult to calculate, for instance by adding rotation to NodeA. Thankfully, you don’t have to calculate it on your own. CCNode has the concept of BoundingBox. However, fully understanding BoundingBox requires a more in depth discussion. In my next post, I’ll dive in to all things BoundingBox.