Spite: Ohtili's Ascension
Action RPG about fighting monsters summoned by an evil shaman. Made at The Game Assembly.
Main Contributions
- Level Loading
- Exporting levels from the editor as binary files
- Files for models, lights, and prefabs
- Importing level files into the game
- Finding all files of specified type in specified directory
- Auto reloading current level when the files changed
- Loading correct navmesh, skybox, and cubemap for each level
- Exporting levels from the editor as binary files
- Player
- Playing animations when doing things
- Animations for idling, walking, attacking, using abilities, dying, and respawning
- Being able to use ability 3 and 4
- Melee AOE and ranged AOE
- Completing a level
- Walking to next level door when clicking on it
- Loading next level when done walking
- Walking to enemy and doing main attack when clicking on it
- Being able to keep doing main or secondary attack when holding button
- Showing an outline on some things when cursor is hovering them
- Used on enemies, skill point crystals, ability altars, and next level doors
- Playing animations when doing things
- Enemies
- Walking towards player if they can see her
- Within a specific range and no walls between them
- Keep walking to where they last saw player if they lose her
- Attacking the player if they are close enough and not on cooldown
- Different range for melee and ranged enemies
- Avoid walking into each other
- Using height of navmesh to correct their height
- Playing animations when doing things
- Animations for idling, walking, attacking, and dying
- Telling other enemies nearby if they see the player
- Walking towards player if they can see her
Level Loading
The level designers used Unreal Engine to design levels, and one of them made a plugin to export the level data. This was already done during our previous project, where the levels would be exported to JSON-files, but at the start of this project we switched to binary files to make the loading faster. I wrote a function for exporting an array of any type to a binary file, which was used in the plugin, and a function for importing a binary file to an array, which was used in our engine.
template <typename T>
bool WriteBinaryFile(const std::string& aFilePath, const TArray<T>& aArray)
{
int count = aArray.Num();
if (count <= 0)
{
return false;
}
int bufferSize = count * sizeof(T);
std::string newFilePath = aFilePath + ".jazz";
std::ofstream file(newFilePath.c_str(), std::ios::out | std::ios::binary);
file.write((char*)&count, sizeof(int));
file.write((char*)&aArray[0], bufferSize);
file.close();
return true;
}
I also wrote the structs which were used to store the level data. The file containing the structs was located in our engine, but was also included by the plugin, so that any changes would affect both export and import at the same time.
struct StaticMeshData
{
Vector3f position;
Quaternion rotation;
Vector3f scale;
int modelIndex;
};
struct LightData
{
Vector3f position;
Vector3f direction;
Color color;
float constantIntensity;
float linearIntensity;
float quadraticIntensity;
//if range < 0 it's a directional light
float range = -1.0f;
//if range >= 0 and angle < 0 it's a point light
//otherwise it's a spot light, but they were added in the next project (Aphelion)
float innerAngle = -1.0f;
float outerAngle = -1.0f;
bool shouldCastShadow = false;
};
struct PrefabData
{
int prefabID;
Vector3f position;
Quaternion rotation;
Vector3f scale;
size_t metaTag = 0;
size_t metaTag2 = 0;
size_t metaTag3 = 0;
};
//structs for static collision and node graphs were added in the next project (Aphelion)
When the game started all level files were loaded and the data was stored in arrays. This data would then be used to create all level objects when a level was loaded. The current level would automatically reload when the files changed, but not in the completed game.
void LevelLoader::ReadAllFiles()
{
CU::GrowingArray<std::string> files = CU::FindFiles(myDirectory, myFileEnding);
for (int index = 0; index < files.Size(); ++index)
{
const std::string& filePath = files[index];
std::string levelName = filePath.substr(myDirectory.size());
if (STRING_ENDS_WITH(levelName, myArtEnding))
{
REMOVE_STRING_ENDING(levelName, myArtEnding);
LevelData& levelData = GetLevelData(levelName);
levelData.myArtFile = filePath;
}
//same thing for lights and prefabs
}
for (int index = 0; index < myLevels.Size(); ++index)
{
LevelData& levelData = myLevels[index];
if (!CU::ReadBinaryFile(levelData.myArtFile, levelData.myArt))
{
PRINT_R("Could not find art file for %s", levelData.myName.c_str());
}
//same thing for lights and prefabs
}
}
LevelLoader::LevelData& LevelLoader::GetLevelData(const std::string& aLevelName)
{
int levelIndex = GetLevelIndex(aLevelName);
if (levelIndex < 0)
{
myLevels.Add(LevelData());
LevelData& levelData = myLevels.GetLast();
levelData.myName = aLevelName;
levelData.mySkyBoxAlias = mySkyboxDirectory + aLevelName + mySkyboxEnding;
levelData.myArt.Init(1);
levelData.myLights.Init(1);
levelData.myPrefabs.Init(1);
return levelData;
}
else
{
return myLevels[levelIndex];
}
}