Log In  

Cart #mot_wolf3d-3 | 2021-01-29 | Code ▽ | Embed ▽ | Forks ▽ | License: CC4-BY-NC-SA
32

This isn't really a game - unless you consider it a short "Walking simulator" - it's more of a tech demo.

The engine is a basic Wolfenstein-3D like 3D engine. It has floor and ceiling textures and render reasonably sized and complex rooms at 60 frames-per-second, in a 128x96 viewport.

  • Arrow keys = move
  • X = toggle map mode

If anyone feels like something out of it, it's fairly easy to get started with (details below).


Levels are built using the map editor, using the sprites on page 1.

The bottom left sprites are walls, except the left-most one which positions a door.
The next row up is for placing objects.
The numbered circles are for placing triggers that trigger code when the player reaches them.
The gray arrows at the top are for setting the player start position and direction.

You can use the top left 124x32 tiles of the map area.

Wall textures (and door texture) are sprite tabs 2 and 3.

You can define up to 8 (including the door).

Objects are sprite tab 4.

They are always 16x16 pixels. You can define up to 16.

Objects must be defined in the "otyp" array (code tab 1):

otyp={
-- y    h   w  solid flat
 { .33,.4, .5, true},
 {-.36,.25,.25,false},
 {   0, 1, .3, true},
 { .5,.45, .7, false,true},
 {.375,.5, .7, true},
 { -.3,.4, .3, false},
 { .3,.35, .4, true},
 { .5,.45, .8, false,true},
 { .1, .8, .4, true},
 { .2, .6, .6, true}    
}
  • y = y position (-0.5 = ceiling, 0.5 = floor)
  • h = height
  • w = width
  • solid = true if object will obstruct player's movement
  • flat = true to flatten object to floor/ceiling

Floor and ceiling textures are defined at the very right of the map.

Floor and ceiling "plane types" must also be defined in code tab 1:

-- plane types
--       tex  scale height xvel yvel
pl_tile ={ 0, 0.5   }
pl_panel={ 1, 0.5   }
pl_dirt ={ 2, 0.125 }
pl_stone={ 3, 0.25  }
pl_sky  ={ 4, 7,    10,    .007,.003}
  • tex = Which "texture" to use. 0 = topmost.
  • scale = Texture scale factor.
  • height = Optional. Set the plane height, e.g. for sky textures. Otherwise defaults to floor/ceiling height.
  • xvel,yvel = Optional. Creates moving planes.

You then select the floor and ceiling planes by setting the "floor" and "roof" variables.

floor={ typ=pl_dirt,  x=0,y=0 }
roof ={ typ=pl_sky,   x=0,y=0 }

P#86903 2021-01-29 07:19 ( Edited 2021-01-29 23:26)

impressed by rasterization precision 👍 - I know some wall rendering code that might need a pixel clean up 😜

minor perf comment:

local i=x-flr(x)
-- can be replaced by :
local i=x%1 -- 2x faster
P#86905 2021-01-29 07:37 ( Edited 2021-01-29 07:50)
:: Mot

@freds72 thanks. %1 feels obvious now you've mentioned it :-), much cleaner.

Regarding the walls, I think sspr just truncates the y and height params before using them. Which means the bottom pixel can jump up and down a bit as they cross integer boundaries.
Calculating the top and bottom, explicitly truncating/rounding them, then subtracting to get the height keeps it a lot more stable.

P#86909 2021-01-29 08:28

distance based dimming of colors would also sell the 3d world better.

P#86911 2021-01-29 08:52
:: Mot

Might have a play with it.
It depends how expensive the pal(table, 0) command is.

P#86913 2021-01-29 10:49
:: Mot

I tried adding some black fog.

P#86916 2021-01-29 12:24

ah yes - need a richer ground texture.
Otherwise you got this strong cut off :/

P#86917 2021-01-29 13:12

I'm just sitting here reading your comments and I'm just very confused. (I dunno anything about making a Pico 8 game.)

P#86931 2021-01-29 16:13
1

Same here @jerry, I just think its neat, reminds me of poom

P#86940 2021-01-29 18:34
:: Mot
1

Making a decent looking noisy ground texture is a bit beyond my pixel art capabilities unfortunately.
I've changed it to exponential fog with a better palette sequence.

At least the hard edge is a bit further away (?)

P#86952 2021-01-29 23:33

seems to work ok

P#86958 2021-01-30 06:12

I love the map. To me this is like Castle Wolfenstein with a 3D view.

P#87323 2021-02-07 03:55 ( Edited 2021-02-07 03:57)

what happens if the CPU hits 100%

P#111681 2022-05-11 11:48
1

boom

P#111687 2022-05-11 15:01
:: IMLXH

So, can you explain how the sprite-to-sprite mapping works? Because I'm trying to write my own game with this engine, and one of the "items" (actually intended to be a moving entity) has to be twice the width of the other items, so I rotated the sprite horizontally and put it in the section with the other items. I've figured out how to get the program to display the two items side-by-side, but as this entity is roughly shaped like a person, I need to display the two squares VERTICALLY. Is there any sort of way to rotate the sprite while rendering in code, or is this engine just Not Designed For portrait-oriented objects?

P#115660 2022-08-12 17:43
:: Mot
2

@IMLXH I'll do my best :-)

So when the game starts it scans the map for tiles between index 32 and 47, in the "scanmapforobjects" function.
When it finds one, it makes an "object"

local ob={pos={x+.5,y+.5},typ=m-32,rel={0,0}}

So "pos" is the position and "typ" is a number indicating what type of object it is, which it gets from subtracting 32 from the tile index. ("rel" is a working variable that it needs later when rendering).
The "typ" will be used to determine which part of the spritesheet (tab 3) to draw.

It adds the object to a 2D grid called "objgrid", so that it can find the objects near the camera efficiently when rendering.

It draws the objects in the "drawobjs" function.
It basically scans the "objgrid" array around the camera to determine which objects to draw, calculates their position relative to the camera and draws them from back to front.
It draws them one vertical pixel column at a time, so that it can tell when parts of the object are hidden by walls.
It uses the ob.typ field to decide which part of the sprite map to draw. So typ=0 will draw the first 2x2 sprite on tab 3 of the spritemap.

Once it has calculated all of this, the actual drawing is:

sspr(sx,sy,1,16,x,y0,1,y1-y0)

For example, lets say you want to replace the vase with the red wizard from Sorcerer.
[128x32]

This is a 2x4 sprites, whereas the engine only handles 2x2 sprite objects.
But we can fix this by changing the rendering code in "drawobjs", by changing:

       -- use z buffer to determine if
       -- column is occluded by a wall
             if z<zbuf[x+1] then
              sspr(sx,sy,1,16,x,y0,1,y1-y0)
             end

to

       -- use z buffer to determine if
       -- column is occluded by a wall
             if z<zbuf[x+1] then
                if ob.typ==6 then
              sspr(sx,sy,1,32,x,y0,1,y1-y0)          
                else
              sspr(sx,sy,1,16,x,y0,1,y1-y0)
             end
             end

Basically we've changed it to draw a 32 pixel vertical strip from the spritesheet instead of 16 pixels when drawing object type 6.

We also need to tell the engine that it should be taller on screen (otherwise it will squash the wizard down to the size of the original vase object).
This is done by editing the otyp array (tab 1), and changing the 7th line from:

    {  .3,.35, .4, true},

to

    {  0,.8, .4, true},

Hopefully that helps a bit? Basically you can use the object "typ" to override the drawing code to do whatever you need it to, as long as you draw one vertical column of the sprite at a time.
I can't think of any way to make it work with rotated sprites though. I think you need to find a way to fit them into the spritesheet upright.

Regarding making it move..
you basically have to:

  1. Remove it from the "objgrid" 2D array.
  2. Update its position.
  3. Add it back into the "objgrid" 2D array.

For example, we could create a global array for tracking our wizard objects (I've called them "ghosts" in the code):

-- gameplay
ghosts={}

Then create some 'ghosts' in the _init function.

 -- add some ghosts
 for i=1,10 do
  local x,y=i*1.5+20,7.5
  local ob={pos={x,y},typ=6,rel={0,0}}
  add(objgrid[x\objgridsize+1][y\objgridsize+1],ob)
  add(ghosts,ob)
 end

The idea is to add the object to the "objgrid" and our "ghosts" array, so that the rendering code will draw them, but we can also access them easily for gameplay.

Then you can move them in _update60:

 -- move ghosts
 for ob in all(ghosts) do

  -- remove from grid
  x,y=ob.pos[1],ob.pos[2]
  del(objgrid[x\objgridsize+1][y\objgridsize+1],ob)

  -- update position
  x+=0.02
  if(x>35.5)x=20.5

  -- add back to grid
  ob.pos={x,y}
  add(objgrid[x\objgridsize+1][y\objgridsize+1],ob) 
 end

Which should result in this:

Here's an example cart with these changes:

Cart #mot_wolf_ex1-0 | 2022-08-13 | Code ▽ | Embed ▽ | Forks ▽ | License: CC4-BY-NC-SA
2

Let me know if that made any sense :-)

P#115677 2022-08-13 01:05 ( Edited 2022-08-13 01:07)
:: IMLXH

That wasn't exactly the problem I have now, but I bet it's one I'll run into at some point, so thanks!

EDIT: dumb noob questions deleted, turned out to TOTALLY be my bad lol

P#115761 2022-08-14 19:06 ( Edited 2022-08-14 20:55)

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2022-09-27 20:46:03 | 0.032s | Q:40