Dynamic scenes in Isaac

Isaac 2D  is a C++ framework based on SFML(Simple and Fast Multimedia Library) and Boost C++ Libraries that provides support for an easy development and understandable(flexible) structure even in complex scenarios for application and games.  Also, Issac it is a multi-platform library, your applications or games can compile and run on the most common operating systems: Windows, Linux, Mac OS X.

Welcome to the “Dynamic scenes in Isaac” tutorial, please make sure that you read the previous tutorials before starting reading this one. In this article I’m going to continue with the scenes that we developed in the last tutorial in order to introduce some new concepts that are available in Isaac.

So far we worked only with static scenes, today I am going to show you how to work with dynamic scenes. There are a lot of differences between static and dynamic scenes, a dynamic one has more then one processing elements, if we see the update method as a processing element. In the next examples I am going to show you how you can use dynamic scenes in your project. First, let’s create a new class and name it DynScene.

The Header:

class DynScene : public isaac::IDynamicScene
{
public:
   DynScene(std::string ac_szSceneName);

   void mp_DefineProcess() const;

   ~DynScene();
};

The Source:

DynScene::DynScene(std::string ac_szSceneName) :
isaac::IDynamicScene(ac_szSceneName)
{
   ;
}

void DynScene::mp_DefineProcess() const
{
   ;
}

DynScene::~DynScene()
{
   ;
}

The header is very simple, a single method that we need to define. Well, in this method all the needed processes are going to be defined, you can see a process like a mini scene, that has an active time or it is active as long as the dynamic scene is active. So far in Isaac there are defined 4 process types:

  1. Permanent –  this type of process is active as long as the dynamic scene is active
  2. PermanentWithStop – this process is active when the dynamic scene is activated, but you can define it a local trigger in order to stop it
  3. Continuous – the Continuous process needs a start and a stop trigger defined
  4. OneTimeProcess – this process it will run only once when the start trigger is disturbed

Now, let’s see how we can define a process and how we can use it. First let’s create a process, add a new class to your project and name it “ProcessOne”.

The header:

#pragma once
#include <IProcessingElement.h>

class ProcessOne : public isaac::IProcessingElement
{
  std::shared_ptr<sf::ConvexShape> mv_xShape;

public:
  ProcessOne(isaac::IProcessingElement::ProcessType ac_enumPEType, std::string ac_szSceneName);

  void mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData);

  void mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
    const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl);

  void mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
    sf::Event av_eventSFMLEvent,
    bool& av_bReturnedBool_WindowClosed);

  void mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const;

  void mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName);

  ~ProcessOne();
};

You can see that it is almost identical with the scene header, this is because this process as I said before is a mini-scene. Now, let’s draw something to see how this process is behaving.

 PEOne::PEOne(isaac::IProcessingElement::ProcessType ac_enumPEType,
    std::string ac_szSceneName) :
    isaac::IProcessingElement(ac_enumPEType, ac_szSceneName)
  {
    ;
  }

  void PEOne::mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData)
  {
    mv_xShape = std::make_shared<sf::ConvexShape>();

    mv_xShape->setPointCount(3);
    mv_xShape->setPoint(0, sf::Vector2f(0, 0));
    mv_xShape->setPoint(1, sf::Vector2f(0, 10));
    mv_xShape->setPoint(2, sf::Vector2f(25, 5));
    mv_xShape->setOutlineColor(sf::Color::Red);
    mv_xShape->setOutlineThickness(5);
    mv_xShape->setPosition(300, 300);

    std::cout << "PE one- Init" << std::endl;
  }

  void PEOne::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
    const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl)
  {
    const std::shared_ptr<const isaac::ITrigger> lv_pTrigger = ac_xLoacalTriggersColl->mf_xGetTriggerByName(("tpe"));
    const std::shared_ptr<isaac::CElementPositionTrigger> lv_pPosition =
      std::const_pointer_cast<isaac::CElementPositionTrigger>(std::static_pointer_cast<const isaac::CElementPositionTrigger>(lv_pTrigger));

    const isaac::CElementPositionTrigger::PositionProp prop = { isaac::Signs::en_GraterThen, isaac::Axis::en_X, 500.0 };

    lv_pPosition->mp_InitTrigger(mv_xShape.get(), prop);
  }

  void PEOne::mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
    sf::Event av_eventSFMLEvent,
    bool& av_bReturnedBool_WindowClosed)
  {
    mv_xShape->move(1, 0);
  }

  void PEOne::mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const
  {
    ac_xMainWindow->draw(*mv_xShape);
  }

  void PEOne::mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName)
  {

  }

  PEOne::~PEOne()
  {
    ;
  }

Now we have a process available, is time to integrate it into the dynamic scene, to do this we will define it inside “mp_DefineProcess” method:

  void DynScene::mp_DefineProcess() const
  {
    std::cout << "Dynamic Scene - defining  processes" << std::endl;

    std::shared_ptr< isaac::IProcessingElement> firstProcess = std::make_shared<ProcessOne>(isaac::IProcessingElement::en_Permanent, "FirstProcess");

    mp_AddProcessingElement(firstProcess);
  }

Because this one is defined like a Permanent process no trigger are needed to be defined. Now we need to instantiate the scene alongside other ones in the “StaticAspect” class.

   void StaticAspect::mp_DefineScenes() const
{
    const std::shared_ptr<const isaac::IStaticScene> firstScene  = std::make_shared<SceneOne>(("Scene1"));
    mp_AddScene(firstScene);

    const std::shared_ptr<const isaac::IStaticScene> secondScene  = std::make_shared<SceneTwo>(("Scene2"));
    mp_AddScene(secondScene);

    const std::shared_ptr<const isaac::IStaticScene> fatherScene  = std::make_shared<SceneThree>(("Scene3"));
    mp_AddScene(fatherScene);

    const std::shared_ptr<const isaac::IDynamicScene> dynScene  = std::make_shared<DynamicScene>(("Dyn"));
    mp_AddScene(dynScene);


    firstScene->mp_SetFatherScene(fatherScene);
    secondScene->mp_SetFatherScene(fatherScene);
}

No triggers are defined for this scene, this means that we are not going to see if the processing element that we just created is working or not. So, let’s instantiate another trigger for this scene in order to create a transition from the second scene to the dynamic scene. We will use “KeyPressTrigger” – the trigger will activate when a specified key is pressed.

  void StaticAspect::mp_DefineTriggers() const
{
    const std::shared_ptr<const isaac::ITrigger> t1 = std::make_shared<isaac::CElementPositionTrigger>(("FirstTrigger"));
    const std::shared_ptr<const isaac::ITrigger> t2 = std::make_shared<isaac::ElementScaleTrigger>(("SecondTrigger"));
    const std::shared_ptr<const isaac::ITrigger> t3 = std::make_shared<isaac::CKeyPressTrigger>(("ThirdTrigger"));

    mp_AddTrigger(t1);
    mp_AddTrigger(t2);
    mp_AddTrigger(t3);
}

Now define the transition:

 void DynamicAspect::mp_DefineScenesTransitions() const
{
    const auto firstTransition  = mf_xDefineTransition(("SceneOne"), ("FirstTrigger"), ("SceneTwo"));
		 
    const auto secondTransition = mf_xDefineTransition(("SceneTwo"), ("SecondTrigger"), ("SceneOne"));
		
    const auto thirdTransition = mf_xDefineTransition(("SceneTwo"), ("ThirdTrigger"), ("DynScene"));

    mp_AddTransition(("FirstTransition "), firstTransition );
    mp_AddTransition(("SecondTransition "), secondTransition );
    mp_AddTransition(("ThirdTransition "), thirdTransition );
}

The trigger and the transition are in place, we need to initialize the trigger now, we will do this in “SceneTwo”:

  void SceneTwo::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl)
{
    const auto lc_pSecondTrigger = ac_xGlobalTriggersColl->mf_xGetTriggerByName(("SecondTrigger"));
    const auto lc_pScaleTrigger = std::static_pointer_cast<const ElementScaleTrigger>(lc_pSecondTrigger);
    lc_pScaleTrigger->mp_InitTrigger(circle, sf::Vector2f(4, 4));
    
    const lc_pThirdTrigger = ac_xGlobalTriggersColl->mf_xGetTriggerByName(("ThirdTrigger"));
    const auto lc_pTriggerKey = std::static_pointer_cast<const isaac::CKeyPressTrigger>(lc_pThirdTrigger);
    lc_pTriggerKey->mp_InitTrigger(sf::Keyboard::Space);
}

You can see that the “TriggerTwo” is the “KeyPressTrigger” and it is initialized with the SPACE key. So, if you are in scene two and press SPACE you will be redirected to the dynamic scene that will initialize the permanent process element and you should see the convex element that we drew.

Isaac Game

Now we have the dynamic scene available on the screen, let’s play a little bit with it and create other processing elements to see how they work. So, create another process and name it “ProcessTwo”.

The header:


class ProcessTwo : public isaac::IProcessingElement
{
  std::shared_ptr<sf::RectangleShape> mv_xShape;

  public:
    ProcessTwo(isaac::IProcessingElement::ProcessType ac_enumPEType,
      std::string ac_szSceneName);

    void mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
      std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData);

    void mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
      const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl);

    void mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
      std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
      sf::Event av_eventSFMLEvent,
      bool& av_bReturnedBool_WindowClosed);

    void mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const;

    void mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName);

    ~ProcessTwo();
};  

The source:

  ProcessTwo::ProcessTwo(isaac::IProcessingElement::ProcessType ac_enumPEType,
    std::string ac_szSceneName) :
    isaac::IProcessingElement(ac_enumPEType, ac_szSceneName)
{
}

void ProcessTwo::mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData)
{
    mv_xShape = std::make_shared<sf::RectangleShape(sf::Vector2f(100, 100));
    mv_xShape->setFillColor(sf::Color::Green);
    mv_xShape->setPosition(200, 200);
}

void ProcessTwo::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
    const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl)
{

}

void ProcessTwo::mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
    sf::Event av_eventSFMLEvent,
    bool& av_bReturnedBool_WindowClosed)
{

}

void ProcessTwo::mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const
{
  ac_xMainWindow->draw(*mv_xShape);
}

void ProcessTwo::mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName)
{

}

ProcessTwo::~ProcessTwo()
{
}

This is the second processing element and it only creates a green rectangle on the screen when he will be activated. Now we need to make sure that the dynamic scene is aware of the existence of it. To do this we are going to define it into the dynamic scene class:

  void DynamicScene::mp_DefineProcess() const
{
    std::cout << "Dynamic Scene - defining  processes" << std::endl;

    std::shared_ptr< isaac::IProcessingElement> firstProcess = std::make_shared<ProcessOne>(isaac::IProcessingElement::en_Permanent, "FirstProcess ");

    std::shared_ptr< isaac::IProcessingElement> seconndProcess = std::make_shared<ProcessTwo>(isaac::IProcessingElement::en_PermanentWithStop, "SeconndProcess");

    std::shared_ptr<const isaac::ITrigger> firstDynTrigger  = std::make_shared<const isaac::CElementPositionTrigger>(("firstDynTrigger"));

    seconndProcess->mp_addStopTrigger(firstDynTrigger);

    mp_AddProcessingElement(firstProcess);
    mp_AddProcessingElement(seconndProcess);
}

We added the second process to the dynamic scene and made it a Permanent with stop process, this means that the process will start once the dynamic scene is activated but it has the possibility to stop once the stop trigger is disturbed. Because of that a trigger was also defined (“CElementPositionTrigger”) and when this trigger it will activate the processing element will stop. So, what we are going to do: when the red-withe moving triangle that is defined in the first processing element will reach a given position the second process will stop. The trigger is defined, now the only thing left to do is to initialize this trigger inside the first processing element.


void PETwo::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
    const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl)
{
    const auto lv_pTrigger = ac_xLoacalTriggersColl->mf_xGetTriggerByName(("firstDynTrigger"));
    const auto lv_pPosition = std::static_pointer_cast<const isaac::CElementPositionTrigger>(lv_pTrigger);
    
    const isaac::CElementPositionTrigger::PositionProp prop = { isaac::en_GraterThen, isaac::Axis::en_X, 500.0 };
    
    lv_pPosition->mp_InitTrigger(mv_xShape.get(), prop);
}
Isaac Game

The trigger is initialized, you can now start the executable and see what is happening when you activate the dynamic scene.
Let’s continue with this exercise and add another processing element to our application, name it “ProcessThree”

The header:

  class ProcessThree : public isaac::IProcessingElement
{
  std::shared_ptr<sf::RectangleShape> mv_xShape;

  public:
    ProcessThree(isaac::IProcessingElement::ProcessType ac_enumPEType,
      std::string ac_szSceneName);

    void mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
      std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData);

    void mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
      const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl);

    void mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
      std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
      sf::Event av_eventSFMLEvent,
      bool& av_bReturnedBool_WindowClosed);

    void mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const;

    void mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName);

    ~ProcessThree();
};

The Source:

ProcessThree::ProcessThree(isaac::IProcessingElement::ProcessType ac_enumPEType,
    std::string ac_szSceneName) :
    isaac::IProcessingElement(ac_enumPEType, ac_szSceneName)
{
}

void ProcessThree::mp_InitProcess(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData)
{
    mv_xShape = std::make_shared<sf::RectangleShape(sf::Vector2f(200, 200));
    mv_xShape->setFillColor(sf::Color::Blue);
    mv_xShape->setPosition(0, 0);
}

void ProcessThree::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
    const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl)
{

}

void ProcessThree::mp_UpdateScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow,
    std::shared_ptr<const isaac::CTransientDataCollection>& ac_xTransientData,
    sf::Event av_eventSFMLEvent,
    bool& av_bReturnedBool_WindowClosed)
{

}

void ProcessThree::mp_DrawScene(std::shared_ptr<sf::RenderWindow> ac_xMainWindow) const
{
  ac_xMainWindow->draw(*mv_xShape);
}

void ProcessThree::mp_Release(std::shared_ptr<const isaac::CTransientDataCollection>& av_xTransientData, std::string ac_szTriggerName)
{

}

ProcessThree::~ProcessThree()
{
}

This will be a continuous process, so it will need a start and a stop trigger, let’s see how we are going to do this in the dynamic scene:

void DynamicScene::mp_DefineProcess() const
{
    std::cout << "Dynamic Scene - defining  processes" << std::endl;

    std::shared_ptr< isaac::IProcessingElement> firstProcess = std::make_shared<ProcessOne>(isaac::IProcessingElement::en_Permanent, "FirstProcess ");

    std::shared_ptr< isaac::IProcessingElement> seconndProcess = std::make_shared<ProcessTwo>(isaac::IProcessingElement::en_PermanentWithStop, "SeconndProcess");
	
	std::shared_ptr< isaac::IProcessingElement> thirdProcess = std::make_shared<ProcessTwo>(isaac::IProcessingElement::en_Continuous, "ThirdProcess");

    std::shared_ptr<const isaac::ITrigger> firstDynTrigger  = std::make_shared<const isaac::CElementPositionTrigger>(("firstDynTrigger"));
    
	std::shared_ptr<const isaac::ITrigger> secondDynTrigger  = std::make_shared<const isaac::CTimeTrigger>(("SecondDynTrigger"));
	
	std::shared_ptr<const isaac::ITrigger> thirdDynTrigger  = std::make_shared<const isaac::CKeyPressTrigger>(("ThirdDynTrigger"));

    seconndProcess->mp_addStopTrigger(firstDynTrigger);
	thirdProcess->mp_addStartTrigger(secondDynTrigger);
	thirdProcess->mp_addStopTrigger(thirdDynTrigger);

    mp_AddProcessingElement(firstProcess);
    mp_AddProcessingElement(seconndProcess);
	mp_AddProcessingElement(thirdDynTrigger);
}

We have all the triggers defined, we need to initialized them in the second processing element. As you can see I used a “TimeTrigger” for the start and a “KeyPressTrigger” for the stop. That means that this process it will start after a given period of time and it will stop when we will press a key. Let’s initialize the triggers:

  void PETwo::mp_InitTriggers(std::shared_ptr<isaac::CTriggerCollection>& ac_xGlobalTriggersColl,
  const std::shared_ptr<isaac::CTriggerCollection>& ac_xLoacalTriggersColl)
{

   const lc_pTrigger = ac_xLoacalTriggersColl->mf_xGetTriggerByName(("secondDynTrigger"));
   const lc_pTime = std::static_pointer_cast<const isaac::CTimeTrigger>(lc_pTrigger);
   sf::Time lc_Time = sf::milliseconds(2000);
   lc_pTime->mp_InitTrigger(lc_Time);
   
   const lc_pTriggerTwo = ac_xLoacalTriggersColl->mf_xGetTriggerByName(("thirdDynTrigger"));
   const lc_pKeyTrigger = std::static_pointer_cast<const isaac::CKeyPressTrigger>(lc_pTriggerTwo);
   lc_pKeyTrigger->mp_InitTrigger(sf::Keyboard::Escape);

}
Isaac Game

Now the triggers are defined, you can try to run the application and see what is happening when you activate the dynamic scene.
You can try to do the same with the One Time Process and see how is working. For more information don’t hesitate to contact me or write a comment. I will come back with more fun tutorials on the future. I hope you understand how Isaac works and I also hope that you will use it into your project. See you next time. Bye. 🙂

Author: Horațiu Condrea

My name is Horațiu Condrea, and I work as a Software Developer Manager at Siemens PLM Software. I hope that my experiments will prove to be useful for many of you guys/girls out there. Don’t forget to leave a comment whenever you run over a bug or something that is not working as it should be. For any kind of information contact me.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.