May 2020 - July 2020
The assets from this project are all from game Aokana: Four Rhythm Across the Blue, and only used for study purpose.
The UI System is developed to be 100% data-oriented. XML files could be parsed to generate a UI Layout.
4 kinds of UI elements are implemented in this system, which are: Canvas, Sprite, TextBox and Button.
Simple UI Animations are also supported in this system, as well as nested UI layouts.
The core of the Visual Novel games is the gameplay is driven by data. Thus parsing data from files is pretty important for this project.
I use csv format for this system instead of xml format because it's more convenient to edit it in excel.
In my Scenario System, it supports following commands:
#include "Game/Framework/UIScene.hpp"
#include "Game/Framework/UIElement.hpp"
#include "Game/Game.hpp"
#include "Game/GameCommon.hpp"
#include "Engine/Math/OBB2.hpp"
#include "Engine/Input/InputSystem.hpp"
#include "Engine/Core/DevConsole.hpp"
#include "Engine/Renderer/BitmapFont.hpp"
#include "Game/Framework/UISprite.hpp"
#include "Game/Framework/UIButton.hpp"
#include "Game/Framework/UITextBox.hpp"
#include "Game/Framework/UICanvas.hpp"
#include "Game/Framework/UIAnimation.hpp"
#include "Game/Framework/UIAnimationClip.hpp"
bool UIScene::LoadSceneFromXMLFile(char const* fileName)
{
//m_elements.clear();
m_rootElement = new UICanvas(Vec2(.5f, .5f), Vec2(0.f, 0.f), Vec2(UI_SIZE_X, UI_SIZE_Y));
m_rootElement->SetName("Root");
MyXMLDocument xmlDocument;
xmlDocument.LoadFile(fileName);
if (xmlDocument.ErrorID() != tinyxml2::XML_SUCCESS) {
g_theConsole->PrintString(Rgba8::red, Stringf("Failed to Load the XML File %s", fileName));
return false;
}
XMLElement* rootXMLElement = xmlDocument.RootElement();
if (!rootXMLElement) {
g_theConsole->PrintString(Rgba8::red, Stringf("XML File %s has no element.", fileName));
return false;
}
CreateNestedElementsFromXMLElement(m_rootElement, rootXMLElement);
g_theConsole->PrintString(Rgba8::green, Stringf("Successfully loaded scene from XML File %s.", fileName));
return true;
}
UIElement* UIScene::LoadElementFromXMLFile(char const* fileName)
{
MyXMLDocument xmlDocument;
xmlDocument.LoadFile(fileName);
if (xmlDocument.ErrorID() != tinyxml2::XML_SUCCESS) {
g_theConsole->PrintString(Rgba8::red, Stringf("Failed to Load the XML File %s", fileName));
return false;
}
XMLElement* rootXMLElement = xmlDocument.RootElement();
if (!rootXMLElement) {
g_theConsole->PrintString(Rgba8::red, Stringf("XML File %s has no element.", fileName));
return false;
}
std::string eleName = rootXMLElement->Name();
if (eleName == "Sprite")
return CreateSpriteFromXMLElement(rootXMLElement);
if (eleName == "Button")
return CreateButtonFromXMLElement(rootXMLElement);
if (eleName == "TextBox")
return CreateTextBoxFromXMLElement(rootXMLElement);
if (eleName == "Canvas")
return CreateCanvasFromXMLElement(rootXMLElement);
g_theConsole->PrintString(Rgba8::red, Stringf("Error in parsing file %s. Unknown element name.", fileName));
return nullptr;
}
UISprite* UIScene::CreateSpriteFromXMLElement(XMLElement* e)
{
std::string name = ParseXmlAttribute(*e, "Name", "Unknown Sprite");
std::string texturePath = ParseXmlAttribute(*e, "Texture", "NULL");
Vec2 position = ParseXmlAttribute(*e, "Position", Vec2(0, 0));
Vec2 size = ParseXmlAttribute(*e, "Size", Vec2(100, 100));
Vec2 pivot = ParseXmlAttribute(*e, "Pivot", Vec2(0.5f, 0.5f));
float rotationDegrees = ParseXmlAttribute(*e, "Rotation", 0.f);
Rgba8 tint = ParseXmlAttribute(*e, "Tint", Rgba8::white);
bool isVisible = ParseXmlAttribute(*e, "Visible", true);
bool isSelectable = ParseXmlAttribute(*e, "Selectable", false);
std::string scaleStr = ParseXmlAttribute(*e, "Scale", "1");
Vec2 scale = Vec2(1, 1);
if (IsStringFloatFormat(scaleStr))
{
float s = ParseXmlAttribute(*e, "Scale", 1.f);
scale = Vec2(s, s);
}
else if (Vec2::IsStringVec2Format(scaleStr.c_str()))
{
Vec2 s = ParseXmlAttribute(*e, "Scale", Vec2(1.f, 1.f));
scale = s;
}
Texture* tex = g_theRenderer->CreateOrGetTextureFromFile(texturePath.c_str());
UISprite* newSprite = new UISprite(tex, pivot, position, size);
newSprite->SetName(name);
newSprite->SetRotation(rotationDegrees);
newSprite->SetVisibility(isVisible);
newSprite->SetColor(tint);
newSprite->SetIsSelectable(isSelectable);
newSprite->SetScale(scale);
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "Animation")
{
UIAnimation* anim = ParseAnimationElement(currentElement);
newSprite->SetAnimation(anim);
}
}
// Get Nested Elements
CreateNestedElementsFromXMLElement(newSprite, e);
return newSprite;
}
UIButton* UIScene::CreateButtonFromXMLElement(XMLElement* e)
{
std::string name = ParseXmlAttribute(*e, "Name", "Unknown Button");
std::string defaultTexturePath = ParseXmlAttribute(*e, "DefaultTexture", "NULL");
std::string hoveredTexturePath = ParseXmlAttribute(*e, "HoveredTexture", "NULL");
std::string clickedTexturePath = ParseXmlAttribute(*e, "ClickedTexture", "NULL");
Vec2 position = ParseXmlAttribute(*e, "Position", Vec2(0, 0));
Vec2 size = ParseXmlAttribute(*e, "Size", Vec2(100, 100));
Vec2 pivot = ParseXmlAttribute(*e, "Pivot", Vec2(0.5f, 0.5f));
float rotationDegrees = ParseXmlAttribute(*e, "Rotation", 0.f);
Rgba8 defaultTint = ParseXmlAttribute(*e, "Tint", Rgba8::white);
Rgba8 hoveredTint = ParseXmlAttribute(*e, "HoveredTint", Rgba8::white);
Rgba8 clickedTint = ParseXmlAttribute(*e, "ClickedTint", Rgba8::white);
bool isVisible = ParseXmlAttribute(*e, "Visible", true);
bool isSelectable = ParseXmlAttribute(*e, "Selectable", true);
std::string scaleStr = ParseXmlAttribute(*e, "Scale", "1");
Vec2 scale = Vec2(1, 1);
if (IsStringFloatFormat(scaleStr))
{
float s = ParseXmlAttribute(*e, "Scale", 1.f);
scale = Vec2(s, s);
}
else if (Vec2::IsStringVec2Format(scaleStr.c_str()))
{
Vec2 s = ParseXmlAttribute(*e, "Scale", Vec2(1.f, 1.f));
scale = s;
}
Texture* defaultTex = g_theRenderer->CreateOrGetTextureFromFile(defaultTexturePath.c_str());
Texture* hoveredTex = nullptr;
Texture* clickedTex = nullptr;
if (hoveredTexturePath != "NULL")
hoveredTex = g_theRenderer->CreateOrGetTextureFromFile(hoveredTexturePath.c_str());
if (clickedTexturePath != "NULL")
clickedTex = g_theRenderer->CreateOrGetTextureFromFile(clickedTexturePath.c_str());
UIButton* newButton = new UIButton(defaultTex, pivot, position, size);
newButton->SetName(name);
newButton->SetHoveredTexture(hoveredTex);
newButton->SetClickedTexture(clickedTex);
newButton->SetRotation(rotationDegrees);
newButton->SetVisibility(isVisible);
newButton->SetColor(defaultTint);
newButton->SetHoveredColor(hoveredTint);
newButton->SetClickedColor(clickedTint);
newButton->SetIsSelectable(isSelectable);
newButton->SetScale(scale);
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "Animation")
{
UIAnimation* anim = ParseAnimationElement(currentElement);
newButton->SetAnimation(anim);
}
}
Strings onClickedEvetStrs;
std::string eventStrs = ParseXmlAttribute(*e, "OnClickedEvent", "NULL");
if (eventStrs != "NULL")
{
onClickedEvetStrs = SplitStringOnDelimiter(eventStrs, ";");
for (int i = 0; i < onClickedEvetStrs.size(); i++)
{
newButton->BindClickedEventFromString(onClickedEvetStrs[i]);
}
}
Strings onHoveredEventStrs;
eventStrs = ParseXmlAttribute(*e, "OnHoveredEvent", "NULL");
if (eventStrs != "NULL")
{
onHoveredEventStrs = SplitStringOnDelimiter(eventStrs, ";");
for (int i = 0; i < onHoveredEventStrs.size(); i++)
{
newButton->BindHoveredEventFromString(onHoveredEventStrs[i]);
}
}
CreateNestedElementsFromXMLElement(newButton, e);
return newButton;
}
UITextBox* UIScene::CreateTextBoxFromXMLElement(XMLElement* e)
{
std::string name = ParseXmlAttribute(*e, "Name", "Unknown TextBox");
std::string text = ParseXmlAttribute(*e, "Text", "Random Text");
Vec2 position = ParseXmlAttribute(*e, "Position", Vec2(0, 0));
Vec2 size = ParseXmlAttribute(*e, "Size", Vec2(100, 100));
Vec2 pivot = ParseXmlAttribute(*e, "Pivot", Vec2(0.5f, 0.5f));
std::string scaleStr = ParseXmlAttribute(*e, "Scale", "1");
Vec2 scale = Vec2(1, 1);
if (IsStringFloatFormat(scaleStr))
{
float s = ParseXmlAttribute(*e, "Scale", 1.f);
scale = Vec2(s, s);
}
else if (Vec2::IsStringVec2Format(scaleStr.c_str()))
{
Vec2 s = ParseXmlAttribute(*e, "Scale", Vec2(1.f, 1.f));
scale = s;
}
std::string alignmentStr = ParseXmlAttribute(*e, "Alignment", "Center");
Vec2 alignment = ALIGN_CENTER;
if (alignmentStr == "TopLeft")
alignment = ALIGN_TOPLEFT;
if (alignmentStr == "TopCenter" || alignmentStr == "TopCentre")
alignment = ALIGN_TOPCENTER;
if (alignmentStr == "TopRight")
alignment = ALIGN_TOPRIGHT;
if (alignmentStr == "CenterLeft" || alignmentStr == "CentreLeft")
alignment = ALIGN_CENTERLEFT;
if (alignmentStr == "Center" || alignmentStr == "Centre")
alignment = ALIGN_CENTER;
if (alignmentStr == "CenterRight" || alignmentStr == "CentreRight")
alignment = ALIGN_CENTERRIGHT;
if (alignmentStr == "BottomLeft")
alignment = ALIGN_BOTTOMLEFT;
if (alignmentStr == "BottomCenter" || alignmentStr == "BottomCentre")
alignment = ALIGN_BOTTOMCENTER;
if (alignmentStr == "BottomRight")
alignment = ALIGN_BOTTOMRIGHT;
float rotationDegrees = ParseXmlAttribute(*e, "Rotation", 0.f);
Rgba8 tint = ParseXmlAttribute(*e, "Tint", Rgba8::white);
bool isVisible = ParseXmlAttribute(*e, "Visible", true);
bool isSelectable = ParseXmlAttribute(*e, "Selectable", false);
float fontSize = ParseXmlAttribute(*e, "FontSize", 18.f);
float leading = ParseXmlAttribute(*e, "Leading", 0.f);
UITextBox* newTextBox = new UITextBox(g_defaultFont, pivot, position, size);
newTextBox->SetName(name);
newTextBox->SetRotation(rotationDegrees);
newTextBox->SetColor(tint);
newTextBox->SetVisibility(isVisible);
newTextBox->SetIsSelectable(isSelectable);
newTextBox->SetScale(scale);
newTextBox->SetLeading(leading);
newTextBox->SetText(text);
newTextBox->SetFontSize(fontSize);
newTextBox->SetTextAlignment(alignment);
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "Animation")
{
UIAnimation* anim = ParseAnimationElement(currentElement);
newTextBox->SetAnimation(anim);
}
}
CreateNestedElementsFromXMLElement(newTextBox, e);
return newTextBox;
}
UICanvas* UIScene::CreateCanvasFromXMLElement(XMLElement* e)
{
std::string name = ParseXmlAttribute(*e, "Name", "Unknown Canvas");
Vec2 position = ParseXmlAttribute(*e, "Position", Vec2(0, 0));
Vec2 size = ParseXmlAttribute(*e, "Size", Vec2(100, 100));
Vec2 pivot = ParseXmlAttribute(*e, "Pivot", Vec2(0.5f, 0.5f));
float rotationDegrees = ParseXmlAttribute(*e, "Rotation", 0.f);
bool isVisible = ParseXmlAttribute(*e, "Visible", true);
bool isSelectable = ParseXmlAttribute(*e, "Selectable", false);
std::string scaleStr = ParseXmlAttribute(*e, "Scale", "1");
Vec2 scale = Vec2(1, 1);
if (IsStringFloatFormat(scaleStr))
{
float s = ParseXmlAttribute(*e, "Scale", 1.f);
scale = Vec2(s, s);
}
else if (Vec2::IsStringVec2Format(scaleStr.c_str()))
{
Vec2 s = ParseXmlAttribute(*e, "Scale", Vec2(1.f, 1.f));
scale = s;
}
UICanvas* newCanvas = new UICanvas(pivot, position, size);
newCanvas->SetName(name);
newCanvas->SetRotation(rotationDegrees);
newCanvas->SetVisibility(isVisible);
newCanvas->SetIsSelectable(isSelectable);
newCanvas->SetScale(scale);
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "Animation")
{
UIAnimation* anim = ParseAnimationElement(currentElement);
newCanvas->SetAnimation(anim);
}
}
CreateNestedElementsFromXMLElement(newCanvas, e);
return newCanvas;
}
UIAnimation* UIScene::ParseAnimationElement(XMLElement* e)
{
UIAnimation* newAnim = new UIAnimation();
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "AnimationClip")
{
UIAnimationClip* newAnimClip = ParseAnimationClipElement(currentElement);
newAnim->AppendClip(*newAnimClip);
}
}
std::string defaultAnimStr = ParseXmlAttribute(*e, "DefaultAnimation", "");
newAnim->SetCurrentClipByName(defaultAnimStr);
return newAnim;
}
UIAnimationClip* UIScene::ParseAnimationClipElement(XMLElement* e)
{
std::string animClipName = ParseXmlAttribute(*e, "Name", "Unknown Clip");
UIAnimationClip* newAnimClip = new UIAnimationClip(animClipName);
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
std::string eleName = currentElement->Name();
if (eleName == "Frame")
{
float t = ParseXmlAttribute(*currentElement, "Time", -1.f);
if (t < 0.f)
{
// Do something here
}
for (const XMLAttribute* attr = currentElement->FirstAttribute(); attr != NULL; attr = attr->Next())
{
std::string attrName = attr->Name();
if (attrName == "Position")
{
Vec2 pos = ParseXmlAttribute(*currentElement, "Position", Vec2::Zero());
newAnimClip->SetPositionAtTime(t, pos);
}
else if (attrName == "Rotation")
{
float rotationDegrees = ParseXmlAttribute(*currentElement, "Rotation", 0.f);
newAnimClip->SetRotationAtTime(t, rotationDegrees);
}
else if (attrName == "Scale")
{
Vec2 scale = ParseXmlAttribute(*currentElement, "Scale", Vec2::Zero());
newAnimClip->SetScaleAtTime(t, scale);
}
else if (attrName == "Color")
{
Rgba8 color = ParseXmlAttribute(*currentElement, "Color", Rgba8::white);
newAnimClip->SetColorAtTime(t, color);
}
}
}
}
return newAnimClip;
}
void UIScene::CreateNestedElementsFromXMLElement(UIElement* rootElement, XMLElement* e)
{
for (XMLElement* currentElement = e->FirstChildElement(); currentElement != NULL; currentElement = currentElement->NextSiblingElement())
{
// Sprite
std::string elementType = currentElement->Name();
if (elementType == "Sprite")
{
UISprite* newSprite = CreateSpriteFromXMLElement(currentElement);
newSprite->m_parentElement = rootElement;
rootElement->m_childElements.push_back(newSprite);
}
else if (elementType == "Button")
{
UIButton* newButton = CreateButtonFromXMLElement(currentElement);
newButton->m_parentElement = rootElement;
rootElement->m_childElements.push_back(newButton);
}
else if (elementType == "TextBox")
{
UITextBox* newTextBox = CreateTextBoxFromXMLElement(currentElement);
newTextBox->m_parentElement = rootElement;
rootElement->m_childElements.push_back(newTextBox);
}
else if (elementType == "Canvas")
{
UICanvas* newCanvas = CreateCanvasFromXMLElement(currentElement);
newCanvas->m_parentElement = rootElement;
rootElement->m_childElements.push_back(newCanvas);
}
else {
if (elementType != "Animation")
g_theConsole->PrintString(Rgba8::yellow, Stringf("Warning! Unknown element from XML File."));
}
}
}
UIElement* UIScene::GetUIElementByName(std::string name)
{
std::vector elements = GetUIElements(m_rootElement);
for (int i = 0; i < elements.size(); i++)
{
if (elements[i]->m_name == name)
return elements[i];
}
return nullptr;
}
const void UIScene::Update()
{
UpdateMouseRaycast();
m_rootElement->Update();
}
const void UIScene::UpdateMouseRaycast()
{
std::vector elements = GetUIElements(m_rootElement);
// Clear all raycast info
for (int i = 0; i < elements.size(); i++)
{
UIElement* currentElement = elements[i];
if (currentElement)
currentElement->SetIsHovered(false);
}
Vec2 mouseNormalizedPos = g_theInput->GetMouseNormalizedClientPos();
Vec2 mousePos = mouseNormalizedPos * Vec2(UI_SIZE_X, UI_SIZE_Y);
for (int i = elements.size() - 1; i >= 0; i--)
{
UIElement* currentElement = elements[i];
if (!currentElement) continue;
OBB2 boundingBox = currentElement->GetBoundingBox();
if (boundingBox.IsPointInside(mousePos) && currentElement->IsSelectable())
{
currentElement->SetIsHovered(true);
break;
}
}
}
const void UIScene::Render() const
{
m_rootElement->Render();
}
const void UIScene::DebugRender() const
{
m_rootElement->DebugRender();
std::string elementsStr = GetElementsStrsForDebug(m_rootElement);
VertexList vertexes;
Vec2 dimensions = g_defaultFont->GetDimensionsForText2D(elementsStr, 31.5f);
AppendVertsForAABB2D(vertexes, AABB2(50, 1000 + 31.5f - dimensions.y, 50 + dimensions.x, 1000 + 31.5f), Rgba8(255, 255, 255, 100));
g_theRenderer->BindTexture(nullptr);
g_theRenderer->DrawVertexArray(vertexes);
vertexes.clear();
g_defaultFont->AddVertsForText2D(vertexes, Vec2(50, 1000), 31.5f, elementsStr, Rgba8::black);
g_theRenderer->BindTexture(g_defaultFont->GetTexture());
g_theRenderer->DrawVertexArray(vertexes);
}
const void UIScene::AppendElement(UIElement* element, UIElement* parentElement)
{
if (element == nullptr) return;
std::vector elements = GetUIElements(m_rootElement);
for (int i = 0; i < elements.size(); i++)
{
if (elements[i] == element)
{
g_theConsole->PrintString(Rgba8::red, "WARNING! APPENDING AN ELEMENT THAT'S ALREADY IN SCENE");
return;
}
}
for (int i = 0; i < elements.size(); i++)
{
if (elements[i] == parentElement)
{
parentElement->m_childElements.push_back(element);
element->m_parentElement = parentElement;
return;
}
}
m_rootElement->m_childElements.push_back(element);
element->m_parentElement = m_rootElement;
return;
}
const void UIScene::RemoveElement(UIElement* element)
{
std::vector elements = GetUIElements(m_rootElement);
for (int i = 0; i < elements.size(); i++)
{
if (elements[i] == element)
{
if (elements[i]->m_parentElement)
{
for (int j = 0; j < elements[i]->m_parentElement->m_childElements.size(); j++)
if (elements[i]->m_parentElement->m_childElements[j] == element)
elements[i]->m_parentElement->m_childElements[j] = nullptr;
}
}
}
}
const void UIScene::RemoveElementByName(std::string name)
{
UIElement* ele = GetUIElementByName(name);
if (ele != nullptr)
RemoveElement(ele);
}
std::vector UIScene::GetUIElements(UIElement* rootElement) const
{
std::vector elements;
elements.push_back(rootElement);
for (int i = 0; i < rootElement->m_childElements.size(); i++)
{
if (!rootElement->m_childElements[i])
continue;
std::vector childElements = GetUIElements(rootElement->m_childElements[i]);
elements.insert(elements.end(), childElements.begin(), childElements.end());
}
return elements;
}
std::string UIScene::GetElementsStrsForDebug(UIElement* rootElement, int level) const
{
std::string elementStrs;
for (int i = 0; i < level; i++)
elementStrs += " ";
elementStrs += rootElement->m_name + "\n";
for (int i = 0; i < rootElement->m_childElements.size(); i++)
{
if (!rootElement->m_childElements[i])
continue;
elementStrs += GetElementsStrsForDebug(rootElement->m_childElements[i], level + 1);
}
return elementStrs;
}
The UIScene is the core of the whole UI system, it can parse a single layout file, as well as managing the UI elements on the fly.
The Scenario code is used for parsing scenario data from files and stored them in this class. Level Managers can get game data from these files.
#include "Game/Scenario.hpp"
#include "ThirdParty/fast-cpp-csv-parser/csv.h"
#include "Engine/Core/FileUtils.hpp"
std::vector Scenario::m_scenarioList;
Scenario::Scenario(std::string fileName)
{
m_name = GetNameFromFilePath(fileName);
m_fileName = fileName;
LoadScenario();
}
void Scenario::LoadScenario()
{
m_commands.clear();
io::CSVReader<9,
io::trim_chars<' ', '\t'>,
io::double_quote_escape<',', '"'>,
io::throw_on_overflow,
io::no_comment> in(m_fileName);
in.read_header(io::ignore_extra_column, "Index", "Event", "Character", "Name", "Cloth", "Emote", "Dialog", "Audio", "Args");
std::string indexStr, eventStr, character, name, cloth, emote, dialog, audio, argsStr;
while (in.read_row(indexStr, eventStr, character, name, cloth, emote, dialog, audio, argsStr))
{
ScenarioCommandData d;
d.m_index = indexStr;
d.m_event = eventStr;
d.m_character = character;
d.m_name = name;
d.m_cloth = cloth;
d.m_emote = emote;
d.m_dialog = dialog;
d.m_audio = audio;
d.m_args = SplitStringOnDelimiter(argsStr, ';');
m_commands.push_back(d);
}
}
void Scenario::CreateAllScenariosFromFile()
{
Strings scenarioFileNames = GetFileNamesInFolder("Data/Gameplay/", "*.csv");
for (int i = 0; i < scenarioFileNames.size(); i++)
{
CreateOrGetScenarioFromFile("Data/Gameplay/" + scenarioFileNames[i]);
}
}
Scenario* Scenario::CreateOrGetScenarioFromFile(std::string fileName)
{
for (int i = 0; i < m_scenarioList.size(); i++)
{
if (m_scenarioList[i]->m_fileName == fileName)
return m_scenarioList[i];
}
Scenario* newScenario = new Scenario(fileName);
m_scenarioList.push_back(newScenario);
return newScenario;
}
Scenario* Scenario::GetScenarioByName(std::string name)
{
for (int i = 0; i < m_scenarioList.size(); i++)
{
if (m_scenarioList[i]->m_name == name)
return m_scenarioList[i];
}
return nullptr;
}