Houseki Engine

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.


Project Information


UI System

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.


Scenario System

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:

  • Parsing single dialogs
  • Switch background
  • Switch BGM
  • Play SFX
  • Switch Character
  • Display branch boxes
  • Jump to other scenarios

#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

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.


Scenario

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;
}

            

Post Mortem


What Went Well

  • Successfully developed a simple UI system from scratch
  • Successfully developed a game based on my system

What Went Wrong

  • Time estimation is a bit wrong while working with this project. Asset editing and planning spend more time than I expected.
  • Animation system is hard, and I didn't prepare much time when I was working on the animations.

What I learned

  • Develop an engine and a game of my love is really enjoyable
  • UI system is not hard to develop from scratch, but you need to care about the details
  • Animation system is hard to implement, plan more time next time when working on the animation system.