WarCraft 3: Последние шаги

[vJass] Создание moving system
Теперь осталось научиться добавлять тело в этот стек. Для этого в структуру body добавьте такой метод:
    // Помещаем тело в стек для обработки движком + добавляем защиту от переполнения
    method Start takes nothing returns boolean
      if mvs_si < 1000 then  
        set mvs_si = mvs_si + 1
        set mvs_Sb[mvs_si] = this
        return true
      else
        return false
      endif
    endmethod
Теперь можно создать тело, задать ему начальные параметры и поместить в движок с помощью этого метода.
Последний штрих – сделать пространство объемным. Для этого к z юнита прибавлять разницу высот. Тогда главная часть движка примет вид:
globals
    body array mvs_Sb [1000]
    integer mvs_si = -1 // при первой записи, элемент запишется в ячейку 0
    location GZL = Location(0,0) 
endglobals

    function GetZ takes real X, real Y returns real        
        call MoveLocation(GZL,X,Y)
        return GetLocationZ(GZL)
    endfunction
    
private function Engine takes nothing returns nothing
    local integer i = 0
    local body b
    loop
        exitwhen i > mvs_si
            if mvs_Sb__ != 0 then
                set b = mvs_Sb__ // для читаемости кода
                // 
                // Считаем ускорение тела и обнуляем силу, чтобы она не накапливалась
		if b.m > 0 then  //  для защиты системы от деления на нуль
                	set b.a.x = b.f.x/b.m
			set b.a.y = b.f.y/b.m
                	set b.a.z = b.f.z/b.m
		endif
                set b.f.x = 0
                set b.f.y = 0
                set b.f.z = 0
                // Теперь считаем скорость и перерасчитываем ее при изменении координат в точка/с
                set b.v.x = b.v.x + b.a.x
                set b.v.y = b.v.y + b.a.y
                set b.v.z = b.v.z + b.a.z
                // Добавление разности высот, чтобы сделать мир "объемным"
                call SetUnitFlyHeight(b.u,GetUnitFlyHeight(b.u) - GetZ(GetUnitX(b.u) + b.v.x*0.025,GetUnitY(b.u) + b.v.y*0.025) + GetZ(GetUnitX(b.u),GetUnitY(b.u)),0)
                // Финальная часть - передвигаем юнит
                call SetUnitX(b.u, GetUnitX(b.u) + b.v.x*0.025)
                call SetUnitY(b.u, GetUnitY(b.u) + b.v.y*0.025)
                call SetUnitFlyHeight(b.u, GetUnitFlyHeight(b.u) + b.v.z*0.025,0)
            else // Опа! Нашли удаленный с помощью body.Remove() элемент массива. Приговор: удалить и закрыть дырку последним элементом.
                set mvs_Sb__ = mvs_Sb[mvs_si]
                set mvs_Sb[mvs_si] = 0
                set mvs_si = mvs_si - 1
                set i = i - 1 // иначе тело, которым мы заткнули дырку, не обработается за это прохождение
				//  цикла, то есть пропустит квант времени
            endif
        set i = i + 1
    endloop
endfunction

function IniMove takes nothing returns nothing
        // нам таймер в глобалку сохранять незачем, так как он работает всю игру
        // периoд 0.025 выбран в результате практики, при 0.05 видны "рывки"
        call TimerStart(CreateTimer(), 0.025, true, function Engine)
endfunction

А в метод уничтожения тела корректируем вот так (при удалении в стеке все равно остается записано тело, точнее integer - номер ячейки где он хранился. ). На самом деле все переменные, ссылающиеся на структуру, являются типом integer, который есть номер ячейки массива, в котором вар хранит структуру:
    // уничтожение тела, когда оно больше не нужно
    method Destroy takes nothing returns nothing
    local integer i = 0
        loop // Ищем в массиве движка наше тело и убираем его от туда
            exitwhen i > mvs_si
                if mvs_Sb__ == this then
                    set mvs_Sb__ = mvs_Sb[mvs_si]
                    set mvs_Sb[mvs_si] = 0
                    set mvs_si = mvs_si - 1
                    set i = mvs_si // выход из цикла              
                endif
            set i = i + 1
        endloop
	// Удаление вложенных структур
        call this.v.destroy()
        call this.a.destroy()
        call this.f.destroy()
	// Удаление самой структуры
        call this.destroy()
    endmethod

Добавляем физические явления (удар об землю, трение и т.п.)

Теперь создадим отдел параметров системы для удобства настройки:
library MoveSys initializer IniMove

// Параметры системы, обычно их сюда помещают для удобства настройки системы
// private - переменная будет доступна только внутри библиотеки
globals
    private real g = 14 // Ускорение свободного падения
    private real Rest = 0.4 // Коэффициент восстановления после удара об землю
    private real mu = 0.4 // Коэффициент трения
endglobals

...

endlibrary
Советую всегда при создании систем выносить коэффициенты к заголовку библиотеки и давать пояснительные комментарии к ним. Это поможет разобраться в собственной системе через несколько месяцев.

Теперь удары об землю и границы карты:
private function Engine takes nothing returns nothing
    local integer i = 0
    local body b
    local real x
    local real y
    loop
        exitwhen i > mvs_si
            if mvs_Sb__ != 0 then
                set b = mvs_Sb__ // для читаемости кода
                // Удар об землю
                if GetUnitFlyHeight(b.u) < 2 then
                    if b.v.z < 0 then
                        set b.v.z = - b.v.z*Rest
                    endif
                endif
                // Границы карты
		set x = GetUnitX(b.u)
		set y = GetUnitY(b.u)
                if x < minX then
                    call SetUnitX(b.u,minX)
                elseif x > maxX then
                    call SetUnitX(b.u,maxX)
                endif
                if y < minY then
                    call SetUnitY(b.u,minY)
                elseif y > maxY then
                    call SetUnitY(b.u,maxY)
                endif
                // Считаем ускорение тела и обнуляем силу, чтобы она не накапливалась
                if b.m > 0 then  //  для защиты системы от деления на нуль
                	set b.a.x = b.f.x/b.m
                    	set b.a.y = b.f.y/b.m
                	set b.a.z = b.f.z/b.m - g // на этой оси еще действует mg
		endif
		...
globals
	private real maxX
	private real maxY
	private real minX
	private real minY
endglobasl

function IniMove takes nothing returns nothing
        // нам таймер в глобалку сохранять незачем, так как он работает всю игру
        // периoд 0.025 выбран в результате практики, при 0.05 видны "рывки"
        call TimerStart(CreateTimer(), 0.025, true, function Engine)
	set maxX =  GetRectMaxX(bj_mapInitialPlayableArea) 
	set maxY =  GetRectMaxY(bj_mapInitialPlayableArea) 
	set minX =  GetRectMinX(bj_mapInitialPlayableArea) 
	set minY =  GetRectMinY(bj_mapInitialPlayableArea) 
endfunction


Сейчас добавим сухое трение:
Принцип такой, если на тело действует сила больше mu*(mg-Fz), тогда вычитаем из вектора силы тела силу трения, иначе отнимаем от скорости эту величину деленную на массу:
private function Engine takes nothing returns nothing
    local integer i = 0
    local body b
    local real x
    local real y
    loop
        exitwhen i > mvs_si
            if mvs_Sb__ != 0 then
                set b = mvs_Sb__ // для читаемости кода
                // Удар об землю
                if GetUnitFlyHeight(b.u) < 2 then
                    if b.v.z < 0 then
                        set b.v.z = - b.v.z*Rest
                    endif
                    // Обсчитываем трение
                    if b.m > 0 then
                            if SquareRoot(b.f.x*b.f.x + b.f.y*b.f.y) > mu*(b.m*g - b.f.z) then // силы трения недостаточно, чтобы останавливать тело
                                set b.f.x = b.f.x - b.f.x*mu*(b.m*g - b.f.z)/b.f.Module()
                                set b.f.y = b.f.y - b.f.y*mu*(b.m*g - b.f.z)/b.f.Module() 
                            elseif SquareRoot(b.v.x*b.v.x + b.v.y*b.v.y) < RAbsBJ(mu*(b.m*g - b.f.z))/b.m then // остановка тела
                                    set b.v.x = 0
                                    set b.v.y = 0
                            else // отнимаем от скорости
                                    if RAbsBJ(b.v.x) > 0 then
                                        set b.v.x = b.v.x - b.v.x*mu*(b.m*g - b.f.z)/(b.v.Module()*b.m)
                                    endif    
                                    if RAbsBJ(b.v.y) > 0 then
                                        set b.v.y = b.v.y - b.v.y*mu*(b.m*g - b.f.z)/(b.v.Module()*b.m)
                                    endif
                            endif
                    endif
                endif
                // Границы карты
		set x = GetUnitX(b.u)
		set y = GetUnitY(b.u)
                if x < minX then
                    call SetUnitX(b.u,minX)
                elseif x > maxX then
                    call SetUnitX(b.u,maxX)
                endif
                if y < minY then
                    call SetUnitY(b.u,minY)
                elseif y > maxY then
                    call SetUnitY(b.u,maxY)
                endif
		...

Заключение

Скелет системы готов, теперь на него можно наращивать остальные части, например: притяжение к земле, трение, столкновения с другими телами. В карте примере находится эта система, но уже с сухим трением и притяжением к земле. Для примера я сделал на основе этой системы способность «Прыжок», чтобы показать систему в действии. Теперь, освоив эту статью, вы можете легко делать реалистичные заклинания. Идеи черпал из моей физической библиотеки «Fisics Dynamik System», по сути, это та же система, но усложненная.

Просмотров: 2 746

Комментарии пока отсутcтвуют