2020 年 5 月—2020 年 7 月
本项目使用的全部资源均来自游戏 Aokana: Four Rhythm Across the Blue,且仅用于学习。
UI 系统采用完全数据驱动的设计,可以解析 XML 文件来生成 UI 布局。
系统实现了四种 UI 元素:Canvas、Sprite、TextBox 和 Button。
系统还支持简单的 UI 动画与嵌套 UI 布局。
对于 Visual Novel 游戏而言,核心玩法由数据驱动,因此从文件解析数据对本项目十分重要。
该系统使用 CSV 而非 XML,因为在 Excel 中编辑 CSV 更方便。
剧情系统支持以下命令:
#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;
}
该 UIScene 是整个 UI 系统的核心,既能解析单个布局文件,也能实时管理 UI 元素。
该 Scenario 代码用于解析文件中的剧情数据并将其保存在该类中,关卡管理器可以从这些文件获取游戏数据。
#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;
}