Log In  


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

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 }

40


1

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

1

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


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


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


I tried adding some black fog.


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


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


1

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


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 (?)


seems to work ok


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


what happens if the CPU hits 100%


1

boom


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?


3

@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 ▽ | License: CC4-BY-NC-SA
3

Let me know if that made any sense :-)


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



[Please log in to post a comment]