- 28 Июн 2023
- 25
- 7
Разобрались как писать логику кастомки на lua? Пора и в js залезть.

Простое меню выбора способностей
Будем стремиться сделать нечто подобное с помощью panorama api.
Кто такой сервер?

Подразумевается, что lua скрипты исполняются на сервере и исполняют основную логику вашей игры. В свою очередь код на javascript исполняется на слабой клиентской машине и нужен для отображения UI компонентов (кнопок, картинок, менюшек). Для общения между собой клиент и сервер посылают события друг другу.
Попробуем послать событие о загрузке от игрока на сервер.
JavaScript:
const data = {
player_id: Players.GetLocalPlayer()
}
GameEvents.SendCustomGameEventToServer("player_loaded", data);
На сервере будем отлавливать клиентские сообщения.
Lua:
CustomGameEventManager:RegisterListener(
"player_loaded",
function(_, event)
print(event.player_id) -- пока ничего не делаем
end
)
ListenToGameEvent.Куда класть js скрипты
Можете ознакомиться с подробным гайдом. Кратко скажу.Директория для xml скелетов
\steam\steamapps\common\dota 2 beta\content\dota_addons\YOUR_ADDON_NAME\panorama\layout\custom_gameДиректория для css стилей
\steam\steamapps\common\dota 2 beta\content\dota_addons\YOUR_ADDON_NAME\panorama\styles\custom_gameДиректория для js скриптов
\steam\steamapps\common\dota 2 beta\content\dota_addons\YOUR_ADDON_NAME\panorama\scripts\custom_gameГлавный файл
layout\custom_game\custom_ui_manifest.xmlПишем меню
Создаем xml скелет для нашей менюшкиlayout\custom_game\abilities_menu.xml. На этом этапе надо решить как будет выглядеть наше меню.
XML:
<root>
<styles>
<include src="file://{resources}/styles/custom_game/abilities_menu.css"/>
</styles>
<Panel class="AbilitySelectionMenu">
<Label id="abilitySelectionMenuHeader" text="TEXT"/>
<DOTAAbilityImage id="abilityImage1" abilityname="ursa_fury_swipes" showtooltip="true"/>
<DOTAAbilityImage id="abilityImage2" abilityname="ursa_enrage" showtooltip="true"/>
</Panel>
</root>
class и id нужны для раздачи стилей. showtooltip добавит описание к способности, как в обычной доте.Подробнее про всю эту магию читать тут.
Делаем красиво. Кратко про css
Новый файл\YOUR_ADDON_NAME\panorama\styles\custom_game\additional_abilities\abilities_menu.cssС помощью css взрослые дядьки пишут современные сайты, это мощный инструмент для задания внешнего вида сайтов. Эти же дядьки написали миллион гайдов в интернете под любую задачу в css. Всегда легко найти готовую красивую анимацию или стиль.
CSS:
.AbilitySelectionMenu {
background-color: rgba(0, 0, 0, 0.6);
horizontal-align: right;
vertical-align: center;
border-radius: 30px;
width: 162px;
height: fit-children;
max-height: 85%;
margin: 2%;
overflow: clip;
flow-children: down;
}
#abilitySelectionMenuHeader {
font-size: 23px;
margin-top: 8%;
margin-left: 3%;
margin-right: 3%;
color: white;
horizontal-align: center;
}
ATTENTION эти стили изменятся в конце!
CSS:
#abilityImage1 {
width: 100px;
height: 100px;
}
#abilityImage2 {
width: 100px;
height: 100px;
}
Опять javascript
JS -- большой самостоятельный язык, который используется для web разработки. Рекомендую прочитать про Javascript api на сайте valve.Создаем файл
\YOUR_ADDON_NAME\panorama\scripts\custom_game\abilities_menu.jsПишем туда уже знакомое событие
player_loaded и начинаем слушать ответ от сервера, событие additional_abilities
JSON:
{
1: "ursa_fury_swipes",
2: "ursa_enrage"
}
JavaScript:
function Setup() {
$.Msg("Setup()");
GameEvents.Subscribe("additional_abilities", data => {
$.Msg(data) // пока ничего не делаем
});
const data = {
player_id: Players.GetLocalPlayer()
};
GameEvents.SendCustomGameEventToServer("player_loaded", data);
}
Setup();
GameEvents.Subscribe -- функция для отлова событий сервера. Внимательный читатель заметит, что сервер никаких событий не генерирует. Давайте исправим!
Lua:
CustomGameEventManager:RegisterListener(
"player_loaded",
function(_, event)
CustomGameEventManager:Send_ServerToPlayer(
PlayerResource:GetPlayer(event.player_id),
"additional_abilities",
{
"ursa_fury_swipes",
"ursa_enrage"
}
)
end
)
Что будет если нажать на способность?
Также надо добавить возможность кликать на иконки способностей. К сожалению, Javascript api в панораме не дает возможности напрямую отловить нажатие на способность. Необходимо предварительно создать невидимую обертку над картинкой. Давайте создадим шаблон такой кликабельной способности в<snippets>, а потом в коде добавим их.
XML:
<root>
<styles>
<include src="file://{resources}/styles/custom_game/abilities_menu.css"/>
</styles>
<scripts>
<include src="file://{resources}/scripts/custom_game/abilities_menu.js"/>
</scripts>
<snippets>
<snippet name="ClickableAbility">
<Panel id="abilityContainer" class="AbilityContainer">
<DOTAAbilityImage id="abilityImage" showtooltip="true"/>
</Panel>
</snippet>
</snippets>
<Panel class="AbilitySelectionMenu">
<Label id="abilitySelectionMenuHeader" text="沃草泥马"/>
</Panel>
</root>
<include>Также напишем функцию, которая будет загружать сниппет и добавлять в меню.
JavaScript:
function CreateAbility(ability_id, ability_name) {
const container = $.CreatePanel("Panel", $.GetContextPanel(), "");
container.BLoadLayoutSnippet("ClickableAbility");
const image = container.FindChild("abilityImage");
image.abilityname = ability_name
container.SetPanelEvent("onactivate", function () {
// Нажали на кнопку!
});
}
SetPanelEvent подписывается на нажатие кнопки. BLoadLayoutSnippet загружает скелет в js код.А помните, сервер присылал нам способности, с которыми мы ничего не делали? Теперь можем отрисовать их!
JavaScript:
GameEvents.Subscribe("additional_abilities", data => {
Object.entries(data).forEach(ability => CreateAbility(...ability));
});
Нажатие -> Событие ability_chosen
После нажатие на кнопку будем отправлять новое событие на сервер.
JavaScript:
function OnAbilitySelected(abilityId) {
const data = {
player_id: Players.GetLocalPlayer(),
ability_id: abilityId
};
GameEvents.SendCustomGameEventToServer("ability_chosen", data);
$.GetContextPanel().DeleteAsync(0)
}
DeleteAsync удаляет меню с экрана.
JavaScript:
"use strict";
function OnAbilitySelected(ability_id) {
const data = {
player_id: Players.GetLocalPlayer(),
ability_id: ability_id
};
GameEvents.SendCustomGameEventToServer("ability_chosen", data);
$.GetContextPanel().DeleteAsync(0)
}
function CreateAbility(ability_id, ability_name) {
const container = $.CreatePanel("Panel", $.GetContextPanel(), "");
container.BLoadLayoutSnippet("ClickableAbility");
const image = container.FindChild("abilityImage");
image.abilityname = ability_name
container.SetPanelEvent("onactivate", function () {
OnAbilitySelected(ability_id);
});
}
function Setup() {
$.Msg("Setup()")
GameEvents.Subscribe("additional_abilities", data => {
Object.entries(data)
.forEach(ability => CreateAbility(...ability))
})
const data = {
player_id: Players.GetLocalPlayer()
}
GameEvents.SendCustomGameEventToServer("player_loaded", data);
}
Setup();
CSS:
.AbilitySelectionMenu {
background-color: rgba(0, 0, 0, 0.6);
horizontal-align: right;
vertical-align: center;
border-radius: 30px;
width: 162px;
height: fit-children;
max-height: 85%;
margin: 2%;
overflow: clip;
flow-children: down;
}
.AbilityContainer {
width: 100px;
height: 100px;
margin-top: 9%;
horizontal-align: center;
padding: 2px;
}
.AbilityContainer:hover {
padding: 0;
border: 2px solid rgba(255, 255, 255, 0.1);
}
#abilityImage {
width: 100%;
height: 100%;
}
#abilitySelectionMenuHeader {
font-size: 23px;
margin-top: 8%;
margin-left: 3%;
margin-right: 3%;
color: white;
horizontal-align: center;
}
Добавляем герою выбранную способность
Ловим на сервере событиеability_chosen
Lua:
CustomGameEventManager:RegisterListener(
"ability_chosen",
function(_, event)
self:OnAbilityChosen(event)
end
)
function AdditionalAbilitiesManager:OnAbilityChosen(event)
local unit = PlayerResource:GetPlayer(event.player_id):GetAssignedHero()
print(event.ability_id)
local ability_name = -- ТУТ надо получить имя по id.
unit:AddAbility(ability_name)
end

Последнее редактирование:

