Como faço para criar uma superfície curva com blocos retangulares?

12

Para um jogo parecido com o Peggle , quero fazer blocos que seguem uma curva, assim:

blocos ao longo de uma curva

Os blocos desapareceriam quando a bola os atingisse.

Consegui desenhar alguns horizontalmente, mas estou tendo problemas para fazê-los seguir um caminho:

minha tentativa de seguir caminhos

Como eu faço isso? Preciso criar objetos Box2D com vértices personalizados?

Moerin
fonte
Deseja que as caixas simplesmente não se sobreponham ou que não haja lacunas em nenhum lugar? (Não sei exatamente o que você quer dizer com "deslocando o eixo Y do objeto de acordo com o ângulo do objeto").
Roy T.
1
Você não pode preencher uma curva com retângulos que não se sobrepõem ; portanto, você precisará criar uma geomety personalizada, se não desejar espaços.
Anko
@RoyT. As lacunas não são importantes. Meu verdadeiro problema é calcular a posição do bloco que se segue com diferentes ângulos.
Moerin
A maneira como abordaria isso é definir uma série de vértices que atuam como os cantos comuns entre cada caixa. Mesmo usando um caminho para defini-los, você ainda precisa de parâmetros adicionais para definir a distância entre os vértices e o comprimento de cada caixa.
4
As "caixas" sobre a primeira imagem não são caixas, eles são pares de triângulos: i.stack.imgur.com/Tzuql.png
egarcia

Respostas:

14

Dada uma curva "raiz", eis como você pode gerar vértices de bloco.

Bézier com blocos

A curva da raiz está no meio, em preto. Seus pontos de controle são mostrados com Xs vermelhos .

Resumindo : fiz um Bézier e experimentei (a uma taxa configurável). Encontrei o vetor perpendicular de cada amostra para a seguinte, normalizei e dimensionei -o para uma meia largura (configurável), primeiro à esquerda e inversamente à direita. Então desenhou.

Coisas que você pode adicionar a isso:


Aqui está o meu código. Está escrito em Lua (para o framework de jogos LÖVE ), mas acho que é legível para qualquer pessoa.

local v = require "vector"

-- A function that makes bezier functions
-- Beziers have start point     p0
--              control point   p1
--              end point       p2
local function makeBezierFunction(p0,p1,p2)
    return function (t)
        local pow = math.pow
        return pow( (1-t),2 ) * p0
               + 2 * (1-t) * t * p1
               + pow(t,2) * p2
    end
end

love.graphics.setBackgroundColor(255, 255, 255)
function love.draw()
    local line = love.graphics.line
    local colour = love.graphics.setColor

    -- Bezier sampling parameters
    local nSegments = 10
    local segIncr = 1/nSegments

    -- Bezier definition: Start (`p0`), control (`p1`) and end `p2`) point
    local p0 = v(100,100)
    local p1 = v( love.mouse.getX(), love.mouse.getY() )
    local p2 = v(500,100)
    local controlPoints = {p0,p1,p2}
    local bez = makeBezierFunction(p0,p1,p2)

    -- Sample the bezier
    for i=0,1-segIncr,segIncr do
        colour(0, 0, 0)
        local x1,y1 = bez(i        ):unpack()
        local x2,y2 = bez(i+segIncr):unpack()
        line(x1,y1,x2,y2)

        -- Find left and right points.
        local center = v(x1, y1)
        local forward = v(x2, y2) - center
        local left = center + forward:perpendicular():normalize_inplace() * 10
        local right = center - forward:perpendicular():normalize_inplace() * 10

        -- Draw a line between them.
        line(left.x, left.y, right.x, right.y)

        -- Find *next* left and right points, if we're not beyond the end of
        -- the curve.
        if i + segIncr <= 1 then
            local x3, y3 = bez(i+segIncr*2):unpack()
            local center2 = v(x2, y2)
            local forward2 = v(x3, y3) - center2
            local left2 = center2 + forward2:perpendicular():normalize_inplace() * 10
            local right2 = center2 - forward2:perpendicular():normalize_inplace() * 10

            -- Connect the left and right of the current to the next point,
            -- forming the top and bottom surface of the blocks.
            colour(0, 0xff, 0)
            line(left.x, left.y, left2.x, left2.y)
            colour(0, 0, 0xff)
            line(right.x, right.y, right2.x, right2.y)
        end
    end

    -- Draw an X at the control points
    for _,p in ipairs(controlPoints) do
        local x,y = p:unpack()
        colour(0xff,0x00,0x00)
        line(x-5,y-5, x+5,y+5)
        line(x-5,y+5, x+5,y-5)
    end
    -- Draw lines between control points
    for i=1,#controlPoints do
        colour(0xff,0x00,0x00, 100)
        local cp1 = controlPoints[i]
        local cp2 = controlPoints[i+1]
        if cp1 and cp2 then
            line(cp1.x, cp1.y
                ,cp2.x, cp2.y)
        end
    end
end

Se você quiser brincar com ele: obtenha o LÖVE e coloque o código acima main.luaem seu próprio diretório. Coloque vector.luada HUMPbiblioteca no mesmo diretório. Execute-o como love <that-directory>em uma linha de comando.

Mova o mouse! O ponto de controle do meio está definido para o local do mouse:

Configurando o ponto de controle com o mouse

Anko
fonte
Você tentou o LibGdx? Se sim, você prefere Löve? Estou me afastando do uso da API padrão do Android depois do meu jogo atual e estou tentando decidir entre LibGdx e Löve. Resposta interessante acima como sempre btw
Green_qaue
@ Anko Muito obrigado, é mais do que eu esperava. Mais, acho que consigo entender facilmente seu código, pois uso o MonkeyX no meu jogo, que é semelhante ao LUA.
Moerin
1
@iQ Não usei o Libgdx, mas já li muito sobre isso e conheço bem o Java. Libgdx é grande . (Ele possui suporte para acelerômetro, geradores de curvas e tudo mais), enquanto o Love2D é muito pequeno (não possui nenhum deles, não há suporte para shader, etc). Graças à sua simplicidade, o Love2D tem sido ótimo para protótipos rápidos e jogos pequenos, mas pode ser muito minimalista para alguns projetos. Quem sabe. (! Você Experimente e veja: D.)
Anko
Ótima resposta, e esse GIF é realmente um ótimo bônus!
Roy T.