Raycaster - Zombies Attack2 »
You do not own this project, so changes will not be saved
/**
* Raycaster
*
* A technical demo of a small raycaster engine.
* This is built using the (excellent) RayCaster tutorial
* here: http://lodev.org/cgtutor/raycasting.html
*
* Feel free to look through the code,
* steal ideas, or re-use for your own projects!
*
* It includes a simple raycasting engine (the Level class),
* a bobbing hand to represent the player moving (the Hand),
* and a day/night system (implemented in the Background class).
*
* For movement is uses a 'ConcanicalMover' class. Conanical
* movement is where you are fixed to tiles. You move forward
* and back one whole tile at a time, and turn in 90 degree chunks.
*
* But you can also move freely if you interface with the Level's
* 'move' and 'turn' methods directly (that gives you Wolfenstein 3D
* style movement).
*/
/*
* TODO fix Quby bug with recursively calling a def
* TODO index walls next to 0 map positions, so they are tinted when drawn
* TODO write light tinting code
*/
showFPS()
textures = new Image("wallsSheet.png")
// move speed in squares per second
$MOVE_SPEED = 0.05
// turn speed in radians per second
$TURN_SPEED = 0.15
$ZOOM_SPEED = 0.005
$BOBBING_SPEED = 0.17
$BOBBING_HEIGHT = 0.00 // 0.10
// how much to draw darkness
$SHADOW_ALPHA = 0.25
// increase/decrease these values to make the hand move
// more/less along the x and y axis
$HAND_UPDATE_SPEED = 2 / 13.0
$HAND_SWAY_X = 5
$HAND_SWAY_Y = 16
worldMap = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,0,0,0,1,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,0,8,0,0,0,1,0,1,0,1,0,8,8,0,1],
[1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1],
[1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1],
[1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1],
[1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,0,0,2,0,0,0,0,0,0,0,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,0,0,2,0,1,1,1,0,0,0,0,0,1,1,1,1],
[1,1,1,1,2,2,2,2,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1],
[1,1,1,1,2,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,1,1],
[1,1,1,1,2,8,0,0,0,0,8,0,1,1,1,1,0,0,0,0,0,1,1,0],
[1,1,1,1,2,0,0,0,0,0,0,0,0,1,0,1,1,1,1,0,0,0,1,1],
[1,1,1,1,2,2,2,2,0,0,0,0,0,1,0,1,1,1,1,0,0,0,1,1],
[1,1,1,1,1,1,1,1,0,0,2,2,0,1,0,1,1,1,1,0,8,0,1,1],
[1,1,1,1,1,1,1,1,0,0,2,0,0,1,0,1,1,1,1,0,0,0,1,1],
[1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
[1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
[1,0,0,0,1,0,0,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,0,1],
[1,0,0,0,1,1,1,1,0,0,2,0,0,1,1,1,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,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,0,0,2,2,2,2,2,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1],
[1,0,0,0,0,0,2,2,2,2,2,0,0,0,2,0,2,2,2,2,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,0,8,0,2,0,0,0,2,0,0,8,0,2,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,0,2,0,0,0,1],
[1,0,0,0,0,0,2,2,0,2,2,0,0,0,2,2,0,0,2,2,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,8,0,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,0,0,0,0,0,0,0,0,0,0,1],
[1,2,0,2,0,0,0,0,0,0,2,2,2,2,2,2,2,2,0,2,2,2,2,1],
[1,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,2,0,0,0,1],
[1,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,2,0,8,0,1],
[1,2,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,2,0,0,0,1],
[1,2,0,2,2,2,2,2,2,0,2,0,0,0,0,0,0,2,0,2,0,0,0,1],
[1,2,0,0,0,0,0,0,2,0,2,0,0,0,0,0,2,2,0,2,2,2,0,1],
[1,2,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,2,0,0,0,0,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,2,2,2,2,2,2,2,2,0,2,0,8,0,0,0,0,0,0,0,0,0,0,1],
[1,2,9,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,2,2,2,2,2,2,2,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]
// NOTE:
// 9 = player start position
// 8 = enemy position
// 0 = empty
// the rest are walls
sprites =[]
// x and y start position
startX = 41.0
startY = 12.0
worldmap.eachIndex() do |row_index|
worldmap[row_index].eachIndex() do |col_index|
if(worldmap[row_index][col_index] == 8)
sprites.push(new Sprite( new Image('sprite.png'), row_index, col_index))
end
if(worldmap[row_index][col_index] == 9)
startY = col_index
startX = row_index
end
if(worldmap[row_index][col_index] > 7)
worldmap[row_index][col_index] = 0 // remove them from the map leaving only walls
end
end
end
// x and y start position
startAngle = 180.toRadians()
back = new Background(
new Image( 'ceiling.jpg' ),
new Image( 'floor.jpg' ),
190, 29, 42,
-10, 160, 200,
0.0003
)
level = new Level( worldMap, textures, $SHADOW_ALPHA, startX, startY, startAngle , sprites)
hand = new Hand(new Image('fist.png'), new Image('clawHand.png'), 300, 180, $HAND_SWAY_X, $HAND_SWAY_Y )
controls = new FreeMover( level, $MOVE_SPEED, $TURN_SPEED/4, hand)
bob = 1
setFont( 'Arial', 16 )
onEachFrame() do |delta|
// update ceiling and floor
back.update( delta )
c = getControls()
if c.isKeyDown('c')
level.zoom( $ZOOM_SPEED*delta )
else if c.isKeyDown('z')
level.zoom( -$ZOOM_SPEED*delta )
else if c.isKeyDown('x')
level.setZoom( 0 )
end
bobHeight = bob.sin() * $BOBBING_HEIGHT
if c.isKeyDown('control')
level.setHeight( bobHeight - 0.2 )
else if
level.setHeight( bobHeight )
end
hand.update_fist(delta)
if controls.update(delta)
hand.update( delta * $HAND_UPDATE_SPEED )
bob = bob + delta*$BOBBING_SPEED
end
// render the scene
back.draw()
level.draw( back.getBrightness() )
hand.draw()
sprites.each() do |sprite|
sprite.update(delta)
end
end
class FreeMover
def new( level, moveSpeed, turnSpeed ,hand)
@level = level
@moveSpeed = moveSpeed
@turnSpeed = turnSpeed
@hand = hand
end
def update( delta )
move = @moveSpeed*delta
turn = @turnSpeed*delta
moved = true
strafed = true
turned = true
c = getControls()
// turning
if c.isKeyDown('right')
@level.turn( -turn )
else if c.isKeyDown('left')
@level.turn( turn )
else
turned = false
end
// strafing
if c.isKeyDown('d')
@level.move( 0, move )
else if c.isKeyDown('a')
@level.move( 0, -move )
else
strafed = false
end
// forward/back
if c.isKeyDown('w')
@level.move( move, 0 )
else if c.isKeyDown('s')
@level.move( -move, 0 )
else
moved = false
end
if c.isKeyDown('enter')
@hand.fist()
end
return moved or strafed or turned
end
end
/**
* This runs the day/night cycle.
* The sky, and the floor, slowly get brighter and darker
* over time.
*/
class Background
def new(
// images for top and bottom
sky, floor,
// standard color
r, g, b,
// offset colors
offsetR, offsetG, offsetB,
speed
)
@tick = 0
@offsetR = offsetR
@offsetG = offsetG
@offsetB = offsetB
@r = r
@g = g
@b = b
@speed = speed
@sky = sky
@floor = floor
end
def update( delta )
@tick = @tick + delta*@speed
end
/**
* Returns a value that represents the percentage
* between night and dark.
*
* This is a value from 0 to 1.
*
* 0.0 is the darkest point of night time, whilst
* 1.0 it the lightest point of day time.
*
* @return a Value from 0.0 to 1.0 that represents the night to day percentage.
*/
def getBrightness()
return (@tick.sin() + 1) / 2
end
def getColor()
brightness = @tick.sin()
yield @r + brightness*@offsetR,
@g + brightness*@offsetG,
@b + brightness*@offsetB
end
def draw()
brightness = @tick.sin()
this.getColor() do |r, g, b|
setColor( r, g, b )
end
drawImage( @sky, 0, 0 )
avg = ( @r + @g + @b ) / 2.5
avgOffset = ( @offsetR + @offsetG + @offsetB ) / 3
floorColor = avg + avgOffset*(brightness/3)
setColor( floorColor, floorColor, floorColor )
drawImage( @floor, 0, getScreenHeight()-@floor.getHeight() )
end
end
class Sprite
def new( img, x, y )
@img = img
@x = x
@y = y
@spriteWidth = 65
@spriteHeight = 65
@elapsed_time = 0
@time_per_frame = 5
number_of_frames = 8
@animation_length = number_of_frames * @time_per_frame
@frame = 0
end
def update(delta)
@elapsed_time = (@elapsed_time + delta) % @animation_length
@frame = (@elapsed_time / @time_per_frame).floor()
end
def x
return @x
end
def width
return @spriteWidth
end
def y
return @y
end
def drawStripe(spriteX, spriteXMax, screenX, drawStartY, drawEndY)
frame_offsetX = @spriteWidth * @frame
texX = (spriteX * @spriteWidth)/spriteXMax
setColor( 255, 255, 255 ,1)
// draw the pixels of the stripe as a vertical line
drawImage( @img, 0 + texX.floor() + frame_offsetX,0, 1, @spriteHeight, screenX , drawStartY,1,drawEndY - drawStartY)
end
end
/**
* This provides the hand,
* which shows the player.
*
* As it updates the hand bobs up and down,
* and drifts from side to side.
*
* This is to help give a walking sensation.
*/
class Hand
def new( fist_img, img, x, y, xSway, ySway )
@img = img
@current_image = img
@fist = fist_img
@x = x
@y = y
@angle = 0
@elapsed_time = 0
@xSway = xSway
@ySway = ySway
end
def fist()
@current_image = @fist
@elapsed_time = 0
end
def open()
@current_image = @img
end
def update( delta )
@angle = @angle + delta
end
def update_fist( delta )
@elapsed_time = @elapsed_time + delta
if (@elapsed_time > 15)
open()
end
end
def draw()
setColor( 255, 255, 255, 1 )
drawImage( @current_image,
@x + @angle.sin()*@xSway,
@y + @angle.cos()*@ySway
)
end
end
/**
* This class is the actual raycaster.
* You provde a map, textures,
* and a starting position and angle.
*
* The Level stores a camera inside,
* and this can be moved around with the
* 'move' and 'turn' methods.
*
* They automatically prevent walking into walls.
*
* Call 'draw' to draw the level from the current
* camera position.
*
* Each tile in the world is 1.0 wide, so if you move
* by 1.0 then you move a whole tile in one step.
*
* All rotations are in radians.
*
* The lighting is put together based on several values,
* these include:
* = the brightness given when you call draw,
* this allows you to have night-time and
* day-time sections.
* = the level of the fog-shadow,
* this affects walls in the background
* getting darker (like a fog).
* Walls close up also get a tiny bit lighter.
* = walls working in one direction are darker,
* this is to stop corners from looking flat.
*
* You can also set a shadow color which is used when
* drawing the shading. By default this is black,
* but other colours can be used to give different effects.
*/
class Level
/**
*
*
* @param map An array describing the layout of the map.
* @param textures Textures to use for rendering the map.
* @param x The starting x location for the camera
* @param y The starting y location for the camera.
* @param angle The starting angle for the camera.
*/
def new(
map, textures, shadowAlpha,
x, y, angle, sprites
)
@map = map
@textures = textures
@sprites = sprites
@posX = x.floor() + 0.5
@posY = y.floor() + 0.5
this.setAngle( angle )
@zoom = 0
@zoomX = 0
@zoomY = 0
@stretch = 0
@stretchFactor = 250
@fogShadow = .5
@fogR = 0
@fogG = 0
@fogB = 0
@onCollision = null
@shadowAlpha = shadowAlpha
@height = 0
@lightMap = null
@lightStrengthMap = null
@lights = []
@zbuffer = new Array(getScreenWidth()).fill(1000)
end
def addLights( lights )
lights.each() do |light|
@lights.add( light )
end
indexLights()
end
def addLight( light )
@lights.add( light )
indexLights()
end
def indexLights()
lightMap = {}
strengthMap = {}
@lights.each() do |light|
x = light.getX()
y = light.getY()
dist = light.getDist()
lightSearch = def( i, j, count )
lightXY = this.getWorldMap( i, j )
if lightXY.is( :number ) and lightXY == 0
index = i + '_' + j
lights = lightMap[ index ]
if lights == null
lightMap[ index ] = [ light ]
strengthMap[ index ] = [ count ]
else if ! lights.contains( light )
lightMap[ index ].add( light )
strengthMap[ index ].add( count )
end
if count > 0
// lightSearch.call([ i+1, j , count-1 ])
// lightSearch.call([ i-1, j , count-1 ])
// lightSearch.call([ i , j+1, count-1 ])
// lightSearch.call([ i , j-1, count-1 ])
end
end
end
end
@lightMap = lightMap
@lightStrengthMap = strengthMap
end
def setFog( alpha )
@fogShadow = alpha
end
def getFog()
return @fogShadow
end
def setShadow( r, g, b )
@fogR = r
@fogG = g
@fogB = b
end
def setHeight( height )
@height = height.limit( -1, 1 )
end
def getHeight()
return @height
end
/**
* A function to call when this collides with a wall.
* The action should take two parameters,
* the x and y location of the wall being collided with.
*
* This x/y location is rounded to the x/y location of
* the wall in the map.
*
* Note that this x and y location is not the location
* of the camera, but the location of the wall.
*
* Optionally, the function can have a third parameter
* this will be this level object.
*/
// def onCollision( action )
// @onCollision = action
// end
/**
* Moves the camera along the x and y axis given.
*
* The movement is in relation to the camera's
* current angle in the level.
*
* Passing in a positive value for forward, moves the
* camera forward, and a negative value moves you back.
*
* A positive value for strafe moves you to the right,
* whilst a negative value moves you to the left.
*
* For any value you don't want to use, like if you
* don't want to strafe, pass in '0'.
*
* @param forward The amount to move forward or back.
* @param strafe The amount to move the camera to the side.
*/
def move( forward, strafe )
if forward != 0
this.moveCamera( forward * @dirX, forward * @dirY )
end
if strafe != 0
this.moveCamera( strafe * @strafeX, strafe * @strafeY )
end
end
/**
* Moves the camera directly by the amounts given.
*
* The difference between 'moveCamera' and the
* other 'move' method is that this moves the camera
* directly, whilst 'move' moves the camera in relation
* to it's current angle.
*
* For most movement you should use the other
* move method instead of this one. This method
* is for absolute direct control over camera
* movement.
*
* @param moveX The amount to move the camera along the x-axis.
* @param moveY The amount to move the camera along the y-axis.
*/
def moveCamera( moveX, moveY )
newX = @posX + moveX
newY = @posY + moveY
isCollision = false
if getWorldMap( @posX.floor(), newY.floor() ) == 0
@posY = newY
else
isCollision = true
end
if getWorldMap( newX.floor(), @posY.floor() ) == 0
@posX = newX
else
isCollision = true
end
if @onCollision and isCollision
@onCollision.call([
@posX.floor(),
@posY.floor(),
this
])
end
end
/**
* Sets a new location for the camera to be
* positioned in.
*
* @param x The new x location for the camera.
* @param y The new y location for the camera.
*/
def setXY( x, y )
if getWorldMap( x.floor(), y.floor() ) == 0
@posX = x
@posY = y
end
end
/**
* The level is split into tiles,
* and the cameara moves across these.
*
* Calling this method will center the camera
* to be in the centre of which ever tile it
* is currently within.
*/
def centerXY()
this.setXY(
this.getX().floor() + 0.5,
this.getY().floor() + 0.5
)
end
/**
* @return The y location of the camera.
*/
def getX()
return @posX
end
/**
* @return The x location of the camera.
*/
def getY()
return @posY
end
/**
* Turns the camera by the amount given.
*/
def turn( rot )
// rotate
if rot != 0
rotateXY( @dirX, @dirY, rot - 90.toRadians() ) do |newX, newY|
@strafeX = newX
@strafeY = newY
end
rotateXY( @dirX, @dirY, rot ) do |newX, newY|
@dirX = newX
@dirY = newY
end
rotateXY( @planeX, @planeY, rot ) do |newX, newY|
@planeX = newX
@planeY = newY
end
rotateXY( @zoomX, @zoomY, rot ) do |newX, newY|
@zoomX = newX
@zoomY = newY
end
@angle = @angle + rot
end
end
def setAngle( angle )
// initial direction vector
@dirX = angle.cos()
@dirY = angle.sin()
strafeAngle = angle - 90.toRadians()
@strafeX = strafeAngle.cos()
@strafeY = strafeAngle.sin()
// the 2d raycaster version of camera plane
rotateXY( 0.0, -0.66, angle ) do |newX, newY|
@planeX = newX
@planeY = newY
end
@angle = angle
end
def getAngle()
return @angle
end
/**
* Increments the zoom by the amount given.
*
* The total zoom can only be between -1 and 1,
* so if the increment pushes the zoom outside of
* this range then it will be capped.
*/
def zoom( inc )
setZoom( getZoom() + inc )
end
/**
* Sets the zoom level for the camera for when the level
* is rendered.
*
* The zoom is expressed as a value from -1 to 1.
* Negative values are a negative zoom,
* positive values are zooming in,
* and 0 is no zoom at all.
*
* @param zoom The zoom level, a value from -1 to 1.
*/
def setZoom( zoom )
zoom = zoom.limit( -1.0, 1.0 ) * 0.65
@zoom = zoom
@zoomX = zoom*@dirY
@zoomY = zoom*@dirX
@stretch = zoom*@stretchFactor
end
/**
* @return The current zoom level, a value from -1.0 to 1.0.
*/
def getZoom()
return @zoom / 0.66
end
/**
* Draws the scene with no brightness set.
*
* See the other draw method for details on the level drawing.
*/
def draw()
return draw( 1.0 )
end
/**
* Draws the level, based on the current location of
* the camera.
*
* To draw the level differently, you can alter the cameras
* location, angle or zoom using the other methods.
*
* You can also set a brightness for the scene, for
* when it is rendered. This is so you can have day/night
* cycles, and sections in darkness.
*
* @param brightness A value from 0.0 (dark) to 1.0 (bright) stating the brightness of the level.
*/
def draw( brightness )
brightness = (1 - brightness.limit(0, 1)) * 0.45
shadowBrightness = (brightness+@shadowAlpha).limit(0, 1)
// get these values here to cache them,
// for faster lookup later
worldMap = @map
textures = @textures
textureSize = @textures.getHeight()
posX = @posX
posY = @posY
dirX = @dirX
dirY = @dirY
planeX = @planeX + @zoomX
planeY = @planeY + @zoomY
stretch = @stretch
rayPosX = @posX
rayPosY = @posY
drawHeight = @height
fogShadow = @fogShadow
fogR = @fogR
fogG = @fogG
fogB = @fogB
width = getScreenWidth()
height = getScreenHeight()
width.times() do |x|
// calculate ray position and direction
cameraX = 2.0 * x / (1.0*width) - 1.0 // x-coordinate in camera space
rayDirX = dirX + planeX * cameraX
rayDirY = dirY + planeY * cameraX
// which box of the map we're in
mapX = rayPosX.floor()
mapY = rayPosY.floor()
// length of ray from current position to next x or y-side
sideDistX = 0
sideDistY = 0
// length of ray from one x or y-side to next x or y-side
deltaDistX = (1.0 + ((rayDirY * rayDirY) / (rayDirX * rayDirX))).sqrt()
deltaDistY = (1.0 + ((rayDirX * rayDirX) / (rayDirY * rayDirY))).sqrt()
perpWallDist = 0
// what direction to step in x or y-direction (+1 or -1)
stepX = 0
stepY = 0
hit = 0 // was there a wall hit?
side = 0 // was a NS or a EW wall hit?
// calculate step and initial sideDist
if rayDirX < 0
stepX = -1
sideDistX = deltaDistX * (rayPosX - mapX)
else
stepX = 1
sideDistX = deltaDistX * (mapX + 1.0 - rayPosX)
end
if rayDirY < 0
stepY = -1
sideDistY = deltaDistY * (rayPosY - mapY)
else
stepY = 1
sideDistY = deltaDistY * (mapY + 1.0 - rayPosY)
end
// perform DDA
while hit == 0
// Jump to next map square,
// OR in x-direction, OR in y-direction.
if sideDistX < sideDistY
sideDistX = sideDistX + deltaDistX
mapX = mapX + stepX
side = 0
else
sideDistY = sideDistY + deltaDistY
mapY = mapY + stepY
side = 1
end
// Check if ray has hit a wall
wall = getWorldMap( mapX, mapY )
if wall == null || wall > 0
hit = 1
end
end
// Calculate distance projected on camera direction
if side == 0
temp = (mapX - rayPosX + ((1.0 - stepX) / 2.0)) / rayDirX
wallX = rayPosY + ( temp * rayDirY )
else
temp = (mapY - rayPosY + ((1.0 - stepY) / 2.0)) / rayDirY
wallX = rayPosX + ( temp * rayDirX )
end
perpWallDist = temp.abs()
@zbuffer[x] = perpWallDist
wallX = wallX - wallX.floor()
offset = ((height / perpWallDist) / 44) * drawHeight
// Calculate height of line to draw on screen
lineHeight = (height / perpWallDist).floor().abs()
percentHeight = lineHeight / textureSize
if stretch < 0 && lineHeight < -stretch
lineHeight = 1
else
lineHeight = lineHeight + stretch
end
if lineHeight > 0
// calculate lowest and highest pixel to fill in current stripe
drawStart = ( -lineHeight / 2 + height / 2 ) + offset*20
//x coordinate on the texture
texX = wallX * textureSize
if side == 0 && rayDirX > 0
texX = textureSize - texX
end
if side == 1 && rayDirY < 0
texX = textureSize - texX
end
// choose wall tex
color = getWorldMap( mapX, mapY )
if color == null
color = 0
end
texX = texX + textureSize*(color-1).min(7)
if texX < 0
texX = texX + textureSize
else if texX >= textures.getWidth()
texX = textures.getWidth() - textureSize
end
setColor( 255, 255, 255 )
// draw the pixels of the stripe as a vertical line
drawImage( textures,
texX.floor(), 0,
1, textureSize,
x, drawStart,
1, lineHeight
)
// give x and y sides different brightness
distShadow = (1-percentHeight) * fogShadow
// When your close to something, you make it brighter
// but your not a walking light, so tone it down a little.
if distShadow < 0
distShadow = distShadow/3
end
if side == 1
drawAlpha = shadowBrightness+distShadow
else
drawAlpha = brightness+distShadow
end
if drawAlpha > 0
setColor( fogR, fogG, fogB, drawAlpha )
// draw the pixels of the stripe as a vertical line
fillRect( x, drawStart-1, 1, lineHeight+1 )
end
end
end
/*
* Draw Sprites!!!
*/
w = getScreenWidth()
h = getScreenHeight()
// calculate distance to each sprite
// order sprites furthest to closest
@sprites.map() do |sprite|
spriteDistanceSquared = ((posX - sprite.x()) * (posX - sprite.x()) + (posY - sprite.y()) * (posY - sprite.y())); //sqrt not taken, unneeded
//after sorting the sprites, do the projection and draw them
return [spriteDistanceSquared, sprite]
end
@sprites.each() do |sprite|
// for(int i = 0; i < numSprites; i++) <-- one sprite for now
// {
//translate sprite position to relative to camera
spriteX = sprite.x() - posX;
spriteY = sprite.y() - posY;
//transform sprite with the inverse camera matrix
// [ planeX dirX ] -1 [ dirY -dirX ]
// [ ] = 1/(planeX*dirY-dirX*planeY) * [ ]
// [ planeY dirY ] [ -planeY planeX ]
invDet = 1.0 / (planeX * dirY - dirX * planeY); //required for correct matrix multiplication
transformX = invDet * (dirY * spriteX - dirX * spriteY);
transformY = invDet * (-planeY * spriteX + planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D
spriteScreenX = ((w / 2) * (1 + transformX / transformY)).floor(); //<<< NWT : Should this be round?
//parameters for scaling and moving the sprites
uDiv =1
vDiv =1
vMove =60.0
vMoveScreen = (vMove / transformY).round(); //<<< NWT Should this be round?
//calculate height of the sprite on screen
spriteHeight = ((h / transformY).round()).abs() / vDiv; //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
drawStartY = -spriteHeight / 2.0 + h / 2.0 + vMoveScreen;
drawEndY = spriteHeight / 2.0 + h / 2.0 + vMoveScreen;
//calculate width of the sprite
spriteWidth = ( (h / transformY).round()).abs() / uDiv;
drawStartX = spriteScreenX -(spriteWidth / 2.0) ;
//loop through every vertical stripe of the sprite on screen
// fillText( "y: " + spriteScreenY, 30, 40 )
0.upTo(spriteWidth) do |i|
stripe = drawStartX + i
//the conditions in the if are:
//1) it's in front of camera plane so you don't see things behind you
//2) it's on the screen (left)
//3) it's on the screen (right)
//4) ZBuffer, with perpendicular distance
if(transformY > 0 && stripe > 0 && stripe < w && transformY < @zbuffer[stripe])
sprite.drawStripe(i, spriteWidth, stripe, drawStartY, drawEndY)
end
end
end
/*
drawBuffer(buffer[0]);
for(int x = 0; x < w; x++) for(int y = 0; y < h; y++) buffer[x][y] = 0; //clear the buffer instead of cls()
*/
end
def getWorldMap( x, y )
row = @map[x]
if row != null
return row[y]
else
return null
end
end
end
class Light
def new(x, y, dist, strength)
@x = x
@y = y
@dist = dist
@strength = strength
@r = 255
@g = 255
@b = 255
end
def new(x, y, dist, strength, r, g, b)
@x = x
@y = y
@dist = dist
@strength = strength
@r = r
@g = g
@b = b
end
def getX()
return @x
end
def getY()
return @y
end
def getDist()
return @dist
end
end
def rotateXY( x, y, rot )
yield x * rot.cos() - y * rot.sin(),
x * rot.sin() + y * rot.cos()
end
ERRORS
- ▶Language Reference
- ▶Core
- ▶Graphics
- ▶Image Class
- new
- clear
- clearTransforms
- clone
- drawCircle
- drawEllipse
- drawImage
- drawInnerSegment
- drawLine
- drawPixel
- drawPolygon
- drawRect
- drawRotatedImage
- drawRoundedRect
- drawSegment
- drawText
- fill
- fillCircle
- fillEllipse
- fillInnerSegment
- fillPolygon
- fillRect
- fillRoundedRect
- fillSegment
- fillText
- getAlpha
- getBlue
- getColors
- getGreen
- getHeight
- getPixel
- getPixelAlpha
- getPixelBlue
- getPixelGreen
- getPixelRed
- getRed
- getWidth
- isOverlap
- isPixelOverlap
- multAlpha
- rotate
- scale
- setAlpha
- setBlend
- setColor
- setFont
- setPixel
- setTransform
- size
- transform
- translate
- undoTransform
- ▶Transformations
- How Alignment Works
- Using Colors
- drawCircle
- drawEllipse
- drawImage
- drawInnerSegment
- drawLine
- drawPixel
- drawPolygon
- drawRect
- drawRotatedImage
- drawRoundedRect
- drawSegment
- drawText
- fill
- fillCircle
- fillEllipse
- fillInnerSegment
- fillPolygon
- fillRect
- fillRoundedRect
- fillSegment
- fillText
- getAlpha
- getBlue
- getColors
- getGreen
- getRed
- multAlpha
- setAlpha
- setBlend
- setColor
- setFont
- ▶Image Class
- ▶Audio
- ▶Controls
- ▶Collisions
- ▶Utility
- ▶Debugging
