Система стрельбы
Демонстрация
С помощью WASD передвигайте персонажа. С помощью ЛКМ производите срельбу. С помощью ПКМ прицеливаетесь. С помощью ножа режете
Мультиплеер
+ Мультиплеерное
+ Количество урона, скорости передвижения, скорости полёта снаряда, дистанция полёта снаряда и скорострельность настраиваются в редакторе объектов
+ Динамическая камера
+ Учитывается высота противников для стрельбы, на которых направлена мышка
+ Отсутствие движения мышки учитывается при ходьбе
+ Прицеливание уменьшает разброс пуль
- Не изменяется положение туловища и ног по Pitch'y
Предыстория
Я редко встречаю карты с системами стрельбы через мышку и на WASD'e, тем более мультиплеерные и тем более на ванилле (я и не ищу их, я ищу счастье, гармонию от слияния с бесконечно вечным)
Референс для этой наработки взят из Морозко против Зла, где подобное было выполнено горекриво на мемхаке и с отсутствием мультиплеера. Систему можно самостоятельно очень сильно развить, добавив на тот же пробел кувырок/рывок/телепорт/прыжок/джетпак персонажа, на ПКМ атаку ближнего боя, на Q/E смену оружия, которое можно самостоятельно сделать абсолютно различными вариациями, ну и так далее. Я не делал ориентацию туловища/ног для этой наработки, поскольку есть 2 варианта это сделать:
- SetUnitLookAt - старый, рабочий костыль, требующий даммика в позиции курсора.
- UnitEnableAutoOrientation + SetUnitOrientation - нативки UjAPI, требует сглаживания. Варкрафтовское изменение ориентации работать больше не будет, GetUnitFacing нужно заменять на GetUnitYaw.
Инструкция по импорту
- Работает исключительно на UjAPI, поэтому вам необходимо установить лаунчер
Скопировать папку Initialization и закинуть в свою карту вместе со всем импортом
Триггер Sound нужен для позиционированных звуков стрельбы и взят с уже существующей наработки 3д звук
В триггере ShooterSystem указать на 324/325 строках равкод туловища/ног персонажа, на строке 258 указать радиус пули, на строках 247-253 указать разброс пуль при свободной стрельбе и прицеливании, на строках 178, 179 и 214 указать во втором аргументе функции индекс анимации ходьбы. Всё остальное требует минимальных знаний джасса
если что-то не понятно - комменты
Код
У меня нет настроения расписывать какое это говно, так что просто представьте, что не представьте, первый раз космос погибнет в уме - второй раз в сердце, мир это лишь твой проектор вне, спасибо за непонимание
AllGlobals
library AllGlobalsLib initializer OnInit
globals
constant hashtable H = InitHashtable( )
unit array PlayerUnitUnderCursor
real array PlayerMouseWorldX
real array PlayerMouseWorldY
real array PlayerMouseWorldZ
real array PlayerMouseWorldXLast
real array PlayerMouseWorldYLast
real MaxX
real MinX
real MaxY
real MinY
endglobals
function AngleDifference takes real a, real a1 returns real
set a = RAbsBJ( a - a1 )
if a > 180.00 then
set a = 360.00 - a
endif
return a
endfunction
function SetUnitPositionSmooth takes unit source, real x, real y returns nothing
local real last_x = GetUnitX(source)
local real last_y = GetUnitY(source)
local boolean bx
local boolean by
call SetUnitPosition(source, x, y)
if (RAbsBJ(GetUnitX(source) - x) > 0.5) or (RAbsBJ(GetUnitY(source) - y) > 0.5) then
call SetUnitPosition(source, x, last_y)
set bx = RAbsBJ(GetUnitX(source) - x) <= 0.5
call SetUnitPosition(source, last_x, y)
set by = RAbsBJ(GetUnitY(source) - y) <= 0.5
if bx then
call SetUnitPosition(source, x, last_y)
elseif by then
call SetUnitPosition(source, last_x, y)
else
call SetUnitPosition(source, last_x, last_y)
endif
endif
endfunction
function SetUnitPositionEX takes unit u, real x, real y returns nothing
if x > MaxX then
set x = MaxX
elseif x < MinX then
set x = MinX
endif
if y > MaxY then
set y = MaxY
elseif y < MinY then
set y = MinY
endif
call SetUnitX( u, x )
call SetUnitY( u, y )
endfunction
function CreateUnitEx takes player id, integer unitid, real x, real y, real face returns unit
set bj_lastCreatedUnit = CreateUnit( id, unitid, x, y, face )
call SetUnitPositionEX( bj_lastCreatedUnit, x, y )
return bj_lastCreatedUnit
endfunction
function TrackUnitUnderCursor takes nothing returns nothing
set PlayerUnitUnderCursor[ GetPlayerId( GetTriggerPlayer( ) ) ] = GetTriggerUnit( )
endfunction
function TrackMouseWorldXYZ takes nothing returns nothing
local integer i = GetPlayerId( GetTriggerPlayer( ) )
set PlayerMouseWorldX[i] = GetTriggerPlayerMouseWorldX( )
set PlayerMouseWorldY[i] = GetTriggerPlayerMouseWorldY( )
set PlayerMouseWorldZ[i] = GetTriggerPlayerMouseWorldZ( )
endfunction
private function OnInit takes nothing returns nothing
local trigger trg = CreateTrigger( )
local trigger ttg = CreateTrigger( )
local integer i = 0
local rect r = GetWorldBounds( )
call SetMouseMoveEventWorldAxisEnabled( true )
call SetMouseMoveEventScreenAxisEnabled( false )
loop
call TriggerRegisterPlayerEvent( trg, Player( i ), EVENT_PLAYER_MOUSE_MOVE )
call TriggerRegisterPlayerEvent( ttg, Player( i ), EVENT_PLAYER_WIDGET_TRACK )
set i = i + 1
exitwhen i >= bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction( trg, function TrackMouseWorldXYZ )
call TriggerAddAction( ttg, function TrackUnitUnderCursor )
set MaxX = GetRectMaxX( r )
set MinX = GetRectMinX( r )
set MaxY = GetRectMaxY( r )
set MinY = GetRectMinY( r )
call RemoveRect( r )
set r = null
set trg = null
set ttg = null
endfunction
endlibrary
//===========================================================================
//function InitTrig_AllGlobals takes nothing returns nothing
//set gg_trg_AllGlobals = CreateTrigger( )
//endfunction
ShooterSystem
library ShooterSystem initializer OnInit requires SoundLib
globals
private constant timer CameraMotionTimer = CreateTimer( )
private constant real TimerPeriodic = 0.02125
private constant group TempGroup = CreateGroup( )
private timer array TempTimer
private boolean array BUTTON_PRESSED[195][12]
private boolean array IsMoved
private string array BulletModel
private real array ReloadTime
unit array CharacterBody
unit array CharacterLegs
endglobals
private struct vector
real x
real y
real z
method length takes nothing returns real
return SquareRoot( x * x + y * y + z * z )
endmethod
method normalize takes nothing returns nothing
local real l = length( )
if l == 0.00 then
set l = 1.00
endif
set x = x / l
set y = y / l
set z = z / l
endmethod
static method create takes real x, real y, real z returns thistype
local thistype this = thistype.allocate( )
set this.x = x
set this.y = y
set this.z = z
return this
endmethod
endstruct
private struct bulletS
vector v
vector p
real speed
real damage
real radius
real distance
effect bullet
player pl
timer t
method Destroy takes nothing returns nothing
call PauseTimer( t )
call FlushChildHashtable( H, GetHandleId( t ) )
call DestroyTimer( t )
call DestroyEffect( bullet )
set t = null
set bullet = null
call p.destroy( )
call v.destroy( )
call destroy( )
endmethod
static method move takes nothing returns nothing
local thistype this = LoadInteger( H, GetHandleId( GetExpiredTimer( ) ), 0 )
local unit u
if this.distance < this.speed then
set this.speed = this.distance
endif
set this.p.x = this.p.x + this.speed * this.v.x
set this.p.y = this.p.y + this.speed * this.v.y
set this.p.z = this.p.z + this.speed * this.v.z
set this.distance = this.distance - this.speed
call SetSpecialEffectPositionWithZ( this.bullet, this.p.x, this.p.y, this.p.z )
call GroupEnumUnitsInRange( TempGroup, this.p.x, this.p.y, this.radius + 200.00, null )
loop
set u = FirstOfGroup( TempGroup )
exitwhen u == null
call GroupRemoveUnit( TempGroup, u )
if IsUnitInRangeXY( u, this.p.x, this.p.y, this.radius ) then
if IsUnitAlive( u ) and IsUnitEnemy( u, this.pl ) then
if RAbsBJ( GetUnitZ( u ) - this.p.z ) <= this.radius + GetUnitRealField( u, UNIT_RF_COLLISION_SIZE ) * 2.00 then
call UnitDamageTarget( CharacterBody[ GetPlayerId( this.pl ) ], u, this.damage, false, false, null, null, null )
call GroupClear( TempGroup )
set this.distance = 0.00
endif
endif
endif
endloop
if this.distance <= 0.00 or this.p.z <= GetAxisZ( this.p.x, this.p.y ) then
call this.Destroy( )
endif
endmethod
method launch takes nothing returns nothing
set t = CreateTimer( )
call SaveInteger( H, GetHandleId( t ), 0, this )
call TimerStart( t, TimerPeriodic, true, function thistype.move )
endmethod
endstruct
private function CheckMoves takes nothing returns nothing
local integer i = LoadInteger( H, GetHandleId( GetExpiredTimer( ) ), 0 )
local integer j = 0
local real a = 0.00
local real s = 0.00
local real k
local real d
local bulletS A
if BUTTON_PRESSED[ GetHandleId( OSKEY_W ) ][i] then
set a = a + 90.00
set j = j + 1
elseif BUTTON_PRESSED[ GetHandleId( OSKEY_S ) ][i] then
set a = a + 270.00
set j = j + 1
endif
if BUTTON_PRESSED[ GetHandleId( OSKEY_A ) ][i] then
set a = a + 180.00
set j = j + 1
elseif BUTTON_PRESSED[ GetHandleId( OSKEY_D ) ][i] then
set a = a + 360.00
if a == 450.00 then
set a = a - 360.00
endif
set j = j + 1
endif
if j != 0 then
set a = a / j
set s = GetUnitMoveSpeed( CharacterBody[i] ) * 0.01
set d = GetUnitFacing( CharacterBody[i] )
set k = AngleDifference( a, d )
if k >= 95.00 then
if k >= 135.00 then
set s = s * 0.85
else
set s = s * 0.75
endif
call SetUnitTimeScale( CharacterBody[i], -1.00 )
call SetUnitTimeScale( CharacterLegs[i], -1.00 )
call SetUnitFacingInstant( CharacterLegs[i], d )
else
call SetUnitTimeScale( CharacterBody[i], 1.00 )
call SetUnitTimeScale( CharacterLegs[i], 1.00 )
call SetUnitFacing( CharacterLegs[i], a )
endif
call SetUnitPositionSmooth( CharacterBody[i], GetUnitX( CharacterBody[i] ) + s * MathCosDeg( a ), GetUnitY( CharacterBody[i] ) + s * MathSinDeg( a ) )
call SetUnitX( CharacterLegs[i], GetUnitX( CharacterBody[i] ) )
call SetUnitY( CharacterLegs[i], GetUnitY( CharacterBody[i] ) )
if not IsMoved[i] then
set IsMoved[i] = true
call SetUnitAnimationByIndex( CharacterBody[i], 7 )
call SetUnitAnimationByIndex( CharacterLegs[i], 7 )
endif
else
call SetUnitFacing( CharacterLegs[i], GetUnitFacing( CharacterBody[i] ) )
if IsMoved[i] then
set IsMoved[i] = false
call SetUnitTimeScale( CharacterBody[i], 1.00 )
call SetUnitTimeScale( CharacterLegs[i], 1.00 )
call SetUnitAnimation( CharacterBody[i], "stand" )
call SetUnitAnimation( CharacterLegs[i], "stand" )
endif
endif
if PlayerMouseWorldXLast[i] == PlayerMouseWorldX[i] then
set PlayerMouseWorldX[i] = PlayerMouseWorldX[i] + s * MathCosDeg( a )
endif
if PlayerMouseWorldYLast[i] == PlayerMouseWorldY[i] then
set PlayerMouseWorldY[i] = PlayerMouseWorldY[i] + s * MathSinDeg( a )
endif
set PlayerMouseWorldXLast[i] = PlayerMouseWorldX[i]
set PlayerMouseWorldYLast[i] = PlayerMouseWorldY[i]
if ReloadTime[i] > 0.00 then
set ReloadTime[i] = ReloadTime[i] - 0.01
elseif BUTTON_PRESSED[ GetHandleId( OSKEY_LBUTTON ) ][i] then
set ReloadTime[i] = GetUnitWeaponRealField( CharacterBody[i], UNIT_WEAPON_RF_ATTACK_BASE_COOLDOWN, 0 )
call SetUnitAnimation( CharacterBody[i], "attack" )
call QueueUnitAnimation( CharacterBody[i], "ready" )
if IsMoved[i] then
call QueueWidgetAnimationByIndex( CharacterBody[i], 7 )
else
call QueueUnitAnimation( CharacterBody[i], "stand" )
endif
set A = bulletS.create( )
set A.p = vector.create( GetUnitX( CharacterBody[i] ), GetUnitY( CharacterBody[i] ), GetUnitZ( CharacterBody[i] ) + 60.00 )
if PlayerUnitUnderCursor[i] != null and PlayerUnitUnderCursor[i] != CharacterBody[i] then
if GetUnitZ( PlayerUnitUnderCursor[i] ) - 60.00 > GetUnitZ( CharacterBody[i] ) then
set A.v = vector.create( GetUnitX( PlayerUnitUnderCursor[i] ) - A.p.x, GetUnitY( PlayerUnitUnderCursor[i] ) - A.p.y, GetUnitZ( PlayerUnitUnderCursor[i] ) - A.p.z )
else
set A.v = vector.create( GetUnitX( PlayerUnitUnderCursor[i] ) - A.p.x, GetUnitY( PlayerUnitUnderCursor[i] ) - A.p.y, GetUnitZ( PlayerUnitUnderCursor[i] ) + 60.00 - A.p.z )
endif
else
set A.v = vector.create( PlayerMouseWorldX[i] - A.p.x, PlayerMouseWorldY[i] - A.p.y, PlayerMouseWorldZ[i] + 60.00 - A.p.z )
endif
set A.pl = Player( i )
set A.bullet = AddSpecialEffect( BulletModel[ GetRandomInt( 0, 5 ) ], A.p.x, A.p.y )
call SetSpecialEffectZ( A.bullet, A.p.z )
call SetSpecialEffectOrientation( A.bullet, Atan2( A.v.y, A.v.x ) * bj_RADTODEG, Atan2( A.v.z, A.v.length( ) ) * bj_RADTODEG, 0.00 )
set bj_lastCreatedEffect = AddSpecialEffect( "confetti_up_small.mdx", A.p.x, A.p.y )
call SetSpecialEffectZ( bj_lastCreatedEffect, A.p.z )
call SetSpecialEffectOrientation( bj_lastCreatedEffect, GetSpecialEffectYaw( A.bullet ), GetSpecialEffectPitch( A.bullet ) - 80.00, 0.00 )
call DestroyEffect( bj_lastCreatedEffect )
call A.v.normalize( )
if BUTTON_PRESSED[ GetHandleId( OSKEY_RBUTTON ) ][i] then
set A.v.x = A.v.x + GetRandomReal( -0.04, 0.04 )
set A.v.y = A.v.y + GetRandomReal( -0.04, 0.04 )
set A.v.z = A.v.z + GetRandomReal( -0.01, 0.01 )
else
set A.v.x = A.v.x + GetRandomReal( -0.08, 0.08 )
set A.v.y = A.v.y + GetRandomReal( -0.08, 0.08 )
set A.v.z = A.v.z + GetRandomReal( -0.03, 0.03 )
endif
set A.speed = GetUnitWeaponRealField( CharacterBody[i], UNIT_WEAPON_RF_ATTACK_PROJECTILE_SPEED, 0 ) * TimerPeriodic
set A.damage = GetRandomInt( GetUnitWeaponIntegerField( CharacterBody[i], UNIT_WEAPON_IF_ATTACK_DAMAGE_BASE_MINIMUM, 0 ), GetUnitWeaponIntegerField( CharacterBody[i], UNIT_WEAPON_IF_ATTACK_DAMAGE_BASE_MAXIMUM, 0 ) ) + GetUnitWeaponIntegerField( CharacterBody[i], UNIT_WEAPON_IF_ATTACK_DAMAGE_BONUS, 0 )
set A.radius = 32.00
set A.distance = GetUnitWeaponRealField( CharacterBody[i], UNIT_WEAPON_RF_ATTACK_RANGE, 0 )
call Sound.playPoint( "groza_shoot" + I2S( GetRandomInt( 0, 1 ) ) + ".wav", A.p.x, A.p.y, 600.00, 3000.00, GetRandomInt( 60, 80 ) )
call Sound.playPoint( "sleigh bells" + I2S( GetRandomInt( 0, 3 ) ) + ".wav", A.p.x, A.p.y, 600.00, 3000.00, GetRandomInt( 60, 80 ) )
call A.launch( )
endif
call SetUnitFacing( CharacterBody[i], MathAngleBetweenPoints( GetUnitX( CharacterBody[i] ), GetUnitY( CharacterBody[i] ), PlayerMouseWorldX[i], PlayerMouseWorldY[i] ) )
endfunction
private function CharacterMove takes nothing returns nothing
set BUTTON_PRESSED[ GetHandleId( GetTriggerPlayerKey( ) ) ][ GetPlayerId( GetTriggerPlayer( ) ) ] = GetTriggerPlayerIsKeyDown( )
endfunction
private function SetCameraMotion takes nothing returns nothing
local integer i = GetPlayerId( GetLocalPlayer( ) )
local real wh = GetWindowHeight( )
local real ww = GetWindowWidth( )
local real x = ww * ( GetMouseScreenX( ) / 0.80 )
local real y = wh * ( GetMouseScreenY( ) / 0.60 )
local real a = Atan2( y - wh / 2.00, x - ww / 2.00 )
local real d
if BUTTON_PRESSED[ GetHandleId( OSKEY_RBUTTON ) ][i] then
set d = SquareRoot( ( x - ww / 2.00 ) * ( x - ww / 2.00 ) + ( y - wh / 2.00 ) * ( y - wh / 2.00 ) ) * 1.50
call SetCameraField( CAMERA_FIELD_TARGET_DISTANCE, 1850.00 + ( GetAxisZ( x, y ) + GetUnitFlyHeight( CharacterBody[i] ) ) * 0.50, 0.10 )
else
set d = RMinBJ( SquareRoot( ( x - ww / 2.00 ) * ( x - ww / 2.00 ) + ( y - wh / 2.00 ) * ( y - wh / 2.00 ) ) * 0.75, 100.00 + 300.00 * ww / 1920.00 )
call SetCameraField( CAMERA_FIELD_TARGET_DISTANCE, 2000.00 + ( GetAxisZ( x, y ) + GetUnitFlyHeight( CharacterBody[i] ) ) * 0.50, 0.10 )
endif
set x = GetUnitX( CharacterBody[i] )
set y = GetUnitY( CharacterBody[i] )
call PanCameraToTimed( x + d * Cos( a ), y + d * Sin( a ), 0.01 )
endfunction
private function OnInit takes nothing returns nothing
local trigger trgWASD = CreateTrigger( )
local integer i = 0
local player p
loop
set p = Player( i )
if GetPlayerController( p ) == MAP_CONTROL_USER and GetPlayerSlotState( p ) == PLAYER_SLOT_STATE_PLAYING then
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_W, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_A, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_S, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_D, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_W, META_KEY_NONE, false )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_A, META_KEY_NONE, false )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_S, META_KEY_NONE, false )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_D, META_KEY_NONE, false )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_LBUTTON, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_RBUTTON, META_KEY_NONE, true )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_LBUTTON, META_KEY_NONE, false )
call TriggerRegisterPlayerKeyEvent( trgWASD, p, OSKEY_RBUTTON, META_KEY_NONE, false )
// створення персонажу
set CharacterBody[i] = CreateUnitEx( p, 'h000', 0.00, 0.00, 0.00 )
set CharacterLegs[i] = CreateUnitEx( p, 'h001', 0.00, 0.00, 0.00 )
call AddSpecialEffectTarget( "autogun.mdx", CharacterBody[i], "hand right ref" )
call SetUnitPathing( CharacterLegs[i], false )
set TempTimer[i] = CreateTimer( )
call SaveInteger( H, GetHandleId( TempTimer[i] ), 0, i )
call TimerStart( TempTimer[i], 0.01, true, function CheckMoves )
set IsMoved[i] = false
endif
set i = i + 1
exitwhen i >= bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction( trgWASD, function CharacterMove )
call TimerStart( CameraMotionTimer, 0.01, true, function SetCameraMotion )
call EnableSelect( false, false )
call EnablePreSelect( false, false )
call EnableDragSelect( false, false )
set BulletModel[0] = "Shot Blue.mdx"
set BulletModel[1] = "Shot Green.mdx"
set BulletModel[2] = "Shot Orange.mdx"
set BulletModel[3] = "Shot Purple.mdx"
set BulletModel[4] = "Shot Red.mdx"
set BulletModel[5] = "Shot Yellow.mdx"
set trgWASD = null
endfunction
endlibrary
//===========================================================================
//function InitTrig_ShooterSystem takes nothing returns nothing
//set gg_trg_ShooterSystem = CreateTrigger( )
//endfunction
выстрелить себе в голоя обязательно не исправлюсь