Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
cJass
Тип:
Наработка
Библиотека является системой для гибкого управления юнитами компьютерных игроков.
Даёт возможность заготовить шаблоны путей и в любой момент отправлять юнитов в движение по ним.
Путь юнитам можно задать в обратном порядке, а так же приказать патрулировать его.
Пути можно легко подменять в зависимости от ситуации.
Система может быть особенно полезна для создания карт жанра Tower Defense или для создания.
Тема на форуме xgm.guru/forum/showthread.php?p=1307984
Код библиотеки
/**
 * Waypoints
 * Библиотека для гибкого управления юнитами компьютерных игроков
 *
 * Created by Zeds
 * Version: 1.1
 */
library Waypoints initializer Init {    
    private hashtable ht
    private path array paths
    
    // Создать путь
    public path CreatePath(integer id) {
        path p = path.create()
        p.Register(id)
        return p
    }
    
    // Добавить область к пути
    public nothing PathAddRect(integer id, rect rt) {
        path p = paths[id]
        if (p == null) {
            p = CreatePath(id)
        }
        
        p.AddRect(rt)
    }
    
    // Прикрепить юниту направляющую
    public nothing SetUnitGuide(unit u, integer pathId, string order, boolean reverse, boolean infinite) {
        integer uId = GetHandleId(u)
        guide g = guide.create()
        guide oldg = LoadInteger(ht, uId, -1)
        
        if (oldg != null) {
            oldg.destroy()
        }
        
        g.u = u
        g.order = order
        g.p = paths[pathId]

        if (reverse) {
            g.pos = g.p.GetCount() - 1
            g.direction = -1
        }
        
        g.infinite = infinite

        SaveInteger(ht, uId, -1, g)

        g.Order()
    }
    
    // Получить направляющую юнита
    public guide GetUnitGuide(unit u) {
        integer uId = GetHandleId(u)
        return LoadInteger(ht, uId, -1)
    }
    
    private nothing EnterAction() {
        unit u = GetTriggerUnit()
        integer uId = GetHandleId(u)
        guide g = LoadInteger(ht, uId, -1)

        g.pos += g.direction

        if (g.infinite and g.direction == 1 and g.pos == g.p.GetCount() - 1) {
            g.direction = -1
            g.Order()
            return
        }
 
        if (g.infinite and g.direction == -1 and g.pos == 0) {
            g.direction = 1
            g.Order()
            return
        }
         
        if (g.direction == 1 and g.pos < g.p.GetCount()) {
            g.Order()
            return
        }
        
        if (g.direction == -1 and g.pos >= 0) {
            g.Order()
            return
        }
        
        g.destroy()
    }
    
    private boolean EnterCondition() {
        unit u = GetTriggerUnit()
        real x = GetUnitX(u)
        real y = GetUnitY(u)
        integer uId = GetHandleId(u)
        guide g = LoadInteger(ht, uId, -1)
        
        if (g == null) {
            return false
        }
        
        rect rt = LoadRectHandle(ht, g.p, g.pos)
        
        rt = Rect(GetRectMinX(rt) - 32, GetRectMinY(rt) - 32, GetRectMaxX(rt) + 32, GetRectMaxY(rt) + 32)
  
        if ((GetRectMinX(rt) <= x) and (x <= GetRectMaxX(rt)) and (GetRectMinY(rt) <= y) and (y <= GetRectMaxY(rt))) {
            return true
        }
        
        return false
    }
    
    private boolean UnitDeathCondition() {
        unit u = GetTriggerUnit()
        integer uId = GetHandleId(u)
        guide g = LoadInteger(ht, uId, -1)
        
        return g != null
    }
    
    private nothing UnitDeathAction() {
        unit u = GetTriggerUnit()
        integer uId = GetHandleId(u)
        guide g = LoadInteger(ht, uId, -1)

        g.destroy()
    }
    
    // Направляющая(объект для управления юнитом)
    struct guide {
        unit u
        path p
        string order
        boolean infinite
        integer direction = 1
        integer pos = 0
        
        // Отдать приказ движения к текущей цели
        nothing Order() {
            rect rt = LoadRectHandle(ht, this.p, this.pos)
            real x = GetRectCenterX(rt)
            real y = GetRectCenterY(rt)

            IssuePointOrder(this.u, this.order, x, y)
        }
        
        nothing onDestroy() {
            FlushChildHashtable(ht, GetHandleId(this.u))
        }
    }

    // Путь(Объект для управления путями)
    struct path {
        private region rg = CreateRegion()
        private integer count = 0
        private integer id = -1

        trigger trig = CreateTrigger()
        event e
        
        region GetRegion() {
            return this.rg
        }
        
        integer GetCount() {
            return this.count
        }
        
        nothing Register(integer id) {
            integer rgId = GetHandleId(this.rg)
            
            SaveInteger(ht, rgId, -1, id)
            
            this.id = id
            paths[id] = this
        }
        
        nothing AddRect(rect rt) {
            integer trId = GetHandleId(rt)
            integer rgId = GetHandleId(this.rg)
            
            RegionAddRect(this.rg, rt)
            
            if (this.e == null) {
                this.e = TriggerRegisterEnterRegion(this.trig, this.rg, null)
                TriggerAddCondition(this.trig, function EnterCondition)
                TriggerAddAction(this.trig, function EnterAction)
             }
            
            SaveInteger(ht, trId, -1, this)
            SaveRectHandle(ht, this, this.count, rt)
            
            this.count++
        }
    }

    private nothing Init() {
        ht = InitHashtable()
        trigger trig = CreateTrigger()
        integer i = 0

        loop {
            TriggerRegisterPlayerUnitEvent(trig, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
            i++
            exitwhen i == 12
        }
        
        TriggerAddCondition(trig, function UnitDeathCondition)
        TriggerAddAction(trig, function UnitDeathAction)
    }
}
История версий
v1.1
* Более логичный нэйминг функций, структур и переменных
+ Добавлена возможность регистрировать пути на триггерах
`
ОЖИДАНИЕ РЕКЛАМЫ...
1
29
9 лет назад
1
  1. Плохой нейминг: waypoint включает в себя слово point и вообще это generally одна точка, а не подследовательность.
  2. Инициализация изнутри либы - плохая практика.
  3. Я не сильно разбирался в коде, но могу заверить, что хештаблица тут не обязательна.
  4. Register может не принимать аргументов и возвращать id который генерирует сама.
  5. На мой взгляд правильной реализацией в плане архитектуры было бы: абстрактный класс WayPoint, представляющий собой логическую единицу пути, его наследники вида RectWayPoint или LocationWayPoint которые позволяют отследить вхождение юнитов собственно в rect или в радиус точки с заданными координатами. И собственно то, что присваивается юниту могло бы называться WayPointSequence, список вейпоинтов по которых юнитам нужно двигаться. Таким образом решаются проблемы например с регистрированием нескольких ивентов на один рект. Да и вообще выглядит солидно.
0
2
9 лет назад
0
  1. Соглашусь, самого немного смущает, но это не критично
  2. Я только учусь. Почему?
  3. Я пытался обойтись без нее, удалось только лишь минимализировать работу с ней, буду рад если глянешь глубже и посоветуешь что-нибудь.
  4. Так и задумывалось, решил ввести аргумент для наглядности =)
  5. Ивенты не регистрируются на каждый рект. Спасибо за подсказки.
2
29
9 лет назад
2
  1. Сам ведь писал, что можно работать из гуи, например. Если я просто скопирую либу в карту, я не хочу модифицировать твой код, я хочу его использовать.
  1. Можно оставить и метод с аргументом и без него, в таком случае второй будет просто вызывать первый.
  1. Ну как-бы все верно в целом у тебя, если твои изначальные данные это просто unit, которого ты получаешь в ивенте, то тут либо перебор, либо unitdata (которую в либах лучше не использовать, т.к. она все-таки может быть использована где-то еще), либо хештаблица. "Правильный" метод (не уверен, что наиболее эффективный, в варкрафте скорость выполнения кода низкая) заключался бы в том, что мы сами определяем, когда ивент вызывается, периодически пробегаясь по вейпоинтам и юнитам привязанным к ним и проверяя вхождение юнита в следующий регион. Тогда наши изначальные данные были бы уже waypoint, в который входит искомый unit.
0
2
9 лет назад
0
  1. Да, пожалуй надо поправить.
  1. Еще один метод, что-бы сократить вызов на один символ, да брось =)
  1. Я так понял ты заметил что не так просто мне было без таблицы обойтись, способ конечно есть, но куда проще заюзать ht и не париться, ибо меньше кода и прироста к производительности особого не получишь. Вижу проблему лишь во втором пункте и неочень логичном нэйминге, придется править :/
3
29
9 лет назад
Отредактирован Doc
3
Да это не проблема, скорее просто вольное рассуждение об архитектуре. Я всегда старался как можно дальше отойти от варкрафта, используя только его базовое апи, в итоге это очень помогло в будущем. Никакого практического смысла бросаться все менять только чтобы обойтись без таблицы нет.
0
2
9 лет назад
0
Библиотека обновлена. Выражаю благодарность Doc за замечания
Чтобы оставить комментарий, пожалуйста, войдите на сайт.