Yet Another Platformer
Yet Another Platformer
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
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.
Re: Yet Another Platformer
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'.
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'.
Re: Yet Another Platformer
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.badidea wrote:- You write WASD, but the control are still the left/right keys. No jumping yet :-(
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 :)
Re: Yet Another Platformer
This could be an interesting read for you (if you don't already know it): http://higherorderfun.com/blog/2012/05/ ... atformers/
Re: Yet Another Platformer
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 :)
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 :)
Re: Yet Another Platformer
sero wrote:... Combat would primarily be ranged like in Metroid and Abuse with mouse aiming and camera focus being an engine feature.
...
Nice to see there's people here that appreciates good platform games ;)c-sanchez wrote: ...
Platform is probably my favorite videogame genre, my two favorite are Castlevania and Megaman /Megaman X series.
...
Re: Yet Another Platformer
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.badidea wrote:http://higherorderfun.com/blog/2012/05/ ... atformers/
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
Re: Yet Another Platformer
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
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
Re: Yet Another Platformer
@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?
Re: Yet Another Platformer
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 doe wrote:Did you, by any chance, toyed with the Abuse level editor? Or its Lisp code?
@paul - If Abuse really is your jam then maybe you've heard of Butcher? https://www.gog.com/game/butcher
Re: Yet Another Platformer
Indeed, it was a novelty at that time (circa 1995). Along with the engine's lighting system.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. ...
Yes, most modern games eventually adopted the scheme. Pity they didn't adopted the concept of 'fun' along with it.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.
...
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:...
I remember my young self looking into the map editor being awed and confused. I don't remember anything about LISP code...
...
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)sero wrote:...
@paul - If Abuse really is your jam then maybe you've heard of Butcher? https://www.gog.com/game/butcher
Re: Yet Another Platformer
@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:
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.
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 ) )
[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.
Re: Yet Another Platformer
Wow, this is great. I will adapt this to what I've got going on with my project.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. 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.
Re: Yet Another Platformer
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)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.
Indeed, it can work with events too (adapting it for them does have some wrinkles, though)....
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.
...
What for? Global variables aren't needed nor useful. For anything. Ever. And a class/type that uses one internally? An abomination....
Two things: I noticed that timer() was called multiple times. Would having one global timer variable work in this example?
Sure. It'll speed up the code when you run it in your abacus by ~34.76%....
Would there be performance benefit from only having to call timer() once per cycle?
...
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....
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.
Very ;)...
Maybe I'm just being nitpicky.
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.
Re: Yet Another Platformer
@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()