Communauté

Raycasting à la Wol...
 
Notifications
Retirer tout

Raycasting à la Wolfenstein 3D

6 Posts
3 Utilisateurs
1 Reactions
423 Vu
(@albanr)
Active Member
Inscription: Il y a 3 ans
Posts: 8
Début du sujet  

Salut

Lors du dernier Mastermind, David parlait des sujets des prochains ateliers et masterclass.

Parmi eux, du raycasting comme utilisé dans le jeu Wolfenstein 3D.

J'ai écrit un raycaster en Love2D l'an dernier, très simple (c'est plus du ray marching que du ray casting).

Environ 300 lignes de codes et ça affiche la map en 2D et la vue 3D.

ça intéresse du monde ? si oui, je colle le code dans le forum.

a+

AlbanR


   
Cyril reacted
Citation
(@david)
Membre Admin
Inscription: Il y a 9 ans
Posts: 153
 

Sujet déplacé dans "Vos progrès".

Bravo pour ta réalisation, je vais bientôt travailler sur le sujet, en partant de la base et en essayant de vulgariser chaque étape, comme je l'ai fait pour les maps hexagonales, on verra ce que j'arrive à pondre :).


   
RépondreCitation
exotux
(@exotux)
Active Member
Inscription: Il y a 2 ans
Posts: 10
 

Salut,

Perso je suis toujours preneur pour voir du code, j'aime bien voir sous le capot 🙂


   
RépondreCitation
(@albanr)
Active Member
Inscription: Il y a 3 ans
Posts: 8
Début du sujet  

Hello,

je pensais que l'on recevait des notifications lors de réponses sur le forum ...

Voici le code en question; fonctionne sur la dernière version de love 2D

Enjoy !

 

--[[ B O F E N S T E I N  3 D ]] --
-- plus du ray marching que du ray casting à la Wolfenstein 3D
-- aurait terriblement ramé sur un PC de l'époque même écrit dans un langage performant
-- car le calcul des intersections rayon/map dans Wolf3D était bien plus efficace que ça (+ n'utilisait que des 
-- calculs sur des entiers avec l'algo DDA)
--
io.stdout:setvbuf("no")

W = 320
H = 240

-- structure pour un vecteur 2D
function vec2()
    v = {
        x = 0,
        y = 0
    }
    return v
end

-- seuille une valeur entre un min et un max
function clamp(val, min, max)
    return math.max(min, math.min(val, max));
end

-- joueur: position (vecteur) et rotation en radians
player = {
    pos = vec2(),
    rot = 0
}

-- champ de vision
fov = math.pi / 4

-- la map : 1 seul type de mur mais il serait facile d'en définir d'autres
MAP_WIDTH = 16
MAP_HEIGHT = 16
-- chaque case de la map mesure 1 x 1 (unité de map)
-- hauteur des murs en unités de map
WALL_HEIGHT = 0.6
-- couleur des murs (ici blancs)
WALL_COLOR = {
    r = 1,
    g = 1,
    b = 1
}
-- plan de la map
map = {
    { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
    { 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1 },
    { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
    { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
    { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
    { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
}

-- pas (incrément) de progression pour le ray marching
step = 0.01
-- distance max pour le ray marching
MAX_DIST = math.max(MAP_WIDTH, MAP_HEIGHT) * 1.5

function love.load()
    love.graphics.setDefaultFilter("nearest", "nearest")
    love.window.setMode(1280, 960)
    love.window.setTitle("B o f e n s t e i n  3 D")

    -- position initiale du joueur dans la map
    player.pos.x = 1.5
    player.pos.y = 1.5
end

function love.update(dt)
    local oldPos = vec2()
    local row, col

    -- mémoriser la position précédente du joueur en cas de collision avec un mur
    oldPos.x = player.pos.x
    oldPos.y = player.pos.y
    
    -- rotation vers la droite
    if love.keyboard.isDown("right") then
        player.rot = player.rot + 2 * dt
        if player.rot > 2 * math.pi then
            player.rot = 0
        end
    end
    -- rotation vers la gauche
    if love.keyboard.isDown("left") then
        player.rot = player.rot - 2 * dt
        if player.rot < 0 then
            player.rot = 2 * math.pi
        end
    end

    -- avancer
    if love.keyboard.isDown("up") then
        player.pos.x = player.pos.x + math.cos(player.rot) * 2 * dt
        player.pos.y = player.pos.y + math.sin(player.rot) * 2 * dt
        col = math.floor(player.pos.x) + 1
        row = math.floor(player.pos.y) + 1
        if map[row][col] > 0 then
            player.pos.x = oldPos.x
            player.pos.y = oldPos.y
        end
    end
    -- reculer
    if love.keyboard.isDown("down") then
        player.pos.x = player.pos.x - math.cos(player.rot) * 2 * dt
        player.pos.y = player.pos.y - math.sin(player.rot) * 2 * dt
        col = math.floor(player.pos.x) + 1
        row = math.floor(player.pos.y) + 1
        if map[row][col] > 0 then
            player.pos.x = oldPos.x
            player.pos.y = oldPos.y
        end
    end
    -- déplacement de côté vers la droite
    if love.keyboard.isDown("x") then
        player.pos.x = player.pos.x - math.sin(player.rot) * 2 * dt
        player.pos.y = player.pos.y + math.cos(player.rot) * 2 * dt
        col = math.floor(player.pos.x) + 1
        row = math.floor(player.pos.y) + 1
        if map[row][col] > 0 then
            player.pos.x = oldPos.x
            player.pos.y = oldPos.y
        end
    end
    -- déplacement de côté vers la gauche
    if love.keyboard.isDown("w") then
        player.pos.x = player.pos.x + math.sin(player.rot) * 2 * dt
        player.pos.y = player.pos.y - math.cos(player.rot) * 2 * dt
        col = math.floor(player.pos.x) + 1
        row = math.floor(player.pos.y) + 1
        if map[row][col] > 0 then
            player.pos.x = oldPos.x
            player.pos.y = oldPos.y
        end
    end
end

function love.draw()
    love.graphics.scale(2, 2)

    -- dessiner la map en 2D (arrière plan)
    
    -- décalage x,y de la map 2D à l'écran
    local map2d = vec2()
    map2d.x = W + 10
    map2d.y = 0
    -- taille des cases de la map 2D à l'écran
    local cellsize = 8
    
    -- dessiner les murs
    local r,c
    for r = 1,16 do
        for c = 1, 16 do
            if map[r][c] > 0 then
                love.graphics.setColor(1, 1, 1)
                love.graphics.rectangle("fill", (c - 1) * cellsize + map2d.x, (r - 1) * cellsize + map2d.y, cellsize, cellsize)
            else
                love.graphics.setColor(0, 0, 0)
                love.graphics.rectangle("fill", (c - 1) * cellsize + map2d.x, (r - 1) * cellsize + map2d.y, cellsize, cellsize)
            end
        end
    end

    
    -- calculer et afficher la vue FPS en 3D
    
    -- dessiner le sol et le plafond
    local x, y, shade
    for y = 1,H/2 do
        -- on obscurcit petit à petit pour simuler la distance (dégradé linéaire)
        shade = (1 - y / (H / 2))
        -- plafond
        love.graphics.setColor(0, 0, 0.5 * shade)
        love.graphics.line(0, y, W, y)
        -- sol (mirroir du plafond par rapport au milieu de l'écran verticalement)
        love.graphics.setColor(0.3 * shade, 0.3 * shade, 0.3 * shade)
        love.graphics.line(0, H - y, W, H - y)
    end
    

    -- pas d'incrément de l'angle du rayon relatif à la largeur de l'écran
    local inc = fov / W

    -- angle de départ par rapport à la direction où regarde le joueur
    local angle = player.rot - fov / 2
    
    -- calculer le vecteur de la direction où regarde le joueur
    local lookat = vec2()
    lookat.x = math.cos(player.rot)
    lookat.y = math.sin(player.rot)

    -- un rayon est juste un vecteur qui représente sa direction
    local ray = vec2()

    -- distance parcourue le long du rayon
    local t

    -- point le long du rayon pour la distance parcourue t
    local march = vec2()

    -- coordonnées en ligne, colonne dans la map pour le test d'intersection rayon/mur
    local row, col

    -- pour chaque colonne de l'écran
    -- les murs sont dessinés en lamelles verticales
    for x = 0, W - 1 do
        
        -- lancer un rayon: calculer sa direction à partir de l'angle de lancé courant
        ray.x = math.cos(angle)
        ray.y = math.sin(angle)
        -- intialiser la distance parcourue
        t = 0

        -- ray marching
        while ( t < MAX_DIST ) do
           
            -- avancer d'une distance d'un pas le long du rayon
            march.x = player.pos.x + ray.x * t
            march.y = player.pos.y + ray.y * t

            -- si on a dépassé les limite de la map
            if march.x < 0 or march.x > MAP_WIDTH or march.y < 0 or march.y > MAP_HEIGHT then -- out of map boundaries
                march.x = clamp(march.x, 0, W)
                march.y = clamp(march.y, 0, H)
                break
            end

            -- calculer la position actuelle sur le rayon en coordonnées dans la table map
            col = math.floor(march.x) + 1
            row = math.floor(march.y) + 1
            -- y a t-il un mur à cet endroit ?
            local hit = map[row][col]
            if  hit > 0 then -- oui
                -- calculer la distance. d = t fonctionne mais donne un effet "fishbowl" (comme vu dans un aquarium)
                -- la multiplication par math.cos(angle - player.rot) supprime cet effet en corrigeant la distance
                local d = t * math.cos(math.abs(angle - player.rot))
                -- l'effet de projection perspective est inversement proportionnel à la distance
                local persp = 1 / d
                -- la hauteur de cette lamelle de mur est calculée en deux parties symétriques par rapport au milieu de l'écran
                local ceiling = H / 2 - H * WALL_HEIGHT * persp -- partie supérieure
                ceiling = clamp(ceiling, 0, H - 1) -- clamp pour ne pas dépasser la hauteur de l'écran
                local floor = H - ceiling  -- partie inférieure, mirroir de la partir supérieure
                -- calculer la luminosité du mur (attenuation en fonction de la distance)
                shade = persp * persp + 0.5 * persp + 0.1
                -- dessiner la lamelle de mur
                love.graphics.setColor(0.7 * shade, 0.7 * shade, 0.7 * shade)
                love.graphics.line(x, ceiling, x, floor)
                break
            end
            
            -- avancer d'un pas
            t = t + step
            if t > MAX_DIST  then
                t = MAX_DIST
                break
            end
        end

        -- incrémenter l'angle de lancer pour le rayon suivant
        angle = angle + inc

        -- dessiner la map en 2D (avant plan)
        -- dessiner le champ de vision du joueur
        if ( x == 0 or x == W - 1 ) then
            love.graphics.setColor(0, 1, 1)
            local x1, y1, x2, y2
            x1 = (player.pos.x * cellsize) + map2d.x
            y1 = (player.pos.y * cellsize) + map2d.y
            x2 = math.floor(march.x + 0.5) * cellsize + map2d.x
            y2 = math.floor(march.y + 0.5) * cellsize + map2d.y
            love.graphics.line(x1, y1, x2, y2)
        end

    end -- fin pour chaque colonne de l'écran

    -- dessiner la map en 2D (avant plan, fin)
    -- afficher un point rouge à l'emplacement du joueur
    love.graphics.setColor(1, 0, 0)
    love.graphics.rectangle("fill", (player.pos.x * cellsize) + map2d.x, player.pos.y * cellsize + map2d.y, 1, 1)

    -- afficher les contrôles
    love.graphics.setColor(1, 1, 1)
    
    love.graphics.print("Contrôles:", 0, H)
    love.graphics.print("- flèches gauche-droite : rotation", 0, H + 12)
    love.graphics.print("- flèches haut-bas         : déplacement avant-arrière", 0, H + 24)
    love.graphics.print("- touches W et X           : déplacement latéral", 0, H + 36)

    
    -- afficher quelques variables pour info sous la vue 3D
    love.graphics.setColor(1, 1, 0)
    love.graphics.print("Debug :", 0, H + 60)
    -- position du joueur
    love.graphics.print("  x=" .. player.pos.x .. " y=" .. player.pos.y, 0, H + 72)
    -- emplacement en ligne,colonne en coordonnées dans la table map
    local c = math.floor(player.pos.x) + 1
    local r = math.floor(player.pos.y) + 1
    love.graphics.print("  row=" .. r .. " col=" .. c, 0, H + 84)
    -- rotation du joueur
    love.graphics.print("  rot=" .. player.rot, 0, H + 96)

end

function love.keypressed(key)
    if key == "escape" then
        love.event.quit()
    end
end

 


   
RépondreCitation
(@albanr)
Active Member
Inscription: Il y a 3 ans
Posts: 8
Début du sujet  

Je poste le code en attachement car il ne s'affiche pas correctement en inline

 


   
RépondreCitation
(@albanr)
Active Member
Inscription: Il y a 3 ans
Posts: 8
Début du sujet  

Je viens de voir qu'il y avait un bouton pour recevoir les réponse par email. Sorry, j'avais pas vu.


   
RépondreCitation
Share:

Dialoguez avec les autres membres de la gamecodeur school.

Accédez maintenant à notre serveur Discord privé : Entraide, Game Jams, Partage de projets, etc.

Vous devez être membre de la Gamecodeur School Premium pour être autorisé à accéder au serveur.