Yet Another Platformer

Game development specific discussions.
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Yet Another Platformer

Post by sero »

Greetings, I've been chipping away at a simple 2d platformer engine. The aim of this engine is to use horizontal tiled polygons instead of the more traditional tiled grid approach. I want to build this engine around responsive and enjoyable player movement using WASD + mouse control. Combat would primarily be ranged like in Metroid and Abuse with mouse aiming and camera focus being an engine feature.

I want the engine code to be open without restrictions. I am a full time student and my experience in computer programming is limited. If you may be interested in collaborating please send me a message.

>> updated May 20, 2020
GitHub repository https://github.com/Serogue72/var-engine

+ automatic resolution fit to desktop
+ blur effect ( use shift and ctrl keys )
+ shadow layer
+ background tiling
+ input polling not bound to screen frame rate
Last edited by sero on Feb 12, 2021 3:29, edited 5 times in total.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Yet Another Platformer

Post by badidea »

Hi, platform games are not my favorite type of games. Maybe I played too much of them in the past.
But I am interested in your progress updates. Also on GitHub experience, which is also on my list to learn more.

On which OS do you develop? I can report the the code seems to run fine on linux here.
Some comments:
- You write WASD, but the control are still the left/right keys. No jumping yet :-(
- If you include mouse out-of-window detection, then you can keep the view on the last valid mouse position.
- I see a lot off nearly identical lines of code and 'magic numbers' e.g: "CHUNK( 6 ).CELL( 19 ).WALL_Y_BOTTOM( 0 ) = 175". I amuse that this is only for this demo and later 'optimized away'.
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

badidea wrote:- You write WASD, but the control are still the left/right keys. No jumping yet :-(
Haha, yeah this demo is lacking a few important things like proper keyboard handling and character movement. Those are next on the list. Jumping will most definitely be an important part of that. I want to make an enjoyable character movement and control experience that the world is built around instead of adapting movement and control to fit a certain world structure.

I use a Windows 10 machine for coding. I don't intend to make this an OS specific engine. I'm glad to hear this buggy and unoptimized prototype works on Linux :)
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Yet Another Platformer

Post by badidea »

This could be an interesting read for you (if you don't already know it): http://higherorderfun.com/blog/2012/05/ ... atformers/
c-sanchez
Posts: 145
Joined: Dec 06, 2012 0:38

Re: Yet Another Platformer

Post by c-sanchez »

Platform is probably my favorite videogame genre, my two favorite are Castlevania and Megaman /Megaman X series.
The last ones I've played are AM2R, Cave Story, Axion Verge, The Mummy Demastered, Momodora: Reverie Under the Moonlight, Timespinner, Ghost 1.0, Iconoclasts, maybe some others? haha, I like really this genre :P
Now I'm playing Hollow Knight, I didn't find it very interesting at first glance, but it ended up being one of the ones I liked the most.

I would like view something nice with your engine :)
paul doe
Moderator
Posts: 1733
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Yet Another Platformer

Post by paul doe »

sero wrote:... Combat would primarily be ranged like in Metroid and Abuse with mouse aiming and camera focus being an engine feature.
...
c-sanchez wrote: ...
Platform is probably my favorite videogame genre, my two favorite are Castlevania and Megaman /Megaman X series.
...
Nice to see there's people here that appreciates good platform games ;)
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

Thanks for sharing this webpage. Having taken a look at it I suppose the structure I'm going for is a combination of Smooth Tile Based with hit boxes like in MegaMan and Vectorial like in Braid. Looks like the second half of the article is about movement Nice! That's what I'm working on next.

I've made some changes to how the keyboard and mouse work. There is also image scaling for higher resolutions. The character is now able to jump in a crude manner. GitHub has been updated.

Code: Select all

type udt_IMAGE_OLD_HEADER field = 1
	BPP : 3 as ushort
	W : 13 as ushort
	H as ushort
end type

type udt_IMAGE field = 1
	union
		OLD as udt_IMAGE_OLD_HEADER
		OLD_TYPE as ulong
	end union
	BPP as long
	W as ulong
	H as ulong
	PITCH as ulong
	RESERVED( 1 to 12 ) as ubyte
end type

type udt_MOUSE
	X as integer
	Y as integer
	W as integer
	B as integer
	PREVIOUS_B as integer
	CLIP as integer
	RESULT as long
	
	REGISTER as boolean
end type

type udt_CAMERA
  DIVIDER as single
  WIDTH_HALF as integer
  HEIGHT_HALF as integer
  X as integer
  Y as integer
end type

type udt_FRAME_RATE
  as ubyte fps_cap
  as double fps_resolution
  
  as double frame_timer_previous
  
  as double cycle_timer_difference
  as double cycle_timer_previous
  as double cycle_timer
  
  as ubyte frame_counter_sum
  as ubyte frame_counter
  
  as long sleep_time_default
  as long sleep_time_previous
  as long sleep_time
  
  declare constructor()
  declare sub START()
  declare sub REGULATE()
end type

type udt_CELL
  WALL_Y_TOP( 0 to 1 ) as integer
  WALL_SLOPE_TOP as single
  WALL_Y_BOTTOM( 0 to 1 ) as integer
  WALL_SLOPE_BOTTOM as single
end type

type udt_CHUNK
  CELL( 0 to 19 ) as udt_CELL
end type

type udt_CHARACTER
  CELL_ID as byte
  X as integer
  Y as integer
end type

type udt_DISPLAY
  as integer SOURCE_W
  as integer SOURCE_H
  as udt_IMAGE ptr SOURCE_PTR
  as integer MULTIPLIER
  as integer W
  as integer H
end type

type event field = 1
    type as long
    union
        type
            SCANCODE as long
            ascii as long
        end type
        type
            x as long
            y as long
            dx as long
            dy as long
        end type
        button as long
        z as long
        w as long
    end union
end type

type udt_KEYBOARD
  NUM_KEYS as ubyte
  KEY_ID( 0 to 5 ) as ubyte
  KEY_STATE( 0 to 5 ) as ubyte
end type

dim shared as udt_FRAME_RATE FRAME_RATE
dim shared as udt_CAMERA CAMERA
dim shared as udt_CHUNK CHUNK( 4 to 6 )
dim shared as udt_CHARACTER CHARACTER
dim shared as udt_MOUSE MOUSE
dim shared as udt_DISPLAY DISPLAY
dim shared as event E
dim shared as udt_KEYBOARD KEYBOARD

declare sub INIT_DISPLAY()
declare sub DESTROY_DISPLAY()
declare sub FLIP_DISPLAY()
declare sub INIT_CHUNK()
declare sub SWAP_CHUNKS( _direction as integer )
declare sub INIT_MOUSE()
declare sub UPDATE_MOUSE()
declare sub INIT_KEYBOARD()
declare sub UPDATE_KEYBOARD()
declare sub INIT_CHARACTER()
declare sub UPDATE_OBJECTS
declare sub INIT_CAMERA()
declare sub UPDATE_CAMERA()

INIT_DISPLAY()
INIT_CHUNK()
INIT_MOUSE()
INIT_KEYBOARD()
INIT_CHARACTER()
INIT_CAMERA()

dim as integer I, J, K
dim as ubyte L, M

FRAME_RATE.START()
do
  UPDATE_KEYBOARD()
  UPDATE_OBJECTS()
	UPDATE_MOUSE()
  UPDATE_CAMERA()
  screenlock
    cls
    
    ' dummy chunk
		for I = 0 to 19
			K = I * 12
			M = L + K
			for J = 0 to 15
				line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + J - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y + ( J * CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP ) )-( ( I * 16 ) - CAMERA.X + J - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y + ( J * CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM ) ), rgb( 255-M, M, M )
			next J
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( ( I + 1 ) * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
		for I = 0 to 19
			K = I * 12
			M = L + K
			for J = 0 to 15
				line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + J, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y + ( J * CHUNK( 5 ).CELL( I ).WALL_SLOPE_TOP ) )-( ( I * 16 ) - CAMERA.X + J, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y + ( J * CHUNK( 5 ).CELL( I ).WALL_SLOPE_BOTTOM ) ), rgb( M, 255-M, M )
			next J
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( ( I + 1 ) * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
		for I = 0 to 19
			K = I * 12
			M = L + K
			for J = 0 to 15
				line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + J + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y + ( J * CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP ) )-( ( I * 16 ) - CAMERA.X + J + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y + ( J * CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM ) ), rgb( M, M, 255-M )
			next J
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( ( I + 1 ) * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 31, 31 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
    ' 
    ' dummy character
		'console( CHARACTER.CELL_ID )
    line DISPLAY.SOURCE_PTR, ( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X - 5, CHARACTER.Y - CAMERA.Y - 7 )-( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X + 5, CHARACTER.Y - CAMERA.Y + 16 ), rgb( 255, 255, 255 ), bf
    circle DISPLAY.SOURCE_PTR, ( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X, CHARACTER.Y - CAMERA.Y ), 3, rgb( 255, 0, 0 ), , , , f
        
    draw string DISPLAY.SOURCE_PTR, ( 0, 1 ), "FPS:" & FRAME_RATE.frame_counter_sum, rgb( 255, 127, 0 )
    
    FLIP_DISPLAY()
    
    line DISPLAY.SOURCE_PTR, ( 0, 0 )-( DISPLAY.W - 1, DISPLAY.H - 1 ), rgb( 0, 0, 0), bf
  screenunlock
  L = L + 3
  FRAME_RATE.REGULATE()
loop until( KEYBOARD.KEY_STATE( 0 ) )

DESTROY_DISPLAY()
END

constructor udt_FRAME_RATE()
  fps_cap = 30
  fps_resolution = 1 / fps_cap
  
  sleep_time_default = 1000 / fps_cap
  sleep_time_previous = sleep_time_default
end constructor

sub udt_FRAME_RATE.START()
  cycle_timer_previous = timer
end sub

sub udt_FRAME_RATE.REGULATE()
  frame_counter += 1
  
  cycle_timer = timer
  cycle_timer_difference = cycle_timer - cycle_timer_previous
  if cycle_timer_difference > 1 then
    cycle_timer_previous += 1
    frame_counter_sum = frame_counter
    frame_counter = 0
  end if
  
  sleep_time = sleep_time_previous + ( ( fps_resolution - cycle_timer + frame_timer_previous ) * 1000 )
  frame_timer_previous = cycle_timer
  if sleep_time < 1 then
    sleep_time = 1
  elseif sleep_time > sleep_time_default then
    sleep_time = sleep_time_default
  end if
  
  sleep sleep_time, 1
  sleep_time_previous = sleep_time
end sub

sub INIT_DISPLAY()
  DISPLAY.SOURCE_W = 320
  DISPLAY.SOURCE_H = 180
  
  dim as integer RESOLUTION_LIST( 1 to 12, 0 to 1 )
  for i as integer = 1 to 12
    RESOLUTION_LIST( i, 0 ) = DISPLAY.SOURCE_W * i
    RESOLUTION_LIST( i, 1 ) = DISPLAY.SOURCE_H * i
  next i
  
  dim as integer DESKTOP_W, DESKTOP_H
  screeninfo DESKTOP_W, DESKTOP_H
  
  for j as integer = 12 to 1 step -1
    if ( RESOLUTION_LIST( j, 0 ) < DESKTOP_W ) and _
      ( RESOLUTION_LIST( j, 1 ) < DESKTOP_H ) then
      DISPLAY.W = RESOLUTION_LIST( j, 0 )
      DISPLAY.H = RESOLUTION_LIST( j, 1 )
      DISPLAY.MULTIPLIER = j
      exit for
    end if
  next j
  
  DISPLAY.W = 960
  DISPLAY.H = 540
  DISPLAY.MULTIPLIER = 3
  
  screencontrol 103, "GDI"
  screenres DISPLAY.W, _
            DISPLAY.H, _
            32,, _
            &h04

  DISPLAY.SOURCE_PTR = imagecreate( DISPLAY.SOURCE_W, DISPLAY.SOURCE_H )
  if DISPLAY.SOURCE_PTR = 0 then end
end sub

sub DESTROY_DISPLAY()
  imagedestroy DISPLAY.SOURCE_PTR
  screen 0
end sub

sub FLIP_DISPLAY()
  static as integer scale_counter_x, scale_counter_y
  static as integer scale_step
  static as integer scale_x_step = 0
  static as integer scale_y_step = 0
  static as ulong ptr scale_source_ptr
  static as ulong ptr scale_source_temp_ptr
  static as ulong ptr screen_ptr
  
  scale_step = ( 1 / DISPLAY.MULTIPLIER ) * 65536
  scale_source_ptr = cptr( ulong ptr, DISPLAY.SOURCE_PTR ) + 8
  scale_source_temp_ptr = scale_source_ptr
  screen_ptr = screenptr()
  
	select case DISPLAY.MULTIPLIER
	case is = 1
		put ( 0, 0 ), DISPLAY.SOURCE_PTR, pset
	case is > 1
		for scale_counter_y = 0 to ( DISPLAY.H - 1 )
			scale_source_temp_ptr = scale_source_ptr + ( scale_y_step shr 16 ) * DISPLAY.SOURCE_W
			for scale_counter_x = 0 to ( DISPLAY.W - 1 )
				*screen_ptr = scale_source_temp_ptr[ scale_x_step shr 16 ]
				screen_ptr += 1
				scale_x_step += scale_step
			next scale_counter_x
			scale_y_step += scale_step
			scale_x_step = 0
		next scale_counter_y
		scale_source_ptr = cptr( ulong ptr, DISPLAY.SOURCE_PTR ) + 8
		screen_ptr = screenptr()
		scale_y_step = 0
	case is < 1
		line ( 0, 0 )-( DISPLAY.W - 1, DISPLAY.H - 1 ), rgb( 255, 0, 255 ), bf
	end select
end sub

sub INIT_CHUNK()
  dim as integer I, J
  
	CHUNK( 4 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 4 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 4 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 4 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 4 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 4 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 8
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) + 2
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - 4
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 9 to 10
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 11 to 18
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - 2
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) + 4
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	
	CHUNK( 4 ).CELL( 19 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 4 ).CELL( 19 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 4 ).CELL( 19 ).WALL_SLOPE_TOP = 0
	CHUNK( 4 ).CELL( 19 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 4 ).CELL( 19 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 4 ).CELL( 19 ).WALL_SLOPE_BOTTOM = 0
  
	CHUNK( 5 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 5 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 5 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 5 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 5 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 5 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 19
		CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 5 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 5 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 5 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 5 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I

	CHUNK( 6 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 6 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 6 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 6 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 6 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 6 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 8
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - 1
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) + 6
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 9 to 10
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 11 to 18
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) + 1
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - 6
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	
	CHUNK( 6 ).CELL( 19 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 6 ).CELL( 19 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 6 ).CELL( 19 ).WALL_SLOPE_TOP = 0
	CHUNK( 6 ).CELL( 19 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 6 ).CELL( 19 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 6 ).CELL( 19 ).WALL_SLOPE_BOTTOM = 0
end sub

sub SWAP_CHUNKS( _direction as integer )
  dim as udt_CHUNK buffer
  select case _direction
  case 4
    buffer = CHUNK( 6 )
    CHUNK( 6 ) = CHUNK( 5 )
    CHUNK( 5 ) = CHUNK( 4 )
    CHUNK( 4 ) = buffer
  case 6
    buffer = CHUNK( 4 )
    CHUNK( 4 ) = CHUNK( 5 )
    CHUNK( 5 ) = CHUNK( 6 )
    CHUNK( 6 ) = buffer
  end select
end sub

sub INIT_MOUSE()
	MOUSE.RESULT = getmouse( MOUSE.X, MOUSE.Y, MOUSE.W, MOUSE.B, MOUSE.CLIP )
	MOUSE.PREVIOUS_B = MOUSE.B
end sub

sub UPDATE_MOUSE()
  static as integer tX, tY, tW, tB, tC
  
	MOUSE.PREVIOUS_B = MOUSE.B
	MOUSE.RESULT = getmouse( tX, tY, tW, tB, tC )
  if MOUSE.RESULT = 0 then
    MOUSE.X = tX
    MOUSE.Y = tY
    MOUSE.W = tW
    MOUSE.B = tB
    MOUSE.CLIP = tC
    
		if ( ( MOUSE.B and 1 ) or ( MOUSE.B and 2 ) ) and _
			 ( MOUSE.PREVIOUS_B = 0 ) then
			MOUSE.REGISTER = true
		else
			MOUSE.REGISTER = false
		end if
  end if
end sub

sub INIT_KEYBOARD()
  KEYBOARD.NUM_KEYS = 5
  KEYBOARD.KEY_ID( 0 ) = 1	 'ESCAPE
  KEYBOARD.KEY_ID( 1 ) = 30  'A
  KEYBOARD.KEY_ID( 2 ) = 32  'D
  KEYBOARD.KEY_ID( 3 ) = 18  'W
  KEYBOARD.KEY_ID( 4 ) = 31  'S
  KEYBOARD.KEY_ID( 5 ) = 57  'SPACEBAR
end sub

sub UPDATE_KEYBOARD()
  static as ubyte I
  while screenevent(@E)
    if E.TYPE = 1 then 'EVENT_KEY_PRESS
      for I = 0 to KEYBOARD.NUM_KEYS
        if E.SCANCODE = KEYBOARD.KEY_ID( I ) then  KEYBOARD.KEY_STATE( I ) = 1
      next I
    elseif E.TYPE = 2 then 'EVENT_KEY_RELEASE
      for I = 0 to KEYBOARD.NUM_KEYS
        if E.SCANCODE = KEYBOARD.KEY_ID( I ) then KEYBOARD.KEY_STATE( I ) = 0
      next I
    end if
  wend
end sub

sub INIT_CHARACTER()
  CHARACTER.CELL_ID = 10
  CHARACTER.X = 0
  CHARACTER.Y = CAMERA.HEIGHT_HALF
  CHARACTER.Y = CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17
end sub

sub UPDATE_OBJECTS()
  if KEYBOARD.KEY_STATE( 1 ) then
    CHARACTER.X = CHARACTER.X - 11
    if CHARACTER.X < 0 then
      CHARACTER.CELL_ID = CHARACTER.CELL_ID - 1
      CHARACTER.X = CHARACTER.X + 16
      if CHARACTER.CELL_ID = -1 then
        CHARACTER.CELL_ID = 19
        SWAP_CHUNKS( 4 )
      end if
    end if
  end if
  if KEYBOARD.KEY_STATE( 2 ) then
    CHARACTER.X = CHARACTER.X + 11
    if CHARACTER.X > 15 then
      CHARACTER.CELL_ID = CHARACTER.CELL_ID + 1
      CHARACTER.X = CHARACTER.X - 16
      if CHARACTER.CELL_ID = 20 then
        CHARACTER.CELL_ID = 0
        SWAP_CHUNKS( 6 )
      end if
    end if
  end if
  
  ' LOL JUMP & GRAVITY
  if KEYBOARD.KEY_STATE( 5 ) then
    CHARACTER.Y = CHARACTER.Y - 16
  end if
  CHARACTER.Y = CHARACTER.Y + 8
  if CHARACTER.Y > CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17 then
    CHARACTER.Y = CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17
  end if
end sub

sub INIT_CAMERA()
  CAMERA.WIDTH_HALF = DISPLAY.W / 2
  CAMERA.HEIGHT_HALF = DISPLAY.H / 2
  CAMERA.DIVIDER = 1.15
end sub

sub UPDATE_CAMERA()
  CAMERA.X = ( ( ( MOUSE.X - CAMERA.WIDTH_HALF ) / CAMERA.DIVIDER ) / DISPLAY.MULTIPLIER )
    CAMERA.X = CAMERA.X + CHARACTER.X + ( ( CHARACTER.CELL_ID - 10 ) * 16 )
  CAMERA.Y = ( ( ( MOUSE.Y - CAMERA.HEIGHT_HALF ) / CAMERA.DIVIDER ) - CAMERA.HEIGHT_HALF ) / DISPLAY.MULTIPLIER
    CAMERA.Y = CAMERA.Y + CHARACTER.Y
end sub
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

School has been keeping me busy and most my updates to this project have been minor. However, this current update is worth mentioning because character movement has been improved to include acceleration and jumping. Now the player can get a feel for character movement. The movement system and physics in this demonstration are temporary and will change over time. Also, there is support for higher resolutions using software scaling. Keyboard and mouse routines have been reworked a bit. WASD to move, spacebar to jump, and mouse to look around.

I'm going to take some time to clean up the code before I add to it. My next major goal is to make a more advanced map structure as originally planned. This will include vertical walls and branching pathways. Besides implementation this will stress the existing system so it may be another week or longer before I post an update (fingers crossed). After that is done I'll move my attention toward graphic tiling for the floor, ceiling, and walls. Also, the character will receive a sprite sheet and frame based animation.

GitHub repository => https://github.com/Serogue72/var-engine

Code: Select all

type udt_IMAGE_OLD_HEADER field = 1
	BPP : 3 as ushort
	W : 13 as ushort
	H as ushort
end type

type udt_IMAGE field = 1
	union
		OLD as udt_IMAGE_OLD_HEADER
		OLD_TYPE as ulong
	end union
	BPP as long
	W as ulong
	H as ulong
	PITCH as ulong
	RESERVED( 1 to 12 ) as ubyte
end type

type udt_MOUSE
	X as long
	Y as long
	W as long
	B as long
	PREVIOUS_B as long
	CLIP as long
	RESULT as long
	
	REGISTER as boolean
end type

type udt_CAMERA
  DIVIDER as single
  WIDTH_HALF as long
  HEIGHT_HALF as long
  X as long
  Y as long
end type

type udt_FRAME_RATE
  as ubyte fps_cap
  as double fps_resolution
  
  as double frame_timer_previous
  
  as double cycle_timer_difference
  as double cycle_timer_previous
  as double cycle_timer
  
  as ubyte frame_counter_sum
  as ubyte frame_counter
  
  as long sleep_time_default
  as long sleep_time_previous
  as long sleep_time
  
  declare constructor()
  declare sub START()
  declare sub REGULATE()
end type

type udt_CELL
  WALL_Y_TOP( 0 to 1 ) as long
  WALL_SLOPE_TOP as single
  WALL_Y_BOTTOM( 0 to 1 ) as long
  WALL_SLOPE_BOTTOM as single
end type

type udt_CHUNK
  CELL( 0 to 19 ) as udt_CELL
end type

type udt_CHARACTER
  CELL_ID as byte
  X as long
  Y as long
  H_ACC as single
  H_TH as single
  H_VEL as single
  IS_GROUND as byte
  IS_JUMP as byte
  JUMP_COUNTER_DEFAULT as ubyte
  JUMP_COUNTER as ubyte
  JUMP_TH as single
  GRAVITY_ACC as single
  GRAVITY_TH as single
  V_VEL as single
end type

type udt_FONT
  as udt_IMAGE ptr FONT_PTR
  as long W
  as long H
end type

type udt_DISPLAY
  as long SOURCE_W
  as long SOURCE_H
  as udt_IMAGE ptr SOURCE_PTR
  as long MULTIPLIER
  as long W
  as long H
  as long CURSOR_X
  as long CURSOR_Y
end type

type event field = 1
    type as long
    union
        type
            SCANCODE as long
            ascii as long
        end type
        type
            x as long
            y as long
            dx as long
            dy as long
        end type
        button as long
        z as long
        w as long
    end union
end type

type udt_KEYBOARD
  NUM_KEYS as ubyte
  KEY_ID( 0 to 5 ) as ubyte
  KEY_STATE( 0 to 5 ) as ubyte
end type

dim shared as udt_FRAME_RATE FRAME_RATE
dim shared as udt_CAMERA CAMERA
dim shared as udt_CHUNK CHUNK( 4 to 6 )
dim shared as udt_CHARACTER CHARACTER
dim shared as udt_MOUSE MOUSE
dim shared as udt_DISPLAY DISPLAY
dim shared as event E
dim shared as udt_KEYBOARD KEYBOARD

declare sub INIT_DISPLAY()
declare sub DESTROY_DISPLAY()
declare sub FLIP_DISPLAY()
declare sub INIT_CHUNK()
declare sub SWAP_CHUNKS( _direction as integer )
declare sub INIT_MOUSE()
declare sub UPDATE_MOUSE()
declare sub INIT_KEYBOARD()
declare sub UPDATE_KEYBOARD()
declare sub INIT_CHARACTER()
declare sub UPDATE_OBJECTS
declare sub INIT_CAMERA()
declare sub UPDATE_CAMERA()

INIT_DISPLAY()
INIT_CHUNK()
INIT_MOUSE()
INIT_KEYBOARD()
INIT_CHARACTER()
INIT_CAMERA()

dim as integer I, J

randomize, 3
FRAME_RATE.START()
do
  UPDATE_KEYBOARD()
  UPDATE_OBJECTS()
	UPDATE_MOUSE()
  UPDATE_CAMERA()
  screenlock
    cls
    
		for I = 0 to 19
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 - 320, CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 - 320, CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
		for I = 0 to 19
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1, CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1, CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
		for I = 0 to 19
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 + 320, CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CAMERA.Y ), rgb( 31, 31, 255 )
			line DISPLAY.SOURCE_PTR, ( ( I * 16 ) - CAMERA.X + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - CAMERA.Y )-( ( ( I + 1 ) * 16 ) - CAMERA.X - 1 + 320, CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CAMERA.Y ), rgb( 31, 255, 31 )
		next I
    
    line DISPLAY.SOURCE_PTR, ( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X, CHARACTER.Y - CAMERA.Y )-( DISPLAY.CURSOR_X, DISPLAY.CURSOR_Y ), RGB( 255, 191, 0 )
    line DISPLAY.SOURCE_PTR, ( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X - 5, CHARACTER.Y - CAMERA.Y - 7 )-( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X + 5, CHARACTER.Y - CAMERA.Y + 16 ), rgb( 255, 255, 255 ), bf
    circle DISPLAY.SOURCE_PTR, ( ( CHARACTER.CELL_ID * 16 ) + CHARACTER.X - CAMERA.X, CHARACTER.Y - CAMERA.Y ), 3, rgb( 255, 0, 0 ), , , , f
    
    draw string DISPLAY.SOURCE_PTR, ( 1, 2 ), "FPS:" & FRAME_RATE.frame_counter_sum, rgb( 255, 127, 0 )
    draw string DISPLAY.SOURCE_PTR, ( 1, 14 ), "VERTICAL:" & cast( byte, CHARACTER.V_VEL ), rgb( 255, 127, 0 )
    if CHARACTER.IS_JUMP then draw string DISPLAY.SOURCE_PTR, ( 1, 26 ), "JUMP", rgb( 255, 127, 0 )
    
    FLIP_DISPLAY()
    
    line DISPLAY.SOURCE_PTR, ( 0, 0 )-( DISPLAY.W - 1, DISPLAY.H - 1 ), rgb( 0, 0, 0), bf
  screenunlock
  FRAME_RATE.REGULATE()
loop until( KEYBOARD.KEY_STATE( 0 ) )

DESTROY_DISPLAY()

constructor udt_FRAME_RATE()
  fps_cap = 30
  fps_resolution = 1 / fps_cap
  
  sleep_time_default = 1000 / fps_cap
  sleep_time_previous = sleep_time_default
end constructor

sub udt_FRAME_RATE.START()
  cycle_timer_previous = timer
end sub

sub udt_FRAME_RATE.REGULATE()
  frame_counter += 1
  
  cycle_timer = timer
  cycle_timer_difference = cycle_timer - cycle_timer_previous
  if cycle_timer_difference > 1 then
    cycle_timer_previous += 1
    frame_counter_sum = frame_counter
    frame_counter = 0
  end if
  
  sleep_time = sleep_time_previous + ( ( fps_resolution - cycle_timer + frame_timer_previous ) * 1000 )
  frame_timer_previous = cycle_timer
  if sleep_time < 1 then
    sleep_time = 1
  elseif sleep_time > sleep_time_default then
    sleep_time = sleep_time_default
  end if
  
  sleep sleep_time, 1
  sleep_time_previous = sleep_time
end sub

sub INIT_DISPLAY()
  DISPLAY.SOURCE_W = 320
  DISPLAY.SOURCE_H = 180
  
  dim as integer RESOLUTION_LIST( 1 to 12, 0 to 1 )
  for i as integer = 1 to 12
    RESOLUTION_LIST( i, 0 ) = DISPLAY.SOURCE_W * i
    RESOLUTION_LIST( i, 1 ) = DISPLAY.SOURCE_H * i
  next i
  
  dim as integer DESKTOP_W, DESKTOP_H
  screeninfo DESKTOP_W, DESKTOP_H
  
  for j as integer = 12 to 1 step -1
    if ( RESOLUTION_LIST( j, 0 ) < DESKTOP_W ) and _
      ( RESOLUTION_LIST( j, 1 ) < DESKTOP_H ) then
      DISPLAY.W = RESOLUTION_LIST( j, 0 )
      DISPLAY.H = RESOLUTION_LIST( j, 1 )
      DISPLAY.MULTIPLIER = j
      exit for
    end if
  next j
  
  DISPLAY.W = 640
  DISPLAY.H = 360
  DISPLAY.MULTIPLIER = 2
  
  screencontrol 103, "GDI"
  screenres DISPLAY.W, _
            DISPLAY.H, _
            32,, _
            &h04

  DISPLAY.SOURCE_PTR = imagecreate( DISPLAY.SOURCE_W, DISPLAY.SOURCE_H )
  if DISPLAY.SOURCE_PTR = 0 then end
end sub

sub DESTROY_DISPLAY()
  imagedestroy DISPLAY.SOURCE_PTR
  screen 0
end sub

sub FLIP_DISPLAY()
  static as integer scale_counter_x, scale_counter_y
  static as integer scale_step
  static as integer scale_x_step = 0
  static as integer scale_y_step = 0
  static as ulong ptr scale_source_ptr
  static as ulong ptr scale_source_temp_ptr
  static as ulong ptr screen_ptr
  
  scale_step = ( 1 / DISPLAY.MULTIPLIER ) * 65536
  scale_source_ptr = cptr( ulong ptr, DISPLAY.SOURCE_PTR ) + 8
  scale_source_temp_ptr = scale_source_ptr
  screen_ptr = screenptr()
  
	select case DISPLAY.MULTIPLIER
	case is = 1
		put ( 0, 0 ), DISPLAY.SOURCE_PTR, pset
	case is > 1
		for scale_counter_y = 0 to ( DISPLAY.H - 1 )
			scale_source_temp_ptr = scale_source_ptr + ( scale_y_step shr 16 ) * DISPLAY.SOURCE_W
			for scale_counter_x = 0 to ( DISPLAY.W - 1 )
				*screen_ptr = scale_source_temp_ptr[ scale_x_step shr 16 ]
				screen_ptr += 1
				scale_x_step += scale_step
			next scale_counter_x
			scale_y_step += scale_step
			scale_x_step = 0
		next scale_counter_y
		scale_source_ptr = cptr( ulong ptr, DISPLAY.SOURCE_PTR ) + 8
		screen_ptr = screenptr()
		scale_y_step = 0
	case is < 1
		line ( 0, 0 )-( DISPLAY.W - 1, DISPLAY.H - 1 ), rgb( 255, 0, 255 ), bf
	end select
end sub

sub INIT_CHUNK()
  dim as integer I, J
  
	CHUNK( 4 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 4 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 4 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 4 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 4 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 4 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 8
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) + 2
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - 4
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 9 to 10
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 11 to 18
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) - 2
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 4 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) + 4
		CHUNK( 4 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 4 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	
	CHUNK( 4 ).CELL( 19 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 4 ).CELL( 19 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 4 ).CELL( 19 ).WALL_SLOPE_TOP = 0
	CHUNK( 4 ).CELL( 19 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 4 ).CELL( 19 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 4 ).CELL( 19 ).WALL_SLOPE_BOTTOM = 0
  
	CHUNK( 5 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 5 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 5 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 5 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 5 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 5 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 19
		CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 5 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 5 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 5 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 5 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 5 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 5 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I

	CHUNK( 6 ).CELL( 0 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 6 ).CELL( 0 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 6 ).CELL( 0 ).WALL_SLOPE_TOP = 0
	CHUNK( 6 ).CELL( 0 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 6 ).CELL( 0 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 6 ).CELL( 0 ).WALL_SLOPE_BOTTOM = 0

	for I = 1 to 8
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) - 1
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) + 6
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 9 to 10
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 )
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 )
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	for I = 11 to 18
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_TOP( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) + 1
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_TOP = ( CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_TOP( 0 ) ) / 16
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) = CHUNK( 6 ).CELL( I - 1 ).WALL_Y_BOTTOM( 1 )
		CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) = CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) - 6
		CHUNK( 6 ).CELL( I ).WALL_SLOPE_BOTTOM = ( CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 1 ) - CHUNK( 6 ).CELL( I ).WALL_Y_BOTTOM( 0 ) ) / 16
	next I
	
	CHUNK( 6 ).CELL( 19 ).WALL_Y_TOP( 0 ) = 64
	CHUNK( 6 ).CELL( 19 ).WALL_Y_TOP( 1 ) = 64
	CHUNK( 6 ).CELL( 19 ).WALL_SLOPE_TOP = 0
	CHUNK( 6 ).CELL( 19 ).WALL_Y_BOTTOM( 0 ) = 175
	CHUNK( 6 ).CELL( 19 ).WALL_Y_BOTTOM( 1 ) = 175
	CHUNK( 6 ).CELL( 19 ).WALL_SLOPE_BOTTOM = 0
end sub

sub SWAP_CHUNKS( _direction as integer )
  dim as udt_CHUNK buffer
  select case _direction
  case 4
    buffer = CHUNK( 6 )
    CHUNK( 6 ) = CHUNK( 5 )
    CHUNK( 5 ) = CHUNK( 4 )
    CHUNK( 4 ) = buffer
  case 6
    buffer = CHUNK( 4 )
    CHUNK( 4 ) = CHUNK( 5 )
    CHUNK( 5 ) = CHUNK( 6 )
    CHUNK( 6 ) = buffer
  end select
end sub

sub INIT_MOUSE()
	MOUSE.RESULT = getmouse( MOUSE.X, MOUSE.Y, MOUSE.W, MOUSE.B, MOUSE.CLIP )
	MOUSE.PREVIOUS_B = MOUSE.B
end sub

sub UPDATE_MOUSE()
  static as integer tX, tY, tW, tB, tC
  
	MOUSE.PREVIOUS_B = MOUSE.B
	MOUSE.RESULT = getmouse( tX, tY, tW, tB, tC )
  if MOUSE.RESULT = 0 then
    MOUSE.X = tX
    MOUSE.Y = tY
    MOUSE.W = tW
    MOUSE.B = tB
    MOUSE.CLIP = tC
    
		if ( ( MOUSE.B and 1 ) or ( MOUSE.B and 2 ) ) and _
			 ( MOUSE.PREVIOUS_B = 0 ) then
			MOUSE.REGISTER = true
		else
			MOUSE.REGISTER = false
		end if
  end if
  DISPLAY.CURSOR_X = MOUSE.X / DISPLAY.MULTIPLIER
  DISPLAY.CURSOR_Y = MOUSE.Y / DISPLAY.MULTIPLIER
end sub

sub INIT_KEYBOARD()
  KEYBOARD.NUM_KEYS = 5
  KEYBOARD.KEY_ID( 0 ) = 1	 'ESCAPE
  KEYBOARD.KEY_ID( 1 ) = 30  'A
  KEYBOARD.KEY_ID( 2 ) = 32  'D
  KEYBOARD.KEY_ID( 3 ) = 18  'W
  KEYBOARD.KEY_ID( 4 ) = 31  'S
  KEYBOARD.KEY_ID( 5 ) = 57  'SPACEBAR
end sub

sub UPDATE_KEYBOARD()
  static as ubyte I
  while screenevent(@E)
    if E.TYPE = 1 then 'EVENT_KEY_PRESS
      for I = 0 to KEYBOARD.NUM_KEYS
        if E.SCANCODE = KEYBOARD.KEY_ID( I ) then  KEYBOARD.KEY_STATE( I ) = 1
      next I
    elseif E.TYPE = 2 then 'EVENT_KEY_RELEASE
      for I = 0 to KEYBOARD.NUM_KEYS
        if E.SCANCODE = KEYBOARD.KEY_ID( I ) then KEYBOARD.KEY_STATE( I ) = 0
      next I
    end if
  wend
end sub

sub INIT_CHARACTER()
  CHARACTER.CELL_ID = 10
  CHARACTER.X = 0
  CHARACTER.Y = CAMERA.HEIGHT_HALF
  CHARACTER.Y = CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17
  CHARACTER.H_ACC = 2.667
  CHARACTER.H_TH = 7
  CHARACTER.H_VEL = 0
  CHARACTER.IS_GROUND = 1
  CHARACTER.IS_JUMP = 0
  CHARACTER.JUMP_COUNTER_DEFAULT = 11
  CHARACTER.JUMP_COUNTER = CHARACTER.JUMP_COUNTER_DEFAULT
  CHARACTER.JUMP_TH = -7
  CHARACTER.GRAVITY_ACC = 1.334
  CHARACTER.GRAVITY_TH = 7
  CHARACTER.V_VEL = 0
end sub

sub UPDATE_OBJECTS()
  if KEYBOARD.KEY_STATE( 1 ) and KEYBOARD.KEY_STATE( 2 ) then
    ' CHANGE NOTHING
  elseif KEYBOARD.KEY_STATE( 1 ) then
    CHARACTER.H_VEL = CHARACTER.H_VEL - CHARACTER.H_ACC
    if CHARACTER.H_VEL < -CHARACTER.H_TH then CHARACTER.H_VEL = -CHARACTER.H_TH
  elseif KEYBOARD.KEY_STATE( 2 ) then
    CHARACTER.H_VEL = CHARACTER.H_VEL + CHARACTER.H_ACC
    if CHARACTER.H_VEL > CHARACTER.H_TH then CHARACTER.H_VEL = CHARACTER.H_TH
  else
    select case CHARACTER.H_VEL
    case 0
      
    case is > 0
      CHARACTER.H_VEL = CHARACTER.H_VEL - CHARACTER.H_ACC
      if CHARACTER.H_VEL < 0 then CHARACTER.H_VEL = 0
    case else
      CHARACTER.H_VEL = CHARACTER.H_VEL + CHARACTER.H_ACC
      if CHARACTER.H_VEL > 0 then CHARACTER.H_VEL = 0
    end select
  end if

  CHARACTER.X = CHARACTER.X + CHARACTER.H_VEL

  if CHARACTER.X < 0 then
    CHARACTER.CELL_ID = CHARACTER.CELL_ID - 1
    CHARACTER.X = CHARACTER.X + 16
    if CHARACTER.CELL_ID = -1 then
      CHARACTER.CELL_ID = 19
      SWAP_CHUNKS( 4 )
    end if
  end if
  if CHARACTER.X > 15 then
    CHARACTER.CELL_ID = CHARACTER.CELL_ID + 1
    CHARACTER.X = CHARACTER.X - 16
    if CHARACTER.CELL_ID = 20 then
      CHARACTER.CELL_ID = 0
      SWAP_CHUNKS( 6 )
    end if
  end if

  if KEYBOARD.KEY_STATE( 5 ) then
    if CHARACTER.IS_GROUND = 1 then
      CHARACTER.IS_GROUND = 0
      CHARACTER.IS_JUMP = 1
      CHARACTER.JUMP_COUNTER = CHARACTER.JUMP_COUNTER_DEFAULT
      CHARACTER.V_VEL = CHARACTER.JUMP_TH
    else
      if CHARACTER.IS_JUMP = 1 then
        CHARACTER.JUMP_COUNTER -= 1
      else
        CHARACTER.V_VEL += CHARACTER.GRAVITY_ACC
      end if
    end if
    if CHARACTER.JUMP_COUNTER = 0 then
      CHARACTER.JUMP_COUNTER = CHARACTER.JUMP_COUNTER_DEFAULT
      CHARACTER.IS_JUMP = 0
    end if
  else
    CHARACTER.IS_JUMP = 0
    if CHARACTER.IS_GROUND = 0 then
      CHARACTER.V_VEL += CHARACTER.GRAVITY_ACC
    else
      CHARACTER.V_VEL += CHARACTER.GRAVITY_ACC
    end if
    CHARACTER.JUMP_COUNTER = CHARACTER.JUMP_COUNTER_DEFAULT
  end if
  
  if CHARACTER.V_VEL > CHARACTER.GRAVITY_TH then CHARACTER.V_VEL = CHARACTER.GRAVITY_TH
  CHARACTER.Y = CHARACTER.Y + CHARACTER.V_VEL
  if CHARACTER.Y > CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17 then
    CHARACTER.Y = CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_BOTTOM( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_BOTTOM ) - 17
    CHARACTER.IS_GROUND = 1
    CHARACTER.IS_JUMP = 0
  end if
  if CHARACTER.Y < CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_TOP( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_TOP ) + 8 then
    CHARACTER.Y = CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_Y_TOP( 0 ) + ( CHARACTER.X * CHUNK( 5 ).CELL( CHARACTER.CELL_ID ).WALL_SLOPE_TOP ) + 8
    CHARACTER.V_VEL = 0
    CHARACTER.IS_JUMP = 0
  end if
end sub
sub INIT_CAMERA()
  CAMERA.WIDTH_HALF = DISPLAY.W / 2
  CAMERA.HEIGHT_HALF = DISPLAY.H / 2
  CAMERA.DIVIDER = 1.15
end sub

sub UPDATE_CAMERA()
  CAMERA.X = ( ( ( MOUSE.X - CAMERA.WIDTH_HALF ) / CAMERA.DIVIDER ) / DISPLAY.MULTIPLIER )
    CAMERA.X = CAMERA.X + CHARACTER.X + ( ( CHARACTER.CELL_ID - 10 ) * 16 )
  CAMERA.Y = ( ( ( MOUSE.Y - CAMERA.HEIGHT_HALF ) / CAMERA.DIVIDER ) - CAMERA.HEIGHT_HALF ) / DISPLAY.MULTIPLIER
    CAMERA.Y = CAMERA.Y + CHARACTER.Y
end sub
paul doe
Moderator
Posts: 1733
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Yet Another Platformer

Post by paul doe »

@sero: looking nice so far! Ah, they don't make them like Abuse anymore, don't they? Did you, by any chance, toyed with the Abuse level editor? Or its Lisp code?
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

paul doe wrote:Did you, by any chance, toyed with the Abuse level editor? Or its Lisp code?
The reason I know of Abuse is because I had a demo of it on my old Macintosh computer back when it first came out. This game provided a keyboard + mouse combination that I'd never encountered before. This combination that has become so standard in first person shooters works well in 2d. At the time I was still playing first person shooters with the keyboard only. Doom, Duke Nukem 3d, and Marathon were perfectly fine games without the mouse. Abuse was the game that initiated me into mouse look. Half Life was what eventually fully converted me. I remember my young self looking into the map editor being awed and confused. I don't remember anything about LISP code...

@paul - If Abuse really is your jam then maybe you've heard of Butcher? https://www.gog.com/game/butcher
paul doe
Moderator
Posts: 1733
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Yet Another Platformer

Post by paul doe »

sero wrote:The reason I know of Abuse is because I had a demo of it on my old Macintosh computer back when it first came out. This game provided a keyboard + mouse combination that I'd never encountered before. ...
Indeed, it was a novelty at that time (circa 1995). Along with the engine's lighting system.
sero wrote:...
This combination that has become so standard in first person shooters works well in 2d. At the time I was still playing first person shooters with the keyboard only. Doom, Duke Nukem 3d, and Marathon were perfectly fine games without the mouse. Abuse was the game that initiated me into mouse look. Half Life was what eventually fully converted me.
...
Yes, most modern games eventually adopted the scheme. Pity they didn't adopted the concept of 'fun' along with it.
sero wrote:...
I remember my young self looking into the map editor being awed and confused. I don't remember anything about LISP code...
...
The game engine was coded (mostly; there were some precompiled sections) in Lisp, and the registered version came with full source code available (another uncommon practice at the time). The editor made full use of that, and used the concept of 'links' (which were all those gray lines you saw crisscrossing the screen). If you didn't managed to make some levels, you can do so now (there's a Win32 port of the game, totally free and authorized by the original authors), and redefine the concept of 'amazing'. Might give you quite a few ideas for the editor for your engine.
sero wrote:...
@paul - If Abuse really is your jam then maybe you've heard of Butcher? https://www.gog.com/game/butcher
No, I hadn't. Will have a look at it. Been ages since I buyed something from GoG (my last purchase was Planescape Torment, which I missed back in the day; came bundled with Tyrian 2000)
paul doe
Moderator
Posts: 1733
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Yet Another Platformer

Post by paul doe »

@sero: You might find this abstraction useful (I just finished coding it for another project). It is a keyboard handler, similar to multiKey() but more functional:

Code: Select all

#include once "fbgfx.bi"

#define fmod( n, d ) _
  ( cdbl( n ) - int( ( n ) / ( d ) ) * cdbl( d ) )
#define min( a, b ) _
  iif( ( a ) < ( b ), ( a ), ( b ) )
#define max( a, b ) _
  iif( ( a ) > ( b ), ( a ), ( b ) )
#define clamp( v, a, b ) _
  max( a, min( v, b ) )
#define wrap( v, a, b ) _
  ( ( ( ( v ) - ( a ) ) mod ( ( b ) - ( a ) ) ) + _
  ( ( b ) - ( a ) ) ) mod ( ( b ) - ( a ) ) + ( a )
#define fwrap( v, a, b ) _
  fmod( ( _
    fmod( ( ( v ) - ( a ) ), _
      ( ( b ) - ( a ) ) ) + ( ( b ) - ( a ) ) ), _
      ( ( b ) - ( a ) ) + ( a ) )

/'
  Represents the input received from the keyboard.
  
  There are four states that can be queried: pressed, released,
  held and repeated. All of them are:
  
  * Independent
    You don't need to query one to get the report for another. That is to
    say, you don't need to query 'pressed()' before querying 'released()':
    they will report their correct status when you query them individually.
  
  * Order-invariant
    Doesn't matter the order in which you place their queries in the code:
    
    pressed( scanCode )
    released( scanCode )
    held( scanCode )
    
    or
    
    held( scanCode )
    released( scanCode )
    pressed( scanCode )
    
    will net the same results, in the order you would expect. Said order, 
    should you query all of them for the same key, will also be invariant:
    
      pressed
      { held
        repeated } -> If you specify intervals; otherwise they'll get
      released        reported in the order in which you query them
  
  See the comments on the method definitions for specificities about each 
  one.
  
  TODO:
    - Implement key state pushing/popping from an internal stack (to make
      the abstraction self-contained).
'/
type _
  KeyboardInput
  
  public:
    declare constructor()
    declare constructor( _
      byval as integer )
    declare destructor()
    
    declare sub _
      lock()
    declare sub _
      unlock()
    
    declare function _
      pressed( _
        byval as long ) _
      as boolean
    declare function _
      released( _
        byval as long ) _
      as boolean
    declare function _
      held( _
        byval as long, _
        byval as double => 0.0 ) _
      as boolean
    declare function _
      repeated( _
        byval as long, _
        byval as double => 0.0 ) _
      as boolean
    
  private:
    enum _
      KeyState
        None
        Pressed => 1
        Released => 2
        ReleasedInitialized => 4
        Held => 8
        HeldInitialized => 16
        Repeated => 32
        RepeatedInitialized => 64
    end enum
    
    '' These will store the bitflags for the key states
    as ubyte _
      _state( any ), _
      _prevState( any )
    
    /'
      Caches when a key started being held/repeated
    '/
    as double _
      _heldStartTime( any ), _
      _repeatedStartTime( any )
    
    /'
      For using the abstraction in a multithread engine
    '/
    as any ptr _
      _mutex
end type

constructor _
  KeyboardInput()
  
  this.constructor( 128 )
end constructor

constructor _
  KeyboardInput( _
    byval aNumberOfKeys as integer )
  
  dim as integer _
    keys => iif( aNumberOfKeys < 128, _
      128, aNumberOfKeys )
  
  redim _
    _state( 0 to keys - 1 )
  redim _
    _prevState( 0 to keys - 1 )
  redim _
    _heldStartTime( 0 to keys - 1 )
  redim _
    _repeatedStartTime( 0 to keys - 1 )
  
  _mutex => mutexCreate()
end constructor

destructor _
  KeyboardInput()
  
  mutexDestroy( _mutex )
end destructor

/'
  If you are using this in a multithreaded engine, you'll probably have to
  acquire the lock before the keyboard is handled, and release it after the
  handling with this instance has ended. This will prevent some very nasty
  hangs, depending on what method you use to render the screen.
'/
sub _
  KeyboardInput.lock()
  
  mutexLock( _mutex )
end sub

sub _
  KeyboardInput.unlock()
  
  mutexUnlock( _mutex )
end sub

/'
  Returns whether or not a key was pressed.
  
  'Pressed' in this context means that the method will return 'true'
  *once* upon a key press. If you press and hold the key, it will
  not report 'true' until you release the key and press it again.
'/
function _
  KeyboardInput.pressed( _
    byval scanCode as long ) _
  as boolean
  
  if( multiKey( scanCode ) ) then
    _prevState( scanCode ) => _state( scanCode )
    
    if( _
      cbool( _prevState( scanCode ) = KeyState.None ) orElse _
      not cbool( _prevState( scanCode ) and KeyState.Pressed ) ) then
      
      _state( scanCode ) or=> KeyState.Pressed
    end if
  else
    _state( scanCode ) => _state( scanCode ) and not KeyState.Pressed
  end if
  
  return( _
    not cbool( _prevState( scanCode ) and KeyState.Pressed ) andAlso _
    cbool( _state( scanCode ) and KeyState.Pressed ) )
end function

/'
  Returns whether or not a key was released.
  
  'Released' means that a key has to be pressed and then released for
  this method to return 'true' once, just like the 'pressed()' method
  above.
'/
function _
  KeyboardInput.released( _
    byval scanCode as long ) _
  as boolean
  
  if( not multiKey( scanCode ) ) then
    _prevState( scanCode ) => _state( scanCode )
    
    if( _
      not cbool( _prevState( scanCode ) and KeyState.ReleasedInitialized ) andAlso _
      not cbool( _state( scanCode ) and KeyState.ReleasedInitialized ) ) then
      
      _state( scanCode ) or=> KeyState.ReleasedInitialized
    end if
    
    _state( scanCode ) or=> KeyState.Released
  else
    _state( scanCode ) => _state( scanCode ) and not KeyState.Released
  end if
  
  return( _
    cbool( _state( scanCode ) and KeyState.Released ) andAlso _
    not cbool( _prevState( scanCode ) and KeyState.Released ) andAlso _
    cbool( _state( scanCode ) and KeyState.ReleasedInitialized ) andAlso _
    cbool( _prevState( scanCode ) and KeyState.ReleasedInitialized ) )
end function

/'
  Returns whether or not a key is being held.
  
  'Held' means that the key was pressed and is being held pressed, so the
  method behaves pretty much like a call to 'multiKey()', if the 'interval'
  parameter is unspecified.
  
  If an interval is indeed specified, then the method will report the 'held'
  status up to the specified interval, then it will stop reporting 'true'
  until the key is released and held again.
  
  Both this and the 'released()' method expect their intervals to be expressed
  in milliseconds.
'/
function _
  KeyboardInput.held( _
    byval scanCode as long, _
    byval interval as double => 0.0 ) _
  as boolean
  
  if( multiKey( scanCode ) ) then
    _prevState( scanCode ) => _state( scanCode )
  
    if( _
      not cbool( _prevState( scanCode ) and KeyState.HeldInitialized ) andAlso _
      not cbool( _state( scanCode ) and KeyState.HeldInitialized ) ) then
      
      _state( scanCode ) or=> KeyState.HeldInitialized
      _heldStartTime( scanCode ) => timer()
    end if
    
    if( _
      cbool( _prevState( scanCode ) and KeyState.Held ) orElse _
      cbool( _prevState( scanCode ) and KeyState.HeldInitialized ) ) then
      
      _state( scanCode ) or=> KeyState.Held
      
      dim as double _
        elapsed => ( timer() - _heldStartTime( scanCode ) ) * 1000.0
      
      if( _
        interval > 0.0 andAlso _
        elapsed >= interval ) then
        
        _state( scanCode ) => _state( scanCode ) and not KeyState.Held
      end if
    end if
  else
    _state( scanCode ) => _state( scanCode ) and not KeyState.Held
    _state( scanCode ) => _state( scanCode ) and not KeyState.HeldInitialized
  end if
  
  return( _
    cbool( _prevState( scanCode ) and KeyState.Held ) andAlso _
    cbool( _state( scanCode ) and KeyState.Held ) )
end function

/'
  Returns whether or not a key is being repeated.
  
  'Repeated' means that the method will intermittently report the 'true'
  status once 'interval' milliseconds have passed. It can be understood
  as the autofire functionality of some game controllers: you specify the
  speed of the repetition using the 'interval' parameter.
  
  Bear in mind, however, that the *first* repetition will be reported
  AFTER one interval has elapsed. In other words, the reported pattern is 
  [pause] [repeat] [pause] instead of [repeat] [pause] [repeat].
  
  If no interval is specified, the method behaves like a call to
  'multiKey()'.
'/
function _
  KeyboardInput.repeated( _
    byval scanCode as long, _
    byval interval as double => 0.0 ) _
  as boolean
  
  dim as boolean _
    isPressed => multiKey( scanCode )
  
  if( isPressed ) then
    _prevState( scanCode ) => _state( scanCode )
  
    if( _
      not cbool( _prevState( scanCode ) and KeyState.RepeatedInitialized ) andAlso _
      not cbool( _state( scanCode ) and KeyState.RepeatedInitialized ) ) then
      
      _state( scanCode ) or=> KeyState.RepeatedInitialized
      _repeatedStartTime( scanCode ) => timer()
    end if
    
    if( _
      cbool( _prevState( scanCode ) and KeyState.Repeated ) orElse _
      cbool( _prevState( scanCode ) and KeyState.RepeatedInitialized ) ) then
      
      _state( scanCode ) => _state( scanCode ) and not KeyState.Repeated
      
      dim as double _
        elapsed => ( timer() - _repeatedStartTime( scanCode ) ) * 1000.0
      
      if( interval > 0.0 ) then
        if( elapsed >= interval ) then
          _state( scanCode ) or=> KeyState.Repeated
          _state( scanCode ) => _state( scanCode ) and not KeyState.RepeatedInitialized
        end if
      else
        return( isPressed )
      end if
    end if
  else
    _state( scanCode ) => _state( scanCode ) and not KeyState.Repeated
    _state( scanCode ) => _state( scanCode ) and not KeyState.RepeatedInitialized
  end if
  
  return( _
    not cbool( _prevState( scanCode ) and KeyState.Repeated ) andAlso _
    cbool( _state( scanCode ) and KeyState.Repeated ) )
end function

/'
  Test code
'/
dim as integer _
  screenWidth => 800, _
  screenHeight => 600

screenRes( _
  screenWidth, screenHeight, 32 )

dim as integer _
  x => 400, _
  y => 300, _
  radius => 10, _
  speed => 3

/'
  Some simple test code.
  
  Use the arrow keys to move the white ball. Each key is handled
  by a different method:
  
                   up
                repeated
  
    left          down          right
  released       pressed        held
  
  In the context of a platformer, these can be very handy to
  implement several things in a straightforward manner. Here are
  some ideas:
  
  Firing:
    Use the 'pressed()' method to begin firing. Afterwards, if the
    weapon is continuous, use the 'held()' method. Otherwise, the
    'repeated()' method can trigger firing at the desired rate.
  
  Jumping:
    At the start of the jump, use the 'pressed()' method to initiate
    it. Afterwards, if you want to implement variable-height jumping
    ala Mario, use the 'held()' method to add a small value to the
    vertical acceleration. The interval will thus determine the
    maximum height of a full jump (tapping/helding the jump key for
    shorter periods will result in lower jumps).
  
  Charging (ala Mega Buster):
    Use the 'pressed()' method for the shot button to initiate the
    charge (cache the starting time), and the 'released()' method to
    trigger the release of the charged shot. The elapsed time between
    both methods determines the power of the shot.
  
  Running (ala River City Ransom/Double Dragon/Countless others)
    Running is frequently implemented as a double tap of the directional
    keys. For this, use two 'pressed()' calls: the first will start
    caching the time between presses, and the second will determine the
    elapsed time between both. If it is below some threshold, initiate
    running and handle it with the 'held()' method.
  
  Tapping/Double tapping
    Same as above, but also with the 'pressed()' and 'released()' methods
    (for tapping).
    Some games (notably Beyond Oasis for the MegaDrive) used a tap/hold
    control scheme: if you tap the C button you'll jump, if you hold it
    you'll crouch. Makes for some quite interesting control schemes that
    discourage button mashing gameplay.
  
  Special powers ala Denjin Makai II (MAME)/Spawn(SNES):
    One of the coolest and funniest side-scrolling Beat'em Up that I've
    ever played was Guardians/Denjin Makai II (you can play it nowadays
    thanks to MAME).
    This game boasted a whooping 8 different characters to play as, each
    with its own set of special powers. These were performed simply by
    helding the attack/special button, making a (very simple) sequence
    of moves, and then releasing the button. The character then unleashed
    the attack which, thanks to this scheme, could easily be comboed
    with normal attacks. Highly recommended game.
    Implementing this is easy enough: use the 'pressed()' method to signal
    the start of a special move. Then, as long as 'held()' is 'true',
    handle 'pressed()' methods to collect the moves that are part of the
    special. Upon 'released()', if the collected moves match some special
    power, unleash!
  
  Capcom-style specials
    As the above, but simply collect the moves as the keys are 'pressed()'.
    'Collecting moves' is simple enough to do with bitflags (and a long can
    represent up to 32 sequential moves so it should be plenty) and activate
    the appropriate power depending on the bitflags.
  
  And lots of others...
'/

'' Create one instance of the KeyboardInput 'class'...
var _
  aKey => KeyboardInput()

'' ..and then use it pretty much like 'multiKey()'
do
  /'
    These brackets are needed only if the code path that handles the keys
    can be reached by more than one thread to ensure atomicity. Otherwise,
    you can safely omit them. I left them here for documentation purposes.
  '/
  aKey.lock()
  
  '' Handle some keypresses
  if( aKey.repeated( Fb.SC_UP, 200.0 ) ) then
    y => max( 0, y - 50 )
	end if
		
  if( aKey.pressed( Fb.SC_DOWN ) ) then
    y => min( screenHeight, y + 50 ) 
  end if
  
  if( aKey.released( Fb.SC_LEFT ) ) then
    x => fwrap( x - 50, 0, screenWidth )
	end if
  
  if( aKey.held( Fb.SC_RIGHT, 500.0 ) ) then
    x => fwrap( x + 1, 0, screenWidth )
  end if
  
  aKey.unlock()
  
  '' Render frame
  screenLock()
    cls()
    
    circle _
      ( x, y ), _
      radius, rgb( 255, 255, 255 ), , , , f
  screenUnlock()
  
  sleep( 1, 1 )
loop until( _
  aKey.pressed( Fb.SC_ESCAPE ) )
Based on some old code that was used by some folks here (most notably, Pitto used it in its entry for Lachie's Compo), and has proven its worth. This one's way better, but has not seen that many battles yet. I'm now coding the same abstraction but for handling mouse input. Hope you find it useful, and give it some shakin'!

[UPDATE]: Exposed a mutex through a simple interface, to be able to use it in a multithreaded environment.
Last edited by paul doe on Nov 14, 2019 21:46, edited 1 time in total.
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

paul doe wrote:You might find this abstraction useful
Wow, this is great. I will adapt this to what I've got going on with my project.

I currently use event over multikey since I noticed some thread here in the forums discussing multikey not being reliably platform transferable. The functions here make sense to me and I think would easily work with event as well. I'll be able to give this more attention over the weekend when I have time.

Two things: I noticed that timer() was called multiple times. Would having one global timer variable work in this example? Would there be performance benefit from only having to call timer() once per cycle? Also, you've got multiplication by 1000 in some functions. Could this be made more efficient by getting rid of the multiplication by 1000? I get that the timer is a decimal thing based in units that are 1/1000. Couldn't you save on needing to multiply by 1000 by instead passing a time that aligns with the timer? Instead of passing 500 as half a second you could pass 0.5 and not multiply. Maybe I'm just being nitpicky.
paul doe
Moderator
Posts: 1733
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Yet Another Platformer

Post by paul doe »

sero wrote:
paul doe wrote:You might find this abstraction useful
...
I currently use event over multikey since I noticed some thread here in the forums discussing multikey not being reliably platform transferable.
Why? Are you planning on creating an engine that also runs on Android/iPhone? If that's the case, you shouldn't even use FBGFX and rely on other, more portable library (like SDL2). Even then, porting FreeBasic code to other platforms besides the ones it natively supports isn't nearly as fun as you might think (see the posts by TJF on the subject)
...
The functions here make sense to me and I think would easily work with event as well. I'll be able to give this more attention over the weekend when I have time.
...
Indeed, it can work with events too (adapting it for them does have some wrinkles, though).
...
Two things: I noticed that timer() was called multiple times. Would having one global timer variable work in this example?
What for? Global variables aren't needed nor useful. For anything. Ever. And a class/type that uses one internally? An abomination.
...
Would there be performance benefit from only having to call timer() once per cycle?
...
Sure. It'll speed up the code when you run it in your abacus by ~34.76%.
...
Also, you've got multiplication by 1000 in some functions. Could this be made more efficient by getting rid of the multiplication by 1000? I get that the timer is a decimal thing based in units that are 1/1000. Couldn't you save on needing to multiply by 1000 by instead passing a time that aligns with the timer? Instead of passing 500 as half a second you could pass 0.5 and not multiply.
And saving on one multiplication (9 cycles)? Buddy, in a real game, there are far worse things than this. And, I want to specify the intervals in milliseconds.
...
Maybe I'm just being nitpicky.
Very ;)

You see, the code, as laid out, meets exactly the requirements that I need, and is laid out exactly as I intended (see the comments on the code). Yours may be different, naturally. I just shared it in case somebody might find it useful. Feel free to butcher/torture/add global variables to it as you see fit XD

BTW: small update to be able to use it also on a multithreaded engine.
sero
Posts: 59
Joined: Mar 06, 2018 13:26
Location: USA

Re: Yet Another Platformer

Post by sero »

@paul - Can you explain what you mean by this ( the mutex lock/unlock ):

Code: Select all

do
  /'
    These brackets are needed only if the code path that handles the keys
    can be reached by more than one thread to ensure atomicity. Otherwise,
    you can safely omit them. I left them here for documentation purposes.
  '/
  aKey.lock()
  ...
  aKey.unlock()
Post Reply