<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>hill climb racing &#8211; Joni Mikkola (c) 2024</title>
	<atom:link href="https://jonimikkola.com/tag/hill-climb-racing/feed/" rel="self" type="application/rss+xml" />
	<link>https://jonimikkola.com</link>
	<description></description>
	<lastBuildDate>Sun, 10 Jan 2016 12:40:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.1.10</generator>
	<item>
		<title>Hill Climb Racing &#8211; Deconstructed &#8211; Hud, checkpoints and tricks</title>
		<link>https://jonimikkola.com/hill-climb-racing-deconstructed-hud-checkpoints-tricks/</link>
					<comments>https://jonimikkola.com/hill-climb-racing-deconstructed-hud-checkpoints-tricks/#comments</comments>
		
		<dc:creator><![CDATA[jonimikkola]]></dc:creator>
		<pubDate>Sun, 10 Jan 2016 12:32:59 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[box2d]]></category>
		<category><![CDATA[camera]]></category>
		<category><![CDATA[car]]></category>
		<category><![CDATA[checkpoint]]></category>
		<category><![CDATA[cocos2d-x]]></category>
		<category><![CDATA[hill climb racing]]></category>
		<category><![CDATA[layer]]></category>
		<category><![CDATA[listener]]></category>
		<category><![CDATA[math]]></category>
		<category><![CDATA[physics]]></category>
		<category><![CDATA[sprite]]></category>
		<guid isPermaLink="false">http://www.mikkolajoni.com/?p=712</guid>

					<description><![CDATA[Hi and sorry for delaying this chapter about HUD, checkpoints and tricks for so long, in this part we will go through how to identify tricks and checkpoints in the game using collision listeners etc. All aforementioned will be displayed on a HUD so user gets the feedback properly. Let&#8217;s start this chapter by creating ... <a title="Hill Climb Racing &#8211; Deconstructed &#8211; Hud, checkpoints and tricks" class="read-more" href="https://jonimikkola.com/hill-climb-racing-deconstructed-hud-checkpoints-tricks/" aria-label="More on Hill Climb Racing &#8211; Deconstructed &#8211; Hud, checkpoints and tricks">Read more</a>]]></description>
										<content:encoded><![CDATA[<p>Hi and sorry for delaying this chapter about HUD, checkpoints and tricks for so long,</p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2016/01/hcr.png"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2016/01/hcr.png" alt="Preview" width="222" height="134" class="aligncenter size-full wp-image-840" /></a></p>
<p>in this part we will go through how to identify tricks and checkpoints in the game using collision listeners etc. All aforementioned will be displayed on a HUD so user gets the feedback properly.</p>
<p>Let&#8217;s start this chapter by creating HUD and after that we&#8217;ll implement all necessary functionality.</p>
<p><a href="#HUD">1. Hud</a><br />
<a href="#Car">2. Car</a><br />
<a href="#Checkpoints">3. Checkpoints</a><br />
<a href="#Fuel">4. Fuel cans and coins</a><br />
<a href="#Tricks">5. Listeners and tricks</a></p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2016/01/hcr_ch3.zip">Download resources</a></p>
<p><a name="HUD"></p>
<h2>1. HUD</h2>
<p></a></p>
<p>HUD is very essential part of a game providing necessary feedback for user interactions. It of course could be called with many other names, but term HUD is pretty well known among gamers.</p>
<p>Our HUD will eventually contain information for:<br />
1. Distance from the start<br />
2. Amount of fuel left<br />
3. Amount of money<br />
4. Airtime and flip reward feedback</p>
<p>Let&#8217;s add template code for the HUD. The method names are pretty much self explanatory.</p>
<p>Hud.h<br />
[code language=&#8221;cpp&#8221;]<br />
#ifndef __HUD_H__<br />
#define __HUD_H__</p>
<p>#include &quot;cocos2d.h&quot;</p>
<p>USING_NS_CC;</p>
<p>class Hud : public Layer  {<br />
private:<br />
    Label* _messageLabel;<br />
    Label* _moneyLabel;<br />
    Label* _nextCheckpointLabel;</p>
<p>    Sprite* _fuelTankSprite;<br />
    Sprite* _fuelTankBarSprite;<br />
    Sprite* _coinSprite;<br />
    Sprite* _distanceSprite;<br />
public:<br />
    void showMessage(std::string message);</p>
<p>    void setFuel(float fuel);<br />
    void setMoney(int money);<br />
    void setMeters(int nextCheckpont, int meters);</p>
<p>    virtual bool init();<br />
    CREATE_FUNC(Hud);<br />
};</p>
<p>#endif<br />
[/code]</p>
<p>Hud.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
#include &quot;Hud.h&quot;</p>
<p>#include &quot;SimpleAudioEngine.h&quot;</p>
<p>USING_NS_CC;</p>
<p>bool Hud::init() {<br />
    if(!Layer::init()) {<br />
        return false;<br />
    }</p>
<p>    int fuelDistanceFromTop = 70;<br />
    int moneyDistanceFromTop = 120;<br />
    int distanceDistanceFromTop = 30;</p>
<p>    Size s = Director::getInstance()-&gt;getVisibleSize();</p>
<p>    _fuelTankSprite = Sprite::create(&quot;images/hud/fuel_can.png&quot;);<br />
    _fuelTankSprite-&gt;setPosition(Point(40, s.height &#8211; fuelDistanceFromTop));<br />
    addChild(_fuelTankSprite);</p>
<p>    _fuelTankBarSprite = Sprite::create(&quot;images/hud/fuel_can_bar.png&quot;);<br />
    _fuelTankBarSprite-&gt;setAnchorPoint(Point(0.0f, 0.5f));<br />
    _fuelTankBarSprite-&gt;setPosition(Point(90, s.height &#8211; fuelDistanceFromTop));<br />
    addChild(_fuelTankBarSprite);</p>
<p>    _coinSprite = Sprite::create(&quot;images/hud/coin.png&quot;);<br />
    _coinSprite-&gt;setPosition(Point(40, s.height &#8211; moneyDistanceFromTop));<br />
    addChild(_coinSprite);</p>
<p>    _distanceSprite = Sprite::create(&quot;images/hud/distance.png&quot;);<br />
    _distanceSprite-&gt;setPosition(Point(40, s.height &#8211; distanceDistanceFromTop));<br />
    addChild(_distanceSprite);</p>
<p>    _messageLabel = Label::createWithTTF(&quot;Checkpoint reached!&quot;, &quot;fonts/arial.ttf&quot;, 60);<br />
    _messageLabel-&gt;setRotation(10.0f);<br />
    _messageLabel-&gt;setScale(0.5f);<br />
    _messageLabel-&gt;setPosition(Point(s.width/2.0f + 100, s.height/2.0f + 50.0f));<br />
    _messageLabel-&gt;setOpacity(0);<br />
    addChild(_messageLabel);</p>
<p>    _moneyLabel = Label::createWithTTF(&quot;0&quot;, &quot;fonts/arial.ttf&quot;, 60);<br />
    _moneyLabel-&gt;setScale(0.4f);<br />
    _moneyLabel-&gt;setAnchorPoint(Point(0.0f, 0.5f));<br />
    _moneyLabel-&gt;setPosition(Point(90, s.height &#8211; moneyDistanceFromTop));<br />
    addChild(_moneyLabel);</p>
<p>    _nextCheckpointLabel = Label::createWithTTF(&quot;0/0&quot;, &quot;fonts/arial.ttf&quot;, 60);<br />
    _nextCheckpointLabel-&gt;setScale(0.4f);<br />
    _nextCheckpointLabel-&gt;setAnchorPoint(Point(0.0f, 0.5f));<br />
    _nextCheckpointLabel-&gt;setPosition(Point(90, s.height &#8211; distanceDistanceFromTop));<br />
    addChild(_nextCheckpointLabel);</p>
<p>    return true;<br />
}</p>
<p>void Hud::showMessage(std::string message) {<br />
    _messageLabel-&gt;setString(message);<br />
    Director::getInstance()-&gt;getActionManager()-&gt;removeAllActionsFromTarget(_messageLabel);<br />
    _messageLabel-&gt;setOpacity(255);<br />
    DelayTime* delay = DelayTime::create(0.5f);<br />
    FadeOut* fade = FadeOut::create(0.2f);<br />
    Sequence* seq = Sequence::create(delay, fade, NULL);<br />
    _messageLabel-&gt;runAction(seq);<br />
}</p>
<p>void Hud::setMeters(int nextCheckpont, int meters) {<br />
    __String* str = String::createWithFormat(&quot;%d/%d&quot;, meters, nextCheckpont);<br />
    _nextCheckpointLabel-&gt;setString(str-&gt;getCString());<br />
}</p>
<p>void Hud::setFuel(float value) {<br />
    const float barMaxWidth = 120;<br />
    value = clampf(value, 0.0f, 120.0f);<br />
    _fuelTankBarSprite-&gt;setScaleX((value * 1.2f) / barMaxWidth);<br />
}</p>
<p>void Hud::setMoney(int money) {<br />
    __String* str = String::createWithFormat(&quot;%d&quot;, money);<br />
    _moneyLabel-&gt;setString(str-&gt;getCString());<br />
}<br />
[/code]</p>
<p>Now we need to add the Hud layer as a child of our main layer by doing the following:</p>
<p>HelloWorldScene.h<br />
[code language=&#8221;cpp&#8221;]<br />
#include &quot;Hud.h&quot;<br />
class HelloWorld : public cocos2d::Layer {<br />
private:<br />
    &#8230;<br />
    Hud* _hud;<br />
[/code]</p>
<p>Initialize _hud variable and add is as a child just before end of bool HelloWorld::init so that HUD will be displayed on top of other layers. We could of course change the rendering order by changing z order, but adding layers in &#8216;correct&#8217; order suffices here.</p>
<p>HelloWorldScene.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
    _hud = Hud::create();<br />
    addChild(_hud);<br />
[/code]</p>
<p>Now if you compile and run the code you should see distance, fuel and money indicators in top left corner. Now we have our HUD frame which we can utilize later on with methods showMessage, setMeters, setFuel and setMoney.</p>
<p><a name="Car"></p>
<h2>2. Car</h2>
<p></a></p>
<p>Next we need to add some new features in our car class. Since our HUD needs a way to display car fuel, we need to add fuel variable in the class along with getAngle method which we use later on to identify flips.</p>
<p>Position method is for getting distance later on.</p>
<p>Car.h<br />
[code language=&#8221;cpp&#8221;]<br />
class Car {<br />
private:<br />
    &#8230;<br />
    float _fuel;<br />
public:<br />
    &#8230;</p>
<p>    float getAngle() {<br />
        return _body-&gt;GetAngle();<br />
    }</p>
<p>    float getFuel() {<br />
        return _fuel;<br />
    }</p>
<p>    Point getPosition() {<br />
        return Point(_body-&gt;GetPosition().x * PTM_RATIO, _body-&gt;GetPosition().y * PTM_RATIO);<br />
    }</p>
<p>    void setFuel(float fuel) {<br />
        _fuel = fuel;<br />
    }<br />
[/code]</p>
<p>When fuel value is <= 0 the fuel bar is empty.

Car.cpp
[code language="cpp"]
void Car::update(float dt) {
...
   _fuel -= 0.1f;
}
[/code]

HelloWorldScene.cpp
[code language="cpp"]
bool HelloWorld::init() {
    ....
    _car = new Car();
    _car-&gt;init(_world, _terrain, _startPosition);
    _car-&gt;setFuel(100.0f);
    ...
}


void HelloWorld::update(float dt) {
   ...
   _hud-&gt;setFuel(_car-&gt;getFuel());
   _camera-&gt;update(dt);
   ...
[/code]

Now if you compile and run your car's fuel should be slowly decreasing on the top left corner.

<a name="Checkpoints"></p>
<h2>3. Checkpoints</h2>
<p></a></p>
<p>Now it&#8217;s time to add checkpoints functionality. Checkpoints are reached after certain amount of distance is driven. We can store distances in simple integer array. Also in order to know the distance, we need to add method for doing that which we later on compare with the checkpoint integer array.</p>
<p>Lets decide that 10 pixels equal on meter. Therefore in code below first checkpoints appears after 500 meters(5000 pixels).</p>
<p>Notice that we are adding car&#8217;s start position as a private variable _startPosition. We use this for calculating distance from start point forwards.</p>
<p>HelloWorldScene.h<br />
[code language=&#8221;cpp&#8221;]<br />
&#8230;<br />
private:<br />
    int _currentCheckpoint;<br />
    std::vector&lt;int&gt; _checkpoints;</p>
<p>    int getMeters();<br />
    void nextCheckpoint(); </p>
<p>    Point _startPosition;<br />
[/code]</p>
<p>HelloWorldScene.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
bool HelloWorldScene::init() {<br />
    _startPosition = Point(400, 500);<br />
    _car = new Car();<br />
    _car-&gt;init(_world, _terrain, _startPosition);</p>
<p>    _currentCheckpoint = 0;<br />
    _checkpoints.push_back(500);<br />
    _checkpoints.push_back(1000);<br />
    _checkpoints.push_back(1700);<br />
    _checkpoints.push_back(2500);<br />
    _checkpoints.push_back(3500);<br />
    _checkpoints.push_back(5000);<br />
}</p>
<p>int HelloWorld::getMeters() {<br />
    const int meterEqualsPixels = 10;<br />
    int totalMeters = (_car-&gt;getPosition().x &#8211; _startPosition.x) / meterEqualsPixels;<br />
    //use std::max to avoid returning negative meters<br />
    return std::max(totalMeters, 0);<br />
}</p>
<p>void HelloWorld::nextCheckpoint() {<br />
   _currentCheckpoint++;<br />
   _hud-&gt;showMessage(&quot;Checkpoint reached!&quot;);<br />
}</p>
<p>void HelloWorld::update(float dt) {<br />
   &#8230;<br />
   //monitor whether player has passed previous checkpoint<br />
   int currentMeters = getMeters();<br />
   if(currentMeters &gt;= _checkpoints[_currentCheckpoint]) {<br />
        nextCheckpoint();<br />
   }</p>
<p>   _hud-&gt;setMeters(_checkpoints[_currentCheckpoint], getMeters());</p>
<p>   _camera-&gt;update(dt);<br />
   &#8230;<br />
}<br />
[/code]</p>
<p>Notice that the _currentCheckpoint variable is increased each time checkpoint is reached, which means that the if comparison will then start comparing against next position in _checkpoints array. Pretty straightforward.</p>
<p>Compile the code and take your car for a ride. After 5000 pixels you should get a &#8220;Checkpoint reached&#8221; displayed on HUD.</p>
<p><a name="Fuel"></p>
<h2>4. Fuel cans and coins</h2>
<p></a></p>
<p>It is now time to do some preliminary actions before diving into tricks. We need to be able to identify between sprite objects of different type. For that purpose add following code in config.h. Before compiling remove ItemType struct enum from Terrain.h!</p>
<p>Config.h<br />
[code language=&#8221;cpp&#8221;]<br />
&#8230;<br />
#define PTM_RATIO 10</p>
<p>struct ItemType {<br />
    enum type {<br />
        Coin,<br />
        Fuel,<br />
        Head,<br />
        Ground,<br />
        Car<br />
    };<br />
};<br />
&#8230;<br />
[/code]</p>
<p>Lets take those types into use. Add following code inside Car::init after sprites are initialized.</p>
<p>Car.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
    _driverSprite-&gt;setTag(ItemType::Head);<br />
    _bodySprite-&gt;setTag(ItemType::Car);<br />
    _frontTireSprite-&gt;setTag(ItemType::Car);<br />
    _rearTireSprite-&gt;setTag(ItemType::Car);<br />
[/code]</p>
<p>Same thing with Terrain class&#8217;s fuel and coin sprites. Insert code</p>
<p>Terrain.h<br />
[code language=&#8221;cpp&#8221;]<br />
&#8230;<br />
private:<br />
   Node* _groundNode;<br />
[/code]</p>
<p>Terrain.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
Terrain::Terrain() {<br />
   &#8230;<br />
   _groundNode = Node::create();<br />
   _groundNode-&gt;setTag(ItemType::Ground);<br />
   addChild(_groundNode);<br />
}</p>
<p>void TerrainG::initWorld(b2World* world) {<br />
   &#8230;<br />
   _body = world-&gt;CreateBody(&amp;bd);<br />
   //with _groundNode we can identify if collision is happening with ground (ItemType::Ground).<br />
   _body-&gt;SetUserData(_groundNode);<br />
}</p>
<p>void Terrain::generateItems(std::vector&lt;Sprite*&gt;&amp; sprites) {<br />
&#8230;<br />
   Sprite* coinSprite = Sprite::create(&quot;images/game/coin.png&quot;);<br />
   coinSprite-&gt;setTag(ItemType::Coin);<br />
&#8230;<br />
   Sprite* fuelCanSprite = Sprite::create(&quot;images/game/fuel_can.png&quot;);<br />
   fuelCanSprite-&gt;setTag(ItemType::Fuel);<br />
&#8230;<br />
}<br />
[/code]</p>
<p>Now we are able to identify collision between different kinds of objects and can more easily do some specific actions required.</p>
<p>Lets utilize HUD class when collecting money or fuel.<br />
HelloWorldScene.h<br />
[code language=&#8221;cpp&#8221;]<br />
private:<br />
&#8230;<br />
    int _money;</p>
<p>    //items<br />
    void playCollectAnim(Sprite* sprite);<br />
    void collectFuelTank(Sprite* sprite);<br />
    void collectMoney(Sprite* sprite);<br />
[/code]</p>
<p>HelloWorldScene.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
    void HelloWorld::playCollectAnim(Sprite* sprite) {<br />
        sprite-&gt;runAction(MoveBy::create(0.5f, Point(0.0f, 40.0f)));<br />
        sprite-&gt;runAction(FadeOut::create(0.5f));<br />
    }</p>
<p>    void HelloWorld::collectFuelTank(Sprite *sprite) {<br />
        _car-&gt;setFuel(120);<br />
        playCollectAnim(sprite);<br />
    }</p>
<p>    void HelloWorld::collectMoney(Sprite *sprite) {<br />
        _money += 100;<br />
        _hud-&gt;setMoney(_money);<br />
        playCollectAnim(sprite);<br />
    }<br />
[/code]</p>
<p>Update HelloWorld::update code where collisions against fuel and coins are checked with following:<br />
[code language=&#8221;cpp&#8221;]<br />
    for(int i = 0; i &lt; _sprites.size(); ++i) {<br />
        if(_car-&gt;getSprite()-&gt;boundingBox().intersectsRect(_sprites[i]-&gt;boundingBox()) &amp;&amp; _sprites[i]-&gt;isVisible()) {<br />
            if(_sprites[i]-&gt;getTag() == ItemType::Coin) {<br />
                collectMoney(_sprites[i]);<br />
            } else if(_sprites[i]-&gt;getTag() == ItemType::Fuel) {<br />
                collectFuelTank(_sprites[i]);<br />
            }<br />
            _sprites[i]-&gt;setTag(-1);<br />
        }<br />
    }<br />
[/code]</p>
<p>Compile your code and collect some fuel cans and coins to see new features in action.</p>
<p><a name="Tricks"></p>
<h2>5. Tricks</h2>
<p></a></p>
<p>Now we get to more hairy part of this chapter. There is many ways to identify flips and airtimes and this is only one among them. </p>
<p>What we essentially need is a way to know whether the car is on the ground or in the air. If car is in the air we monitor if car&#8217;s angle changes as much as is needed for a complete flip.</p>
<p>In order to monitor collisions using Box2d, we need to start by creating MyContactListener class, which Box2d fills with collision information for us to utilize. So create new files MyContactListener.h accordingly.</p>
<p>MyContactListener.h<br />
[code language=&#8221;cpp&#8221;]<br />
#ifndef __MYCONTACTLISTENER_H__<br />
#define __MYCONTACTLISTENER_H__</p>
<p>#import &lt;Box2d/Box2D.h&gt;<br />
#import &lt;vector&gt;<br />
#import &lt;algorithm&gt;</p>
<p>#include &quot;cocos2d.h&quot;</p>
<p>USING_NS_CC;</p>
<p>struct MyContact {<br />
    b2Body *bodyA;<br />
    b2Body *bodyB;<br />
    float impulseA;<br />
    float impulseB;<br />
    bool operator==(const MyContact&amp; other) const<br />
    {<br />
        return (bodyA == other.bodyA) &amp;&amp; (bodyB == other.bodyB);<br />
    }</p>
<p>    bool hasCollision(int typeA, int typeB) {<br />
        bool collision = false;<br />
        if(bodyA &amp;&amp; bodyB) {<br />
            Node* aNode = (Node*)bodyA-&gt;GetUserData();<br />
            Node* bNode = (Node*)bodyB-&gt;GetUserData();</p>
<p>            if(aNode &amp;&amp; bNode) {<br />
                log(&quot;id %d&quot;, aNode-&gt;getTag());<br />
                if((aNode-&gt;getTag() == typeA &amp;&amp; bNode-&gt;getTag() == typeB) ||<br />
                   (bNode-&gt;getTag() == typeA &amp;&amp; aNode-&gt;getTag() == typeB)) {<br />
                    collision = true;<br />
                }<br />
            }<br />
        }</p>
<p>        return collision;<br />
    }<br />
};</p>
<p>class MyContactListener : public b2ContactListener {</p>
<p>public:<br />
    std::vector&lt;MyContact&gt; beginContacts;<br />
    std::vector&lt;MyContact&gt; endContacts;</p>
<p>    void BeginContact(b2Contact* contact) {<br />
        MyContact myContact = { contact-&gt;GetFixtureA()-&gt;GetBody(), contact-&gt;GetFixtureB()-&gt;GetBody() };<br />
        beginContacts.push_back(myContact);<br />
    }</p>
<p>    void EndContact(b2Contact* contact) {<br />
        MyContact myContact = { contact-&gt;GetFixtureA()-&gt;GetBody(), contact-&gt;GetFixtureB()-&gt;GetBody() };<br />
        endContacts.push_back(myContact);<br />
    }</p>
<p>};</p>
<p>#endif<br />
[/code]</p>
<p>Next thing we need is to utilize this class. Create _contactListener variable pointer in HelloWorldScene.h.</p>
<p>HelloWorldScene.h<br />
[code language=&#8221;cpp&#8221;]<br />
private:<br />
&#8230;<br />
   //tricks<br />
   float _aerialTimer;<br />
   float _lastRotation;<br />
   int _collisionPoints;<br />
   int _flips;</p>
<p>   MyContactListener* _contactListener;</p>
<p>   void flip();<br />
[/code]</p>
<p>HelloWorldScene.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
bool HelloWorld::init() {<br />
    &#8230;<br />
    //add after Box2d&#8217;s _world variable is initialized<br />
    _contactListener = new MyContactListener();<br />
    _world-&gt;SetContactListener(_contactListener);</p>
<p>    //initialize variables<br />
    _aerialTimer = 0.0f;<br />
    _collisionPoints = 0;<br />
    _flips = 0;<br />
    _lastRotation = 0.0f;<br />
}</p>
<p>void HelloWorld::flip() {<br />
    _flips++;<br />
    _money += 1000;<br />
    _hud-&gt;setMoney(_money);<br />
    _hud-&gt;showMessage(&quot;Flip!&quot;);<br />
}</p>
<p>void HelloWorld::update(float dt) {<br />
&#8230;<br />
    std::vector&lt;MyContact&gt;&amp; beginContacts = _contactListener-&gt;beginContacts;<br />
    bool groundHit = false;<br />
    for(int i = 0; i &lt; beginContacts.size(); ++i) {<br />
       if(beginContacts[i].hasCollision(ItemType::Ground, ItemType::Car)) {<br />
            groundHit = true;<br />
            _aerialTimer = 0.0f;<br />
            _collisionPoints++;<br />
        }<br />
    }</p>
<p>    std::vector&lt;MyContact&gt;&amp; endContacts = _contactListener-&gt;endContacts;<br />
    for(int i = 0; i &lt; endContacts.size(); ++i) {<br />
        if(endContacts[i].hasCollision(ItemType::Ground, ItemType::Car)) {<br />
            _collisionPoints&#8211;;<br />
        }<br />
    }</p>
<p>    endContacts.clear();<br />
    beginContacts.clear();</p>
<p>    //in case where car is on the ground, there are 2 collision points because<br />
    //both tires are touching the ground<br />
    if(_collisionPoints == 0) {<br />
        _aerialTimer += dt;<br />
        //air score bonus after every second(1.0f)<br />
        if(_aerialTimer &gt;= 1.0f) {<br />
            _hud-&gt;showMessage(&quot;Air score +100!&quot;);<br />
            _aerialTimer = _aerialTimer &#8211; 1.0f;<br />
        }<br />
    }</p>
<p>    //compare new angle with last angle. If new angle is larger or smaller than PI*2<br />
    //it means car has rotated a full circle.<br />
    float angle = _car-&gt;getAngle();<br />
    if(!groundHit) {<br />
        if(angle &gt; _lastRotation + M_PI * 2) {<br />
            _lastRotation = _lastRotation + M_PI * 2;<br />
            flip();<br />
        } else if(angle &lt; _lastRotation &#8211; M_PI * 2) {<br />
            _lastRotation = _lastRotation &#8211; M_PI * 2;<br />
            flip();<br />
        }<br />
    }</p>
<p>    _camera-&gt;update(dt);<br />
&#8230;<br />
}</p>
<p>[/code]</p>
<p>Now if you hit compile and run you should start gaining some rewards by doing flips and airtimes.</p>
<p>I hope I didn&#8217;t miss anything relevant information with this chapter. Anyway if that was case, please don&#8217;t hesitate to comment. Thanks!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jonimikkola.com/hill-climb-racing-deconstructed-hud-checkpoints-tricks/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>How to develop Hill Climb Racing with Cocos2d-x &#8211; Terrain</title>
		<link>https://jonimikkola.com/how-to-develop-hill-climb-racing-with-cocos2d-x-2/</link>
					<comments>https://jonimikkola.com/how-to-develop-hill-climb-racing-with-cocos2d-x-2/#comments</comments>
		
		<dc:creator><![CDATA[jonimikkola]]></dc:creator>
		<pubDate>Sat, 07 Mar 2015 06:40:41 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Games]]></category>
		<category><![CDATA[box2d]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[cocos]]></category>
		<category><![CDATA[cocos2d-x]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[game]]></category>
		<category><![CDATA[gl]]></category>
		<category><![CDATA[hill climb racing]]></category>
		<category><![CDATA[mesh]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[physics]]></category>
		<category><![CDATA[polygon]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[vertex]]></category>
		<category><![CDATA[vertices]]></category>
		<guid isPermaLink="false">http://www.mikkolajoni.com/?p=670</guid>

					<description><![CDATA[Hi, this is the first part of the How to develop Hill Climb Racing with Cocos2d-x tutorial. I will presume you have some basic knowledge about programming and of using Cocos2d-x v3.0 engine. I&#8217;ll try to follow good coding practises while also giving links to related topics as we progress. Let&#8217;s get straight to the ... <a title="How to develop Hill Climb Racing with Cocos2d-x &#8211; Terrain" class="read-more" href="https://jonimikkola.com/how-to-develop-hill-climb-racing-with-cocos2d-x-2/" aria-label="More on How to develop Hill Climb Racing with Cocos2d-x &#8211; Terrain">Read more</a>]]></description>
										<content:encoded><![CDATA[<p>Hi,</p>
<p>this is the first part of the How to develop Hill Climb Racing with Cocos2d-x tutorial. I will presume you have some basic knowledge about programming and of using Cocos2d-x v3.0 engine. I&#8217;ll try to follow good coding practises while also giving links to related topics as we progress. Let&#8217;s get straight to the point and start deconstruction.</p>
<p>Here&#8217;s what we&#8217;ll have after finishing this first tutorial:<br />
<a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/hillclimb.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/hillclimb-300x199.jpg" alt="Hill Climb - deconstructed" width="300" height="199" class="aligncenter size-medium wp-image-762" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/hillclimb-300x199.jpg 300w, https://jonimikkola.com/wp-content/uploads/2015/03/hillclimb.jpg 768w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<h4>What is it made of?</h4>
<p><strong>Algorithm</strong> which creates (x, y) points in 2d space for our upcoming track. These points have been achieved by using sine and cosine functions to create cylindrical shapes. We will later on implement the code, but for clarity let&#8217;s go through these concepts one by one.</p>
<p><strong>Physics</strong> will define the track which will eventually interact with a car, by keeping the car on top of the track. We will use Box2D for handling physics calculations.</p>
<p><strong>OpenGL meshes</strong> are used for visualising the track. Meshes consist of polygons which consist of vertices. The grass on top of the track is a thin slice of mesh drawn on top of the ground mesh. These two meshes are separate, because we don&#8217;t want to stretch the grass layer along with the ground mesh.</p>
<p><strong>What is the problem?</strong><br />
Because track is &#8216;endless&#8217;, it is a very long strip of track to draw and calculate physics to. In reality it doesn&#8217;t need to be really endless, but we can create array big enough to contain track for so far that the finish line virtually unreachable. If we&#8217;d create physics and draw meshes for the whole track, it would jam the CPU and GPU which is not what we want.</p>
<p>We can just generate enough x, y points during game startup in an array, which can be then used to draw and create physics while the player keeps driving forwards. In this tutorial we will be creating x, y points in every 20th pixel, which means iPad2 screen can be filled with track with only 52 keypoints(x, y) 1024px/20px ~= 52. So if we generate 5200 of these points, there will be 100 screenfuls of track to drive and so on.</p>
<p>Ok, so now I&#8217;ve introduced you the basic concepts; let&#8217;s start coding. I presume you know how to create HelloWorld Cocos2d-x project which on top we will be building this game. We will first write all the code and later implement it on HelloWorld template. Download necessary graphics resources below:</p>
<p><a href="http://karvis.kapsi.fi/hr_tutorial/hr_ch1.zip">Download resources</a></p>
<p><a href="#Terrain">1. Terrain class</a><br />
<a href="#XYPoints">2. X, Y points</a><br />
<a href="#Physics">3. Box2D Physics</a><br />
<a href="#OpenGL">4. OpenGL meshes and scrolling</a><br />
<a href="#Optimising">5. Optimising</a><br />
<a href="#Implementation">6. Implementation</a></p>
<p><a name="Terrain"></p>
<h2>1. Terrain class</h3>
<p></a><br />
Let&#8217;s create new class called <em>Terrain</em> in Classes folder. We need to inherit Node class because we will be adding all the sprites etc on this Node which acts as the terrain which scrolls when player drives. </p>
<p>So lets set up Terrain class with following template code. We will implement those unimplemented methods later on.</p>
<p>Terrain.h<br />
[code language=&#8221;cpp&#8221;]<br />
#ifndef __TERRAIN_H__<br />
#define __TERRAIN_H__</p>
<p>#include &quot;cocos2d.h&quot;<br />
#include &quot;config.h&quot;</p>
<p>USING_NS_CC;</p>
<p>#define PTM_RATIO 32</p>
<p>#define GroundStep 20<br />
#define MaxXYPoints 20000<br />
#define MaxHillVertices 40000</p>
<p>class Terrain : public Node {<br />
    private:<br />
        int _nHillVertices;<br />
        int _numKeyIndices;<br />
        int _maxPhysicsPoints;</p>
<p>        Point _xyPoints[MaxXYPoints];</p>
<p>        Point _groundVertices[MaxHillVertices];<br />
        Point _groundTexCoords[MaxHillVertices];<br />
        Point _grassVertices[MaxHillVertices];<br />
        Point _grassTexCoords[MaxHillVertices];</p>
<p>        Texture2D* _grassTexture;<br />
        Texture2D* _groundTexture;</p>
<p>        std::list&lt;b2Fixture*&gt; _fixtures;</p>
<p>        //this body will contain all physics for the track<br />
        b2Body* _body;<br />
        b2World* _world;</p>
<p>        //cocos2d-x 3.0 requires OpenGL calls to be wrapped in CustomCommand<br />
        CustomCommand _renderCmds[1];<br />
    public:<br />
        Terrain();<br />
        ~Terrain();</p>
<p>        void initWorld(b2World* world);<br />
        void destroy();<br />
        void generateXYPoints();<br />
        void generateMeshes();<br />
        void updatePhysics(int prevKeyIndex, int newKeyIndex);</p>
<p>        void onDraw(const Mat4 &amp;transform);<br />
        virtual void draw(Renderer *renderer, const Mat4&amp; transform, uint32_t flags);<br />
        virtual void update(float dt);</p>
<p>        virtual bool init();<br />
        CREATE_FUNC(Terrain);<br />
    };<br />
}</p>
<p>#endif</p>
<p>[/code]</p>
<p>Terrain.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
#include &lt;Box2D/Box2D.h&gt;<br />
#include &quot;Terrain.h&quot;</p>
<p>USING_NS_CC;</p>
<p>Terrain::Terrain() {<br />
    _body = nullptr;</p>
<p>    _maxPhysicsPoints = Director::getInstance()-&gt;getVisibleSize().width/GroundStep;</p>
<p>    Sprite* groundSprite = Sprite::create(&quot;images/game/ground.png&quot;);<br />
    groundSprite-&gt;retain();<br />
    _groundTexture = groundSprite-&gt;getTexture();<br />
    _groundTexture-&gt;setTexParameters( { GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT});</p>
<p>    Sprite* grassSprite = Sprite::create(&quot;images/game/grass.png&quot;);<br />
    grassSprite-&gt;retain();<br />
    _grassTexture = grassSprite-&gt;getTexture();<br />
    _grassTexture-&gt;setTexParameters( { GL_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT});</p>
<p>    GLProgram *shaderProgram_ = ShaderCache::getInstance()-&gt;getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE);<br />
        setGLProgram(shaderProgram_);<br />
    scheduleUpdate();<br />
}</p>
<p>Terrain::~Terrain() {</p>
<p>}</p>
<p>bool Terrain::init() {<br />
    if(!Node::init()) {<br />
        return false;<br />
    }</p>
<p>    return true;<br />
}</p>
<p>void Terrain::initWorld(b2World* world) {<br />
    _world = world;</p>
<p>    //set up our _body variable which will eventually contain the track<br />
    b2BodyDef bd;<br />
    bd.position.Set(0, 0);<br />
    bd.type = b2_staticBody;<br />
    _body = world-&gt;CreateBody(&amp;bd);<br />
}</p>
<p>void Terrain::update(float dt) {</p>
<p>}</p>
<p>[/code]<br />
<a name="XYPoints"></p>
<h2>X, Y points</h2>
<p></a></p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/xypoints.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/xypoints.jpg" alt="xypoints" width="391" height="191" class="aligncenter size-full wp-image-759" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/xypoints.jpg 391w, https://jonimikkola.com/wp-content/uploads/2015/03/xypoints-300x147.jpg 300w" sizes="(max-width: 391px) 100vw, 391px" /></a></p>
<p>First of all we need an algorithm which generates all the required x, y points that we will use for building physics and graphics for our track. So let&#8217;s create new method called generateXYPoints()</p>
<p>[code language=&#8221;cpp&#8221;]<br />
//Generates x, y points every in every 20th x unit point<br />
void Terrain::generateXYPoints() {<br />
    float yOffset = 350.0f;</p>
<p>    int index = 0;<br />
    for (int i = 0; i &lt; MaxXYPoints; i++) {<br />
        _xyPoints[i].x = index;<br />
        _xyPoints[i].y = yOffset &#8211; sinf(i) * 60 * i / 100.0f;<br />
        index += GroundStep;<br />
    }<br />
}<br />
[/code]</p>
<p>Now we have created 20000(MaxXYPoints) x, y points which will be the backbone of our track. If you want to play with the track design, this method is where it should be done. In case 20000 points is too low for your purposes, feel free to increase the MaxXYPoints definition in Terrain.h file.</p>
<p><a name="Physics"></p>
<h2>Physics</h2>
<p></a></p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/physics.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/physics.jpg" alt="physics" width="550" height="110" class="aligncenter size-large wp-image-760" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/physics.jpg 768w, https://jonimikkola.com/wp-content/uploads/2015/03/physics-300x60.jpg 300w" sizes="(max-width: 550px) 100vw, 550px" /></a><br />
Cocos2d-x samples include <em>GLES-Render</em> class, which is a tool for drawing shapes of a physic objects. Copy GLES-Render.cpp and GLES-Render.h files from your Cocos2d-x directory, tests/cpp-tests/Classes/Box2DTestBed/ to your project&#8217;s Classes folder.</p>
<p>After you&#8217;ve done this, we may continue adding physics on our track. Now create method which takes startIndex and endIndex as parameters, which means we can define which portion of the track should physics be calculated. We will modify this method later on to optimise physics calculation properly.</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::updatePhysics(int startIndex, int endIndex) {<br />
   b2EdgeShape shape;<br />
   for (int i=prevKeyIndex; i &lt; newKeyIndex; i++) {<br />
       b2Vec2 p1 = b2Vec2((_xyPoints[i].x)/PTM_RATIO, _xyPoints[i ].y/PTM_RATIO);<br />
       b2Vec2 p2 = b2Vec2((_xyPoints[i+1].x)/PTM_RATIO, _xyPoints[i +1].y/PTM_RATIO);<br />
       shape.Set(p1, p2);<br />
       _numKeyIndices++;<br />
   }<br />
}</p>
<p>[/code]</p>
<p><a name="OpenGL"></p>
<h2>OpenGL meshes</h2>
<p></a></p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/meshes.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/meshes.jpg" alt="meshes" width="301" height="426" class="aligncenter size-large wp-image-761" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/meshes.jpg 301w, https://jonimikkola.com/wp-content/uploads/2015/03/meshes-212x300.jpg 212w" sizes="(max-width: 301px) 100vw, 301px" /></a></p>
<p>Now we have create X, Y points, which are already used for physics of our track. Next we need to use that same x, y information to generate visuals for the track.<br />
So lets create meshes for the ground and grass. We loaded up textures earlier in the constructor of Terrain, so that part is done. Now we need to create vertices and texture coordinates. We need to create continuous set of polygons next to each other and we need to wrap a texture over and that is what we&#8217;re doing next. </p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::generateMeshes() {<br />
    _nHillVertices = 0;<br />
    Point p;<br />
    //generate thin slice of grass mesh on top of the ground mesh using X, Y points generated in generateXYPoints() method<br />
    for (int i=0; i &lt; MaxXYPoints; i++) {<br />
        p = _xyPoints[i];<br />
        _grassVertices[_nHillVertices] = Point(p.x, p.y &#8211; 12.0f);<br />
        _grassTexCoords[_nHillVertices++] = Point(p.x/_grassTexture-&gt;getPixelsWide(), 1.0f);<br />
        _grassVertices[_nHillVertices] = Point(p.x, p.y);<br />
        _grassTexCoords[_nHillVertices++] = Point(p.x/_grassTexture-&gt;getPixelsWide(), 0);<br />
    }</p>
<p>    _nHillVertices = 0;</p>
<p>    //generate mesh for ground below grass<br />
    for (int i = 0; i &lt; MaxXYPoints; i++) {<br />
        p = _xyPoints[i];<br />
        _groundVertices[_nHillVertices] = Point(p.x, 0);<br />
        _groundTexCoords[_nHillVertices++] = Point(p.x/_groundTexture-&gt;getPixelsWide(), 0);<br />
        _groundVertices[_nHillVertices] = Point(p.x, p.y &#8211; 5.0f);<br />
        _groundTexCoords[_nHillVertices++] = Point(p.x/_groundTexture-&gt;getPixelsWide(), (p.y / _groundTexture-&gt;getPixelsHigh()));<br />
    }<br />
}<br />
[/code]</p>
<p>Now we are at a point where we have generated physics and meshes, so we are one step away from the end results, without the optimisations. We are missing drawing code so lets continue from that.</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::draw(Renderer* renderer, const Mat4 &amp;transform, uint32_t flags) {<br />
    Node::draw(renderer, transform, flags);</p>
<p>    _renderCmds[0].init(0.0f);<br />
    _renderCmds[0].func = CC_CALLBACK_0(Terrain::onDraw, this, transform);<br />
    renderer-&gt;addCommand(&amp;_renderCmds[0]);<br />
}<br />
[/code]</p>
<p>[code language=&#8221;cpp&#8221;]<br />
    void Terrain::onDraw(const Mat4 &amp;transform) {<br />
    auto glProgram = getGLProgram();<br />
    glProgram-&gt;use();<br />
    glProgram-&gt;setUniformsForBuiltins(transform);</p>
<p>    GL::bindTexture2D(_groundTexture-&gt;getName());<br />
    GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX );</p>
<p>    glBindBuffer(GL_ARRAY_BUFFER, 0);<br />
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, _groundVertices);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, _groundTexCoords);<br />
    glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)MaxHillVertices);</p>
<p>    GL::bindTexture2D(_grassTexture-&gt;getName());<br />
    GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);</p>
<p>    glBindBuffer(GL_ARRAY_BUFFER, 0);<br />
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, _grassVertices);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, _grassTexCoords);<br />
    glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)MaxHillVertices);</p>
<p>    //set this flag to true to see physics debug draw in action<br />
    static bool debugDraw = false;<br />
    if(_world &amp;&amp; debugDraw) {<br />
        Director* director = Director::getInstance();<br />
        director-&gt;pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);<br />
        director-&gt;loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform);</p>
<p>        GL::enableVertexAttribs(cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION);<br />
        _world-&gt;DrawDebugData();<br />
        CHECK_GL_ERROR_DEBUG();</p>
<p>        director-&gt;popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);<br />
    }<br />
}<br />
[/code]</p>
<p>Now we have created nice physics and visuals for our track. Now we should be able to scroll along the road. Lets create simple code to handle that.</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::update(float dt) {<br />
    //lets move the track -0.5 pixel at each update<br />
    setPositionX(getPositionX() &#8211; 0.5f);<br />
}<br />
[/code]</p>
<p>If you test this with a mobile platform you might encounter some performance issues. These issues arise from physics calculation and from OpenGL rendering that is done for the whole track at once. Because our track needs to be endless, this kind of solution doesn&#8217;t really scale up. If you are not observing these performance issues, tweak up these macros:<br />
[code language=&#8221;cpp&#8221;]<br />
#define MaxXYPoints 10000<br />
#define MaxHillVertices 20000<br />
[/code]</p>
<p><a name="Optimising"></p>
<h2>Optimising</h2>
<p></a></p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/ipad1.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/ipad1-300x133.jpg" alt="ipad" width="300" height="133" class="aligncenter size-medium wp-image-767" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/ipad1-300x133.jpg 300w, https://jonimikkola.com/wp-content/uploads/2015/03/ipad1.jpg 768w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>glDrawArrays() method takes in the start index and how many triangles should be drawn and this is where we need to take into account the &#8216;camera&#8217; position. At this point we don&#8217;t need Box2d&#8217;s debug draw, so we&#8217;ll remove those lines. Lets optimise onDraw method:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::onDraw(const Mat4 &amp;transform) {<br />
    //here we calculate starting index where we start drawing<br />
    int start = abs((getPositionX()) / GroundStep) * 2;<br />
    //and how many points to draw. Both variables are multiplied by 2 because<br />
    //ground and grass polygons have top and bottom points defined for one x value<br />
    int numPoints = 1024.0f / GroundStep * 2;</p>
<p>    auto glProgram = getGLProgram();<br />
    glProgram-&gt;use();<br />
    glProgram-&gt;setUniformsForBuiltins(transform);</p>
<p>    GL::bindTexture2D( _groundTexture-&gt;getName() );<br />
    GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX );</p>
<p>    glBindBuffer(GL_ARRAY_BUFFER, 0);<br />
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, _groundVertices);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, _groundTexCoords);<br />
    glDrawArrays(GL_TRIANGLE_STRIP, start, (GLsizei)numPoints);</p>
<p>    GL::bindTexture2D(_grassTexture-&gt;getName() );<br />
    GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX );</p>
<p>    glBindBuffer(GL_ARRAY_BUFFER, 0);<br />
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, _grassVertices);<br />
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, _grassTexCoords);<br />
    glDrawArrays(GL_TRIANGLE_STRIP, start, (GLsizei)numPoints);<br />
}<br />
[/code]</p>
<p>Physics optimization is little more tricky. We could probably tweak Box2Ds collision algorithm to avoid calculating of the track points which are not visible on the screen, but that sounds like too far fetched.<br />
Our track&#8217;s B2Body contains b2EdgeShape fixtures, which are those lines that the GLES debug draws. There is a very performance cheap way to remove those lines one by one from the back and increase one by one on the end. It&#8217;s sort of like a moving worm in those early days Nokia phones. So let&#8217;s get to it.</p>
<p>Now lets modify physics updating code:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void Terrain::updatePhysics(int prevKeyIndex, int newKeyIndex) {<br />
    b2EdgeShape shape;<br />
    for (int i=prevKeyIndex; i &lt; newKeyIndex; i++) {<br />
        b2Vec2 p1 = b2Vec2((_xyPoints[i].x)/PTM_RATIO,_xyPoints[i].y/PTM_RATIO);<br />
        b2Vec2 p2 = b2Vec2((_xyPoints[i+1].x)/PTM_RATIO,_xyPoints[i +1].y/PTM_RATIO);<br />
        shape.Set(p1, p2);<br />
        _fixtures.push_back(_body-&gt;CreateFixture(&amp;shape, 0));<br />
        _numKeyIndices++;<br />
    }</p>
<p>    while(_fixtures.size() &gt; _maxPhysicsPoints) {<br />
        _body-&gt;DestroyFixture(_fixtures.front());<br />
        _fixtures.pop_front();<br />
    }<br />
}<br />
[/code]</p>
<p>Now all we have left is to update track shape on each update call<br />
[code language=&#8221;cpp&#8221;]<br />
void Terrain::update(float dt) {<br />
    setPositionX(getPositionX() &#8211; 0.5f);</p>
<p>    int start = abs((getPositionX()) / GroundStep);<br />
    updatePhysics(start, start + _maxPhysicsPoints);<br />
}<br />
[/code]</p>
<p><a href="http://www.mikkolajoni.com/wp-content/uploads/2015/03/optimized.jpg"><img decoding="async" loading="lazy" src="http://www.mikkolajoni.com/wp-content/uploads/2015/03/optimized-300x225.jpg" alt="optimized" width="300" height="225" class="aligncenter size-medium wp-image-790" srcset="https://jonimikkola.com/wp-content/uploads/2015/03/optimized-300x225.jpg 300w, https://jonimikkola.com/wp-content/uploads/2015/03/optimized.jpg 447w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>If you want to verify this works, feel free to set different scale for the HelloWorldLayer and observe the results. </p>
<p><a name="Implementation"></p>
<h2>Implementation</h2>
<p></a><br />
HelloWorldScene.h<br />
[code language=&#8221;cpp&#8221;]<br />
#ifndef __HELLOWORLD_SCENE_H__<br />
#define __HELLOWORLD_SCENE_H__</p>
<p>#include &lt;Box2d/Box2D.h&gt;<br />
#include &quot;cocos2d.h&quot;<br />
#include &quot;GLES-Render.h&quot;<br />
#include &quot;Terrain.h&quot;</p>
<p>class HelloWorld : public cocos2d::Layer {<br />
private:<br />
    GLESDebugDraw* _debugDraw;<br />
    b2World* _world;<br />
    LayerGradient* _backgroundGradientLayer;</p>
<p>    Terrain* _terrain;<br />
public:<br />
    // there&#8217;s no &#8216;id&#8217; in cpp, so we recommend returning the class instance pointer<br />
    static cocos2d::Scene* createScene();</p>
<p>    // Here&#8217;s a difference. Method &#8216;init&#8217; in cocos2d-x returns bool, instead of returning &#8216;id&#8217; in cocos2d-iphone<br />
    virtual bool init();</p>
<p>    // implement the &quot;static create()&quot; method manually<br />
    CREATE_FUNC(HelloWorld);<br />
};</p>
<p>#endif // __HELLOWORLD_SCENE_H__</p>
<p>[/code]</p>
<p>HelloWorldScene.cpp<br />
[code language=&#8221;cpp&#8221;]<br />
#include &quot;HelloWorldScene.h&quot;</p>
<p>USING_NS_CC;</p>
<p>Scene* HelloWorld::createScene()<br />
{<br />
    // &#8216;scene&#8217; is an autorelease object<br />
    auto scene = Scene::create();</p>
<p>    // &#8216;layer&#8217; is an autorelease object<br />
    auto layer = HelloWorld::create();</p>
<p>    // add layer as a child to scene<br />
    scene-&gt;addChild(layer);</p>
<p>    // return the scene<br />
    return scene;<br />
}</p>
<p>// on &quot;init&quot; you need to initialize your instance<br />
bool HelloWorld::init() {<br />
    if (!Layer::init()) {<br />
        return false;<br />
    }</p>
<p>    Size size = Director::getInstance()-&gt;getVisibleSize();<br />
    _backgroundGradientLayer = LayerGradient::create(Color4B(106, 164, 208, 255), Color4B(159, 202, 243, 255));<br />
    addChild(_backgroundGradientLayer);</p>
<p>    _terrain = Terrain::create();<br />
    addChild(_terrain);</p>
<p>    b2Vec2 gravity = b2Vec2(0.0f, -30.0f);<br />
    bool doSleep = true;<br />
    _world = new b2World(gravity);<br />
    _world-&gt;SetAllowSleeping(doSleep);</p>
<p>    _terrain-&gt;initWorld(_world);<br />
    _terrain-&gt;generateXYPoints();<br />
    _terrain-&gt;generateMeshes();<br />
    _terrain-&gt;updatePhysics(0, 1024);</p>
<p>    _debugDraw = new GLESDebugDraw(PTM_RATIO);<br />
    _world-&gt;SetDebugDraw(_debugDraw);<br />
    _debugDraw-&gt;SetFlags(GLESDebugDraw::e_shapeBit | GLESDebugDraw::e_jointBit);</p>
<p>    return true;<br />
}<br />
[/code]</p>
<p>Now you should be able to compile this project and see how it looks. Keep tweaking the algorithm that creates the track, so you will have nice track to drive when we create controls, collectibles and car in next chapter!</p>
<p>If you ever read this far, thanks for reading! Feel free to comment and ask any questions you might have and I&#8217;ll be happy to answer!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jonimikkola.com/how-to-develop-hill-climb-racing-with-cocos2d-x-2/feed/</wfw:commentRss>
			<slash:comments>40</slash:comments>
		
		
			</item>
	</channel>
</rss>
