Blood spray on weapons and hands
Contents
Introduction
This is a tutorial for a simple material proxy system that we will use to switch viewmodel textures dependent on CQB.
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.
Requirements
- Beginner/Intermediate knowledge of C++.
- Source SDK 2007 engine branch (2013 compatible version in process of writing).
- 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.
Known Bugs
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.
Tutorial
Step 1: Programming
proxy_blood.cpp
On the client-side, create a file called proxy_blood.cpp:
#include "cbase.h"
#include <KeyValues.h>
#include "materialsystem/IMaterialVar.h"
#include "materialsystem/IMaterial.h"
#include "materialsystem/ITexture.h"
#include "materialsystem/IMaterialSystem.h"
#include "FunctionProxy.h"
#include "toolframework_client.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Forward declarations
void ToolFramework_RecordMaterialParams( IMaterial *pMaterial );
ConVar cl_viewmodel_blood( "cl_viewmodel_blood", "0" );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_BaseEntity::IsCoveredInBlood()
{
return cl_viewmodel_blood.GetBool();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_BaseEntity *C_BaseViewModel::GetCoveredBloodEntity()
{
C_BaseEntity *pWeapon = m_hWeapon.Get();
if ( pWeapon )
{
return pWeapon;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CViewmodelBlood_Proxy : public IMaterialProxy
{
public:
CViewmodelBlood_Proxy();
bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
void OnBind( void *pC_BaseEntity );
IMaterial *GetMaterial();
virtual void Release() { delete this; }
private:
IMaterialVar *m_pDetailBlendFactor;
IMaterial *m_pMaterial;
bool m_bPlayerBlood;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CViewmodelBlood_Proxy::CViewmodelBlood_Proxy()
{
m_pMaterial = NULL;
m_pDetailBlendFactor = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CViewmodelBlood_Proxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
m_pMaterial = pMaterial;
bool bFound;
m_pDetailBlendFactor = pMaterial->FindVar( "$detailblendfactor", &bFound );
if ( !bFound )
{
m_pDetailBlendFactor = NULL;
return false;
}
m_bPlayerBlood = pKeyValues->GetInt( "player", 0 ) > 0;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CViewmodelBlood_Proxy::OnBind( void *pRenderable )
{
IMaterial *pMaterial = GetMaterial();
if ( pMaterial )
{
if ( pRenderable )
{
IClientRenderable *pRend = (IClientRenderable *)pRenderable;
C_BaseEntity *pEnt = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEnt )
{
C_BaseEntity *pWeapon = pEnt->GetCoveredBloodEntity();
if ( pWeapon )
{
CBaseEntity *pTarget = m_bPlayerBlood ? pWeapon->GetOwnerEntity() : NULL;
if ( pTarget )
{
pWeapon = pTarget;
}
if ( pWeapon->IsCoveredInBlood() )
{
m_pDetailBlendFactor->SetFloatValue( 1.0f );
}
else
{
m_pDetailBlendFactor->SetFloatValue( 0.0f );
}
}
else
{
m_pDetailBlendFactor->SetFloatValue( 0.0f );
}
}
}
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
IMaterial *CViewmodelBlood_Proxy::GetMaterial()
{
return m_pMaterial;
}
EXPOSE_INTERFACE( CViewmodelBlood_Proxy, IMaterialProxy, "ViewmodelBloodProxy" IMATERIAL_PROXY_INTERFACE_VERSION );
c_baseentity.h
In c_baseentity.h, inside the public: section add the following lines:
virtual C_BaseEntity *GetCoveredBloodEntity() { return this; }
virtual bool IsCoveredInBlood();
Tutorial being made now...