Вам наверное всегда было интересно сделать динамический конус разброса как в Counter-Strike:Source ... Ну вот и пришло время этим заняться :) Для рассмотрения пусть будет weapon_357.cpp
Взглянув на класс, мы видим такую картину :
class CWeapon357 : public CBaseHLCombatWeapon { DECLARE_CLASS( CWeapon357, CBaseHLCombatWeapon ); public:
CWeapon357( void );
void PrimaryAttack( void ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
DECLARE_SERVERCLASS(); DECLARE_DATADESC(); };
Чтож, добавим сразу после Operator_HandleAnimEvent метод :
virtual const Vector& GetBulletSpread( void );
И проимплементурем его где-то ниже в cpp'шнике чтобы было вот так:
const Vector &CWeapon357::GetBulletSpread( void ) { // Lolmen : Берём указатель на игрока CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( !pPlayer ){ return vec3_origin; } // если он нулевой ничего не делаем
static Vector cone = vec3_origin; // Игрок стоит на земле if ( ( pPlayer->GetFlags() & FL_ONGROUND ) != 0 ) { if ( ( pPlayer->GetFlags() & FL_DUCKING ) != 0 ) { // игрок сидит cone = VECTOR_CONE_1DEGREES; } else { if ( pPlayer->GetAbsVelocity().Length2D() > 120 ) { // игрок двигается быстро cone = VECTOR_CONE_5DEGREES; } else { // Игрок медленно двигается или стоит cone = VECTOR_CONE_3DEGREES; } } } // Игрок в прыжке else { cone = VECTOR_CONE_6DEGREES; } return cone; }
Это отлично подходит для медло стрелокового типа оружия, что делать со скорострельным, например автомат или тому подобное? Тогда для примера возьмём SMG1 weapon_smg1.cpp
И увидим мы следующее :
class CWeaponSMG1 : public CHLSelectFireMachineGun { DECLARE_DATADESC(); public: DECLARE_CLASS( CWeaponSMG1, CHLSelectFireMachineGun );
CWeaponSMG1();
DECLARE_SERVERCLASS(); void Precache( void ); void AddViewKick( void ); void SecondaryAttack( void );
int GetMinBurst() { return 2; } int GetMaxBurst() { return 5; }
virtual void Equip( CBaseCombatCharacter *pOwner ); bool Reload( void );
float GetFireRate( void ) { return 0.075f; } // 13.3hz int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } int WeaponRangeAttack2Condition( float flDot, float flDist ); Activity GetPrimaryAttackActivity( void );
virtual const Vector& GetBulletSpread( void ) { static const Vector cone = VECTOR_CONE_5DEGREES; return cone; }
const WeaponProficiencyInfo_t *GetProficiencyValues();
void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { (...) // Троеточие подразумевает код который нам видить не надо }
DECLARE_ACTTABLE();
protected:
Vector m_vecTossVelocity; float m_flNextGrenadeCheck; };
Кстати, GetMinBurst() и GetMaxBurst() это минимальное и максимально кол-во выстрелов для NPC, т.е если вы хотите чтобы комбин пулял как бешеный например из weapon_pistol то добавьте эти методы если их нет, либо поменяйте значение, например 10-15 вместо 2.
Вернёмся к нашим апельсинам.
добавим после
extern ConVar sk_plr_dmg_smg1_grenade;
дефайн:
#define SMG_REACH_FULL_SPREAD_TIME 2.0f // Lolmen : время достижения максимального разброса
и после
int GetMaxBurst() { return 5; }
методы :
// Lolmen : Возвращает конус разброса от мин к макс по времени Vector GetDynamicSpread( const Vector &min, const Vector &max ); void UpdateAccuracy( void ); // обновляет точность void ItemPreFrame( void ); // пре кадровая функа void PrimaryAttack( void ); // видоизменим от базового класса
естественно не забудем про переменные и после переменной :
float m_flNextGrenadeCheck;
вставим такую вещь :
float m_flAccuracyPercent;
и естественно не забудем её продекларировать для SAVE/LOAD вещей.
DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
и после него впишем :
DEFINE_FIELD( m_flAccuracyPercent, FIELD_FLOAT ), // идёт как переменная с плавающей точкой
так. Теперь идём ниже (скроллим наш код) и начинаем после :
void CWeaponSMG1::AddViewKick( void ) { ... // опущено }
Писать такой вот аццкий код :
void CWeaponSMG1::ItemPreFrame( void ) { UpdateAccuracy(); BaseClass::ItemPreFrame(); } void CWeaponSMG1::UpdateAccuracy( void ) { // Lolmen : указатель на игрока CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( !pPlayer ){ return; } // Если игрока нету то ничего не делаем
// Проверка на разъедание накопленной неточности if ( !( pPlayer->m_nButtons & IN_ATTACK ) ) { m_flAccuracyPercent -= clamp( gpGlobals->frametime, 0.1f, 0.2f ); m_flAccuracyPercent = clamp( m_flAccuracyPercent, 0.0f, SMG_REACH_FULL_SPREAD_TIME ); } } void CWeaponSMG1::PrimaryAttack( void ) { BaseClass::PrimaryAttack();
// добавляем с каждой аттакой по процентной доле времени между выстрелами m_flAccuracyPercent += GetFireRate(); } Vector CWeaponSMG1::GetDynamicSpread( const Vector &min, const Vector &max ) { float ramp = RemapValClamped( m_flAccuracyPercent, 0.0f, SMG_REACH_FULL_SPREAD_TIME, 0.0f, 1.0f ); Vector ret; VectorLerp( min, max, ramp, ret ); return ret; }
Теперь найдём такой кусок кода :
bool CWeaponSMG1::Reload( void ) { bool fRet; float fCacheTime = m_flNextSecondaryAttack;
fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { // Undo whatever the reload process has done to our secondary // attack timer. We allow you to interrupt reloading to fire // a grenade. m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime;
WeaponSound( RELOAD ); }
return fRet; }
И видо изменим :
bool CWeaponSMG1::Reload( void ) { bool fRet; float fCacheTime = m_flNextSecondaryAttack;
fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { // Undo whatever the reload process has done to our secondary // attack timer. We allow you to interrupt reloading to fire // a grenade. m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime; // Lolmen : обнуляем щётчик m_flAccuracyPercent = 0.0f; WeaponSound( RELOAD ); }
return fRet; }
Теперь находим кусок в начале :
virtual const Vector& GetBulletSpread( void ) { static const Vector cone = VECTOR_CONE_5DEGREES; return cone; }
И начинаем знакомую по началу тутора процедуру :
virtual const Vector& GetBulletSpread( void ) { // Lolmen : Берём указатель на игрока CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( !pPlayer ){ return vec3_origin; } // если он нулевой ничего не делаем
static Vector cone = VECTOR_CONE_PRECALCULATED; // Игрок стоит на земле if ( ( pPlayer->GetFlags() & FL_ONGROUND ) != 0 ) { if ( ( pPlayer->GetFlags() & FL_DUCKING ) != 0 ) { // игрок сидит cone = GetDynamicSpread( VECTOR_CONE_1DEGREES, VECTOR_CONE_3DEGREES ); } else { if ( pPlayer->GetAbsVelocity().Length2D() > 120 ) { // игрок двигается быстро cone = GetDynamicSpread( VECTOR_CONE_5DEGREES, VECTOR_CONE_8DEGREES ); } else { // Игрок медленно двигается или стоит cone = GetDynamicSpread( VECTOR_CONE_3DEGREES, VECTOR_CONE_5DEGREES ); } } } // Игрок в прыжке else { cone = GetDynamicSpread( VECTOR_CONE_7DEGREES, VECTOR_CONE_10DEGREES ); }
return cone; }
Теперь, поэкспереминтировав с силой значений можно получить требуемый результат. главное что у GetDynamicSpread первый аргумент должен быть меньше второго но больше ноля...
Источник: http://wiki.hl2.ru/index.php?title=%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%A0%D0%B0 |