FUNGICIDE - Retro Game Competition Entry

Started by Scaremonger, January 19, 2018, 23:40:38

Previous topic - Next topic

Scaremonger

FUNGICIDE
The Earth has been infected by Alien Spores!
They arrive on Meteors from infected Asteroids and grow into Giant Fungus!
Your mission is to destroy them before they take over the planet!

Controls:






MovementCursor Keys (Up, Down, Left, Right)
Main WeaponSpace
BombZ
Quit or PauseEscape
ScreenshotF12

Game available for Windows and Linux.

I have not documented the dependencies for Linux, but it should run on your system if you already use BlitzMax.

Please post a screenshot of your high score table. I'd like to see how far you guys get :)

Conjured Entertainment

Quote from: Scaremonger on January 19, 2018, 23:40:38

Please post a screenshot of your high score table. I'd like to see how far you guys get :)

You got it...

Very retro and fun to play.

I really like this game.


iWasAdam


therevills

For some reason the ship moves very slowly up and down - was this on purpose?

Scaremonger

Quote from: Conjured Entertainment on January 20, 2018, 03:00:29
Very retro and fun to play.
I really like this game.

Thanks. Always good to hear that.

Quote from: therevills on January 21, 2018, 04:22:55
For some reason the ship moves very slowly up and down - was this on purpose?

Yeah, I slowed it down because it was starting to cause problems hitting the ground all the time. Maybe I overdid it a little.



Steve Elliott

#5
Yes it's far too slow - quick change the speed before anybody else notices ;D
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Scaremonger

Quote from: Steve Elliott on January 21, 2018, 11:14:48
Yes it's far too slow - quick change the speed before anybody else notices ;D

On a plus note. You can't hit the ground so easily.  Lol


RemiD

I have just tested your game.

It works well on my computer/os (Windows 7 64bits)

Graphics : nice, consistant style. And it looks good even when scaled (not like my game :-p)
Sounds : cool effects , thanks for the option to configure the volume
Controls : easy enough to understand how to play
Gameplay : original world/enemies, shoot and hit, fun and addicitive, however instead of moving toward left or moving toward right, i would have added a way to move the ship more quickly or less quickly to be able to avoid the "spores"

Conjured Entertainment

#9
Okay guys, where are the high score images?  :o

There are so many great games in this competition, and my top 3 were very close and almost equal favorites.

The thing that set this apart for me from most of the other games (including mine) was the length of play.

While my game and others had a set number of levels for a limited time of play, this one ends based on skill and increasing difficulty.

Most retro games had limited time to play with fixed ends to the game after the first batch of arcade machines, once the operators realized that allowing a player to marathon play on Asteroids or Pac-Man for hours on one quarter did not result in very much revenue for the arcade.

Then came games like Pole Position that had a fixed number of laps for a quick but exciting game, and it became the highest grossing arcade machine for the year it was released, so operators looked for games that offered fixed play.

Games like mine, or Get to Da Chopper, have a fixed number of levels for quick play and a decisive end, but Fungicide uses increasing difficulty to make the game play short lived, especially for noobs honing their Fungicide skills.

I get to the end of the other games, like mine, and feel a level of accomplishment for 'beating the game', but games like Fungicide give me that feeling every time I beat my personal best score, because increasing my skills can keep extending the game time.

Also, the fixed number of digits for the score is classic, because you want to make it to 100,000 and get rid of that leading zero, but that seems to be nearly impossible with the increase in spore population in later waves.

This game keeps me wanting to go back and do more damage, and fight my way to that 100k.

Classic, Epic, and tons of Retro fun!

Despite the few bugs in the game restarts, this one is my favorite, and now you know why.




Scaremonger

Quote from: RemiD on January 22, 2018, 21:04:34Graphics : nice, consistant style. And it looks good even when scaled (not like my game :-p)
Sounds : cool effects , thanks for the option to configure the volume Controls : easy enough to understand how to play
Gameplay : original world/enemies, shoot and hit, fun and addicitive,
Appreciate that. Good to hear. Thanks.

Quote from: RemiD on January 22, 2018, 21:04:34
however instead of moving toward left or moving toward right, i would have added a way to move the ship more quickly or less quickly to be able to avoid the "spores"
Turbo mode. That is simple to add and is now on my list for post competition changes along with improved Up/Down controls and fixing the online high score system that I had to comment out at the last minute!

Quote from: Conjured Entertainment on January 23, 2018, 01:07:45
Okay guys, where are the high score images?  :o
Yeah, please upload them. The online high score system is non functional and I'm going to have to add you all manually when I fix it.

Quote from: Conjured Entertainment on January 23, 2018, 01:07:45
The thing that set this apart for me from most of the other games (including mine) was the length of play.

While my game and others had a set number of levels for a limited time of play, this one ends based on skill and increasing difficulty.

Most retro games had limited time to play with fixed ends to the game after the first batch of arcade machines, once the operators realized that allowing a player to marathon play on Asteroids or Pac-Man for hours on one quarter did not result in very much revenue for the arcade.

Then came games like Pole Position that had a fixed number of laps for a quick but exciting game, and it became the highest grossing arcade machine for the year it was released, so operators looked for games that offered fixed play.

Games like mine, or Get to Da Chopper, have a fixed number of levels for quick play and a decisive end, but Fungicide uses increasing difficulty to make the game play short lived, especially for noobs honing their Fungicide skills.

I get to the end of the other games, like mine, and feel a level of accomplishment for 'beating the game', but games like Fungicide give me that feeling every time I beat my personal best score, because increasing my skills can keep extending the game time.

Also, the fixed number of digits for the score is classic, because you want to make it to 100,000 and get rid of that leading zero, but that seems to be nearly impossible with the increase in spore population in later waves.

This game keeps me wanting to go back and do more damage, and fight my way to that 100k.

Classic, Epic, and tons of Retro fun!

Despite the few bugs in the game restarts, this one is my favorite, and now you know why.
Thanks for the vote. Pleased you like it.

I had not thought about levels and increased difficulty in that way before. I remember the days playing games like defender for hours on one coin and then spending a fortune trying to finish dragons lair (never did).

Fungicide originally had four enemy types designed and the graphics for them are already drawn. They were introduced at different levels but two of them never made it due to time restraints (except the shrooms which I use as a desktop icon in Windows). I hope I get to finish this at some point.




Derron

#11
Was not really able to play it on linux (excuse the big GIF file)





Game stucked here and there - sometimes ship was "invisible" (think it passed a barrier in the "stuck"-loops).
Used Wine then to run the windows binary... way smoother (but still slight stucks here and there).
The startscreen (story + testers) is not in the rotation of control and highscore. Escape did not return to the titlescreen. It went away too fast so I was not able to completely read the "story" .


"Z" is placed on the "Y" key on German keyboard layouts...


Why do small "moving" fungicides give less points than the easier to hit bigger enemies?


Highscore initials are tough to make: similar to Adams SyntaxBomb you need to hit the keys for each up/down movement. KeyDown() and a lastDownTime-comparison would help here.


My Wife told me that the sounds reminded her on the old Atari 2600 games.




bye
Ron


Scaremonger

@Derron: Thanks for the feedback, much appreciated.

Invisible ship! Hmm, not seen that myself, but looking at the gif when the meteor arrives there is a gap in the tail which shows its stuttering. That can only happen when the game loop tries to play catch up and DeltaTime from the last loop cycle is a large duration and so it moves a long distance. Can't think what would be doing that.

What version of Linux are you using? It was developed mostly on my 32bit Linux Mint notebook and runs much smoother than on windows. I didn't try Wine myself.

I hadn't considered the story to be part of the lobby system, but I'll release another version after the competition with some changes and this is now on the list. I've also added the Z and Y keys issue to my list, although I'm not sure what to do about it at the moment.

I see what you mean about the points. Should be more points for the spore as it's more difficult to hit.

High score system is already on my list.


Derron

I also made sure to not have a >28 days uptime session (to avoid borked up delta timing because of the integer wrap).

Dunno what caused this. Am running Linux Mint 18.3 64Bit.
If you want you could code me a simple "game loop"-example outputting the necessary stuff.


My game loop looks like this:
Code (BlitzMax) Select

[...]
   Method RunRender()
      'the time available for rendering has to consider the time
      'used for updating - so subtract that from the accumulator
      _renderAccumulator:+ Max(0, _lastLoopTime - getCurrentLoopTime()) / 1000.0

      if(_renderAccumulator > _renderRate)
         local start:Long = Time.GetTimeGone()

         'if there is a function connected - run it
         if _funcRender then _funcRender()

         'subtract the time reserved for a render from the accumulator
         _renderAccumulator = 0

         'for stats
         timesRendered:+1

         _renderTime :+ (Time.GetTimeGone() - start)
      endif
   End Method

   Method RunUpdate()
      'each loop the looptime is added to an accumulator
      'as soon as the accumulator is bigger than the time reserved
      'for an update ("timeStep"), the loop does as much updates
      'as the accumulator "fits"
      _updateAccumulator:+ Max(0, _lastLoopTime)/1000.0

      local start:Long = Time.GetTimeGone()
     
      while(_updateAccumulator > _updateRate)
         'if there is a function connected - run it
         if _funcUpdate then _funcUpdate()

         'subtract the time reserved for an update from the accumulator
         _updateAccumulator = Max(0.0, _updateAccumulator - _updateRate)
         'for stats
         timesUpdated:+1
      wend

      _updateTime :+ (Time.GetTimeGone() - start)
   End Method


   Method RunSystemUpdate()
      'each loop the looptime is added to an accumulator
      'as soon as the accumulator is bigger than the time reserved
      'for an system update ("timeStep"), the loop does as much updates
      'as the accumulator "fits"
      _systemUpdateAccumulator:+ Max(0, _lastLoopTime)/1000.0

      local start:Long = Time.GetTimeGone()
     
      while(_systemUpdateAccumulator > _systemUpdateRate)
         'if there is a function connected - run it
         if _funcSystemUpdate then _funcSystemUpdate()

         'subtract the time reserved for an update from the accumulator
         _systemUpdateAccumulator = Max(0.0, _systemUpdateAccumulator - _systemUpdateRate)
         'for stats
         timesSystemUpdated:+1
      wend

      _systemUpdateTime :+ (Time.GetTimeGone() - start)
   End Method


   Method Loop()
      'compute time last loop neeeded
      '1/2: compute delta
      _lastLoopTime  = Time.GetTimeGone() - _loopBeginTime
      '2/2: store for next run
      _loopBeginTime = Time.GetTimeGone()

      'update values for FPS/UPS stats
      updateStatistics()

      'the loop time is limited to 250 ms to avoid spiral of dead
      _lastLoopTime  = Min(250, _lastLoopTime)


      RunSystemUpdate()
      RunUpdate()
      RunRender()

      'for looptime-average-calculation
      _loopTimeSum :+ GetCurrentLoopTime()
      _loopTimeCount :+ 1

      'if there was time left but no updates need to be done
      'ALTERNATIV?: hierfuer feststellen wieviel zeit ein loop haette
      'und wieviel davon benutzt worden ist... den rest dann "delayen"
      if _renderRate > 0 and (_systemUpdateRate*1000 + _updateRate*1000 - getCurrentLoopTime() > 0)
         delay(1)
      endif
   End Method



And it works for me (and some hundreds/thousands of users) on Linux, Mac and Windows - at least nobody created issues about timing problems.



bye
Ron

Scaremonger

Quote from: Derron on January 28, 2018, 07:34:49
If you want you could code me a simple "game loop"-example outputting the necessary stuff.

Thanks @Derron, I'll transplant your game loop code into the sample and see what I get.

I have drawn up the following sample using the game loop from Fungicide and adding 1000 sprites it seems to run smoothly. On my old  Dell Latitude D620! (32bit running Linux Mint), I get 35fps for 1000 sprites and 53fps for 100 sprites which was better than I expected.

I'm using a MilliSecsLong() function based on some code by ImaginaryHuman in my Delta Time code.

Code (BlitzMax) Select

SuperStrict
?linux
'# Fix for Linux Mint / Ubuntu
Import "-ldl"
?Win32
'# Windows Application Icon
' Import "bin/ProjectIcon.o"
?

'# Randomise the game
SeedRnd MilliSecs()

'# Frame and game timings
Global fps:Float = 0
Global NextFrame:Long = MilliSecsLong()
Global KeyFrame:Long = MilliSecsLong()

'# Start the game by Launching the lobby
AppTitle = "MyGame"
Global game:TEngine = New TEngine.Create()
Global player:TPlayer

HideMouse()

'# Add some sample "enemies"
For Local n:Int = 1 To 100
New TEnemy.Create()
Next

While game.running

'# Delta Time
Delta.Update()

'# Collisions
ResetCollisions()

'# Allow key-press every 16ms
If MilliSecsLong()> KeyFrame Then
KeyFrame = MilliSecsLong() + 16
game.update( Delta.time, True )
Else
game.update( Delta.time, False )
End If


'# REFRESH SCREEN 60FPS
If MilliSecsLong() > nextframe Then

'# Clear the screen
SetClsColor( $f,$f,$f )
Cls

' game.backdrop()
game.draw()
If player Then player.draw()

SetColor( $ff,$ff,$ff )
DrawText( "FPS:"+FramesPerSecond(), 0,game.GRH-game.TXH )

Flip 0
nextframe = MilliSecsLong() + 16.7 '60 Frames per second
End If

Wend

'############################################################
'# Delta Time
'# Based on code by WAVE:
'# http://www.truplo.com/blitzmaxbeginnersguide/wave18.html
'#
'# Uses LONG miliisecs functions to overcome INT over-run after 28 days.
Type Delta
Global _last:Long = MilliSecsLong()
Global _now:Long  = Delta.Update()
'#
Global Time:Float
'------------------------------------------------------------
Function Update()
_now  = MilliSecsLong()
Time  = Float( _now - _last ) / 60.0
_last = _now
End Function
End Type

'############################################################
'# Fix for Millisecs() integer over-run
'# Based on code by ImaginaryHuman:
'# http://www.blitzbasic.com/Community/post.php?topic=84114&post=950107
Function MilliSecsLong:Long()
Global MilliSeconds:Long=0:Long 'Initialize before use
Global LastMilliSeconds:Long=get() 'Initialize before use
Return get()
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Function get:Long()
Local Milli:Long=Long(MilliSecs())+2147483648:Long           'Convert to 32-bit unsigned
If Milli<LastMilliSeconds Then MilliSeconds:+4294967296:Long 'Accumulate 2^32
LastMilliSeconds=Milli
Return MilliSeconds+Milli
End Function
End Function

'############################################################
Function FramesPerSecond:Int()
Global _counter:Int, _time:Long, _frames:Int
Local _now:Long
_counter :+ 1
_now = MillisecsLong()
If _time < _now Then
_frames  = _counter
_time    = _now + 1000 '# Update again in 1 second
_counter = 0
End If
Return _frames
End Function

'############################################################
Type TEngine
Field running:Int = True '# Application is still running
Field paused:Int = False '# Game has been paused
Field enemies:Int = 0
Field GRW:Int = 640
Field GRH:Int = 480
Field TXH:Int = 0 '# Height of text

'------------------------------------------------------------
Method Create:TEngine()
Graphics( GRW, GRH )
TXH = TextHeight("8")
Return Self
End Method

'------------------------------------------------------------
' UPDATE ALL SPRITES AND GAME STATES
Method update( delta:Float, keystate:Int=False )
'#
If AppTerminate() Then running = False
'# Update all Sprites
Enemies = 0
For Local Sprite:TSprite = EachIn TSprite.list
If( TEnemy(Sprite) ) Then Enemies:+1
Sprite.update( delta, keystate )
Next
End Method

'------------------------------------------------------------
' DRAW OBJECTS
Method draw()
For Local Sprite:TSprite = EachIn TSprite.list
Sprite.draw()
Next
End Method

'------------------------------------------------------------
Method quit()
running = False
End Method

End Type

'############################################################
Type TSprite
Global list:TList = CreateList()
Field x:Float, y:Float
Field vx:Float, vy:Float
Field speed:Float = 2.0
Field size:Float = 3.0
Field r:Int=$FF, g:Int=$FF, b:Int=$FF

Method update( delta:Float, keystate:Int )
x:+vx*speed*delta
y:+vy*speed*delta
If x+size>game.GRW Then
x  = game.GRW-(x-game.GRW)
vx = -vx
End If
If x-size<0 Then
x  = -x
vx = -vx
End If
If y+size>game.GRH Then
y  = game.GRH-(y-game.GRH)
vy = -vy
End If
If y-size<0 Then
y  = -y
vy = -vy
End If
End Method

Method draw()
SetColor( r, g, b )
DrawOval( x-size,y-size,size*2, size*2)
End Method
End Type

'############################################################
Type TEnemy Extends TSprite
Field points:Int = 10
Method New()
ListAddLast( list, Self )
End Method

Method Create()
x=Rand(size, game.GRW-size)
y=Rand(size, game.GRW-size)
vx=RndFloat()*5-2.5
vy=RndFloat()*5-2.5
g=Rand(0,8)*32
b=Rand(0,8)*32
End Method
End Type

'############################################################
Type TPlayer Extends TSprite
Field angle:Int =0

Method Create()
x=game.GRW/2
y=game.GRH/2
End Method

Method update( delta:Float, keystate:Int )
' If keystate Then
' If KeyDown( KEY_SPACE ) Then fire()
' End If
End Method

Method draw()
SetColor( r, g, b )
DrawRect( x-size,y-size,x+size,y+size)
End Method

Method fire()
End Method

End Type