Урок Panorama api. Меню выбора скилов

avtakhov

Пользователь
28 Июн 2023
25
7

Разобрались как писать логику кастомки на lua? Пора и в js залезть.​


stupid js mem

Простое меню выбора способностей​

Будем стремиться сделать нечто подобное с помощью panorama api.
1690182556592.png

Кто такой сервер?​

1690179164994.png
Подразумевается, что 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. На этом этапе надо решить как будет выглядеть наше меню.
Безымянный.png
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;
}
Web разработчики должны были заметить, что некоторые имена из классического css изменены. Все верно, будьте внимательны, когда копируете готовые стили. Сверяйтесь тут

ATTENTION эти стили изменятся в конце!
CSS:
#abilityImage1 {
    width: 100px;
    height: 100px;
}

#abilityImage2 {
    width: 100px;
    height: 100px;
}

1690213941033.png

Опять 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
)
Тут неподготовленный читатель уже должен сойти с ума.
Безымянный.png
Сразу после получения информации о загрузке конкретного игрока, сервер отправляет ему способности событием additional_abilities.

Что будет если нажать на способность?​

Также надо добавить возможность кликать на иконки способностей. К сожалению, 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
Полная схема взаимодействия клиента и сервера
Безымянный.png
 
Последнее редактирование:
  • Нравится
Реакции: Sannin, LuciFerka и AlkY
Реклама: