Отслеживание мышки

Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
Jass
Тип:
Алгоритм
Версия Warcraft:
1.31
Введение
С давных времен люди задумывались как отслеживать в варкрафте положение мышки без помощи сторонних утилит. Например, для возможности управлять персонажем от третьего лица. Предложенный здесь алгоритм решает данную проблему самым простым существующим на данный момент способом, другая похожая возможная реализация является составляющей следующей статьи
Реализаия
Реализация выполнена созданием сетки фреймов в стандартном окне 0.8*0.6 с размерами:
local real dx = 0.005
local real dy = 0.005
Выбранный фрейм является ползунком не случайно, как известно в 1.31 у фреймов типа BUTTON существует баг, что при нажатии с него не сбрасывается фокус и после чего нельзя, например, получить CallBack с клавиатуры.
Следующая библиотека постоянно отслеживает положение мышки на фрейм-сетке с началом координат в центре и записывает его в массивы fraim_mous_dix, fraim_mous_diy по индексу игрока
code
library FrameNet initializer InitTrig_FrameNet

globals
	private trigger FrameNetINI
	private hashtable gl_hash
	private integer MAX_MOUSE_X
	private integer MAX_MOUSE_Y
	//
	real array fraim_mous_dix
	real array fraim_mous_diy
endglobals

private function FraimCallBack takes nothing returns nothing
   local framehandle frame = BlzGetTriggerFrame()
   local player pl = GetTriggerPlayer()
   local integer pl_index = GetConvertedPlayerId(pl)-1
   local integer gl_X = LoadInteger(gl_hash,GetHandleId(frame),1)
   local integer gl_Y = LoadInteger(gl_hash,GetHandleId(frame),2)
   // 
   set fraim_mous_dix[pl_index] = gl_X - MAX_MOUSE_X/2
   set fraim_mous_diy[pl_index] = gl_Y - MAX_MOUSE_Y/2
endfunction


private function Trig_frame_Actions takes nothing returns nothing
	local trigger trig = CreateTrigger()
	local framehandle mainButton
	local real dx = 0.005
	local real dy = 0.005
	local integer XCount = R2I(0.8/dx)
	local integer YCount = R2I(0.6/dy)
	local integer i = 0
	local integer j = 0
	local integer HashID
	//
	local integer i_loop = 0
	loop
		exitwhen i_loop == 25//need_const
		set fraim_mous_dix[i_loop] = 0
		set fraim_mous_diy[i_loop] = 0
		set i_loop = i_loop + 1
	endloop
	//
	set MAX_MOUSE_X = XCount
	set MAX_MOUSE_Y = YCount
	loop
		exitwhen j == YCount
		set i = 0
		loop
			exitwhen i == XCount
			set mainButton  = BlzCreateFrameByType("SCROLLBAR", "FrameGridBoss", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0),"",0)
			//SCROLLBAR - работет
			call BlzFrameSetAbsPoint(mainButton, FRAMEPOINT_CENTER, dx/2+dx*i, dy/2+dy*j)
			call BlzFrameSetSize(mainButton, dx, dy)
			call BlzTriggerRegisterFrameEvent(trig, mainButton, FRAMEEVENT_MOUSE_ENTER)
			//
			set HashID = GetHandleId(mainButton)
			call SaveInteger(gl_hash,HashID,1,i)
			call SaveInteger(gl_hash,HashID,2,j)
			set i = i + 1
    		endloop
		set j = j + 1
		//call BJDebugMsg(I2S(i)+" "+I2S(j))
	endloop
	call TriggerAddAction ( trig, function FraimCallBack )
endfunction

//===========================================================================

private function InitTrig_FrameNet takes nothing returns nothing
    set gl_hash = InitHashtable()
    set FrameNetINI = CreateTrigger(  )
    call TriggerRegisterTimerEventSingle( FrameNetINI, 1.00 )
    call TriggerAddAction( FrameNetINI, function Trig_frame_Actions )
endfunction

endlibrary
Использование наработки предполагается с отключением нижнего интерфейса, пример того как это сделать можно найти тут. Или готовый вариант в карте-примере xgm.guru/files/100/238563/FrameNet.w3x.
Так-как подобный алгоритм требуется, в основном, для того, чтобы управлять персонажем от первого-третьего лица фреймы за стандартными размерами часто не требуются, так как мышка постоянно возвращается в центр командой.
if(loc_pl == GetLocalPlayer()) then
			call BlzSetMousePos(R2I(BlzGetLocalClientWidth()/2),R2I(BlzGetLocalClientHeight()/2))
endif
где:
loc_pl - игрок которому необходимо вернуть мышку
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
32
3 года назад
0
BlzGetLocalClientWidth
BlzGetLocalClientHeight
нормально работают возвращают размер экрана
0
27
3 года назад
Отредактирован MpW
0
Bergi_Bear, да я уже только затестил дебагом
0
13
3 года назад
Отредактирован Nelloy
0
МрачныйВорон, BlzGetFrameByName("ConsoleUIBackdrop", 0) тот самый волшебный предок. Позволяет своим наследникам выходить за область 4:3
0
32
3 года назад
0
Nelloy, он то позволяет, но как я сказал выше - события начинают фаталить, если нужно просто разместить элементы то нет
0
13
3 года назад
Отредактирован Nelloy
0
Bergi_Bear, странно, у меня все нормально с GlueTextButton FRAMEEVENT_MOUSE_ENTER / LEAVE / CONTROL_CLICK
0
27
3 года назад
0
Bergi_Bear, ну я бы не стал так критичен. Надо самому затестить.
1
13
3 года назад
Отредактирован Nelloy
1
Сделал свою реализацию, правда на TypeScript и своих либах, но думаю тут главное уловить суть. Несмотря на код, у всех фреймов реальный предок - BlzGetFrameByName("ConsoleUIBackdrop", 0). В видео мерцание, вызвано неполной прозрачность фреймов, для демонстрации. Координаты обновляются примерно каждые 0.2сек. Используется 64 фрейма.
Основная идея была сделать сетку из GlueTextButton (в коде сетка 8х8), в сетке с помощью определения видимости Tooltip определяется ячейка, содержащая курсор. Как оказалось работает гораздо быстрее ивента FRAMEEVENT_MOUSE_ENTER (раз в 10).
Сначала сетка строится на весь экран, определяется ячейка с курсором. Вокруг этой ячейки выделяется регион интереса (3х3) и сетка перестраивается в этом регионе. Процедура повторяется пока точность не достигнет заданной или пока сетка не потеряет курсор. Стоит добавить выходные фильтры.
Работает в том числе вне 8:6
Видео
Grid.ts
import * as Frame from '../../FrameExt'
import { Color, Vec2 } from '../../Utils'

const COLOR = new Color(0, 0, 0, 0.1)

export class MouseDetectGrid extends Frame.SimpleEmpty {
    constructor(cols: number, rows: number){
        super()
        
        this.__grid_btn = []
        this.__grid_tool = []
        for (let x = 0; x < cols; x++){
            this.__grid_btn.push([])
            this.__grid_tool.push([])
            for (let y = 0; y < rows; y++){
                let btn = new Frame.GlueTextButton()
                this.__grid_btn[x].push(btn)
                btn.parent = this
                btn.color = COLOR

                let tool = new Frame.Backdrop()
                this.__grid_tool[x].push(tool)
                tool.visible = false
                tool.color = new Color(0, 0, 0, 0)
                BlzFrameSetTooltip(btn.handle, tool.handle)
            }
        }
    }

    getMousePos(){
        for (let x = 0; x < this.__grid_btn.length; x++){
            for (let y = 0; y < this.__grid_btn[x].length; y++){
                let tool = this.__grid_tool[x][y]
                if (tool.visible){
                    let btn = this.__grid_btn[x][y]
                    return $multi(true, this.pos.add(btn.pos), btn.size)
                }
            }
        }
        return $multi(false, this.pos, this.size)
    }

    protected _set_size(size: Vec2){
        super._set_size(size)

        let btn_size = new Vec2(size.x / this.__grid_btn.length,
                                size.y / this.__grid_btn[0].length)
        for (let x = 0; x < this.__grid_btn.length; x++){
            for (let y = 0; y < this.__grid_btn[x].length; y++){
                this.__grid_btn[x][y].size = btn_size
                this.__grid_btn[x][y].pos = new Vec2(x * btn_size.x, y * btn_size.y)
            }
        }

    }

    private __grid_btn: Frame.GlueTextButton[][]
    private __grid_tool: Frame.Backdrop[][]
}
MouseDetector.ts
import * as Frame from '../../FrameExt'
import { hTimer } from '../../Handle'
import { Vec2 } from '../../Utils'

import { MouseDetectGrid } from './Grid'

const STEP_TIME = 0.025
const GRID_SIZE = 8
const ROI_SIZE = 3

export class MouseDetector {
    constructor(){
        this.precision = 0.01
        this.__pos = Frame.Screen.pos
        this.__size = Frame.Screen.size

        this.__grid = new MouseDetectGrid(GRID_SIZE, GRID_SIZE)

        let t = new hTimer()
        t.addAction(() => {
            this.__pos = Frame.Screen.pos
            this.__size = Frame.Screen.size
            this.__grid.pos = this.__pos
            this.__grid.size = this.__size
            this.__getBetterPos()
        })

        let period = STEP_TIME
        let tmp = this.precision
        while (tmp < Frame.Screen.size.x){
            tmp *= GRID_SIZE / ROI_SIZE
            period += STEP_TIME
        }

        t.start(period, true)
    }

    getMouse(){
        return this.__pos
    }

    private __getBetterPos(){
        let t = new hTimer()
        t.addAction(() => {
            t.destroy()
            let found: boolean
            [found, this.__pos, this.__size] = this.__grid.getMousePos()

            if (!found){
                return
            }

            this.__grid.pos = this.__pos.sub(this.__size.mult((ROI_SIZE - 1) / 2))
            this.__grid.size = this.__size.mult(ROI_SIZE)

            print(this.__pos.toString(), this.__size.toString())

            if (this.__size.x > this.precision || this.__size.y > this.precision){
                this.__getBetterPos()
            }
        })
        t.start(STEP_TIME, false)
    }

    precision: number

    private __pos: Vec2
    private __size: Vec2

    private __grid: MouseDetectGrid
}
Загруженные файлы
0
27
3 года назад
0
Nelloy, можно такую на jass/lua?
0
13
3 года назад
Отредактирован Nelloy
0
МрачныйВорон, абсолютно не тестировал, но примерно так
Screen.lua
local Screen = {}

Screen.pos = {0, 0}
Screen.size = {0.8, 0.6}

local pixel_w = 0
local pixel_h = 0

local function update()
    local cur_pixel_width = BlzGetLocalClientWidth()
    local cur_pixel_height = BlzGetLocalClientHeight()

    if (cur_pixel_width == pixel_w and cur_pixel_height == pixel_h) then
        return
    end

    pixel_w = cur_pixel_width
    pixel_h = cur_pixel_height

    local default_zone_width = cur_pixel_height * 0.8 / 0.6
    Screen.size[0] = 0.8 * cur_pixel_width / default_zone_width
    Screen.pos[0] = - (Screen.__size.x - 0.8) / 2
end

local timer = CreateTimer()
TimerStart(timer, 1, true, update)
update()

return Screen
Grid
local Grid = {}

local COLS = 7
local ROWS = 7

local __pos = {0, 0}
local __size = {0, 0}
local btns = {}
local tools = {}

local parent = BlzGetFrameByName("ConsoleUIBackdrop", 0)

for x = 1, COLS do
    btns[x] = {}
    tools[x] = {}
    for y = 1, ROWS do
        btns[x][y] = BlzCreateFrameByType("GLUETEXTBUTTON", "MyButton", parent, "ScriptDialogButton", 0)
        BlzFrameSetAlpha(btns[x][y], 25)
        BlzFrameSetAbsPoint(btns[x][y], FRAMEPOINT_BOTTOMLEFT, 0, 0)
        BlzFrameSetSize(btns[x][y], 0, 0)

        tools[x][y] = BlzCreateFrameByType("FRAME", "FaceFrame", parent, "", 0)
        BlzFrameSetAlpha(tools[x][y], 0)
        BlzFrameSetAbsPoint(tools[x][y], FRAMEPOINT_BOTTOMLEFT, 0, 0)
        BlzFrameSetSize(tools[x][y], 0, 0)

        BlzFrameSetTooltip(btns[x][y], tools[x][y])
    end
end

local function __updateGrid()
    local w = __size[0] / COLS
    local h = __size[1] / ROWS

    for x = 1, COLS do
        for y = 1, ROWS do
            BlzFrameSetAbsPoint(btns[x][y], FRAMEPOINT_BOTTOMLEFT, __pos[0] + x * w, __pos[1] + y * h)
        end
    end
end

function Grid.setPos(pos)
    __pos = pos
    __updateGrid()
end

function Grid.setSize(size)
    __size = size
    __updateGrid()
end

-- return true, x, y, w, h
-- return false
function Grid.mouseROI()
    local w = __size[0] / COLS
    local h = __size[1] / ROWS

    for x = 1, COLS do
        for y = 1, ROWS do
            local found = BlzFrameIsVisible(tools[x][y])
            if (found) then
                return true, __pos[0] + x * w, __pos[1] + y * h, w, h
            end
        end
    end

    return false
end

return Grid
Detector
local Screen = require('Screen')
local Grid = require('Grid')

local Detector = {}

local timer = CreateTimer()

local ROI = 3
local PREC = 0.01

Detector.x = 0
Detector.y = 0
Detector.w = 0
Detector.h = 0

local function __getBetterPos()
    local t = CreateTimer()
    TimerStart(t, 0.025, false, function()
        DestroyTimer(t)

        local found, x, y, w, h = Grid.mouseROI()
        Detector.x = x
        Detector.y = y
        Detector.w = w
        Detector.h = h
        if (not found) then
            return
        end

        if (w > PREC or h > PREC) then
            __getBetterPos()
        end

        Grid.setPos({x - w * (ROI - 1) / 2, y - h * (ROI - 1) / 2})
        Grid.setSize({w * ROI, h * ROI})
    end)
end

local timer = CreateTimer()
TimerStart(t, 0.5, true, function()
    Detector.x = Screen.pos[0]
    Detector.y = Screen.pos[1]
    Detector.w = Screen.size[0]
    Detector.h = Screen.size[1]
    __getBetterPos()
end)

return Detector
Чтобы оставить комментарий, пожалуйста, войдите на сайт.