I have created a new side scroller action game inspired by Defender and its ilk.
You can find it on github at:
https://github.com/ixiz9/freebasic-game ... descroller
Or on itch.io at:
https://ixiz9.itch.io/alien-destroyer-sidescroller-game
Enjoy and looking forward to your comments!
Alien Destroyer - Sidescroller Game
Re: Alien Destroyer - Sidescroller Game
My my what do we have here? Seems like a collection of classic games, each of them really well done!
I was getting uneven performance in Pacman, so I went to check what was happening:
Aha! The sleep statement sleeps the thread for the specified time, but it turns out it can be interrupted when you press a key. To prevent that (and thus preventing the sleep time to be uneven), call it like this:
The second '1' tells the command to not interrupt the sleeping when you press a key. After the change, the game runs like a charm.
I like the idea of encoding the graphics/assets in the code itself. It can be further refined to have a more efficient representation, of course; but then you'll lose the ability to quickly make changes to them if needed (there's a discussion of an encoder for such a thing on another thread, meant to be able to post proof of concept code without the need to attach any extra assets )
Very nice work indeed
I was getting uneven performance in Pacman, so I went to check what was happening:
Code: Select all
...
next
draw_pman(maze_x, maze_y)
screenunlock
frame_counter += 1
frames_per_sec = frame_counter / (timer() - start_t)
sleep 1
if pman.state = DEAD then
...
Code: Select all
sleep 1, 1
I like the idea of encoding the graphics/assets in the code itself. It can be further refined to have a more efficient representation, of course; but then you'll lose the ability to quickly make changes to them if needed (there's a discussion of an encoder for such a thing on another thread, meant to be able to post proof of concept code without the need to attach any extra assets )
Very nice work indeed
Re: Alien Destroyer - Sidescroller Game
Thanks for the nice comments. Much appreciated!
Thanks also for looking at the code and identifying the issue with sleep. I had been treating sleep 1 as a kind of thread_yield(). I'm surprised it makes such a big difference since it's supposed to be measured in milliseconds. I'd thought the difference between a sleep 1 that completes and one that is interrupted would be at most 1 msec, well below what humans can detect, but that's clearly not always the case. I wonder if this is a Windows thing. Anyway, what you describe matches reports I had from another user which I couldn't reproduce and couldn't fix, so I'm very grateful for the correction. I will update the code.
Thanks also for looking at the code and identifying the issue with sleep. I had been treating sleep 1 as a kind of thread_yield(). I'm surprised it makes such a big difference since it's supposed to be measured in milliseconds. I'd thought the difference between a sleep 1 that completes and one that is interrupted would be at most 1 msec, well below what humans can detect, but that's clearly not always the case. I wonder if this is a Windows thing. Anyway, what you describe matches reports I had from another user which I couldn't reproduce and couldn't fix, so I'm very grateful for the correction. I will update the code.
Re: Alien Destroyer - Sidescroller Game
Exactly. Turns out that (if you don't specify otherwise), the default resolution for Windows is 15ms (you can't sleep below that). There are other topics here that explore this very same issue and how to change the resolution, so it might be well worth checking those.
I also like to code games (it was the reason why I started coding when I was a toddler), but I have lost most of my work on a HDD crash a while back. If you're interested, I made a simple Asteroids clone as a tutorial for a member of the Discord server. You can find it in my repo:
https://github.com/glasyalabolas/fb-asteroids
There are also lots of other stuff in there so feel free to have a look at them. Have fun
Re: Alien Destroyer - Sidescroller Game
Thanks. I've been reading through the asteroids code and found it very useful to see how you approached the problem. I like how cleanly the code is organized.
I noticed that you, like me, implemented your own keyboard handler. However, for my upcoming project I was thinking of maybe dropping that and just using Multikey(). I'm wondering if for a game where I want to react to users' keypresses that would work. Specifically I'm thinking if it's possible to miss a keypress if the user presses down and then quickly releases the key between calls to multikey(), and if it can happen, how big of an issue that is.
Do you have any thoughts?
I noticed that you, like me, implemented your own keyboard handler. However, for my upcoming project I was thinking of maybe dropping that and just using Multikey(). I'm wondering if for a game where I want to react to users' keypresses that would work. Specifically I'm thinking if it's possible to miss a keypress if the user presses down and then quickly releases the key between calls to multikey(), and if it can happen, how big of an issue that is.
Do you have any thoughts?
Re: Alien Destroyer - Sidescroller Game
Yes, it would work, and yes, it could 'miss a beat' every now and then (especially if the framerate is too low). Generally speaking it won't happen, and if it does happen how big of an issue it is depends on your game of course. In any case, working with multikey is a bit different than working with events, so write some test code so you can see how both differ. For comparison, this is the keyboard handler for multikey:ixiz9 wrote: ↑Apr 07, 2023 18:31 ...
I'm wondering if for a game where I want to react to users' keypresses that would work. Specifically I'm thinking if it's possible to miss a keypress if the user presses down and then quickly releases the key between calls to multikey(), and if it can happen, how big of an issue that is.
...
Code: Select all
/'
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( sc )
released( sc )
held( sc )
or
held( sc )
released( sc )
pressed( sc )
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.
'/
type KeyboardInput
public:
declare constructor()
declare constructor( as integer )
declare destructor()
declare sub lock()
declare sub unlock()
declare function pressed( as long ) as boolean
declare function released( as long ) as boolean
declare function held( as long, as double = 0.0 ) as boolean
declare function repeated( as long, as double = 0.0 ) as boolean
private:
enum KeyState
None
Pressed = 1 shl 0
AlreadyPressed = 1 shl 1
Released = 1 shl 2
ReleasedInitialized = 1 shl 3
Held = 1 shl 4
HeldInitialized = 1 shl 5
Repeated = 1 shl 6
RepeatedInitialized = 1 shl 7
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
'' Set and clear flags
#define SETF( _c_, _f_ ) _c_ or= ( _f_ )
#define CLRF( _c_, _f_ ) _c_ = _c_ and not ( _f_ )
'' Check flags
#define ISSET( _c_, _f_ ) cbool( _c_ and ( _f_ ) )
end type
constructor KeyboardInput()
constructor( 128 )
end constructor
constructor KeyboardInput( 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( sc as long ) as boolean
if( multiKey( sc ) ) then
_prevState( sc ) = _state( sc )
if( cbool( _prevState( sc ) = KeyState.None ) orElse _
not ISSET( _prevState( sc ), KeyState.Pressed ) ) then
SETF( _state( sc ), KeyState.Pressed )
end if
else
CLRF( _state( sc ), KeyState.Pressed )
end if
return( not ISSET( _prevState( sc ), KeyState.Pressed ) andAlso _
ISSET( _state( sc ), 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( sc as long ) as boolean
if( not multiKey( sc ) ) then
_prevState( sc ) = _state( sc )
if( not ISSET( _prevState( sc ), KeyState.ReleasedInitialized ) andAlso _
not ISSET( _state( sc ), KeyState.ReleasedInitialized ) ) then
SETF( _state( sc ), KeyState.ReleasedInitialized )
end if
SETF( _state( sc ), KeyState.Released )
else
CLRF( _state( sc ), KeyState.Released )
end if
return( ISSET( _state( sc ), KeyState.Released ) andAlso _
not ISSET( _prevState( sc ), KeyState.Released ) andAlso _
ISSET( _state( sc ), KeyState.ReleasedInitialized ) andAlso _
ISSET( _prevState( sc ), 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( sc as long, interval as double = 0.0 ) as boolean
if( multiKey( sc ) ) then
_prevState( sc ) = _state( sc )
if( not ISSET( _prevState( sc ), KeyState.HeldInitialized ) andAlso _
not ISSET( _state( sc ), KeyState.HeldInitialized ) ) then
SETF( _state( sc ), KeyState.HeldInitialized )
_heldStartTime( sc ) = timer()
end if
if( ISSET( _prevState( sc ), KeyState.Held ) orElse _
ISSET( _prevState( sc ), KeyState.HeldInitialized ) ) then
SETF( _state( sc ), KeyState.Held )
dim as double _
elapsed = ( timer() - _heldStartTime( sc ) ) * 1000.0
if( interval > 0.0 andAlso elapsed >= interval ) then
CLRF( _state( sc ), KeyState.Held )
end if
end if
else
CLRF( _state( sc ), KeyState.Held )
CLRF( _state( sc ), KeyState.HeldInitialized )
end if
return( ISSET( _prevState( sc ), KeyState.Held ) andAlso _
ISSET( _state( sc ), 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( sc as long, interval as double = 0.0 ) as boolean
dim as boolean isPressed = multiKey( sc )
if( isPressed ) then
_prevState( sc ) = _state( sc )
if( not ISSET( _prevState( sc ), KeyState.RepeatedInitialized ) andAlso _
not ISSET( _state( sc ), KeyState.RepeatedInitialized ) ) then
SETF( _state( sc ), KeyState.RepeatedInitialized )
_repeatedStartTime( sc ) = timer()
end if
if( ISSET( _prevState( sc ), KeyState.Repeated ) orElse _
ISSET( _prevState( sc ), KeyState.RepeatedInitialized ) ) then
CLRF( _state( sc ), KeyState.Repeated )
dim as double _
elapsed = ( timer() - _repeatedStartTime( sc ) ) * 1000.0
if( interval > 0.0 ) then
if( elapsed >= interval ) then
SETF( _state( sc ), KeyState.Repeated )
CLRF( _state( sc ), KeyState.RepeatedInitialized )
end if
else
return( isPressed )
end if
end if
else
CLRF( _state( sc ), KeyState.Repeated )
CLRF( _state( sc ), KeyState.RepeatedInitialized )
end if
return( not ISSET( _prevState( sc ), KeyState.Repeated ) andAlso _
ISSET( _state( sc ), KeyState.Repeated ) )
end function