Viewmodel Blood Splatter Overlay: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Cleanup and QoL changes)
(Finished the article)
Line 1: Line 1:
== Introduction ==
== Introduction ==


This is a tutorial for a simple material proxy system that we will use to switch viewmodel textures dependent on CQB.
In this tutorial, we'll be setting up a simple material proxy system that we can use to overlay a blood detail texture onto our viewmodel based on close-quarters combat.


Final results that could be made from this are, for example, hands and weapons covered in goo, acid, blood, water, animated raindrop water normals, and many more visual design elements.
Final results that can be made from this are, for example, hands and weapons covered in goo, acid, blood, water, animated raindrop water normals, and many more visual design elements.


== Requirements ==
== Requirements ==


* A Source SDK 2013 engine branch mod.
* Beginner/Intermediate knowledge of C++.
* Beginner/Intermediate knowledge of C++.
* Source SDK 2007 engine branch (2013 compatible version in process of writing).
* Knowledge and familiarity with textures and materials.
* Knowledge and familiarity with textures and materials.
{{Note|This tutorial was written on the 2007 engine branch due to many issues within the port of this gameplay mechanic to the 2013 branch. I am seeking the community's help with this! '''Please update this page'''.}}


== What You Will Learn ==
== What You Will Learn ==


* To create a new material proxy system using the [[$detail]] parameter.
* To create a new material proxy system using the [[$detail]] parameter.
* To create an overlay of blood onto a viewmodel after shooting flesh or blood materials (NPCs included of course) at close quarters.
* To create an overlay of blood onto a viewmodel after shooting flesh or blood materials (NPCs included of course) at close-quarters.


== Known Bugs ==
== The Implementation ==


Without using different names for materials and textures relating to multiple weapons, a blood overlay switch will happen to all weapons - Compile models with different material name associations.
=== bloodoverlayproxy.cpp ===


[[Image:Blood proxy System.jpg]]
On the client-side, create a file called '''bloodoverlayproxy.cpp''' and copypaste the following in it:
<source lang=cpp>
//========= Copyright Bernt Andreas Eide, All rights reserved. ============//
//
// Purpose: When you take damage or you damage someone and blood splats on
//          you then you'll draw that blood on your hands/weapon. (overlay)
//
//=============================================================================//


== Tutorial ==
=== Step 1: Programming ===
==== proxy_blood.cpp ====
On the client-side, create a file called '''proxy_blood.cpp''':
<source lang=cpp>
#include "cbase.h"
#include "cbase.h"
#include <KeyValues.h>
#include "materialsystem/imaterialvar.h"
#include "materialsystem/IMaterialVar.h"
#include "materialsystem/imaterialproxy.h"
#include "materialsystem/IMaterial.h"
#include "baseviewmodel_shared.h"
#include "materialsystem/ITexture.h"
#include "c_baseplayer.h"
#include "materialsystem/IMaterialSystem.h"
#include "FunctionProxy.h"
#include "toolframework_client.h"
#include "toolframework_client.h"


Line 42: Line 39:
#include "tier0/memdbgon.h"
#include "tier0/memdbgon.h"


// Forward declarations
class C_BloodyTextureProxy : public IMaterialProxy
void ToolFramework_RecordMaterialParams( IMaterial *pMaterial );
{
public:
C_BloodyTextureProxy();
virtual ~C_BloodyTextureProxy();
 
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
C_BaseEntity *BindArgToEntity( void *pArg );
virtual void OnBind( void *pC_BaseEntity );
virtual void Release() { delete this; }
IMaterial *GetMaterial();


ConVar cl_viewmodel_blood( "cl_viewmodel_blood", "0" );
private:
IMaterialVar *m_pBlendFactor;
};


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:  
// Purpose: Constructor
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsCoveredInBlood()  
C_BloodyTextureProxy::C_BloodyTextureProxy()
{  
{
return cl_viewmodel_blood.GetBool();  
m_pBlendFactor = NULL;
}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:  
// Purpose: Destructor
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseViewModel::GetCoveredBloodEntity()
C_BloodyTextureProxy::~C_BloodyTextureProxy()
{
{
C_BaseEntity *pWeapon = m_hWeapon.Get();
if ( pWeapon )
{
return pWeapon;
}
return NULL;
}
}


Line 72: Line 73:
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CViewmodelBlood_Proxy : public IMaterialProxy
bool C_BloodyTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
{
public:
bool found;
CViewmodelBlood_Proxy();


bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
m_pBlendFactor = pMaterial->FindVar( "$detailblendfactor", &found, false );
void OnBind( void *pC_BaseEntity );
if ( !found )
IMaterial *GetMaterial();
return false;
virtual void Release() { delete this; }


private:
return true;
IMaterialVar *m_pDetailBlendFactor;
}
 
IMaterial *m_pMaterial;
 
bool m_bPlayerBlood;
};


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CViewmodelBlood_Proxy::CViewmodelBlood_Proxy()
C_BaseEntity *C_BloodyTextureProxy::BindArgToEntity( void *pArg )
{
{
m_pMaterial = NULL;
IClientRenderable *pRend = (IClientRenderable *)pArg;
m_pDetailBlendFactor = NULL;
return pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
}
}


Line 102: Line 96:
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CViewmodelBlood_Proxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
void C_BloodyTextureProxy::OnBind( void *pC_BaseEntity )
{
{
m_pMaterial = pMaterial;
if ( !pC_BaseEntity )
return;


bool bFound;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
m_pDetailBlendFactor = pMaterial->FindVar( "$detailblendfactor", &bFound );
C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel *>( pEntity );
if ( !bFound )
if ( pViewModel )
{
{
m_pDetailBlendFactor = NULL;
C_BasePlayer *pOwner = ToBasePlayer( pViewModel->GetOwner() );
return false;
if ( pOwner )
m_pBlendFactor->SetFloatValue( pOwner->m_bShouldDrawBloodOverlay ? 1.0f : 0.0f );
}
}


m_bPlayerBlood = pKeyValues->GetInt( "player", 0 ) > 0;
if ( ToolsEnabled() )
 
ToolFramework_RecordMaterialParams( GetMaterial() );
return true;
}
}


Line 122: Line 117:
// Purpose:  
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CViewmodelBlood_Proxy::OnBind( void *pRenderable )
IMaterial *C_BloodyTextureProxy::GetMaterial()
{
{
IMaterial *pMaterial = GetMaterial();
return m_pBlendFactor->GetOwningMaterial();
if ( pMaterial )
}
 
EXPOSE_INTERFACE( C_BloodyTextureProxy, IMaterialProxy, "BloodyTexture" IMATERIAL_PROXY_INTERFACE_VERSION );
</source>
 
=== c_baseplayer.h ===
 
In '''c_baseplayer.h''', inside the <code>public:</code> section, add the following line:
<source lang=cpp>
bool m_bShouldDrawBloodOverlay;
</source>
 
=== c_baseplayer.cpp ===
 
In '''c_baseplayer.cpp''', below <code>RecvPropString( RECVINFO(m_szLastPlaceName) ),</code> add:
<source lang=cpp>
RecvPropBool( RECVINFO(m_bShouldDrawBloodOverlay) ),
</source>
 
Then in the constructor, below <code>ListenForGameEvent( "base_player_teleported" );</code>, add:
<source lang=cpp>
m_bShouldDrawBloodOverlay = false;
</source>
 
=== player.h ===
 
In '''player.h''', inside the <code>public:</code> section, add the following line:
<source lang=cpp>
CNetworkVar( bool, m_bShouldDrawBloodOverlay ); // Have we been hit or have blood splatted on us?
</source>
 
=== player.cpp ===
 
In '''player.cpp''', inside the constructor and below <code>m_bitsDamageType = 0;</code>, add:
<source lang=cpp>
m_bShouldDrawBloodOverlay = false;
</source>
 
At the bottom of the <code>InitialSpawn( void )</code> function, add:
<source lang=cpp>
m_bShouldDrawBloodOverlay = false; // Reset blood overlay
</source>
 
Then inside <code>IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer )</code>, below <code>SendPropString (SENDINFO(m_szLastPlaceName) ),</code>, add:
<source lang=cpp>
SendPropBool( SENDINFO(m_bShouldDrawBloodOverlay) ),
</source>
 
=== basecombatcharacter.cpp ===
 
In '''basecombatcharacter.cpp''', inside the <code>OnTakeDamage_Alive( const CTakeDamageInfo &info )</code> function above <code>return 1;</code>, add:
<source lang=cpp>
// Handle the viewmodel blood splatter overlay effect here:
if ( ( info.GetDamageType() & ( DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_BUCKSHOT ) ) )
{
{
if ( pRenderable )
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if ( pPlayer == this )
pPlayer->m_bShouldDrawBloodOverlay = true;
 
pPlayer = ToBasePlayer( info.GetAttacker() );
if ( pPlayer && ( this->BloodColor() != BLOOD_COLOR_MECH ) )
{
{
IClientRenderable *pRend = (IClientRenderable *)pRenderable;
if ( pPlayer->GetAbsOrigin().DistTo( this->GetAbsOrigin() ) < 200.0f )
C_BaseEntity *pEnt = pRend->GetIClientUnknown()->GetBaseEntity();
pPlayer->m_bShouldDrawBloodOverlay = true;
if ( pEnt )
}
{
}
C_BaseEntity *pWeapon = pEnt->GetCoveredBloodEntity();
</source>
 
== The Materials ==
 
=== Viewmodel VMTs ===


if ( pWeapon )
Now you'll have to edit every single viewmodel '''.vmt''' to include the following snippet of code:
{
<source lang="text" highlight=5-15>
CBaseEntity *pTarget = m_bPlayerBlood ? pWeapon->GetOwnerEntity() : NULL;
"VertexLitGeneric"
if ( pTarget )
{
{
...
pWeapon = pTarget;
}


if ( pWeapon->IsCoveredInBlood() )
"$detail" "detail/blood_detail"
{
"$detailblendmode" "2"
m_pDetailBlendFactor->SetFloatValue( 1.0f );
"$detailblendfactor" "0.0"
}
"$detailscale" "1.0"
else
{
m_pDetailBlendFactor->SetFloatValue( 0.0f );
}
}
else
{
m_pDetailBlendFactor->SetFloatValue( 0.0f );
}
}
}


if ( ToolsEnabled() )
"Proxies"
{
"BloodyTexture"
{
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
}
}
}
}
</source>


//-----------------------------------------------------------------------------
Notice how we're calling for '''materials/detail/blood_detail.vtf''', this is the blood detail texture that gets placed on top of our viewmodel.
// Purpose:
 
//-----------------------------------------------------------------------------
== Conclusion ==
IMaterial *CViewmodelBlood_Proxy::GetMaterial()
 
{
Now all that remains is making your own blood detail texture for this, but for convenience/testing sake, you can download the included placeholder texture found in the "External links" section down below.
return m_pMaterial;
}


EXPOSE_INTERFACE( CViewmodelBlood_Proxy, IMaterialProxy, "ViewmodelBloodProxy" IMATERIAL_PROXY_INTERFACE_VERSION );
And that's all there is to it, if set up right you should now be able to see the blood splatter texture appear on your viewmodel based on the snippet of code we placed into '''basecombatcharacter.cpp'''.
</source>


==== c_baseentity.h ====
== External links ==
In '''c_baseentity.h''', inside the '''public:''' section add the following lines:
<source lang=cpp>
virtual C_BaseEntity *GetCoveredBloodEntity() { return this; }
virtual bool IsCoveredInBlood();
</source>


Tutorial being made now...
[https://mega.nz/file/RsNm2DgK#qHysT50ZYbX_WyXg8FcnyO7Bwb88lfHE1qlXo__9FAI Download BloodDetailTexture.rar] - A placeholder blood detail texture by [[User:GamerDude27|Ian B.]]


[[Category: Programming]] [[Category: Tutorials]] [[Category: Weapons]] [[Category: Miscellaneous]]
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:Free source code]]

Revision as of 19:43, 19 April 2021

Introduction

In this tutorial, we'll be setting up a simple material proxy system that we can use to overlay a blood detail texture onto our viewmodel based on close-quarters combat.

Final results that can be made from this are, for example, hands and weapons covered in goo, acid, blood, water, animated raindrop water normals, and many more visual design elements.

Requirements

  • A Source SDK 2013 engine branch mod.
  • Beginner/Intermediate knowledge of C++.
  • Knowledge and familiarity with textures and materials.

What You Will Learn

  • To create a new material proxy system using the $detail parameter.
  • To create an overlay of blood onto a viewmodel after shooting flesh or blood materials (NPCs included of course) at close-quarters.

The Implementation

bloodoverlayproxy.cpp

On the client-side, create a file called bloodoverlayproxy.cpp and copypaste the following in it:

//========= Copyright Bernt Andreas Eide, All rights reserved. ============//
//
// Purpose: When you take damage or you damage someone and blood splats on 
//          you then you'll draw that blood on your hands/weapon. (overlay)
//
//=============================================================================//

#include "cbase.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/imaterialproxy.h"
#include "baseviewmodel_shared.h"
#include "c_baseplayer.h"
#include "toolframework_client.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

class C_BloodyTextureProxy : public IMaterialProxy
{
public:
	C_BloodyTextureProxy();
	virtual ~C_BloodyTextureProxy();

	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	C_BaseEntity *BindArgToEntity( void *pArg );
	virtual void OnBind( void *pC_BaseEntity );
	virtual void Release() { delete this; }
	IMaterial *GetMaterial();

private:
	IMaterialVar *m_pBlendFactor;
};

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
C_BloodyTextureProxy::C_BloodyTextureProxy()
{
	m_pBlendFactor = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
C_BloodyTextureProxy::~C_BloodyTextureProxy()
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_BloodyTextureProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	bool found;

	m_pBlendFactor = pMaterial->FindVar( "$detailblendfactor", &found, false );
	if ( !found )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_BaseEntity *C_BloodyTextureProxy::BindArgToEntity( void *pArg )
{
	IClientRenderable *pRend = (IClientRenderable *)pArg;
	return pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_BloodyTextureProxy::OnBind( void *pC_BaseEntity )
{
	if ( !pC_BaseEntity )
		return;

	C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
	C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel *>( pEntity );
	if ( pViewModel )
	{
		C_BasePlayer *pOwner = ToBasePlayer( pViewModel->GetOwner() );
		if ( pOwner )
			m_pBlendFactor->SetFloatValue( pOwner->m_bShouldDrawBloodOverlay ? 1.0f : 0.0f );
	}

	if ( ToolsEnabled() )
		ToolFramework_RecordMaterialParams( GetMaterial() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
IMaterial *C_BloodyTextureProxy::GetMaterial()
{
	return m_pBlendFactor->GetOwningMaterial();
}

EXPOSE_INTERFACE( C_BloodyTextureProxy, IMaterialProxy, "BloodyTexture" IMATERIAL_PROXY_INTERFACE_VERSION );

c_baseplayer.h

In c_baseplayer.h, inside the public: section, add the following line:

	bool m_bShouldDrawBloodOverlay;

c_baseplayer.cpp

In c_baseplayer.cpp, below RecvPropString( RECVINFO(m_szLastPlaceName) ), add:

		RecvPropBool( RECVINFO(m_bShouldDrawBloodOverlay) ),

Then in the constructor, below ListenForGameEvent( "base_player_teleported" );, add:

	m_bShouldDrawBloodOverlay = false;

player.h

In player.h, inside the public: section, add the following line:

	CNetworkVar( bool, m_bShouldDrawBloodOverlay ); // Have we been hit or have blood splatted on us?

player.cpp

In player.cpp, inside the constructor and below m_bitsDamageType = 0;, add:

	m_bShouldDrawBloodOverlay = false;

At the bottom of the InitialSpawn( void ) function, add:

	m_bShouldDrawBloodOverlay = false; // Reset blood overlay

Then inside IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer ), below SendPropString (SENDINFO(m_szLastPlaceName) ),, add:

	SendPropBool( SENDINFO(m_bShouldDrawBloodOverlay) ),

basecombatcharacter.cpp

In basecombatcharacter.cpp, inside the OnTakeDamage_Alive( const CTakeDamageInfo &info ) function above return 1;, add:

	// Handle the viewmodel blood splatter overlay effect here:
	if ( ( info.GetDamageType() & ( DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_BUCKSHOT ) ) )
	{
		CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
		if ( pPlayer == this )
			pPlayer->m_bShouldDrawBloodOverlay = true;

		pPlayer = ToBasePlayer( info.GetAttacker() );
		if ( pPlayer && ( this->BloodColor() != BLOOD_COLOR_MECH ) )
		{
			if ( pPlayer->GetAbsOrigin().DistTo( this->GetAbsOrigin() ) < 200.0f )
				pPlayer->m_bShouldDrawBloodOverlay = true;
		}
	}

The Materials

Viewmodel VMTs

Now you'll have to edit every single viewmodel .vmt to include the following snippet of code:

"VertexLitGeneric"
{
	...

	"$detail" "detail/blood_detail"
	"$detailblendmode" "2"
	"$detailblendfactor" "0.0"
	"$detailscale" "1.0"

	"Proxies"
	{
		"BloodyTexture"
		{
		}
	}
}

Notice how we're calling for materials/detail/blood_detail.vtf, this is the blood detail texture that gets placed on top of our viewmodel.

Conclusion

Now all that remains is making your own blood detail texture for this, but for convenience/testing sake, you can download the included placeholder texture found in the "External links" section down below.

And that's all there is to it, if set up right you should now be able to see the blood splatter texture appear on your viewmodel based on the snippet of code we placed into basecombatcharacter.cpp.

External links

Download BloodDetailTexture.rar - A placeholder blood detail texture by Ian B.