Extras
Sound
I solely integrated all the music and sound effects into the project. This included using fmod studio and adding all the libraries needed. Here's a couple of snippets of the Sound module (manager) code and the main character's step sound effects.
void CModuleSound::start()
{
result = Studio::System::create(&system);
result = system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, extraDriverData);
// Deshabilitamos el log de FMOD
FMOD::Debug_Initialize(FMOD_DEBUG_LEVEL_NONE);
// Deshabilitamos el log de FMOD
FMOD::Debug_Initialize(FMOD_DEBUG_LEVEL_NONE);
// arranco los Bancos Basicos
result = system->loadBankFile("../Bin/data/sounds/banks/Master.bank", FMOD_STUDIO_LOAD_BANK_NORMAL, &masterBank);
result = system->loadBankFile("../Bin/data/sounds/banks/Master.strings.bank", FMOD_STUDIO_LOAD_BANK_NORMAL, &stringsBank);
// un par de bancos de ejemplo
result = system->loadBankFile("../Bin/data/sounds/banks/sinnBank.bank", FMOD_STUDIO_LOAD_BANK_NORMAL, &sinnBank);
myBanks.emplace("Master", masterBank);
myBanks.emplace("MasterString", stringsBank);
myBanks.emplace("SINN", sinnBank);
//recorro todos los banco y cargo los eventos
for (auto& b : myBanks) {
Studio::Bank* banquito = b.second;
loadEvents(banquito);
}
}
void CModuleSound::update(float elapsed)
{
if (CApplication::get().getInPause()) {
if (!pause_music) {
pauseAllEvents();
SoundEvent* pause =playEvent("menu");
if(getBSO())prevbso = getBSO()->getName();
pause->setVolume(pause->getVolume() * current_bso_volume);
pause_music = true;
}
}
else {
forceStopEvent("menu");
unPauseAllEvents();
prevbso = "";
pause_music = false;
}
/* Set listener according to active camera */
CHandle candidate_h_listener = getEntityByName("Player");
if (candidate_h_listener.isValid()) {
setListener(candidate_h_listener);
}
/* Look for finished event instances */
std::vector done;
for (auto& iter : myEventInstances) {
FMOD::Studio::EventInstance* e = iter.second;
FMOD_STUDIO_PLAYBACK_STATE state;
e->getPlaybackState(&state);
if (state == FMOD_STUDIO_PLAYBACK_STOPPED) {
e->release();
done.emplace_back(iter.first);
}
}
for (auto id : done) {
myEventInstances.erase(id);
}
if(effectsVolume != current_effects_volume)
updateVolumeToEffects();
if (BSOVolume != current_bso_volume)
updateVolumeToBSO();
current_effects_volume = effectsVolume;
current_bso_volume = BSOVolume;
//esto siempre tiene que hacerlo
system->update();
}
SoundEvent* CModuleSound::playEvent(const std::string& name)
{
std::string eventpath = "event:/" + name;
unsigned int retID = 0;
auto iter = myEvents.find(eventpath);
SoundEvent* soundEvent;
if (name == "") {
soundEvent =new SoundEvent();
return soundEvent;
}
if (iter != myEvents.end()) {
/* Create instance of an event */
FMOD::Studio::EventInstance* event = nullptr;
iter->second->createInstance(&event);
if (event) {
/* Start the event instance */
event->start();
/* Get the next id and add it to map */
eventID++;
retID = eventID;
myEventInstances.emplace(retID, event);
}
soundEvent = new SoundEvent(retID);
soundEvent->setName(name);
event->setUserData(&soundEvent);
if (current_bso) {
if (name.compare("menu") != 0 || current_bso->getName().compare(name) != 0) {
soundEvent->setVolume(current_effects_volume);
}
}
else {
if (name.compare("menu") != 0) {
soundEvent->setVolume(current_effects_volume);
}
}
}
else {
soundEvent = new SoundEvent(retID);
}
return soundEvent;
}
void TCompPlayer::playSteps()
{
if (!stepsActivated) return;
/*if (VEC3::Distance(t_player->getPosition(), playerStepPos) > stepDistance) {
playerStepPos = t_player->getPosition();
TCompAudio* audio = get();
audio->playEvent("Test1/OnStep");
}*/
if (collisions.collision_down) {
TCompTransform* t_player = get();
TCompSkeleton* skel = get();
// Si acabamos de empezar andar y aun no hemos pisado con ninguno de los dos pies
if (!leftFootInFloor && !rightFootInFloor) {
if (skel->hasRightFootInFloor(t_player->getPosition().y, stepDistanceRight)) {
TCompAudio* audio = get();
//dbg("pie derecho Idle\n");
steps_event = audio->playEvent("Test1/OnStep");
if (plant.isValid()) {
TMsgStepPlant msg;
CEntity* e = plant;
e->sendMsg(msg);
}
rightFootInFloor = true;
}
else if (skel->hasLeftFootInFloor(t_player->getPosition().y, stepDistanceLeft)) {
TCompAudio* audio = get();
//dbg("pie izquierdo Idle\n");
steps_event = audio->playEvent("Test1/OnStep");
if (plant.isValid()) {
TMsgStepPlant msg;
CEntity* e = plant;
e->sendMsg(msg);
}
leftFootInFloor = true;
}
// Si ya hemos pisado con el pie derecho pero aun no con el izquierdo
}
else if (!leftFootInFloor && rightFootInFloor) {
if (skel->hasLeftFootInFloor(t_player->getPosition().y, stepDistanceLeft)) {
TCompAudio* audio = get();
//dbg("pie izquierdo \n");
steps_event = audio->playEvent("Test1/OnStep");
if (plant.isValid()) {
TMsgStepPlant msg;
CEntity* e = plant;
e->sendMsg(msg);
}
leftFootInFloor = true;
rightFootInFloor = false;
}
}
// Si ya hemos pisado con el pie izquierdo pero aun no con el derecho
else if (leftFootInFloor && !rightFootInFloor) {
if (skel->hasRightFootInFloor(t_player->getPosition().y, stepDistanceRight)) {
TCompAudio* audio = get();
//dbg("pie derecho \n");
steps_event = audio->playEvent("Test1/OnStep");
if (plant.isValid()) {
TMsgStepPlant msg;
CEntity* e = plant;
e->sendMsg(msg);
}
rightFootInFloor = true;
leftFootInFloor = false;
}
}
}
}
Particles
Computed particles, with a coded system and generator. Here's a snippet of the particle system and an example of a smoke particle (the one in the video, when the column impacts on the floor).
CSystem::CSystem()
{
_combinativeTechnique = Resources.get("particles.tech")->as();
_additiveTechnique = Resources.get("particles_additive.tech")->as();
_quadMesh = Resources.get("quad_xy.mesh")->as();
}
void CSystem::start(float warmUpTime)
{
if (!_active)
{
_active = true;
interval = _emitter->interval;
numParticles = _emitter->numParticles;
emit();
if (warmUpTime > 0.f)
{
constexpr float kStep = 1.f / 60.f;
while (warmUpTime > 0.f)
{
const float stepTime = std::min(kStep, warmUpTime);
update(stepTime);
warmUpTime -= stepTime;
}
}
}
}
void CSystem::fadeOut(float duration)
{
_fadeDuration = duration;//multiplico por dos si no empieza el fade demasiado pronto (falta ver why)
_fadeTime = 0.f;
}
void CSystem::updateFading(float delta) {
if (_fadeDuration != 0.f)
{
_fadeTime += delta;
_fadeRatio = (1.f - (_fadeTime / _fadeDuration));
}
else {
_fadeRatio = 1.0f;
}
}
void CSystem::emit()
{
// remove old particles
while (_particles.size() + numParticles > _emitter->maxParticles)
{
if (_particles.size() > 0) _particles.pop_front();
else break;
}
const MAT44& ownerTransform = getOwnerTransform().asMatrix();
for (int i = 0; i < numParticles; ++i)
{
TParticle p = generate();
if (!_emitter->followOwner)
{
p.position = VEC3::Transform(p.position, ownerTransform);
}
_particles.push_back(std::move(p));
}
_time = 0.f;
}
float RandomFloat(float max, float min) {
float random = ((float)rand()) / (float)RAND_MAX;
// generate (in your case) a float between 0 and (4.5-.78)
// then add .78, giving you a float between .78 and 4.5
float range = max - min;
return (random * range) + min;
}
void CSystem::update(float elapsed)
{
if (!_active)
{
return;
}
updateFading(elapsed);
VEC3 gravity;
MAT44 world = MAT44::Identity;
MAT44 world_rot = MAT44::Identity;
TCompSkeleton* skeleton = nullptr;
if (_emitter->followOwner) {
CEntity* e = _owner;
TCompTransform* e_transform = e->get();
world = e_transform->asMatrix();
VEC3 proj_vector = e_transform->getFront();
world_rot = MAT44::CreateFromQuaternion(e_transform->getRotation());
}
gravity= VEC3(0.f, -9.8f, 0.f);
for (auto& p : _particles)
{
p.time += elapsed;
if(_emitter->lifetime != 0.f && p.time >= p.duration) continue;
const float lifetimeRatio = p.duration != 0.f ? std::clamp(p.time / p.duration, 0.f, 1.f) : 0.f;
p.velocity += gravity * _emitter->gravityFactor * elapsed;
if (_emitter->followOwner)p.velocity += VEC3::Transform(_emitter->tvelocity.get(lifetimeRatio), world_rot)*elapsed;
p.position += p.velocity * elapsed;
p.color = _emitter->colors.get(lifetimeRatio)*_fadeRatio;
p.size = _emitter->sizes.get(lifetimeRatio);
if (_emitter->noise_strength > 0)//some particles will have a noise to them so they're more "natural"
{
p.random_direction = VEC3(p.random_direction.x + (2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f), p.random_direction.y + (2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f), p.random_direction.z + (2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f));
p.velocity += p.random_direction * _emitter->noise_strength * elapsed;
}
}
// remove dead particles
auto eraseIt = std::remove_if(_particles.begin(), _particles.end(), [](TParticle& p){ return p.duration != 0.f && p.time >= p.duration; });
_particles.erase(eraseIt, _particles.end());
// shall we emit again?
_time += elapsed;
if (interval > 0.f && _time >= interval)
{
if (_emitter->maxInterval > 0.0f)//check if the interval we want is constant or in a range
{
interval = RandomFloat(_emitter->maxInterval, _emitter->minInterval);
}
if (_emitter->randomParticles)//check if the num
{
numParticles = rand() % _emitter->numRandMaxParticles + _emitter->numParticles;
}
else {
numParticles = _emitter->numParticles;
}
emit();
}
}
void CSystem::render()
{
if (!_active)
{
return;
}
const CTechnique* tech = _emitter->additive ? _additiveTechnique : _combinativeTechnique;
tech->activate();
_emitter->texture->activate(TS_ALBEDO);
CEntity* eCamera = CEngine::get().getRender().getCamera();
if(!eCamera) return;
TCompTransform* cCameraTransform = eCamera->get();
if (!cCameraTransform) return;
const VEC3 cameraPosition = cCameraTransform->getPosition();
const VEC3 cameraUp = cCameraTransform->getUp();
const MAT44& ownerTransform = getOwnerTransform().asMatrix();
for (auto& p : _particles)
{
VEC3 position = _emitter->followOwner ? VEC3::Transform(p.position, ownerTransform) : p.position;
if (_emitter->followAnimation) {
CEntity* e = _owner;
TCompSkeleton* skeleton =e-> get();
string name = e->getName();
QUAT rot = skeleton->getAbsRotationBone(1);
MAT44 sk = MAT44::CreateScale(1)
* MAT44::CreateFromQuaternion(rot)
* MAT44::CreateTranslation(skeleton->getAbsTraslationBone(1));
position = VEC3::Transform(p.position, sk);
}
const MAT44 sc = MAT44::CreateScale(p.size);
const MAT44 bb = MAT44::CreateBillboard(position, cameraPosition, cameraUp);
const MAT44 world = sc * bb ;
setObjRenderCtes(world, p.color);
_quadMesh->activateAndRender();
}
}
{
"texture" : "data/textures/particles/Dust_Smoke_new.dds",
"additive" : false,
"color" : "0.44 0.46 0.5 0.4",
"xcolor" : "0.29 0.24 0.21 1",
"position" : "0 0.2 0",
"xposition" : "0 7.5 0.2",
"type" : "cube",
"volume" : "1 0.3 1",
"interval" :0,
"num_particles" : 26,
"max_particles" :26,
"fade_time":5,
"onlyOnce":true,
"velocity" : "0 0 0",
"velocity_variation" : "0 0 0",
"gravity_factor" : 0,
"follow" : false,
"lifetime" : 5,
"lifetime_variation" : 0,
"sizes" : [
[1, 1.5],
[1.3, 1.8],
[2, 2.5 ]
]
}
Footstep effect
Using plane meshes with a fading shader and particles, I designed a small system of "footprints" that follow the main character around when in "2D mode"
Scripting
I also integrated LUA into the project, it was almost only used to implement trigger behaviour. Here's a couple of snippets of the script and the code that the script calls to.
function interact(j)
if j == "materia" then
playSound("collectMateria")
--lm:ExecScriptDelayed("removeEntityByName(\"materia\")",100)
end
onInteraction(tostring(j))
end
function on_Finish_Game()
fadeOut(1)
lm:ExecScriptDelayed("UI(\"Final_Screen\")",1500)
lm:ExecScriptDelayed("Exit()",9000)
end
function on_Trigger_Enter(j,p)
if isPlayer(tostring(p)) then
if isDeadZone(tostring(j)) then
sendMessageFallingDead()
elseif j == "end" then
fadeOut(1)
lm:ExecScriptDelayed("Exit()",9000)
lm:ExecScriptDelayed("UI(\"Final_Screen\")",1500)
elseif isCheckpoint(tostring(j)) then
getCheckpoint(tostring(j), tostring(p))
else
if isAudioTrigger(tostring(j)) then
triggerAudio(tostring(j),tostring(p))
elseif isCutsceneTrigger(tostring(j),tostring(p))then
playCutscene(tostring(j))
elseif isAreaTrigger(tostring(j),tostring(p))then
enterArea(tostring(j))
else
onInteractionTrigger(tostring(j), true)
end
end
elseif isWater(tostring(j)) then
sendContactWater(tostring(j))
end
end
function on_Trigger_Exit(j,p)
if isEnemyTrigger(tostring(j),tostring(p)) then
playTemple()
end
if isAreaTrigger(tostring(j),tostring(p))then
exitArea(tostring(j))
end
if isPlayer(tostring(p)) and not isPlayer(tostring(j)) then
onInteractionTrigger(tostring(j), false)
end
end
function throwCutscene(voice,voice_delay,music,music_delay)
--playSound(voice)
--playBSO(music)
lm:ExecScriptDelayed("playSound("..'"'..tostring(voice)..'"'..")",tonumber(voice_delay))
lm:ExecScriptDelayed("playBSO("..'"'..tostring(music)..'"'..")",tonumber(music_delay))
end
function startScene(name)
startCutscene(tostring(name))
end
void CModuleScripting::ThrowEvent(Event e, const std::string &event_name, float delay)
{
switch (e) {
case TRIGGER_ENTER:
if (delay > 0) {//las funciones se nombran on_Trigger_Enter_nombreEntidadTrigger_player() o on_Trigger_Enter_nombreEntidadTrigger_enemy(nombreEntidadEnemigo)
execDelayed("on_Trigger_Enter(" + event_name + ")", delay);
}
else {
execCommand("on_Trigger_Enter(" + event_name + ")");
}
break;
case TRIGGER_EXIT:
if (delay > 0) {//las funciones se nombran on_Trigger_Exit_nombreEntidadTrigger_player() o on_Trigger_Enter_nombreEntidadTrigger_enemy(nombreEntidadEnemigo)
execDelayed("on_Trigger_Exit(" + event_name + ")", delay);
}
else {
//dbg("Trigger Exit %s\n", event_name.c_str());
execCommand("on_Trigger_Exit(" + event_name + ")");
}
break;
case COLUMN_PUSH:
execCommand("interact(" + event_name + ")");
break;
case INTERACT:
execCommand("interact(" + event_name+ ")" );
break;
case CHECKPOINT:
execCommand("saveCheckpoint( '" + event_name +"')");
break;
case INPUT:
execCommand("on_input()");
break;
case TELEPORT_PLAYER:
execCommand("teleportPlayer(" + event_name + ")");
break;
case CUTSCENE_AUDIO:
execCommand("throwCutscene(" + event_name + ")");
break;
case CUTSCENE:
execDelayed("startScene(" + event_name + ")",delay);
break;
case FINISH_GAME:
execCommand("on_Finish_Game()");
break;
}
}
void LM::Bind() {
//Binds the functions and classes of Lua.
/*PUBLISH ALL CLASSES*/
SLB::Class("LM", m)
//a comment/documentation for the class [Optional]
.comment("LUA LM class")
//empty constructor, we can also wrapper constructor
// with arguments using .constructor()
.constructor()
//a method/function/value...
.set("ExecScriptDelayed", &LM::ExecScriptDelayed)
.set("ExecScript", &LM::ExecScript)
.set("HelloWorld", &LM::HelloWorld)
.set("motherlode", &LM::motherlode)
.set("dbg", &LM::dbg)
;
SLB::Class ("CHandle", m)
.comment("CHandle wrapper")
.constructor()
.set("fromUnsigned", &CHandle::fromUnsigned)
.set("destroy", &CHandle::destroy)
.set("isValid", &CHandle::isValid)
;
SLB::Class< VEC3 >("VEC3", m)
.constructor()
.comment("the VEC3 class")
.property("x", &VEC3::x)
.property("y", &VEC3::y)
.property("z", &VEC3::z)
;
SLB::Class ("SoundEvent", m)
.comment("SoundEvent wrapper")
.set("isValid", &SoundEvent::isValid)
.set("restart", &SoundEvent::restart)
.set("stop", &SoundEvent::stop)
.set("setPaused", &SoundEvent::setPaused)
.set("setVolume", &SoundEvent::setVolume)
.set("setPitch", &SoundEvent::setPitch)
.set("setParameter", &SoundEvent::setParameter)
.set("getPaused", &SoundEvent::getPaused)
.set("getVolume", &SoundEvent::getVolume)
.set("getPitch", &SoundEvent::getPitch)
.set("getParameter", &SoundEvent::getParameter)
.set("is3D", &SoundEvent::is3D)
.set("isRelativeToCameraOnly", &SoundEvent::isRelativeToCameraOnly)
.set("setIsRelativeToCameraOnly", &SoundEvent::setIsRelativeToCameraOnly)
.set("isPlaying", &SoundEvent::isPlaying)
;
/*PUBLISH ALL FUNCTIONS*/
//gets
m->set("getPlayerHandle", SLB::FuncCall::create(&getPlayerHandle));
m->set("getLocationByHandle", SLB::FuncCall::create(&getLocationByHandle));
m->set("getHandleByName", SLB::FuncCall::create(&getHandleByName));
m->set("getCheckpoint", SLB::FuncCall::create(&getCheckpoint));
m->set("getLastCheckPoint", SLB::FuncCall::create(&getLastCheckPoint));
//spawns
m->set("spawnEnemy", SLB::FuncCall::create(&spawnEnemy));
m->set("spawn", SLB::FuncCall::create(&spawn));
m->set("UI", SLB::FuncCall::create(&UI));
// Generic Interaction
m->set("onInteractionTrigger", SLB::FuncCall::create(&onInteractionTrigger));
m->set("onInteraction", SLB::FuncCall::create(&onInteraction));
//column
m->set("sendInteractionMessage", SLB::FuncCall::create(&sendInteractionMessage));
m->set("sendInteractionMsgToPlayer", SLB::FuncCall::create(&sendInteractionMsgToPlayer));
m->set("removeTriggerByEntity", SLB::FuncCall::create(&removeTriggerByEntity));
m->set("isColumn", SLB::FuncCall::create(&isColumn));
//Checks
m->set("isCheckpoint", SLB::FuncCall::create(&isCheckpoint));
m->set("isPlayer", SLB::FuncCall::create(&isPlayer));
m->set("isWater", SLB::FuncCall::create(&isWater));
//remove
m->set("removeEntityByName", SLB::FuncCall::create(&removeEntityByName));
//Dead
m->set("isDeadZone", SLB::FuncCall::create(&isDeadZone));
m->set("sendMessageDead", SLB::FuncCall::create(&sendMessageDead));
m->set("sendMessageFallingDead", SLB::FuncCall::create(&sendMessageFallingDead));
//End
m->set("sendMessageEnd", SLB::FuncCall::create(&sendMessageEnd));
m->set("Exit", SLB::FuncCall::create(&Exit));
m->set("fadeOut", SLB::FuncCall::create(&fadeOut));
m->set("fadeIn", SLB::FuncCall::create(&fadeIn));
//utils
m->set("teleportPlayer", SLB::FuncCall::create(&teleportPlayer));
m->set("tp", SLB::FuncCall::create(&tp));
//sound
m->set("playSound", SLB::FuncCall::create(&playSound));
m->set("isAudioTrigger", SLB::FuncCall::create(&isAudioTrigger));
m->set("triggerAudio", SLB::FuncCall::create(&triggerAudio));
m->set("playBSO", SLB::FuncCall::create(&playBSO));
m->set("sendContactWater", SLB::FuncCall::create(&sendContactWater));
m->set("isEnemyTrigger", SLB::FuncCall::create(&isEnemyTrigger));
m->set("playTemple", SLB::FuncCall::create(&playTemple));
//cutscenes
m->set("isCutsceneTrigger", SLB::FuncCall::create(&isCutsceneTrigger));
m->set("playCutscene", SLB::FuncCall::create(&playCutscene));
m->set("resurrect", SLB::FuncCall::create(&resurrect));
m->set("startCutscene", SLB::FuncCall::create(&startCutscene));
m->set("playPrebattle", SLB::FuncCall::create(&playPrebattle));
//eventArea
m->set("isAreaTrigger", SLB::FuncCall::create(&isAreaTrigger));
m->set("enterArea", SLB::FuncCall::create(&enterArea));
m->set("exitArea", SLB::FuncCall::create(&exitArea));
}
void getCheckpoint(string name, string Player)
{
CEntity* e = getEntityByName(name);
CEntity* p = getEntityByName(Player);
if (!e->get().isValid()) return;
if (!p->get().isValid()) return;
TCompPlayer* c_p = e->get();
TCompCheckpoint* c_check = e->get();
TMsgCheckpoint msg;
msg.check_point = c_check->getCheckPoint();
msg.form_name = e->getName();
p->sendMsg(msg);
TMsgSaveState msg_save;
getObjectManager()->forEach([&msg_save](CEntity* e) {
e->sendMsg(msg_save);
});
CEngine::get().getUI().startAutoSave();
CHandle(e).destroy();
}
void triggerAudio(string handle, string p)
{
if (p.compare("Player") != 0)return;
if(handle._Starts_with("audio_enemy")){
string currentGS = CEngine::get().getModules().getCurrentGS();
if (currentGS.compare("gameplay") != 0 && CApplication::get().getPrevState().compare("loading") != 0)return;
CEntity* ai = getEntityByName("Boss");
CAI_Controller* con = ai->get();
CEngine::get().getSound().playBSO("guardiansBSO");
con->setGuardiansBSO(CEngine::get().getSound().getBSO());
return;
}
string aux = handle;
handle.erase(0, 6);
if (handle._Starts_with("BSO")) {
handle.erase(0, 4);
CEngine::get().getSound().playBSO(handle);
}
else {
if (handle.compare("Tutorial_Quetz_003")==0) {
CEntity* pl = getEntityByName("Player");
TCompPlayer* player = pl->get();
if (player->firstTimeProjectedCave) return;
}
CEngine::get().getSound().playEvent(handle);
//destroy trigger
CEntity* e = getEntityByName(aux);
CHandle(e).destroy();
}
}