Developing an endless race game for iOS using Cocos2D-iphone

    Today I want to tell you about the creation of an iOS game based on Cocos2D using the recently released Bee Racing game as an example (Bee Race).
    The gameplay does not contain anything complicated - it is essentially an endless runner in which you need to collect points and dodge obstacles. Only instead of a red-haired girl or a treasure hunter, a two-dimensional bee flies here.
    For those interested, I ask for kat (Akhtung! Minen und many letters).
    Main sections to consider:
    1. A very brief introduction to Cocos2D
    2. We use Cocos2D simultaneously with StoryBoard
    3. Short description of gameplay and project structure
    4. We buy tools and what to do if a toad strangles
    5. What the application doesn’t smell like or connect in-app billing
    6. We are socializing. We connect Game Center and create a multiplayer version for two players
    7. What Akela missed
    8. Publish


    Spoiler:




    1. A very brief introduction to Cocos2D.

    Cocos2d-iphone is a free 2D engine for iOS that uses OpenGL for hardware graphics acceleration and supports Chipmunk or Box2D as physics engines.
    Actually, why do we need an engine? In order to avoid the need to write a sprite loader (in particular from sprite sheets), correctly start / stop animations, game timers, and the physics engine. Well and the main thing is the hardware acceleration of graphics, if you create a game, albeit with simple graphics, based on Cocoa Touch components, then after the Nth number of game elements, you will actively get the bullet-time effect out of place and out of place, or the game is generally revered in the Bose.
    Cocos2D stores all sprites in the cache, duplicates of one sprite are relatively light, because the sprite stores a link to the frame, but not the graphics themselves. At the same time, rendering a sprite in an OpenGL buffer is a faster operation than rendering a Cocoa element in a tree of other graphic elements.
    As for physical objects, they are not directly related to sprites, so if you want a sprite, for example a flower, to contain the corresponding physical polygon, you need to write the SpriteFlower heir. But a more flexible system - a flower can be assigned not just one, but several polygons, in addition, you can even choose which physics engine to use.
    Now that we have figured out all the inconceivable subtleties and read the promised introduction to Cocos2D, let's get down to some specifics.


    2. We use Cocos2D simultaneously with StoryBoard

    After all the praises of Cocos2D, it looks a bit illogical. However, in reality, for static windows it is much easier to use Interface Builder and Storyboard mechanisms. Firstly, you can sip all of these pictures with your mouse, and secondly, the Storyboard software interface itself is too convenient not to use it. And yes, I am aware that this is cocosbuilder.com
    What it looks like in Interface Builder:

    All the windows are described here in the Storyboard, and the main window of the game just contains Cocos2D scene as a background, and on top of it is a HUD, made also based on standard elements.

    Pay attention to what the screen looks like - the white background is Cocos2D, which will load the necessary graphics at the start of the game, and the life indicator, lines, and more - this is done using the builder interface.
    I will not enlarge an already large article with a description of how this is done, just give a link where I copied the code from. I got inspiration from github.com/tinytimgames/CCViewController


    3. Brief description of gameplay and project structure

    The gameplay is this: the bee hangs motionless, and according to the theory of relativity, the field rushes towards, bearing various dangers in the form of spiders and poisonous plants, as well as various buns in the form of dandelions and dandelion seeds. Cocos2D actually has a camera object that can follow the player, but in practice it turned out to be easier to move the CCLayer on which the game world is located.
    Management is the simplest - tap on the screen, the bee flew down, tap again - up. Nevertheless, it turned out that the players intuitively want to make a slide and therefore it’s rather not convenient at first.
    Generation of the world is not uniform, but in batches according to a simple algorithm. The game has three special layers to fill with objects. During the start of the game, all three are filled with objects. Then one of the layers is taken and filled with objects to a certain length. After some time, the next section is filled, but in the second layer, and then the third. When the bee flies from the second section to the third, the first layer is cleaned, and then the fourth section begins to fill in it and so on. Two layers could be dispensed with, but then it would be more difficult to deal with the effect when an object that flies from one section to another disappears (in my case, such objects are only flying dandelion seeds). This is due to the fact that objects are deleted in one fell swoop, and not coordinate-wise.
    The project uses the physics engine Chipmunk. I took it for two reasons. Firstly, it seems to be simpler, and secondly, I already dealt with Box2D when I used AndEngine, but I wanted something new. The engine is needed only in order to determine the collision of a bee with game objects, there is no physics, as in evil birds.


    4. We buy tools and what to do if a toad strangles

    The main thing in developing casual games is not to go in cycles. I can safely say that the main killer of Angry Birds could only be perfectionism. But even if it is not possible to create a super framework with the MyObject base class, which can be edited by a specially designed editor, this does not mean that many things cannot be automated.
    First of all, Linux-way life is greatly simplified. For example, an artist dumps me drawings in high resolution in the dropbox. I have a script that converts them to a lower resolution (mogrify, huh) + adds the –hd version (@ 2x depending on the situation).
    Using Cocos2D in conjunction with Interface builder is also primarily a matter of time optimization. TexturePacker
    can also be very useful ., which will pack all the sprites into one sprite sheet and thus reduce the amount of memory consumed, unless you already manage to make all sprites in size a multiple of two. TexturePacker is paid, but worth it. With sprite sheet, it’s even easier in that when adding a new sprite, there is no need to add new files to the project.
    The main difficulty for 2D physics is to create a polygon from pictures, which will be loaded by a physical engine. Manually doing this is unrealistic, so you need to use some kind of tool. There are various types of offers to do the vector structure using Inkspace program, but personally I have written for this utility AndengineVertexHelper code.google.com/p/andengine-vertex-helper/downloads/list
    It took about one day, but this thing turned out to be very useful. By default, the program has a template for code generation of the Andengine engine, details are here www.andengine.org/forums/features/vertex-helper-t1370.html
    Change the template to
    %.5f%.5f
    and get the formatting in plist.

    Next, create a plist file with descriptions of objects:
    Expand Example
    bee0vertices-0.41786-0.14844-0.407140.07031-0.039290.144530.271430.156250.46071-0.023440.45714-0.296880.26786-0.464840.02143-0.46094-0.29643-0.31250


    And the object loader:
    Expand Example
    - (void)createBodyAtLocation:(CGPoint)location{
        float mass = 1.0;
        body = cpBodyNew(mass, cpMomentForBox(mass, self.sprite.contentSize.width*self.sprite.scale,
       		 self.sprite.contentSize.height*self.sprite.scale));
        body->p = location;
        body->data = self;
        cpSpaceAddBody(space, body);
        NSString *path =[[NSBundle mainBundle] pathForResource:@"obj _descriptions" ofType:@"plist"]; // <- load plist
        NSDictionary *objConfigs = [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease];
        NSArray *vertices = [[objConfigs objectForKey:namePrefix] objectForKey:@"vertices"];
        shape = [ChipmunkUtil polyShapeWithVertArray:vertices
                                            withBody:body
                                               width:self.sprite.contentSize.width
                                              height:self.sprite.contentSize.height];
        shape->e = 0.7; 
        shape->u = 1.0;
        shape->data = self;
        shape->collision_type = OBJ_COLLISION_TYPE;
        cpSpaceAddShape(space, shape);
    }

    In order to visually test the correspondence of sprites and their physical representations, I highly recommend using this thing: github.com/nubbel/CPDebugLayer It
    will look much clearer:

    For another game, I wrote a custom map editor (I used the same PyQt) and I can say that this time returned many times.
    Summarizing this section, I want to say - automate your work, even simple scripts will save you a lot of time, and most importantly, you will be busy programming this time.


    5. What the application does not smell like or connect in-app billing

    By collecting dandelions in the game, the player actually receives in-game money. They can be spent on new characters. By and large, the characters do not differ in anything but appearance - they are neither more dexterous, nor any more. However, where there is progress, there is also a cheat. The game has the opportunity to buy dandelions for real money. Because characters are practically no different, then there is no server check for the validity of the purchase.


    6. We are socializing. We connect GameCenter and create a multiplayer version for two players

    To socialize the application, I added two leaderboards - a distance meter and the number of points scored. Although I am sincerely interested, does anyone even use this?
    A more useful feature of the game center is the ability to create a multiplayer game.
    I didn’t do portioning of card generation in vain - in order to fill the buffer with a sufficient number of objects “for the future” if there are problems with the network. One of the players will be a server, the other a client.
    To determine who is who, at the beginning of the game, along with other meta-information about the player, a random number is transmitted. The one who has it more is the server.
    And although the size of the buffer with objects is large enough to smooth out the unevenness of the network, the delay can lead to the fact that one player is far ahead of the other. In this case, the opponent will be shown by an arrow, i.e. its height will be displayed.
    The sprite of the second player was also motionless, however, from time to time, an adjustment was made to his position, taking into account the information transmitted to him and the current position of the "world" position. Ideally (with instant transmission over the network), the player would "move" evenly, without transmitting too much information.
    However, in practice, such an update caused side effects - the player twitched, because even slight delays between updates were noticeable to the eye. Therefore, I did the update not instantly, but in the form of an animation of movement in half a second. In practice, it looks as if the bee is slowly slowing down or accelerating.


    7. What missed Akela

    As a result of development, some problems surfaced:
    1. The aforementioned control problem - tap is not intuitive in this game

    2. The graphics stutter a little each restart of the general layer.
    Moreover, if the action is looped like repeatForever, then there is practically no such problem:
    Expand Example
    -(void) infiniteMove{
        id actionBy = [CCMoveBy actionWithDuration: BUFFER_DURATION position: ccp(-BUFFER_LENGTH, 0)];
        id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)];
        id actionSequence = [CCSequence actions: actionBy, actionCallFunc, nil];
        id repeateForever = [CCRepeatForever actionWithAction:actionSequence];
        [self.bufferContainer runAction:repeateForever];
    }

    But I made the movement gradually accelerate, so each action creates a new action. This causes the graphics to stutter a bit:
    Expand Example
    -(void) infiniteMoveWithAccel{
        float duration = BUFFER_DURATION-BUFFER_ACCEL*self.lastBufferNumber;
        duration = max(duration, MIN_BUFFER_DURATION);
        id actionBy = [CCMoveBy actionWithDuration: duration position: ccp(-BUFFER_LENGTH, 0)];
        id restartMove = [CCCallFunc actionWithTarget:self selector:@selector(infiniteMoveWithAccel)];
        id fillBuffer = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)];
        id actionSequence = [CCSequence actions: actionBy, restartMove, fillBuffer, nil];
        [self.bufferContainer runAction:actionSequence];
    }

    In other matters, when I tested “on cats”, none of the subjects complained about it, but if you look closely, you can see.

    3. Using the Game Center for multiplayer, on the one hand, eliminated the need for authorization, and also made it possible to make a game without a server part (only the Peer-to-Peer is used), but, on the other hand, made it impossible to write a bot.
    But it is unlikely that this game will gain so many users so that there are always several people online. The bot in this regard is a wonderful solution - a person defeats AI, thinking that he is struggling with a real person and even the simplest game seems several times more interesting.


    8. And finally, the publication.

    In fact, the publication was nothing of the sort. Unless for four days I could not understand why the application with the status of ready for sale is not visible in iTunes. It turned out that I just forgot to change the date “Rights and Pricing -> Availability Date”, which was already in my possession for July this year.

    I hope it was interesting to read and someone came up with useful things.

    Also popular now: