Pra melhorar um pouco o FPS, deixe o dxCreateTexture fora da função, numa variável local.
E ali no getElementsByType, faça assim pra pegar só os jogadores que estão sendo sincronizados pelo localPlayer. Não faz sentido pegar player que está muito longe.
getElementsByType("player", root, true)
Sobre o ângulo estranho, como a imagem sempre ficará voltada para a câmera, então é melhor voltar ao 2D mesmo.
Uma coisa que você pode usar para calcular melhor a escala do dxDrawImage, é pegar 3 posições X,Y,Z ao redor da cabeça do jogador, onde começa e termina a imagem.
Mas essas posições levam em consideração a rotação da câmera, sendo assim, não adianta simplesmente somar ou subtrair os valores, pois fazendo isso, as posições ficarão relativas ao cenário fixo.
Para isso, você precisa criar um objeto qualquer invisível e sem colisão na cabeça do jogador (no osso). Depois disso você deve rotacionar esse objeto para sempre ficar apontado para a câmera utilizando a função útil findRotation3D. (o objeto não pode ser anexado no jogador, ele apenas tem sua posição setada na cabeça dele a cada frame.)
Só depois de você ter um objeto invisível e sem colisão na cabeça do jogador sempre apontado para a câmera, utilize getElementMatrix para obter as posições do lado e em cima desse objeto, essas posições você usará no getScreenFromWorldPosition para desenhar o dxDrawImage final.
Como eu faria:
local texture = dxCreateTexture("Logo.png") -- Cria a textura.
local objetos = {} -- Tabela onde os objetos invisíveis vão ficar.
local options = {
distance = 100,
escala = 1,
}
addEventHandler("onClientRender", root, function()
for _, player in pairs(getElementsByType("player", root, true)) do -- Para cada jogador sendo sincronizado por este cliente, faça:
if isElement(player) then -- Se o jogador existe, então: (evita erros quando o player desconecta neste frame)
if getElementData(player, "imortalp") then -- Se essa elementData não for false, então:
local alpha = getElementAlpha(player)
local px, py, pz = getElementPosition(player)
local camX, camY, camZ = getCameraMatrix()
local dist = getDistanceBetweenPoints3D(camX, camY, camZ, px, py, pz)
if dist < options.distance then -- Se a distância da câmera para o jogador for menor que o limite, então:
local headPosX, headPosY, headPosZ = getPedBonePosition(player, 4) -- Obtém a posição do osso da cabeça do jogador.
if headPosX then -- Se obteve uma das posições (significa que obteve as outras também), então:
if not isElement(objetos[player]) then -- Se ainda não existe o objeto invisível na cabeça desse jogador, então:
objetos[player] = createObject (3003, headPosX, headPosY, headPosZ, 0, 0, 0, true) -- Cria um objeto sem colisão associado ao jogador.
setElementAlpha(objetos[player], 0) -- Deixa o objeto invisível.
else -- Se já existe o objeto, apenas mantém ele na cabeça do jogador a cada frame.
setElementPosition(objetos[player], headPosX, headPosY, headPosZ)
end
local rx, ry, rz = findRotation3D(headPosX, headPosY, headPosZ, camX, camY, camZ) -- Encontra a rotação relativa do objeto com a câmera.
setElementRotation(objetos[player], rx, ry, rz) -- Aponta o objeto para a câmera.
local rightX, rightY, rightZ = getPositionFromElementOffset(objetos[player], options.escala / 2, 0, 0) -- Obtém a posição XYZ na direita da cabeça do jogador.
local leftX, leftY, leftZ = getPositionFromElementOffset(objetos[player], (options.escala / 2) * -1, 0, 0) -- Obtém a posição XYZ na esquerda da cabeça do jogador.
local screenX1, screenY1 = getScreenFromWorldPosition(rightX, rightY, rightZ)
local screenX2 = getScreenFromWorldPosition(leftX, leftY, leftZ)
local _, screenY3 = getScreenFromWorldPosition(headPosX, headPosY, headPosZ + options.escala)
-- Apenas para testes
-- dxDrawText("1", screenX1 or 0, screenY1 or 0)
-- dxDrawText("2", screenX2 or 0, screenY1 or 0)
-- dxDrawText("3", screenX2 or 0, screenY3 or 0)
if screenX1 and screenX2 and screenY3 then
local width, height = screenX2 - screenX1, screenY1 - screenY3 -- Calcula os tamanhos subtraindo as posições da tela.
dxDrawImage(screenX1, screenY3, width, height, texture, 0, 0, 0, tocolor(255, 255, 0, alpha)) -- A imagem precisa ser quadrada, preferencialmente múltipla de 2. (16x16, 32x32, 64x64, 128x128, etc)
end
end
elseif isElement(objetos[player]) then -- Se o jogador não está perto o suficiente e tem o objeto invisível em sua cabeça, então:
destroyElement(objetos[player]) -- Destrói o objeto.
objetos[player] = nil -- Limpa a variável dele para liberar espaço na memória.
end
end
end
end
end)
addEventHandler("onClientElementStreamOut", root, function() -- Ativa esse evento quando um elemento para de ser sincronizado por este client.
if getElementType(source) == "player" then -- Se foi um jogador, então:
if isElement(objetos[source]) then -- Se existe o objeto invisível daquele jogador, então:
destroyElement(objetos[source]) -- Destrói o objeto.
objetos[source] = nil -- Limpa a variável dele para liberar espaço na memória.
end
end
end)
-- Funções úteis.
function findRotation3D(x1, y1, z1, x2, y2, z2)
local rotx = math.atan2 (z2 - z1, getDistanceBetweenPoints2D (x2, y2, x1, y1))
rotx = math.deg(rotx)
local rotz = -math.deg(math.atan2(x2 - x1, y2 - y1))
rotz = rotz < 0 and rotz + 360 or rotz
return rotx, 0, rotz
end
function getPositionFromElementOffset(element,offX,offY,offZ)
local m = getElementMatrix(element)
local x = offX * m[1][1] + offY * m[2][1] + offZ * m[3][1] + m[4][1]
local y = offX * m[1][2] + offY * m[2][2] + offZ * m[3][2] + m[4][2]
local z = offX * m[1][3] + offY * m[2][3] + offZ * m[3][3] + m[4][3]
return x, y, z
end