- 17
- 475
Оффтоп: Давно меня просили написать гайд : "Как писать скрипты", простите я не напишу этот гайд - т.к. нужно сначала гайд "Как научится программировать".
Для начала полезные ссылки, которые нам пригодятся во время написания\тестирования:
А там на самом деле очень важные правила, которые нужно будет соблюдать - чтобы ваши скрипты работали и при этом не кушали такой важный для пользователей FPS.
Самое важное правило - Используйте таймеры, если вам не нужно 30-50 раз в секунду (а именно столько вызывается OnUpdate) что-то рассчитывать своим скриптом или проверять - то делаем проверку по времени.
Когда это нужно делать:
Следующее правило - Локализуйте Переменные, вызовы, списки - кешируйте данные!!! (Тесты 1, 8, 11, 12, 13)
В тестах конечно же хорошие примеры, но я Вам приведу рабочие примеры:
Тема на жесткой модерации, не пишите бреда, для благодарности - есть кнопка, если есть серьезные замечания - пишите конкретно и подробно, ну или если есть что добавить по оптимизации - аналогично.
Вот подъехал и отличный пример
Минимальные изменения на быструю руку КАК НУЖНО было написать.
Проверка оптимизации вашего скрипта:
В логе при включении будут данные по затратам в мс на каждый апдейт.
Для хорошего(компьютера 150+ фпс):
Маленькие скрипты - AVGTime=1, ScriptTime=1-5, Update=0-3
Средние (скрипт героя, универсальный большой) - AVGTime=<15, ScriptTime=<50, Update=<35
Большие (несколько скриптов в одном FAIO) - AVGTime=<50, ScriptTime=<150, Update=<100
Если хотите по практиковаться писать скрипты, или игры:
Для начала полезные ссылки, которые нам пригодятся во время написания\тестирования:
-
You must be registered for see links- все что доступно из встроенных функций DOTA 2, подглядывайте в скрипты других людей, которые я буду рекомендовать.
-
You must be registered for see links- все что доступно из самого LUA, тут также всегда можно просить помощь у Google.
-
You must be registered for see links- тут ищемYou must be registered for see links,You must be registered for see links, если нужно будет добавлять картинки\частицы.
-
You must be registered for see links- сюда обращаемся при синтаксических ошибках - в логе это "try call a string value".
You must be registered for see links
.А там на самом деле очень важные правила, которые нужно будет соблюдать - чтобы ваши скрипты работали и при этом не кушали такой важный для пользователей FPS.
Самое важное правило - Используйте таймеры, если вам не нужно 30-50 раз в секунду (а именно столько вызывается OnUpdate) что-то рассчитывать своим скриптом или проверять - то делаем проверку по времени.
Код:
--Объявляем таймеры и выставляем время обновления.
InfoScreen.EnemyTeamStatus.Timer = 0;
InfoScreen.EnemyTeamStatus.TimerInterval = 0.5;
--.. тут ваш код функций...
function InfoScreen.EnemyTeamStatus.OnUpdate()
if Menu.IsEnabled(InfoScreen.Menu.EnemyTeamStatus)
and Engine.IsInGame()
and (InfoScreen.EnemyTeamStatus.Timer <= GameTime)
then
InfoScreen.EnemyTeamStatus.Timer = GameTime + InfoScreen.EnemyTeamStatus.TimerInterval;
--полезный код ниже
end;
end;
--вызов самой функции
function InfoScreen.OnUpdate()
--обновляем время в локальную переменную об этом ниже
GameTime = GameRules.GetGameTime();
InfoScreen.Menu.LoadSettings();--вызывается раз в 0.5 не часто в меню люди меняют что-то
InfoScreen.EnemyTeamStatus.OnUpdate();--обновляем ВСЕХ вражеских героев раз в 0.3 для записи всех кд и статов НР\МР и т.д. чаще не нужно
end;
- Обновление ваших списков (0.2-0.5) - предметы, способности, обновление меню, пере расчеты относительно экрана(разрешения), другие вещи не часто меняющиеся со временем, еще какие-то вещи не сильно привязанные к СКОРОСТИ использования пользователем.
- При рисовании (статику рисуем в OnUpdate, динамику в OnDraw), когда настройки висят на кнопке (другой инпут от пользователя), когда нужно выполнить действие мгновенно (допустим рассчитали позицию и ждем когда можно дать санстрайк\хук или другой спел, но только когда это действительно нужно)
Код:
function InfoScreen.ManaBar.OnDraw()
if User and InfoScreen.Menu.ManaBar or InfoScreen.Menu.HealthText and Engine.IsInGame() then
for heroent, EnemyHero in pairs(EnemyHeroes) do
if EnemyHero.Valid then
local origin = Entity.GetAbsOrigin(heroent);
local HBO = EnemyHero.HBO;
origin:SetZ(origin:GetZ() + HBO);
local hx, hy = Renderer.WorldToScreen(origin);
if Renderer.IsOnScreen(hx, hy) and InfoScreen.Menu.ManaBar then
if (InfoScreen.Renderer.XBarOffset == 0) or (InfoScreen.Renderer.YBarOffset == 0) then
InfoScreen.Renderer.CalcPanelSize();
end;
local MP = ceil(EnemyHero.MP);
local MaxMP = ceil(EnemyHero.MaxMP);
local MPText = MP.."/"..MaxMP;
Renderer.DrawHPBar(hx + InfoScreen.Renderer.XBarOffset, hy + InfoScreen.Renderer.YBarOffset, InfoScreen.Renderer.HPBarWidth, InfoScreen.Renderer.HPBarHeight, {79, 120, 250, 255}, (MP / MaxMP), MPText, -2);
end;
if Renderer.IsOnScreen(hx, hy) and Menu.IsEnabled(InfoScreen.Menu.HealthText) then
local HP = ceil(EnemyHero.HP);
local MaxHP = ceil(EnemyHero.MaxHP);
local HPText = HP.."/"..MaxHP;
Renderer.SetDrawColor(222, 222, 222, 255);
local w, h = Renderer.GetTextSize(InfoScreen.Renderer.Font, HPText);
Renderer.DrawTextAlign(InfoScreen.Renderer.Font, hx + InfoScreen.Renderer.XBarOffset,
hy + InfoScreen.Renderer.YBarOffset + InfoScreen.Renderer.HPBarHeight, InfoScreen.Renderer.HPBarWidth, InfoScreen.Renderer.HPBarHeight, HPText, 0, 0);
end;
end;
end;
end;
end;
Следующее правило - Локализуйте Переменные, вызовы, списки - кешируйте данные!!! (Тесты 1, 8, 11, 12, 13)
В тестах конечно же хорошие примеры, но я Вам приведу рабочие примеры:
- Выше уже видели локальную переменную GameTime, если у Вас вызовов функции больше 5-10 то нужно ОБЯЗАТЕЛЬНО локализовать их в одну переменную.
Код:
--наиболее часто встречающейся вещи local GameTime = 0;--функция в переменную local User = nil;--объект\значение local ceil = math.ceil;--локализация функции local floor = math.floor;--локализация функции --проверка большого меню function InfoScreen.Menu.LoadSettings() InfoScreen.ItemPanel.EnabledSide = Menu.IsEnabled(InfoScreen.Menu.ItemPanel.EnabledSide); InfoScreen.ItemPanel.HeroPanel = Menu.IsEnabled(InfoScreen.Menu.ItemPanel.HeroPanel); end;
- НЕ ИСПОЛЬЗУЙТЕ
You must be registered for see linksВООБЩЕ, лучше объявите ее и передайте\вызовите их!
- Локализация\Кеширование объектов\таблиц объектов, чаще всего это LocalHero и все наши параметры и вражеские герои.
Код:
--подобные функции можно сделать для любых обьектов которые вы используете и ТОЛЬКО ТЕ ДАННЫЕ КОТОРЫЕ ВАМ НУЖНЫ function UpdateHeroInfo(object, entity) if (entity and (entity ~= 0)) then if not object then object = {}; object.LastUpdateTime = 0; object.Entity = entity; --static properties ЭТИ ДАННЫЕ НЕ МЕНЯЮТСЯ СО ВРЕМЕНЕМ object.Name = NPC.GetUnitName(entity); object.IsRanged = NPC.IsRanged(entity); object.TeamNum = Entity.GetTeamNum(entity); end; object.LastUpdateTime = GameTime; --получем\нужны ли нам данные object.Visible = NPC.IsVisible(entity); object.Alive = Entity.IsAlive(entity); object.Valid = (object.Visible and object.Alive); if object.Valid then object.HP = Entity.GetHealth(entity); object.MaxHP = Entity.GetMaxHealth(entity); object.MP = NPC.GetMana(entity); object.MaxMP = NPC.GetMaxMana(entity); object.Damage = NPC.GetTrueDamage(entity); object.Level = NPC.GetCurrentLevel(entity); end; return true; end; end; UpdateHeroInfo(User,Heroes.GetLocal()); --Далее в функциях используем function InfoScreen.OnUpdate() GameTime = GameRules.GetGameTime(); if ((GameTime - User.LastUpdateTime) > User.UpdateTime) then UpdateHeroInfo(User,Heroes.GetLocal()); end; --и используем уже данные которые хотим if (User.HP < (User.MaxHP * 0.15)) then -- у нас мало НР можно выпить фласку end; end;
- Умножение - быстрее деления, если это возможно лучше умножайте на 0.1, а не /10.
- Рассчитывайте один раз, одинаковые расчеты - часто нужно для рисования панелек или расчета DMG по героям, если не изменились артефакты и скилы - нет смысла пересчитывать урон по герою.
- Не используете Insert для добавления в таблицу - лучший способ TableName[index]=value;
- Не используйте pairs, ipairs в циклах, где можно обойтись без этого - добавьте в список циклом и запомните где-то количество, можно использовать #TableName.
- Если знаете кол-во элементов в таблице - задайте это заранее при создании (врагов чаще всего 5), если часто нужно таблица из 5ти элементов зададим ее как TableName = {true, true, true, true, true}; Если делаете это в цикле и часто - создайте локальную переменную (Тест 13 листинг 1)
- Инкапсулируйте код, это читается и смотрится на много лучше и в итоге потом проще редактировать, чем тонны кода:
Код:
function InfoScreen.OnUpdate() GameTime = GameRules.GetGameTime(); InfoScreen.Menu.ItemPanel.LoadSettings(); InfoScreen.Renderer.CalcPanelSize(); User:Update(Heroes.GetLocal()); InfoScreen.EnemyHeroes.OnUpdate(); end;
- Все что повторяется (расчеты какие-то или проверки, для одного параметра) - тоже оформляйте в виде функции отдельной, как я это показывал в примерах выше.
- Называйте переменные своими именами (да это иногда сложно) и всегда в одинаковом стиле (можете отдельно почитать об этом в интернете), не используйте нижнее подчеркивание в именах!
- Хорошая IDE - Visual Studio Code (там есть для АПИ доты даже библиотеки).
- Отступы и переносы строк, не делайте if .. then .. end; в одну строку.
Тема на жесткой модерации, не пишите бреда, для благодарности - есть кнопка, если есть серьезные замечания - пишите конкретно и подробно, ну или если есть что добавить по оптимизации - аналогично.
Вот подъехал и отличный пример
Код:
local wisp = {}
wisp.optionEnable = Menu.AddOptionBool({"Hero Specific", "Io"}, "Auto TP After Relocate", false)
wisp.toggleKey = Menu.AddKeyOption({"Hero Specific", "Io"}, "Toggle Key", Enum.ButtonCode.KEY_NONE)
-переменные не локальные
font = Renderer.LoadFont("Tahoma", 18, Enum.FontWeight.BOLD)
toggled1 = false
base = nil
function wisp.OnUpdate()
if not Heroes.GetLocal() or not Menu.IsEnabled(wisp.optionEnable) then return end
--каждый раз одно и тоже получаем, хотя можно было один раз за игру
local myHero = Heroes.GetLocal()
local myTeam = Entity.GetTeamNum(myHero)
if NPC.GetUnitName(myHero) ~= "npc_dota_hero_wisp" then return end
if Menu.IsKeyDownOnce(wisp.toggleKey) then
if toggled1 == false then
toggled1 = true
else
toggled1 = false
end
end
if not toggled1 then return end
--два раза получаем модифаер
if NPC.HasModifier(myHero, "modifier_wisp_relocate_return") then
local mod = NPC.GetModifier(myHero, "modifier_wisp_relocate_return")
--каждый раз ищем тп свиток
local tp = NPC.GetItem(myHero, "item_tpscroll", true)
if not tp then
tp = NPC.GetItem(myHero, "item_travel_boots", true)
if not tp then
tp = NPC.GetItem(myHero, "item_travel_boots_2", true)
end
end
-каждый раз пересчитываем
local dieTime = Modifier.GetCreationTime(mod) + 12
if myTeam == 3 then
base = Vector(7264.000000, 6560.000000, 512.000000)
else
base = Vector(-7317.406250, -6815.406250, 512.000000)
end
--проверка не верная, нужно уточнить значение.
if tp and Ability.IsReady(tp) and dieTime - GameRules.GetGameTime() <= 3 then
Ability.CastPosition(tp, base)
end
end
end
function wisp.OnDraw()
if not Menu.IsEnabled(wisp.optionEnable) or not Heroes.GetLocal() then return end
--В Он Драв - только отрисовка!
if NPC.GetUnitName(Heroes.GetLocal()) ~= "npc_dota_hero_wisp" then return end
local x, y = Renderer.GetScreenSize()
--если разрешение изменится на 1 пиксель, это все не сработает
if x == 1920 and y == 1080 then
x, y = 1150, 910
elseif x== 1600 and y == 900 then
x, y = 950, 755
elseif x== 1366 and y == 768 then
x, y = 805, 643
elseif x==1280 and y == 720 then
x, y = 752, 600
elseif x==1280 and y == 1024 then
x, y = 800, 860
elseif x==1440 and y == 900 then
x, y = 870, 755
elseif x== 1680 and y == 1050 then
x, y = 1025, 885
end
if toggled1 then
Renderer.SetDrawColor(90, 255, 100)
Renderer.DrawText(font,x,y, "[Auto-TP: ON]")
else
Renderer.SetDrawColor(255, 90, 100)
Renderer.DrawText(font, x, y, "[Auto-TP: OFF]")
end
end
return wisp
Код:
local wisp = {}
wisp.optionEnable = Menu.AddOptionBool({"Hero Specific", "Io"}, "Auto TP After Relocate", false)
wisp.toggleKey = Menu.AddKeyOption({"Hero Specific", "Io"}, "Toggle Key", Enum.ButtonCode.KEY_NONE)
--переменные теперь локальные, но лучше их в 1 массив по типу перенести конечно
local font = Renderer.LoadFont("Tahoma", 18, Enum.FontWeight.BOLD)
local toggled1 = false
local base = nil
local myHero = nil;
local myTeam = 0;
local casted = false;
local modWispRelocate = nil;
local TPitem = nil;
local dieTime = 0;
--как я сделал тут
local StatusScreenPos = {};
StatusScreenPos.x = 0;
StatusScreenPos.y = 0;
--получаем данные не меняющиейся со временем
function wisp.init()
--только если мы в игре
if Engine.IsInGame() then
--героя обычно в игре не меняют.
myHero = Heroes.GetLocal();
myTeam = Entity.GetTeamNum(myHero);
dieTime = 0;
if myTeam == 3 then
base = Vector(7264.000000, 6560.000000, 512.000000)
else
base = Vector(-7317.406250, -6815.406250, 512.000000)
end
--расчет один раз по формуле, а не неивестными условиями и значениями.
local x, y = Renderer.GetScreenSize();
StatusScreenPos.x = math.floor(x * 0.594);
StatusScreenPos.y = math.floor(y * 0.84);
end;
end;
--если игра уже стартанула и Софт включили
wisp.init();
--или на старте игры вызываем их
function wisp.OnGameStart()
wisp.init();
end;
function wisp.OnUpdate()
if not myHero or not Menu.IsEnabled(wisp.optionEnable) then return end
--проверку по названию лучше тоже делать один раз на весь скрпит, но я оставил тут, так делат ьне хорошо
if NPC.GetUnitName(myHero) ~= "npc_dota_hero_wisp" then return end
if Menu.IsKeyDownOnce(wisp.toggleKey) then
if toggled1 == false then
toggled1 = true
else
toggled1 = false
end
end
if toggled1 then
--локализация частого вызова
local GameTime = GameRules.GetGameTime();
--вызов функции только когда это необходимо, но лучше еще добавть таймер в 0.5 сек, когда у нас целых 10 есть не пропустим
if (dieTime <= 0) or ((dieTime - GameTime) < 0) then
--получаем модифаер один раз
modWispRelocate = NPC.GetModifier(myHero, "modifier_wisp_relocate_return");
--и проверяем его один раз
if modWispRelocate then
-расчитываем время один раз если получили, больше этого не будет
dieTime = Modifier.GetCreationTime(modWispRelocate) + 12;
Log.Write(dieTime - GameTime);
end;
else
if modWispRelocate and not casted then
--уточнили время, учли пинг
if (dieTime - GameTime <= 2.96 + NetChannel.GetAvgLatency(Enum.Flow.FLOW_OUTGOING)) and (dieTime- GameTime > 2.8) then
--проверяем ТП только, если можем сделать комбину, но лучше и это вынести в отдельную функцию
local TPitem = NPC.GetItem(myHero, "item_tpscroll", true)
if not TPitem then
TPitem = NPC.GetItem(myHero, "item_travel_boots", true)
if not TPitem then
TPitem = NPC.GetItem(myHero, "item_travel_boots_2", true)
end
end
if TPitem and Entity.IsAbility(TPitem) and Ability.IsReady(TPitem) then
Log.Write(dieTime - GameTime);
Ability.CastPosition(TPitem, base)
casted = true;
end;
end
else
casted = false;
end
end;
end;
end
function wisp.OnDraw()
if not Menu.IsEnabled(wisp.optionEnable) or not Heroes.GetLocal() then return end
if toggled1 then
Renderer.SetDrawColor(90, 255, 100)
Renderer.DrawText(font, StatusScreenPos.x, StatusScreenPos.y, "[Auto-TP: ON]")
else
Renderer.SetDrawColor(255, 90, 100)
Renderer.DrawText(font, StatusScreenPos.x, StatusScreenPos.y, "[Auto-TP: OFF]")
end
end
return wisp
Проверка оптимизации вашего скрипта:
Код:
https://github.com/ivanius51/UmbrellaD2LUA/blob/master/PerfomanceTest.lua
Для хорошего(компьютера 150+ фпс):
Маленькие скрипты - AVGTime=1, ScriptTime=1-5, Update=0-3
Средние (скрипт героя, универсальный большой) - AVGTime=<15, ScriptTime=<50, Update=<35
Большие (несколько скриптов в одном FAIO) - AVGTime=<50, ScriptTime=<150, Update=<100
Код:
FAIO ScriptTime=66.2(=0/0) Update=65.5/148 Draw=0.7/522 AVGTime=14
LastHitCreep ScriptTime=5.3(=0/0) Update=5.1/151 Draw=0.2/638 AVGTime=1
много крипов рядом - LastHitCreep ScriptTime=16.0(=0/0) Update=16.0/151 Draw=0.0/215 AVGTime=1
InfoScreen ScriptTime=108.8(=0/0) Update=94.3/148 Draw=14.5/489 AVGTime=20
Если хотите по практиковаться писать скрипты, или игры:
You must be registered for see links
- заходите сюда, выбираете язык (LUA) и учитесь.
Последнее редактирование модератором: