Communauté

Raycasting à la Wol...
 
Notifications
Retirer tout

Raycasting à la Wolfenstein 3D

6 Posts
3 Utilisateurs
1 Likes
310 Vu
AlbanR
(@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

Capture d’écran 2023 04 04 à 19.37.28

   
Cyril reacted
Citation
David de Gamecodeur
(@david)
Membre Admin
Inscription: Il y a 8 ans
Posts: 154
 

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
(@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
> 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
> 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
> 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
> 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
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
(@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
(@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.