The alchemist
Game project in C++ / Dx11, uses the Overlord Engine
This project was made in 4 weeks. It's a top down single player game that's made in C++, DirectX11 and uses the Overlord Engine.
The player character can shoot arrows and use abilities such as healing and fear. The quiver class is used for holding these different abilities. The player has a quiver that nicely manages the different abilities and their cooldowns.
quiver.cpp
void Quiver::Initialize(const GameContext& gameContext)
{
for (UINT i = 0; i < m_AmountOfArrows; i++)
{
auto arrow = new Arrow();
m_pArrowsVec.push_back(arrow);
AddChild(arrow);
}
m_pHealingPotion = new HealingPotion;
AddChild(m_pHealingPotion);
m_PanicBolt = new PanicBolt;
AddChild(m_PanicBolt);
m_ClarityPotion = new ClarityPotion;
AddChild(m_ClarityPotion);
m_ToxicPool = new ToxicPool;
AddChild(m_ToxicPool);
}
void Quiver::Update(const GameContext & gc)
{
for (auto & skill : m_SkillsData)
{
auto & canCast = std::get<0>(skill);
if (!canCast)
{
auto & totalCdTime = std::get<1>(skill);
auto & currentCd = std::get<2>(skill);
if (currentCd > totalCdTime)
{
currentCd = 0;
canCast = true;
}
else
{
currentCd += gc.pGameTime->GetElapsed();
}
}
}
}
void Quiver::FireArrow(const DirectX::XMFLOAT3 & pos, float yrot)
{
//std::cout << "Fire arrow" << std::endl;
auto & canCast = std::get<0>(m_SkillsData[0]);
if (!canCast)
{
return;
}
std::list::iterator arrowIt;
for (arrowIt = m_pArrowsVec.begin(); arrowIt != m_pArrowsVec.end() ; ++arrowIt)
{
auto & arrow = *arrowIt;
if (!arrow->m_IsFlying )// && ! arrow->m_IsStuck)
{
arrow->Launch(pos, yrot);
m_pArrowsVec.emplace_back(arrow);
m_pArrowsVec.erase(arrowIt);
canCast = false;
return;
}
}
}
quiver.h
class Quiver : public GameObject
{
public:
//A quiver is a container for holding different abilities (arrows & vials)
Quiver();
virtual ~Quiver() = default;
Quiver(const Quiver& other) = delete;
Quiver(Quiver&& other) noexcept = delete;
Quiver& operator=(const Quiver& other) = delete;
Quiver& operator=(Quiver&& other) noexcept = delete;
void Initialize(const GameContext& gameContext) override;
void PostInitialize(const GameContext& gameContext) override;
void Update(const GameContext& gameContext) override;
void PostDraw(const GameContext & gameContext) override;
void FireArrow(const DirectX::XMFLOAT3 & pos, float yrot);
void ThrowHealingPot(const DirectX::XMFLOAT3 & charPos, const DirectX::XMFLOAT3 & targetPos);
void Shield();
void FirePanicBolt(const DirectX::XMFLOAT3 & pos, float yrot);
void ThrowClarityPot(const DirectX::XMFLOAT3 & charPos, const DirectX::XMFLOAT3 & targetPos);
void ThrowToxicPool(const DirectX::XMFLOAT3 & charPos, const DirectX::XMFLOAT3 & targetPos);
UINT m_AmountOfArrows = 60;
std::list m_pArrowsVec;
HealingPotion * m_pHealingPotion;
Character * m_pPlayer;
PanicBolt * m_PanicBolt;
ClarityPotion * m_ClarityPotion;
ToxicPool * m_ToxicPool;
//CoolDown list
// 0 : Can Cast // 1 : Total Cooldown Time // 2: Current CoolDown time // 3: Energy Required // 4: id // 5: Name
std::vector> m_SkillsData =
{
{true, 0.7f, 0.f, 0.f , 0, "ToxicBolt"}, //0
{true, 3.f, 0.f, 0.f , 1, "HealingPotion"}, //1
{true, 4.f, 0.f, 0.f , 2, "ClarityPotion"}, //2
{true, 10.f, 0.f, 0.f , 3,"PanicBolt"}, //3
{true, 30.f, 0.f, 100.f,4, "ToxicGoo"}, //4
{true, 1.f, 0.f, 25.f, 5,"InjectionEx"}, //5
{true, 1.f, 0.f, 25.f, 6,"PetrifyEx"}, //6
{true, 20.f, 0.f, 0.f, 7,"Shield"} //7
};
protected:
QuadDrawer * m_pDrawer, *m_pDrawer2, *m_pDrawer3, *m_pDrawer4, *m_pDrawer5, *m_pDrawer6;
IconMaterial * m_pIconMat;
};
The enemy has a state machine with a couple of different states: Running, attacking, chasing and idle. AiState is the virtual base class for the other states, once the player comes in range the enemy switches to the AttackAiState.
std::pair AttackAiState::Update(const GameContext& gc)
{
auto enemyT = m_Owner.GetTransform();
auto targetT = m_pTarget->GetTransform();
auto pos = enemyT->GetPosition();
auto targetPos = targetT->GetPosition();
auto dt = gc.pGameTime->GetElapsed();
auto xvec = (targetPos.x - pos.x);
auto yvec = (targetPos.z - pos.z);
auto dist = sqrt(xvec * xvec + yvec * yvec);
xvec /= dist;
yvec /= dist;
if (m_Owner.m_IsFeared)
{
xvec = -xvec;
yvec = -yvec;
}
auto vecToTarget = DirectX::XMFLOAT3(xvec * dt * 15 * m_Owner.m_Move, -1, yvec * dt * 15 * m_Owner.m_Move);
if (m_Owner.m_IsSlowed) {
vecToTarget.x *= 0.5;
vecToTarget.y *= 0.5;
}
auto angle = acos(-yvec);
if(!m_Owner.m_IsAttacking)
m_Owner.m_pController->Move(vecToTarget);
if (xvec > 0)
m_Owner.m_pCharacterModel->GetTransform()->Rotate(DirectX::XMFLOAT3(0.0f, -angle, 0.0f), false);
else
m_Owner.m_pCharacterModel->GetTransform()->Rotate(DirectX::XMFLOAT3(0.0f, angle, 0.0f), false);
if (dist < 8)
{
m_Owner.m_IsAttacking = true;
m_Owner.m_pCharacterModel->GetAnimator()->SetAnimation(3);
}
return { false, new IdleAiState(m_Owner) };
}
The shader that takes care of displaying the player's health uses a procedural animated material that is created by blending multiple texture samples that are displaced over time in different directions.
// Vertex Shader
VS_OUTPUT VS(VS_INPUT input){
VS_OUTPUT output;
output.pos = float4(input.pos, 1.0f);
output.texCoord = input.texCoord;
return output;
}
// Pixel Shader
PS_OUTPUT PS(VS_OUTPUT input) : SV_TARGET{
PS_OUTPUT output = (PS_OUTPUT)0;
float hpscale = 0.01 * (100-gHealth) * 0.17;
if (input.texCoord.y < 0.83 + hpscale)
{
return output;
}
output.color = 0;
float res = 1 / 1024.0;
float2 tex = input.texCoord;
tex.x = tex.x * 5.51;//
tex.y = tex.y * 5.1- 0.017*gTime;
float4 sam = gSourceMap.Sample(samLinear, tex);
tex.y = tex.y * 0.61 - 0.014*gTime;
tex.x += 0.5;
float4 sam2 = gSourceMap.Sample(samLinear, tex);
tex.x = tex.x * 2.3 - 0.025 * gTime;
tex.y = tex.y * 1.3 - 0.012 * gTime;
float4 sam3 = gSourceMap.Sample(samLinear, tex);
tex.x = tex.x * 1.3 + 0.1542 * gTime;
tex.y = tex.y * 2 - 0.04 * gTime;
float4 sam4 = gSourceMap.Sample(samLinear, tex);
float4 alpha = gAlphaMap.Sample(samLinear, input.texCoord);
if (sam.a < 0.05)
{
discard;
}
output.color = lerp(lerp(sam, sam2 +sam3 * 0.5, 0.5), lerp(sam3, sam4, 0.5), 0.5);
output.color.a = alpha.r + alpha.b + alpha.g ;
output.color.rgb = changeSaturation(output.color.rgb, 7);
output.color = pow(output.color, 0.75);
output.color.a = 0.85 * 0.33 * output.color.a * (output.color.r+.3);
return output;
}
float3 changeSaturation(float3 RGB, float change) {
// public-domain function by Darel Rex Finley
float G;
float B;
float R;
R = RGB.r;
G = RGB.g;
B = RGB.b;
float Pr = 0.299;
float Pg = 0.587;
float Pb = 0.114;
float P = sqrt(
(R)*(R)*Pr +
(G)*(G)*Pg +
(B)*(B)*Pb);
R = P + ((R)-P)*change;
G = P + ((G)-P)*change;
B = P + ((B)-P)*change;
return float3(R, G, B);
}
The toxic pool attack uses a combination of texture warping, blending and the geometry shader.
// GEOMETRY SHADER
void ToxicGenerator(triangle VS_DATA vertices[3], inout TriangleStream triStream)
{
CreateVertex(triStream, vertices[0].Position, vertices[0].Normal, vertices[0].TexCoord * .1 , 1);
CreateVertex(triStream, vertices[1].Position, vertices[1].Normal, vertices[1].TexCoord * .1 , 1);
CreateVertex(triStream, vertices[2].Position, vertices[2].Normal, vertices[2].TexCoord * .1 , 1);
triStream.RestartStrip();
float pos1 = vertices[0].Position;
float pos2 = vertices[1].Position;
float pos3 = vertices[2].Position;
for (int i = 1; i < 15; i++)
{
float f = 1 / i;
int c = i * (3 + sin(pow(m_Time,f)));
float angle2 = (0.05*(m_Time+i)) * .37;
angle2 = 0;
float2x2 uvRotation2 = { cos(angle2), -sin(angle2), sin(angle2), cos(angle2) };
CreateVertex(triStream, pos1, vertices[0].Normal, (mul(uvRotation2,(vertices[0].TexCoord -0.5))+0.5) * (0.42 - (0.03 * i)), 0.002);
CreateVertex(triStream, pos2, vertices[1].Normal, (mul(uvRotation2,(vertices[1].TexCoord -0.5))+0.5) * (0.42 - (0.03 * i)), 0.002);
CreateVertex(triStream, pos3, vertices[2].Normal, (mul(uvRotation2,(vertices[2].TexCoord -0.5))+0.5) * (0.42 - (0.03 * i)), 0.002);
triStream.RestartStrip();
}
}
// PIXEL SHADER *
float4 MainPS(GS_DATA input) : SV_TARGET
{
float x = input.TexCoord.x;
float y = input.TexCoord.y;
float distance = sqrt((x * x) + (y * y));
if (distance > 0.25)
{
discard;
}
float angle = + m_Time;
float2x2 uvRotation = { cos(angle), -sin(angle), sin(angle), cos(angle) };
float2 uv = mul(input.TexCoord, uvRotation);
float4 r = m_Texture1.Sample(samLinearb, uv + 0.5);
float4 r12 = m_Texture2.Sample(samLinear, input.TexCoord);
float angle2 = -m_Time * .37 + 0.8;
float2x2 uvRotation2 = { cos(angle2), -sin(angle2), sin(angle2), cos(angle2) };
float2 uv2 = mul(input.TexCoord, uvRotation2);
float4 r2 = m_Texture1.Sample(samLinearb, uv2 + 0.5);
float4 r22 = m_Texture2.Sample(samLinear, input.TexCoord + m_Time * 0.1);
float angle3 = m_Time * .17 + 8.8;
float2x2 uvRotation3 = { cos(angle3), -sin(angle3), sin(angle3), cos(angle3) };
float2 uv3 = mul(input.TexCoord, uvRotation3);
float4 r3 = m_Texture1.Sample(samLinearb, uv3 + 0.5);
float4 r32 = m_Texture2.Sample(samLinear, input.TexCoord - cos(m_Time * 0.1));
float angle4 = 0.3* cos(m_Time) + 0.7 * sin(m_Time+.7) * .27 + 18.8;
float2x2 uvRotation4 = { cos(angle4), -sin(angle4), sin(angle4), cos(angle4) };
float2 uv4 = mul(input.TexCoord, uvRotation4);
float4 r4 = m_Texture1.Sample(samLinearb, uv4 + 0.5);
float4 r42 = m_Texture2.Sample(samLinear, input.TexCoord + sin(m_Time *0.73));
r *= 0.9;
r2 *= 0.9;
r3 *= 0.9;
r4 *= 0.9;
float4 ret = r * sin(r2) * r3 * r4;
float4 ret2 = r12 * r22 * r32 * r42;
float avg = ret.r + ret.g + ret.b;
ret.g *= 2.8;
if (ret.r + ret.g + ret.b < 0.01)
ret.a -= 1 - (50 *avg);
ret.g += 6*avg;
ret.b -= 2 * avg;
ret.r -= .2 * avg;
if (distance > 0.20)
{
ret *= 0.07;
}
if (distance > 0.15)
{
ret *= 0.24;
}
return pow(ret , 1);
}