[Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library - DEV

Developed or developing a new product in PureBasic? Tell the world about it.
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

[Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library - DEV

Post by Mijikai »

RetroPixel GFX Library - DEV
A EXPERIMENTAL (OLDSCHOOL) 2D 8-BIT 256 COLOR SOFTWARE RENDERER
Written in PureBasic 6.0 (C-Backend) and FASM 1.73.30

Image

Download (v.alpha 13): https://www.dropbox.com/s/etjp55fauk3ow ... 3.zip?dl=0

Documentation: https://www.dropbox.com/s/shjnreko4fd3y ... 0.txt?dl=0

Turorials (1 - 5): viewtopic.php?p=590990#p590990



Features:
  • supports palettes, sprites, tiles, layers, fonts, animations, clipping, collisions (pixel & rect), input handling (mouse & keyboard)
  • supports normal and relative mouse positions (window or screenspace) plus clipping & hiding of the cursor
  • full access to all surface (frame, sprite, tile) and palette buffers
  • screenshots of surfaces in native or screen resolution
  • support for bitmap images (direct) and for any other image format indirect (handle to a bitmap)
  • basic drawing functions dot, line, square, fill
  • palette color cycling and palette presets (grayscale, vga ,ega, cga, random)
  • all surfaces can draw or manipulate each other (there are also effect filters)
  • change the rendersize (native resolution) and toggle between fullscreen and windowed mode on the fly
  • can render into a window or a window control (ex. canvas)
  • it is possible to retain the aspect ratio if the host window or control gets resized
  • comes with a custom fileformat to store and load palettes and surfaces
  • garbage collection ensures that all resources get cleaned up on release
  • its definetly fast enough for small to midsize retro game projects
  • blitter (drawing) callbacks allow for the easy implementation of custom filters and effects

Cons:
  • may flicker (depending on the hardware)
  • scaling may cause artefacts
  • its not that fast (it is a software renderer after all)
  • no rotation for sprites
  • documentation is incomplete (i will release tutorials and see where this goes)


Interface:

Code: Select all

EnableExplicit

;--------------------------------------------------------------------------------------
; RPix - RetroPixel GFX Library
; A EXPERIMENTAL (OLDSCHOOL) 2D 8-BIT 256 COLOR SOFTWARE RENDERER
; Written in PureBasic 6.0 C-Backend and FASM 1.73.30
;--------------------------------------------------------------------------------------
; Version: dev.25.05 (alpha 11)
; Author: Mijikai
; Platform: Windows (x64)
;--------------------------------------------------------------------------------------
; Copyright 2022 by Mijikai all rights reserved
;--------------------------------------------------------------------------------------
; License:
; Attribution-NonCommercial-NoDerivatives 4.0 International:
; https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
;--------------------------------------------------------------------------------------

Import "rpix.lib"
  rpixInterface.i(WindowHandle.i,ScreenWidth.i = #Null,ScreenHeight.i = #Null,Aspect.i = #False,FrameRate.i = 60)
  rpixWindowOpen.i(WindowType.i,Width.i,Height.i,ScreenWidth.i = #Null,ScreenHeight.i = #Null,Aspect.i = #False,FrameRate.i = 60)
  rpixWindowTitle(*Interface,Title.s)
  rpixWindowExit.i(*Interface)
  rpixWindowClose.i(*Interface)
  rpixVersion.i()
EndImport

#RPIX_VERSION = $0011

Enumeration RPIX_WINDOW
  #RPIX_WINDOW_NORMAL
  #RPIX_WINDOW_SCREEN
  #RPIX_WINDOW_POPUP
  #RPIX_WINDOW_SIZEABLE
EndEnumeration

Enumeration RPIX_PALETTE
  #RPIX_PALETTE_GRAYSCALE
  #RPIX_PALETTE_VGA
  #RPIX_PALETTE_EGA
  #RPIX_PALETTE_CGA
  #RPIX_PALETTE_RANDOM
EndEnumeration

Enumeration RPIX_ROP
  #RPIX_ROP_COPY
  #RPIX_ROP_MASK
  #RPIX_ROP_TINT
  #RPIX_ROP_TEST
  #RPIX_ROP_PROC
EndEnumeration

Enumeration RPIX_CHANGE
  #RPIX_MATH_ADD
  #RPIX_MATH_MUL
  #RPIX_FILTER_1
  #RPIX_FILTER_2
  #RPIX_FILTER_3
  #RPIX_FILTER_4
  #RPIX_FILTER_5
  #RPIX_FILTER_6
  #RPIX_FILTER_7
  #RPIX_FILTER_8
EndEnumeration

Structure RPIX_PIXEL_STRUCT
  p.a[0]
EndStructure

Structure RPIX_RECT_STRUCT
  x.l
  y.l
  w.l
  h.l
EndStructure

Structure RPIX_DRAW_STRUCT
  *dst.RPIX_PIXEL_STRUCT
  dst_span.i
  dst_rect.RPIX_RECT_STRUCT
  *src.RPIX_PIXEL_STRUCT
  src_span.i
  src_rect.RPIX_RECT_STRUCT
  *parameter
EndStructure

Interface RPIX
  WindowHandle.i()
  ScreenToggle.i()
  ScreenMode.i()
  OutputDevice.i()
  OutputAspect.i(Flag.i)
  OutputResize.i(Width.i,Height.i)
  OutputSize.i(*Width.Integer,*Height.Integer)
  OutputView.i(*X.Integer,*Y.Integer,*Width.Integer,*Height.Integer)
  OutputMap.i(X.i,Y.i,*X.Integer,*Y.Integer)
  OutputClip.i(X.i,Y.i,Width.i,Height.i,Center.i = #False)
  OutputUnclip.i()
  Buffer.i(*BufferSize.Integer = #Null)
  BufferWrite.i(*Surface,Raw.i = #False)
  BufferRead.i(*Surface,Raw.i = #False)
  BufferSet.i(X.i,Y.i,Index.i)
  BufferGet.i(X.i,Y.i,*Index.Ascii)
  BufferFill.i(Index.i = #Null)
  BufferReplace.i(IndexA.i,IndexB.i)
  BufferSwap.i(IndexA.i,IndexB.i)
  BufferChange.i(Mode.i,Value.i = #Null,Flag.i = #False)
  BufferScroll.i(Amount.i,Vertical.i = #False)
  BufferFlip.i(Vertical.i = #False)
  BufferSurface.i(Copy.i = #True)
  BufferSurfaceSave.i(File.s)
  BufferDraw.i()
  BufferDrawEvent.i(*Frames.Integer = #Null,*Time.Double = #Null)
  Draw.i(*Memory,Width.i,Height.i,X.i,Y.i,Center.i = #False)
  DrawMask.i(*Memory,Width.i,Height.i,X.i,Y.i,IndexMask.i,Center.i = #False)
  DrawTint.i(*Memory,Width.i,Height.i,X.i,Y.i,IndexMask.i,IndexTint.i,Center.i = #False)
  DrawTest.i(*Memory,Width.i,Height.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  DrawRect.i(*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,Center.i = #False)
  DrawRectMask.i(*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,Center.i = #False)
  DrawRectTint.i(*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTint.i,Center.i = #False)
  DrawRectTest.i(*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTest.i,Center.i = #False)
  DrawProc.i(*Proc,*Parameter,*Memory,Width.i,Height.i,X.i,Y.i,Center.i = #False)
  DrawProcRect.i(*Proc,*Parameter,*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,Center.i = #False)
  DrawBuffer.i(Rop.i,Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcWidth.i,SrcHeight.i,IndexA.i = #Null,IndexB.i = #Null,Center.i = #False)
  DrawPixel.i(Rop.i,*Dst,X.i,Y.i,DstW.i,DstH.i,*Src,SrcSpan.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexA.i = #Null,IndexB.i = #Null,Center.i = #False)
  DrawText.i(Surface.i,X.i,Y.i,Text.s,IndexTint.i,CenterH.i = #False,CenterV.i = #False)
  DrawTextSpacingGet.i()
  DrawTextSpacingSet.i(Spacing.i)
  DrawTextMapChr.i(Code.i)
  DrawTextMapAscii.i(*Ascii)
  DrawTextSize.i(Text.s)
  DrawPalette.i(Surface.i,X.i,Y.i,Center.i = #False)
  DrawLine.i(Surface.i,X1.i,Y1.i,X2.i,Y2.i,Index.i)
  DrawBox.i(Surface.i,X.i,Y.i,Width.i,Height.i,Index.i,Filled.i = #False,Center.i = #False)
  DrawCircle.i(Surface.i,X.i,Y.i,Radius.i,Bend.i,Index.i)
  DrawFill.i(Surface.i,X.i,Y.i,IndexFill.i,IndexSkip.i = #Null,Skip.i = #False)
  TestPoint.i(PosX.i,PosY.i,X.i,Y.i,Width.i,Height.i)
  TestPointEx.i(PosX.i,PosY.i,X.i,Y.i,Width.i,Height.i,Center.i = #False)
  TestRect.i(DstX.i,DstY.i,DstW.i,DstH,SrcX.i,SrcY.i,SrcW.i,SrcH.i)
  TestRectEx.i(DstX.i,DstY.i,DstW.i,DstH,DstCenter.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,SrcCenter.i)
  TestSurface.i(DstSurface.i,DstX.i,DstY.i,SrcSurface.i,SrcX.i,SrcY.i)
  TestSurfaceEx.i(DstSurface.i,DstX.i,DstY.i,DstCenter.i,SrcSurface.i,SrcX.i,SrcY.i,SrcCenter.i)
  TestPixel.i(*Memory,Width.i,Height.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  TestPixelRect.i(*Memory,Span.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTest.i,Center.i = #False)
  TestPixelSurface.i(DstSurface.i,SrcSurface.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  TestPixelRegion.i(DstSurface.i,SrcRegion.i,Animation.i,Index.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  PalettePreset.i(Palette.i,Preset.i)
  PaletteCreate.i()
  PaletteLoad.i(*Palette,File.s = #Null$)
  PaletteCatch.i(*Memory)
  PaletteFill.i(Color.i = #Null)
  PaletteSet.i(Index.i,Color.i)
  PaletteGet.i(Index.i,*Color.Long)
  PaletteReplace.i(ColorA.i,ColorB.i)
  PaletteSwap.i(IndexA.i,IndexB.i,Colors.i)
  PaletteCycle.i(Index.i,Colors.i,Steps.i)
  PaletteSave.i(File.s)
  SurfaceCreate.i(Width.i,Height.i)
  SurfaceLoad.i(*Surface,File.s = #Null$)
  SurfaceCatch.i(*Memory,Width.i,Height.i,Copy.i = #False)
  SurfaceBitmap.i(Handle.i,*Bitmap = #Null,File.s = #Null$)
  AnimationCreate.i(Start.i,Stop.i,Cycle.i,Active.i = #True,Once.i = #False,Last.i = #False)
  AnimationUpdate.i()
  LayerProc.i(Layer.i,*Proc,*Parameter)
  LayerProcess.i()
  LayerReset.i()
  BitmapCreate.i(Surface.i,Palette.i = #Null,File.s = #Null$)
  BitmapSize.i(*Bitmap)
  BitmapFree.i(*Bitmap)
  BitmapScreenshot.i(File.s)
  TimerEvent.i(FrameRate.i)
  TimerWait.i(*Frames.Integer = #Null,*Time.Double = #Null)
  TimerTick.i(*Tick.Double)
  TimerDelay.i(Milliseconds.i)
  InputUpdate.i(MouseHide.i = #False,MouseClip.i = #False,MouseRelative.i = #False)
  InputAvailable.i()
  InputMouseActive.i()
  InputMousePosition.i(*OutX.Integer,*OutY.Integer)
  InputMouseTranslate.i(*OutX.Integer,*OutY.Integer)
  InputMouseWheel.i(*Value.Integer = #Null)
  InputKeyActive.i()
  InputKeyDown.i(Code.i)
  InputKeyUp.i(Code.i)
  InputKeyToggle.i(Code.i)
  InputKeyChar.i(*Char)
  InputKeyCode.i(*Code)
  InputKeyMapToChar.i(Code.i)
  InputKeyMapToCode.i(Char.s)
  InputFlush.i()
  Release.i()
EndInterface

Interface RPIX_PALETTE
  Use.i(SwapPalette.i = #False)
  Buffer.i(*BufferSize.Integer = #Null)
  BufferWrite.i(*Palette,Raw.i = #False)
  BufferRead.i(*Palette,Raw.i = #False)
  BufferFill.i(Color.i = #Null)
  BufferSet.i(Index.i,Color.i)
  BufferGet.i(Index.i,*Color)
  BufferReplace.i(ColorA.i,ColorB.i)
  BufferSwap.i(IndexA.i,IndexB.i,Colors.i)
  BufferCycle.i(Index.i,Colors.i,Steps.i)
  LayerUse.i(Layer.i,SwapPalette.i = #False)
  LayerReplace.i(Layer.i,ColorA.i,ColorB.i)
  LayerSwap.i(Layer.i,IndexA.i,IndexB.i,Color.i)
  LayerCycle.i(Layer.i,Index.i,Colors.i,Steps.i)
  Duplicate.i()
  Save.i(File.s)
  Release.i()
EndInterface

Interface RPIX_SURFACE
  OutputSize.i(*width,*Height)
  OutputClip.i(X.i,Y.i,Width.i,Height.i,Center.i = #False)
  OutputUnclip.i()
  Buffer.i(*BufferSize.Integer = #Null)
  BufferWrite.i(*Surface,Raw.i = #False)
  BufferRead.i(*Surface,Raw.i = #False)
  BufferSet.i(X.i,Y.i,Index.i)
  BufferGet.i(X.i,Y.i,*Index)
  BufferFill.i(Index.i = #Null)
  BufferReplace.i(IndexA.i,IndexB.i)
  BufferSwap.i(IndexA.i,IndexB.i)
  BufferChange.i(Mode.i,Value.i = #Null,Flag.i = #False)
  BufferScroll.i(Amount.i,Vertical.i = #False)
  BufferFlip.i(Verical.i = #False)
  Draw.i(Surface.i,X.i,Y.i,Center.i = #False)
  DrawMask.i(Surface.i,X.i,Y.i,IndexMask.i,Center.i = #False)
  DrawTint.i(Surface.i,X.i,Y.i,IndexMask.i,IndexTint.i,Center.i = #False)
  DrawTest.i(Surface.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  DrawRect.i(Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,Center.i = #False)
  DrawRectMask.i(Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,Center.i = #False)
  DrawRectTint.i(Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTint.i,Center.i = #False)
  DrawRectTest.i(Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTest.i,Center.i = #False)
  DrawProc.i(*Proc,*Parameter,Surface.i,X.i,Y.i,Center.i = #False)
  DrawProcRect.i(*Proc,*Parameter,Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,Center.i = #False)
  Test.i(Surface.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  TestRect.i(Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,IndexMask.i,IndexTest.i,Center.i = #False)
  LayerClip.i(Layer.i,X.i,Y.i,Width.i,Height.i,Center.i = #False)
  LayerUnclip.i(Layer.i)
  LayerReplace.i(Layer.i,IndexA.i,IndexB.i)
  LayerSwap.i(Layer.i,IndexA.i,IndexB.i)
  LayerChange.i(Mode.i,Value.i = #Null,Flag.i = #False)
  LayerScroll.i(Layer.i,Amount.i = 1,Vertical.i = #False)
  LayerFlip.i(Layer.i,Verical.i)
  LayerDraw.i(Layer.i,Rop.i,Surface.i,X.i,Y.i,Par1.i,Par2.i = #Null,Par3.i = #Null)
  LayerDrawRect.i(Layer.i,Rop.i,Surface.i,X.i,Y.i,SrcX.i,SrcY.i,SrcW.i,SrcH.i,Par1.i,Par2.i = #Null,Par3.i = #Null)
  RegionCreate.i(CountX.i,CountY.i,X.i,Y.i,Width.i,Height.i,OffsetX.i = #Null,OffsetY.i = #Null)
  Duplicate.i()
  Save.i(File.s)
  Release.i()
EndInterface

Interface RPIX_REGION
  Count.i()
  OutputSize.i(*Width,*Height)
  Draw.i(Surface.i,Animation.i,Index.i,X.i,Y.i,Center.i = #False)
  DrawMask.i(Surface.i,Animation.i,Index.i,X.i,Y.i,IndexMask.i,Center.i = #False)
  DrawTint.i(Surface.i,Animation.i,Index.i,X.i,Y.i,IndexMask.i,IndexTint.i,Center.i = #False)
  DrawTest.i(Surface.i,Animation.i,Index.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  DrawText.i(Surface.i,X.i,Y.i,Text.s,CenterH.i = #False,CenterV.i = #False)
  DrawTextMask.i(Surface.i,X.i,Y.i,Text.s,IndexMask.i,CenterH.i = #False,CenterV.i = #False)
  DrawTextTint.i(Surface.i,X.i,Y.i,Text.s,IndexMask.i,IndexTint.i,CenterH.i = #False,CenterV.i = #False)
  DrawProc.i(*Proc,*Parameter,Surface.i,Animation.i,Index.i,X.i,Y.i,Center.i = #False)
  DrawProcText.i(*Proc,*Parameter,Surface.i,X.i,Y.i,Text.s,CenterH.i = #False,CenterV.i = #False)
  Test.i(Surface.i,Animation.i,Index.i,X.i,Y.i,IndexMask.i,IndexTest.i,Center.i = #False)
  LayerDraw.i(Layer.i,Rop.i,Surface.i,Animation.i,Index,X.i,Y.i,Par1.i,Par2.i = #Null,Par3.i = #Null)
  LayerDrawText.i(Layer.i,Rop.i,Surface.i,X.i,Y.i,Text.s,Par1.i,Par2.i = #Null,Par3.i = #Null,Par4.i = #Null)
  FontRegister.i(Offset.i,Layout.s,Spacing.i)
  FontSpacingGet.i()
  FontSpacingSet.i(Spacing.i)
  FontMapChr.i(Code.i)
  FontMapAscii.i(*Ascii)
  FontTextSize.i(Text.s)
  Release.i()
EndInterface

Interface RPIX_ANIMATION
  Run.i(Flag.i = #True,Reset.i = #False)  
  RunOnce.i(Flag.i = #True,Reset.i = #False)
  Running.i()
  FrameStart.i(Frame.i)
  FrameStop.i(Frame.i)
  FrameCycleSet.i(Cycle.i)
  FrameCycleGet.i()
  FrameSet.i(Frame.i)
  FrameGet.i()
  FrameReport.i(*Frame)
  FrameCount.i()
  Release.i()
EndInterface

; DrawProc - Callback Example:
; Procedure.i Callback(*Draw.RPIX_DRAW_STRUCT)
;   With *Draw
;     \dst + \dst_rect\x + (\dst_rect\y * \dst_span);<- calculate the destination address
;     \src + \src_rect\x + (\src_rect\y * \src_span);<- calculate the source address
;     Repeat
;       CopyMemory(\src,\dst,\dst_rect\w);<- copy a span of pixels defined by the destination rect width
;       \dst + \dst_span;<- advance to next scanline / span
;       \src + \src_span
;       \dst_rect\h - 1;<- cover the whole height of the destination rect
;     Until \dst_rect\h = #Null
;     ProcedureReturn
;   EndWith
; EndProcedure

More images:

Image

Image

Have fun 8)
Last edited by Mijikai on Sun Nov 05, 2023 11:08 am, edited 27 times in total.
User avatar
pendle
New User
New User
Posts: 7
Joined: Tue Jun 28, 2022 7:41 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by pendle »

Awesome 8)
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Thanks @pendle

Update & a small demo :)

Update alpha 2:
  • fixed two small bugs in \Buffer() & \PaletteCycle()
Update alpha 4:
  • added more functions for pixel collisions :)
  • renamed one inferface and numerous drawing functions to better reflect their purpose
  • updated all demo & tutorial codes to this version
A small demo with rpix (how the effect looks - choppy gif preview):
Image

Demo 1:

Code: Select all

EnableExplicit

;RPIX Demo 1

XIncludeFile "rpix.pbi"

UsePNGImageDecoder()

Procedure.i DownloadLogo();<- will download the purebasic site logo
  Protected *buffer
  Protected load.i
  *buffer = ReceiveHTTPMemory("https://www.purebasic.fr/english/styles/prolight/theme/images/site_logo.png")
  If *buffer
    load = CatchImage(#PB_Any,*buffer,MemorySize(*buffer))
    FreeMemory(*buffer)
  EndIf
  ProcedureReturn load
EndProcedure

Procedure.i DownloadMusic();.<- will download the sunbathing.mod by Daddy Freddy (scoopex)
  Protected *buffer
  Protected load.i
  *buffer = ReceiveHTTPMemory("https://api.modarchive.org/downloads.php?moduleid=89990#sunbathing.mod")
  If *buffer
    load = CatchMusic(#PB_Any,*buffer,MemorySize(*buffer))
    FreeMemory(*buffer)
  EndIf
  ProcedureReturn load
EndProcedure

Procedure.i Main()
  Protected exit.i
  Protected img.i
  Protected mod.i
  Protected *rpix.RPIX
  Protected *sprite.RPIX_SURFACE
  Protected fx.i
  Protected fc.d
  Protected fs.d
  If InitSound()
    img = DownloadLogo()
    If img
      mod = DownloadMusic()
      If mod
        If OpenWindow(0,0,0,960,600,#Null$,#PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_MinimizeGadget)
          *rpix = rpixInterface(WindowID(0),320,200)
          If *rpix
            *rpix\PalettePreset(#Null,#RPIX_PALETTE_VGA)
            *sprite = *rpix\SurfaceBitmap(ImageID(img))
            If *sprite
              *sprite\BufferChange(#RPIX_MATH_ADD,-116,#False)
              PlayMusic(mod)
              Repeat
                Repeat
                  Select WindowEvent()
                    Case #Null
                      Break
                    Case #PB_Event_CloseWindow
                      exit = #True
                  EndSelect
                ForEver
                fx + 1
                fc = Cos(fx / 16)
                fs = Sin(fx / 8)
                *rpix\BufferFill()
                *rpix\DrawText(#Null,160 + fc * 6.0,78 + fs * 10.0,"RPIX powered by",100,#True)
                *rpix\BufferChange(#RPIX_FILTER_2)
                *rpix\PaletteCycle(28,64,4 * Bool(fx % 8 = 0))
                *sprite\DrawMask(#Null,160 - fc * 8.0,120 + fs * 6.0,139,#True)
                *rpix\BufferChange(#RPIX_MATH_MUL,2,#True)
                *rpix\DrawText(#Null,160 + fc * 6.0,78 + fs * 10.0,"RPIX powered by",15,#True)
                *rpix\BufferDrawEvent()
              Until exit
            EndIf
            *rpix\Release()
          EndIf
          CloseWindow(0)  
        EndIf
        FreeMusic(mod)
      EndIf
      FreeImage(img)
    EndIf
  EndIf
  ProcedureReturn #Null
EndProcedure

Main()

End
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Update alpha 5:
  • internal changes to the flipping and scrolling functions
  • new function: RPIX\DrawPixel.i() - to blit from and to arbitrary memory directly
Note:
All codes/tutorial so far released still work :)
User avatar
jacdelad
Addict
Addict
Posts: 1432
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by jacdelad »

Hi Mijikai,
this looks great. Unfortunately I don't have any use for that (now). And I think it's the same with others, so keep going, even if the feedback is a bit low.
Also, do you have a project beside the demos to use this?
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Thank you @jacdelad.
Im working on a bigger project which is not ready yet but i will release more demos/tutorials in the meantime.

Update alpha 6:
  • fixed a memory bug and other minor improvements

Space Invaders Demo

ImageImage

Controls:

Code: Select all

[ENTER] = Play/Replay
[SPACE] = Shoot
[LEFT/RIGHT] = Move
[F1] = Toggle between windowed and fullscreen mode
[F2] = Use filter (on/off)
[F3] = Show fps (on/off)
Code Demo 2:

Code: Select all

EnableExplicit

XIncludeFile "rpix.pbi"

;Demo 2 (for RPIX alpha 6)
;1 Player - Space Invaders (sort of)

;Originally written to test the RPIX gfx library - the code is not optimized (messy testcode).
;Since its somewhat playable i decided to release it.
;Have fun :)

Structure GAME_MOTHERSHIP_STRUCT
  active.i
  direction.i
  pos_x.i
  pos_y.i
  hit.i
EndStructure

Structure GAME_INVADER_STRUCT
  tick.i
  drop.i
  index.i
  direction.i
  limit_x.i
  limit_w.i
  limit_y.i
  speed.i
  pos_x.i
  pos_y.i
  bullet.i
  bullet_a.i
  bullet_x.i
  bullet_y.i
EndStructure

Structure GAME_PLAYER_STRUCT
  pos_x.i
  pos_y.i
  bullet.i
  bullet_x.i
  bullet_y.i
  hit.i
EndStructure

Structure GAME_STRUCT
  *rpix.RPIX
  *background.RPIX_SURFACE[2]
  *sprite.RPIX_SURFACE
  *object.RPIX_REGION
  *bullet.RPIX_ANIMATION
  state.i
  info.i
  fps.i
  dlt.d
  filter.i
  tick.i
  lives.i
  score_1.i
  score_2.i
  highscore.i
  credits.i
  player.GAME_PLAYER_STRUCT
  invader.GAME_INVADER_STRUCT
  mothership.GAME_MOTHERSHIP_STRUCT
EndStructure

Global game.GAME_STRUCT

Procedure.i siWindowOpen(Title.s = #Null$);<- create a game window
  Protected flags.i
  With game
    If rpixVersion() = #RPIX_VERSION
      flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered
      If OpenWindow(#Null,#Null,#Null,448,512,Title,flags)
        \rpix = rpixInterface(WindowID(#Null),224,256,#True)
        If \rpix
          ProcedureReturn #True
        EndIf
        CloseWindow(#Null)
      EndIf
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i siWindowExit();<- was the window closed?
  Protected exit.i
  With game
    Repeat
      Select WindowEvent()
        Case #Null
          Break
        Case #PB_Event_CloseWindow
          exit = #True
      EndSelect
    ForEver
    ProcedureReturn exit
  EndWith
EndProcedure

Procedure.i siWindowClose();<- cleanup everthing when the window / game was closed
  With game
    \rpix\Release()
    CloseWindow(#Null)
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siInitInvaders();<- init the invaders
  With game
    FillMemory(?invaders,?alien_1 - ?invaders)
    \invader\index = 1
    \invader\tick = 16
    \invader\drop = 4
    \invader\limit_x = 0
    \invader\limit_w = 48
    \invader\limit_y = 256
    \invader\speed = 1
    \invader\pos_x = 20
    \invader\pos_y = 60
    \mothership\pos_x = -32
    \mothership\pos_y = 50
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siInitPlayer();<- init the player
  With game
    \lives = 3
    \player\pos_x = 112
    \player\pos_y = 222
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siInit();<- init everything
  Static start.i
  With game
    If start = 0;<- is it the first time this function is called? - if yes -> create the background sprites
      \rpix\PalettePreset(#Null,#RPIX_PALETTE_VGA);<- use the vga color palette
      \rpix\DrawTint(?shelter,26,20,40,200,0,50,#True);<- draw all shelters on the framebuffer
      \rpix\DrawTint(?shelter,26,20,88,200,0,50,#True)
      \rpix\DrawTint(?shelter,26,20,224 - 40,200,0,50,#True)
      \rpix\DrawTint(?shelter,26,20,224 - 88,200,0,50,#True)
      \rpix\DrawLine(#Null,0,30,224,30,50);<- draw lines on the framebuffer
      \rpix\DrawLine(#Null,0,240,224,240,50)
      \rpix\DrawText(#Null,6,2,"SCORE<1>",30);<- draw the info texts on the framebuffer
      \rpix\DrawText(#Null,158,2,"SCORE<2>",30)
      \rpix\DrawText(#Null,112,2,"HI-SCORE",30,#True)
      \rpix\DrawText(#Null,128,246,"CREDIT",30)
      \background[0] = \rpix\BufferSurface(#True);<- create two (identical) background sprites from the framebuffer
      \background[1] = \rpix\BufferSurface(#True)
      \sprite = \rpix\SurfaceCatch(?alien_1,16,80 + 8);<- catch all alien sprites
      \bullet = \rpix\AnimationCreate(0,1,20);<- create a animation that will switch from 0 to 1 every 20 frames
      If \sprite
        \object = \sprite\RegionCreate(1,12,0,0,16,8);<- create a region (tilesheet) from the sprite (in order to access different sprite regions via index) 
      EndIf
      If \background[0] And \background[1] And \object And \bullet;<- check if all went well
        \bullet\FrameReport(@\invader\bullet_a);<- report the current animation frame to this variable (note: the animation interface is usually/normally used as parameter) 
        start = #True
      EndIf
    EndIf
    If start
      \background[0]\BufferRead(\background[1]);<- override the first background sprite with the second (backup) backround sprite (to fix the damaged shelters)
      siInitInvaders();<- init invader and player parameters
      siInitPlayer()
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure


Procedure.i siRenderInvaders();<- render invaders (it does more so the neame is a bit misleading)
  Protected.i ix,iy,px,py,pi,pc,pb
  Protected.i lm,lx,lw
  Protected.Ascii *ps,*pa
  With game
    If \mothership\hit;<- was the mother ship hit with a bullet
      \object\DrawTint(#Null,#Null,8 + \invader\bullet_a,\mothership\pos_x,\mothership\pos_y,0,40,#True);<- draw the animated explotsion
    Else
      \object\DrawTint(#Null,#Null,6,\mothership\pos_x,\mothership\pos_y,0,40,#True);<- draw the mothership
    EndIf  
    *ps = ?invaders;<- point *ps to the state grid of the space invaders (keeps track of animation & alive/dead)
    FillMemory(?attack,11);<- reset the attack state of the space invaders (who can attack?)
    \invader\limit_y = 0;<- reset the maximum height the space invaders can advance to before its game over (it gets adjusted depending on how many lines of invaders are left)
    For iy = 0 To 4;<- go through all invader positions (5 lines a 11 invaders)
      *pa = ?attack;<- point *pa to tha attack state
      pi + (iy % 2) << 1;<- this calculates the index which in turn draws a specific alien (0 - 1 = alien_1, 2 - 3 = alien_2 and so on...)
      py = \invader\pos_y + (iy << 4);<- calculate the alien x position (grid offset + index * tile width)
      For ix = 0 To 10
        px = \invader\pos_x + (ix << 4);<- calulate the alien y position
        lm = ((10 - ix) << 4);<- calculate the limit (right side of the invader grid)
        If *ps\a;<- was the alien hit with a bullet
          If *ps\a > 2
            \object\DrawTint(#Null,#Null,8 + \invader\bullet_a,px,py,0,30);<- draw animated explosion
            *ps\a - 1;<- this times the animation
          EndIf
        Else
          \object\DrawTint(#Null,#Null,pi + \invader\index,px,py,0,30);<- alien/invader is alive draw it
          If lx = 0
            \invader\limit_x = - (ix << 4);<- update the limit on how far the invader grid can move to the right
            \invader\limit_w = 48 + lm
            lx = 1
          Else
            If py > 0 And \invader\limit_w > 48 + lm
              \invader\limit_w = 48 + lm
            EndIf
          EndIf
          If Not *pa\a;<- count invader that can shoot
            pb + 1
          EndIf
          *pa\a = iy + 1
          If *pa\a > \invader\limit_y;<- update the limit on how far the invader grid can move down
            \invader\limit_y = *pa\a
          EndIf
          pc + 1
        EndIf
        *ps + 1
        *pa + 1
      Next      
    Next 
    If pc > 16
      \invader\tick = pc / 3;<- change the invaders speed depending on how many are alive (horrible...)
    Else
      \invader\tick = (pc >> 2) + 1
      If \invader\tick < 5
        \invader\speed = 2
      EndIf
    EndIf
    If \invader\bullet;<- is any invader shooting?
      If \invader\bullet_y < 0;has the bullet hit something?
        \invader\bullet_y + 1
        \object\DrawTint(#Null,#Null,8 + \invader\bullet_a,\invader\bullet_x + 8,\player\bullet_y,0,30,#True);<- draw animated explosion
        If \invader\bullet_y = 0
          \invader\bullet = 0
        EndIf
      Else
        \invader\bullet_y + 1;<- update the bullet position
        If \filter;<- bullez hip player? - if the filter is used the pixel collision doesnt work here check via point in rect
          If \rpix\TestPointEx(\invader\bullet_x + 8,\invader\bullet_y,\player\pos_x,\player\pos_y,12,8,1) And Not \player\hit
            If \lives > 0
              \lives - 1
              \player\hit = 48
            EndIf
            \sprite\DrawRectTint(#Null,\invader\bullet_x + 8,\invader\bullet_y + 4,5,80,4,10,0,30,#True);<- draw animated explosion
          EndIf    
        EndIf 
        If \sprite\TestRect(#Null,\invader\bullet_x + 8,\invader\bullet_y,\invader\bullet_a << 1,80,2,8,0,50,#True);<- test for bullet vs. player and shelter collision
          If \invader\bullet_y > 210;<- player was hit
            If \lives > 0
              \lives - 1
              \player\hit = 48
            EndIf
            \sprite\DrawRectTint(#Null,\invader\bullet_x + 8,\invader\bullet_y + 4,5,80,4,10,0,30,#True)
          Else
            \sprite\DrawRectTint(#Null,\invader\bullet_x + 8,\invader\bullet_y + 4,5,80,4,10,0,30,#True)
            \sprite\DrawRectTint(\background[0],\invader\bullet_x + 8,\invader\bullet_y + 4,5,80,7,10,0,0,#True)
          EndIf
          \invader\bullet = 0
        EndIf
        \sprite\DrawRectTint(#Null,\invader\bullet_x + 8,\invader\bullet_y,\invader\bullet_a << 1,80,2,8,0,30,#True);<- draw animated bullet
        If \invader\bullet_y > 236
          \invader\bullet = 0
        EndIf
      EndIf
    Else
      *pa = ?attack
      pb = Random(pb);<- who can attack / decide random
      For px = 0 To 10
        If *pa\a
          If pb = 0
            \invader\bullet_x = \invader\pos_x + (px << 4) - 4;<- invader shoots!
            \invader\bullet_y = \invader\pos_y + (*pa\a << 4) - 4
            \invader\bullet = 1
            Break
          EndIf
          pb - 1
        EndIf
        *pa + 1
      Next 
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siRenderPlayer()
  Protected px.i
  Protected py.i
  Protected *ps.Ascii
  With game
    If \player\hit > 0
      \player\hit - 1
      \object\DrawTint(#Null,#Null,8 + \invader\bullet_a,\player\pos_x,\player\pos_y,0,50,#True);<- draw animated explosion if player was hit
    Else
      If \lives = 0
        \state = 3;game over
      EndIf
      \object\DrawTint(#Null,#Null,7,\player\pos_x,\player\pos_y,0,50,#True);<- draw player sprite
    EndIf
    If \player\bullet
      \player\bullet_y - 1
      If \sprite\TestRect(#Null,\player\bullet_x,\player\bullet_y,0,80,2,8,0,40,#True) And \mothership\hit = 0;<- did the player bullet hit the mothership?
        \mothership\hit = 48
        \score_1 + 100
      EndIf
      If \sprite\TestRect(#Null,\player\bullet_x,\player\bullet_y,0,80,2,8,0,50,#True);<- test player bullet vs. shelter
        \sprite\DrawRectTint(#Null,\player\bullet_x,\player\bullet_y - 4,5,80,4,10,0,30,#True)
        \sprite\DrawRectTint(\background[0],\player\bullet_x,\player\bullet_y - 4,5,80,7,10,0,0,#True)
        \player\bullet = #False
      ElseIf \sprite\TestRect(#Null,\player\bullet_x,\player\bullet_y,0,80,2,8,0,30,#True);<- test player bullet vs. invader
        px = (\player\bullet_x - \invader\pos_x) >> 4;<- which invader is in that grid position
        py = (\player\bullet_y - \invader\pos_y) >> 4
        If py > 4
          \invader\bullet_y = -48
        Else
          \score_1 + 10
          *ps = ?invaders + px + (py * 11);<- change invader state
          If Not *ps\a
            *ps\a = 48
          EndIf
        EndIf
       \player\bullet = #False
      EndIf
      If \player\bullet
        \rpix\DrawBox(#Null,\player\bullet_x - 1,\player\bullet_y - 2,1,6,29);<- draw player bullet
      EndIf
      If \player\bullet_y < 36
        \player\bullet_y = 36
        \player\bullet = #False
      EndIf
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siRender()
  Protected index.i
  With game
    Select \state
      Case 0;<- game was started display info
        \rpix\DrawBox(#Null,0,180,224,32,0,#True);<- draw basic info screen
        \rpix\DrawText(0,112,64,"SPACE INVADERS",30,1)
        \rpix\DrawText(0,112,80,"PRESS [ENTER] TO PLAY",30,1)
        \rpix\DrawText(0,112,112,"*SCORE ADVANCE TABLE*",30,1)
        \object\DrawTint(#Null,#Null,6,64,132,0,40,#True)
        \rpix\DrawText(0,76,128,"= ?? MYSTERY",30)
        \object\DrawTint(#Null,#Null,0,64,148,0,30,#True)
        \rpix\DrawText(0,76,144,"= 10 POINTS",30)
        \object\DrawTint(#Null,#Null,2,64,164,0,30,#True)
        \rpix\DrawText(0,76,160,"= 10 POINTS",30)
        \object\DrawTint(#Null,#Null,4,64,180,0,30,#True)
        \rpix\DrawText(0,76,176,"= 10 POINTS",30)
         If \rpix\InputKeyToggle(#VK_RETURN)
          siInit()
          \state = 1  
        EndIf
      Case 1;<- game is played
        siRenderInvaders()
        siRenderPlayer()  
      Case 3;<- game over screen (retain highscore)
        index = \highscore
        If \highscore < \score_1
          \highscore = \score_1  
        EndIf
        \rpix\DrawBox(#Null,0,180,224,32,0,#True)
        \rpix\DrawText(#Null,110,112,"GAME OVER",30,#True)
        \rpix\DrawText(#Null,112,132,"PRESS [RETURN] TO PLAY!",30,#True)
        If \rpix\InputKeyToggle(#VK_RETURN)
          siInit()
          \highscore = index
          \state = 1  
        EndIf
    EndSelect
    \rpix\DrawText(#Null,38,16,RSet(Str(\score_1),4,"0"),30,#True)
    \rpix\DrawText(#Null,190,16,RSet(Str(\score_2),4,"0"),30,#True)
    \rpix\DrawText(#Null,112,16,RSet(Str(\highscore),4,"0"),30,#True)
    \rpix\DrawText(#Null,182,246,RSet(Str(\credits),2,"0"),30)
    \rpix\DrawText(#Null,28,246,Str(\lives),30)
    For index = 0 To \lives - 1
      \object\DrawTint(#Null,#Null,7,40 + index << 4,244,0,50)
    Next
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siUpdateInvaders();<- update invader/mothership
  With game
    If \mothership\active
      If \mothership\active > 1
        \mothership\active - 1
      Else
        If \mothership\hit
          \mothership\hit - 1
          If \mothership\hit = 0
            If \mothership\direction
              \mothership\pos_x = 256
            Else
              \mothership\pos_x = -32
            EndIf
            \mothership\direction ! 1
            \mothership\active = 0
          EndIf
        Else
          If \mothership\direction
            \mothership\pos_x - 1
            If \mothership\pos_x < -32
              \mothership\direction ! 1
              \mothership\active = 0
            EndIf
          Else
            \mothership\pos_x + 1
            If \mothership\pos_x > 265
              \mothership\direction ! 1
              \mothership\active = 0
            EndIf
          EndIf
        EndIf
      EndIf
    Else
      \mothership\active = Random(2000,800)
    EndIf  
    If Bool(\tick % \invader\tick = 0)
      \invader\index ! 1
      If \invader\direction
        \invader\pos_x - \invader\speed
      Else
        \invader\pos_x + \invader\speed
      EndIf
      If \invader\pos_x < \invader\limit_x
        \invader\pos_y + \invader\drop
        \invader\pos_x + 1
        \invader\direction ! 1
      ElseIf \invader\pos_x > \invader\limit_w
        \invader\pos_y + \invader\drop
        \invader\pos_x - 1
        \invader\direction ! 1
      EndIf
      \invader\limit_y = \invader\pos_y + (\invader\limit_y << 4)
      If \invader\limit_y > 200
        \state = 3
      EndIf
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siUpdatePlayer();<- update the player
  With game
    \player\pos_x - \rpix\InputKeyDown(#VK_LEFT) << 1;<- if the [LEFT] or [RIGHT] key is pressed update the players position
    \player\pos_x + \rpix\InputKeyDown(#VK_RIGHT) << 1
    If \player\pos_x < 9;<- dont let the player escape
      \player\pos_x = 9 
    EndIf
    If \player\pos_x > 216
      \player\pos_x = 216
    EndIf
    If \rpix\InputKeyToggle(#VK_SPACE);<- if [SPACE] is pressed shoot a bullet if possible (no other bullet in the air)
      If Not \player\bullet
        \player\bullet_x = \player\pos_x;<- shoot from here
        \player\bullet_y = \player\pos_y - 8
        \player\bullet = #True;<- set bullet to active/true
      EndIf
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siUpdate();<- update everything
  With game
    \tick + 1;<- increase the game tick by one
    If \state = 1;<- if there is a active game update player & invaders
      siUpdatePlayer()
      siUpdateInvaders()
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siMain();<- main routine
  With game
    If siWindowOpen("Demo 2 - Space Invaders");<- create the window
      If siInit();<- initialize everything
        Repeat
          \rpix\InputUpdate();<- update the input (mouse/keyboard)
          If \rpix\InputKeyToggle(#VK_F1);<- if [F1] is pressed toggle between windowed and screen mode
            \rpix\ScreenToggle()
          EndIf
          If \rpix\InputKeyToggle(#VK_F2);<- if [F2] is pressed apply a effect filter onto the framebuffer
            \filter ! 1
          EndIf
          If \rpix\InputKeyToggle(#VK_F3);<- if [F3] is pressed show fps & dlt time
            \info ! 1
          EndIf
          siUpdate();<- update everything
          \rpix\BufferRead(\background[0]);<- now start rendering by overwriting the framebuffer with the backround sprite 
          siRender();<- render everthing (first pass)
          If \filter
            \rpix\BufferChange(#RPIX_FILTER_2);<- apply the filter if this option is active
            \background[0]\DrawMask(#Null,0,0,0);<- draw the background again to retain some detail by overlaying it again on top
          EndIf
          siRender();<- render everything (second pass - mainly there to facilitate the filter) 
          If \info
            \rpix\DrawText(#Null,112,36,"FPS " + Str(\fps) + " @ " + StrD(\dlt,2),20,#True);<- draw the info if this option is active
          EndIf
          \rpix\BufferDrawEvent(@\fps,@\dlt);<- draw the framebuffer/backbuffer to the screen
          \rpix\AnimationUpdate();<- update the animation (call is equal to one animation tick)
        Until siWindowExit()
      EndIf
      siWindowClose()
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

siMain()

End

DataSection
  invaders:
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  attack:
  Data.a 0,0,0,0,0,0,0,0,0,0,0
  alien_1:
  Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
  Data.a 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,0,1,1,0,1,1,0,1,1,0,0,0,0
  Data.a 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0
  Data.a 0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0
  Data.a 0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0
  Data.a 0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
  Data.a 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,0,1,1,0,1,1,0,1,1,0,0,0,0
  Data.a 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0
  Data.a 0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0
  Data.a 0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,0
  Data.a 0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0
  alien_2:
  Data.a 0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0
  Data.a 0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0
  Data.a 0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,1,1,0,1,1,1,0,1,1,0,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,0,1,0,1,1,1,1,1,1,1,0,1,0,0,0
  Data.a 0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0
  Data.a 0,0,1,0,0,1,1,0,1,1,0,0,1,0,0,0
  Data.a 0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0
  Data.a 0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0
  Data.a 0,0,1,0,1,1,1,1,1,1,1,0,1,0,0,0
  Data.a 0,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0
  Data.a 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0
  alien_3:
  Data.a 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
  Data.a 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,0,0,0,1,1,1,0,0,1,1,1,0,0,0,0
  Data.a 0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0
  Data.a 0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0
  Data.a 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
  Data.a 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,0,0,0,1,1,1,0,0,1,1,1,0,0,0,0
  Data.a 0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0
  Data.a 0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0
  mothership:
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
  Data.a 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0
  Data.a 0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0
  player:
  Data.a 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  explosion:
  Data.a 0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0
  Data.a 0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0
  Data.a 0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,0
  Data.a 0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0
  Data.a 0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0
  Data.a 0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,0
  Data.a 0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0
  Data.a 0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0
  Data.a 0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0
  Data.a 0,0,0,1,0,0,1,1,0,1,1,0,0,1,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
  Data.a 0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,1,0,0,1,1,0,1,1,0,0,1,0,0
  Data.a 0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0
  ;bullet:
  Data.a 0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0
  Data.a 1,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0
  Data.a 1,0,0,1,0,0,1,1,1,1,1,0,0,0,0,0
  Data.a 0,1,1,0,0,0,1,1,1,1,1,0,0,0,0,0
  Data.a 0,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0
  Data.a 1,0,0,1,1,0,1,1,1,1,1,0,0,0,0,0
  Data.a 1,0,0,1,0,1,1,1,1,1,1,1,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  shelter:
  Data.a 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0
  Data.a 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0
  Data.a 0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0
  Data.a 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0
  Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0
  Data.a 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0
  Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
EndDataSection
jamirokwai
Enthusiast
Enthusiast
Posts: 771
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by jamirokwai »

Wow, nice project. Do you have a version for macOS as well??

Thanks :-)
Regards,
JamiroKwai
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

@jamirokwai, thank you for the question.

Currently there are no plans for a MacOS (or Linux) port.
RPix uses Windows OS functions and there is some x64 assembly that would need to be converted to ARM.
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Update alpha 7:
  • more bug fixes
Demo 3 - Erase & Redraw Pixels (choppy gif preview)
Image

The code also shows an alternative way to move the invaders.

Code (included in the alpha 7 release):

Code: Select all

EnableExplicit

;RPIX (alpha 7) Demo 3 - Erase & Redraw Pixels

;Info:
;[MOUSE LEFT CLICK] = ERASE PIXELS
;[MOUSE RIGHT CLICK] = REDRAW PIXELS

XIncludeFile "rpix.pbi"

Structure GAME_STRUCT
  *rpix.RPIX
  *invader.RPIX_SURFACE[5]
  *animation.RPIX_ANIMATION
  tick.i
EndStructure

Global game.GAME_STRUCT

Procedure.i siWindowOpen(Title.s = #Null$);<- create a game window
  Protected flags.i
  With game
    If rpixVersion() = #RPIX_VERSION;<- check if the rpix version matches
      flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered
      If OpenWindow(#Null,#Null,#Null,448,512,Title,flags)
        \rpix = rpixInterface(WindowID(#Null),224,256,#True);<- create the rpix interface
        If \rpix
          ProcedureReturn #True
        EndIf
        CloseWindow(#Null)
      EndIf
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i siWindowExit();<- was the window closed?
  Protected exit.i
  With game
    Repeat
      Select WindowEvent()
        Case #Null
          Break
        Case #PB_Event_CloseWindow
          exit = #True
      EndSelect
    ForEver
    ProcedureReturn exit
  EndWith
EndProcedure

Procedure.i siWindowClose();<- cleanup everthing when the window / game was closed
  With game
    \rpix\Release();<- release the rpix interface (cleans up all surfaces, palettes, regions and animation automatically)
    CloseWindow(#Null)
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i siInit()
  Protected index.i
  Protected color.i
  With game
    \rpix\PalettePreset(#Null,#RPIX_PALETTE_VGA);<- load the vga palette
    \invader[0] = \rpix\BufferSurface();<- create 4 empty surfaces (framebuffers) from the backbuffer (same size as the backbuffer but without copying its content)
    \invader[1] = \rpix\BufferSurface()
    \invader[2] = \rpix\BufferSurface()
    \invader[3] = \rpix\BufferSurface()
    \invader[4] = \rpix\SurfaceLoad(?invaders);<- load the invader surface (sprite) note: *.rpix = rpix file format! (for surfaces and palettes)
    \animation = \rpix\AnimationCreate(0,1,6) ;<- create a animation 0 - 1 (every 6 frames)
    If \invader[0] And \invader[1] And \invader[2] And \invader[3] And \invader[4] And \animation;<- check if all went well
      For index = 0 To 255
        \rpix\DrawLine(\invader[3],0,index,244,index,158 + color);<- draw the background
        color + 1
        If color = 4
          color = 0
        EndIf
      Next
      \animation\FrameReport(@\tick);<- tick will receive the current frame number of the animation (used for movement & animation later)
      \invader[4]\DrawRect(\invader[0],0,0,0,0,176,80);<- draw the invader sprite on surface 0 - 2
      \invader[4]\DrawRect(\invader[1],0,0,0,80,176,80)
      \invader[4]\DrawRect(\invader[2],8,8,0,0,176,80)
      \invader[4]\DrawRect(\invader[2],8,108,0,80,176,80)
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i Main()
  Protected d.i
  Protected x.i
  Protected y.i
  Protected tx.i
  Protected ty.i
  Protected mx.i
  Protected my.i
  With game
    If siWindowOpen("RPIX - Demo 3");<- create a window
      If siInit();<- init all
        d = 1;<- in which direction should the invaders move first? (1 = right)
        Repeat
          \rpix\InputUpdate();<- update the input
          \rpix\InputMouseTranslate(@mx,@my);<- where is the mouse located (screen space)
          If \tick;<- move the invaders?
            If x = 48;<- where should the invaders move (max distance allowed to the right is 48)
              x = 0
              d * -1;<- which direction?
              y + 4
              If y = 176;<- max distance allowed in the y direction before the invaders get reset
                y = 0
                \invader[0]\BufferScroll(-176,1);<- reset invaders y position (all invaders)
                \invader[1]\BufferScroll(-176,1)
              EndIf
              \invader[0]\BufferScroll(4,1);<- update the y position (4 pixels at once) of all invaders
              \invader[1]\BufferScroll(4,1)
            Else
              x + 1
              \invader[0]\BufferScroll(d,0);<- update the x position (1 or -1 pixel) of all invaders
              \invader[1]\BufferScroll(d,0)
            EndIf
          EndIf
          \rpix\BufferRead(\invader[3]);<- read the the surface 3 (framebuffer) to the backbuffer (faster than blit - this straight copies all pixels)
          \invader[\tick]\DrawTint(#Null,-1,2,0,0);<- draw the shadow under the invaders
          \invader[\tick]\DrawTint(#Null,0,0,0,90);<- draw the invaders (yes only 2 draw calls for the invaders in this example)
          \rpix\DrawBox(#Null,mx,my,8,8,40,#False,#True);<- draw a red box at the current mouse position
          If \rpix\InputKeyDown(#VK_LBUTTON);<- if the left mouse button is pressed the pixels of the invaders on surface 0 & 1 get erased (blacked out)
            \rpix\DrawBox(\invader[0],mx,my,8,8,0,#True,#True)
            \rpix\DrawBox(\invader[1],mx,my,8,8,0,#True,#True)
          EndIf
          If \rpix\InputKeyDown(#VK_RBUTTON);<- if rge right mouse button is pressed the pixels of the invaders get drawn back in from surface 2
            If d = 1;<- where is the invader grid?
              tx = x
            Else
              tx = 48 - x
            EndIf
            ty = y
            \rpix\DrawBox(#Null,tx,ty,178,80,70,#False,#False);<- draw a frame around the invaders
            \rpix\DrawBox(#Null,mx,my,8,8,70,#False,#True);<- draw a green box at the mouse position (this overrides the red box from before)
            If \rpix\TestRectEx(tx,ty,178,80,#False,mx,my,8,8,#True);<- are we within the invaders surface coordinates (we dont want to overdraw)
              tx = mx - tx - 4;<- calculate the offset into the surface 2 to grab the correct invader pixels
              ty = my - ty - 4
              \invader[2]\DrawRect(\invader[0],mx,my,tx + 8,ty + 8,8,8,#True);<- draw back the pixels to surface 0 & 1
              \invader[2]\DrawRect(\invader[1],mx,my,tx + 8,ty + 108,8,8,#True)
            EndIf
          EndIf
          \rpix\BufferDrawEvent();<- render all while managin the timings
          \rpix\AnimationUpdate();<- advance the animation
        Until siWindowExit()
      EndIf
      siWindowClose()
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

Main()

End

DataSection
  invaders:
  IncludeBinary "demo_3.rpix";<- included in the alpha 7 release!
EndDataSection
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Demo 4 - SpriteSheets & Animation (choppy gif preview)
Image

Code:

Code: Select all

EnableExplicit

;RPIX (alpha 7) Demo 4 - SpriteSheets & Animation

UsePNGImageDecoder();<- to support png image files

XIncludeFile "rpix.pbi"

Structure GAME_STRUCT
  *rpix.RPIX
  *sprite.RPIX_SURFACE
  *tile.RPIX_REGION
  *animation.RPIX_ANIMATION[2]
EndStructure

Global game.GAME_STRUCT

Procedure.i DownloadSpriteSheet();<- download a Diablo SpriteSheet
  Protected *buffer
  Protected load.i
  *buffer = ReceiveHTTPMemory("https://www.spriters-resource.com/resources/sheets/66/68657.png?updated=1460965562")
  If *buffer
    load = CatchImage(#PB_Any,*buffer,MemorySize(*buffer))
    FreeMemory(*buffer)
  EndIf
  ProcedureReturn load
EndProcedure

Procedure.i WindowOpen(Title.s = #Null$)
  Protected flags.i
  With game
    If rpixVersion() = #RPIX_VERSION
      flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered
      If OpenWindow(#Null,#Null,#Null,800,600,Title,flags)
        \rpix = rpixInterface(WindowID(#Null))
        If \rpix
          ProcedureReturn #True
        EndIf
        CloseWindow(#Null)
      EndIf
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i WindowExit()
  Protected exit.i
  With game
    Repeat
      Select WindowEvent()
        Case #Null
          Break
        Case #PB_Event_CloseWindow
          exit = #True
      EndSelect
    ForEver
    ProcedureReturn exit
  EndWith
EndProcedure

Procedure.i WindowClose()
  With game
    \rpix\Release()
    CloseWindow(#Null)
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i ImageAlign(*Image.Integer);<- rpix only accepts images that are a multiple of 2
  Protected img_w.i
  Protected img_h.i
  Protected fix_w.i
  Protected fix_h.i
  Protected fix.i
  If *Image
    If IsImage(*Image\i)
      img_w = ImageWidth(*Image\i)  
      img_h = ImageHeight(*Image\i)
      fix_w = img_w + Bool(img_w % 2 <> 0)
      fix_h = img_h + Bool(img_h % 2 <> 0)
      If fix_w = img_w And fix_h = img_h
        ProcedureReturn #True
      EndIf
      fix = CreateImage(#PB_Any,fix_w,fix_h,ImageDepth(*Image\i))
      If fix
        If StartDrawing(ImageOutput(fix))
          DrawImage(ImageID(*Image\i),0,0)
          StopDrawing()
          FreeImage(*Image\i)
          *Image\i = fix
          ProcedureReturn #True
        EndIf
        FreeImage(fix)
      EndIf
    EndIf
  EndIf
  ProcedureReturn #False
EndProcedure

Procedure.i Init()
  Protected sprite.i
  With game
    sprite = DownloadSpriteSheet();<- download the spritesheet
    If sprite
      If ImageAlign(@sprite)
        \sprite = \rpix\SurfaceBitmap(ImageID(sprite));<- load the spritesheet into a surface by grabbing its bitmap
      EndIf
      FreeImage(sprite)
    EndIf
    If \sprite
      \tile = \sprite\RegionCreate(12,2,20,21,64,96,64,1);<- create tiles (define a sequence of regions that describe the images within the spritesheet)
      \animation[0] = \rpix\AnimationCreate(0,11,4);<- create a animation for tile 0 - 11 that fires every 4 frames (animation updates)
      \animation[1] = \rpix\AnimationCreate(12,23,2);<- create another animation for the next tiles this time it fires every 2 frames
      If \tile And \animation[0] And \animation[1];<- all went well?
        ProcedureReturn #True
      EndIf
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i Main()
  Protected mx.i
  Protected my.i
  With game
    If WindowOpen()
      If Init()
        Repeat
          \rpix\InputUpdate(#True);<- update the input & hide the mouse cursor (inside the screen area)
          \rpix\InputMouseTranslate(@mx,@my);<- get the mouse position in screenspace
          \rpix\BufferFill(40);<- fill the background with the color index 40 (index into the current palette)
          \tile\DrawMask(#Null,\animation[0],4,400,300,146,#True);<- draw and animate the first skeleton
          \tile\DrawMask(#Null,\animation[1],4,mx,my,146,#True);<- draw and animate the second skeleton
          \rpix\BufferDrawEvent();<- render everything
          \rpix\AnimationUpdate();<- advance all animations 1 step
        Until WindowExit()
      EndIf
      WindowClose()
    EndIf
  EndWith
  ProcedureReturn #Null
EndProcedure

Main()

End
Fred
Administrator
Administrator
Posts: 16619
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Fred »

It looks so cool ! Good work !
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Thank you Fred :D

No update this time but another demo.
I recently saw a post about Conway’s Game of Life in the forum and immidiatly thought it would make a great demo project.
However i wanted a twist on it so i decided to use the drawing functions to examine the neighbouring cells :)
A fun little project not meant to be taken too serious ;)

Demo 5 - Conway’s Game of Life

Normal mode (choppy gif preview):
Image

Raindrop mode (choppy gif preview):
Image

Controls:

Code: Select all

;[LEFT MOUSE BUTTON] = SPAWN CELLS
;[KEY F1] = TOGGLE FULLSCREEN
;[KEY F2] = TOGGLE RAINDROP MODE
;[KEY F3] = CLEAR ALL CELLS
;[KEY ESCAPE] = EXIT GAME
Code:

Code: Select all

EnableExplicit

;------------------------------------------------------------------------------------
;RPIX (alpha 7) Demo 5 - Game Of Life
;Utilizes the drawing functions to check the neighbouring cells :>
;------------------------------------------------------------------------------------
;Keys:
;------------------------------------------------------------------------------------
;[LEFT MOUSE BUTTON] = SPAWN CELLS
;[KEY F1] = TOGGLE FULLSCREEN
;[KEY F2] = TOGGLE RAINDROP MODE
;[KEY F3] = CLEAR ALL CELLS
;[KEY ESCAPE] = EXIT GAME
;------------------------------------------------------------------------------------

XIncludeFile "rpix.pbi"

Structure DEMO_STRUCT
  *rpix.RPIX
  *gol.RPIX_SURFACE[4]
  width.i
  height.i
  *buffer
  buffersize.i
EndStructure

Global demo.DEMO_STRUCT

Procedure.i rpixWindowOpen(Width.i,Height.i,FullScreen.i = #False,ScreenWidth.i = #Null,ScreenHeight.i = #Null,Aspect.i = #False,FrameRate.i = 60)
  Protected flags.i
  With demo
    If rpixVersion() = #RPIX_VERSION
      flags = #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered
      If OpenWindow(#Null,#Null,#Null,Width,Height,#Null$,flags)
        \rpix = rpixInterface(WindowID(#Null),ScreenWidth,ScreenHeight,Aspect,FrameRate)
        If FullScreen
          StickyWindow(#Null,#True)
          \rpix\ScreenToggle()
        EndIf
        If \rpix
          ProcedureReturn #True
        EndIf
        CloseWindow(#Null)
      EndIf
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i rpixWindowTitle(Title.s)
  With demo
    SetWindowTitle(#Null,Title)
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i rpixWindowExit()
  Protected exit.i
  With demo
    Repeat
      Select WindowEvent()
        Case #Null
          Break
        Case #PB_Event_CloseWindow
          exit = #True
      EndSelect
    ForEver
    ProcedureReturn exit
  EndWith
EndProcedure

Procedure.i rpixWindowClose()
  With demo
    \rpix\Release()
    CloseWindow(#Null)
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i Init();<- prpare all colors & surfaces
  Protected w.i
  Protected h.i
  With demo
    \rpix\PaletteSet(0,$002FC7);<- set the colors at index 0 & 1
    \rpix\PaletteSet(1,$006FC7)
    \rpix\OutputSize(@\width,@\height);<- what is the screensize (aka. backbuffer size) 
    \gol[0] = \rpix\SurfaceCreate(\width + 2,\height + 2);<- create a surface with 2 extra pixels in width and height (dead cell border)
    \gol[1] = \rpix\SurfaceCreate(4,4);<- create two surfaces which are later used to hold the cells that get examined
    \gol[2] = \rpix\SurfaceCreate(4,4);<- will be used to mask out unwanted cells
    \gol[3] = \rpix\SurfaceCreate(\width + 2,\height + 2);<- to store the next generation
    If \gol[0] And \gol[1] And \gol[2] And \gol[3];<- all went well?
      \gol[1]\Buffer(@\buffer,@\buffersize);<- get a pointer to the surface buffer (later used to read out pixel states)
      \gol[2]\BufferSet(1,1,1);<- generate a mask that is used to get all neighbouring pixels
      \rpix\DrawBox(\gol[2],-1,-1,5,5,1)
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndWith
EndProcedure

Procedure.i golEvolve()
  Protected *cell.Ascii
  Protected x.i
  Protected y.i
  Protected w.i
  Protected h.i
  Protected alive.i
  Protected index.i
  Protected count.i
  With demo
    w = \width - 1
    h = \height - 1
    For y = 0 To h;<- iterate through all pixels
      For x = 0 To w
        \gol[0]\DrawRect(\gol[1],0,0,X,Y,4,4);<- draw/copy the pixels to examine onto the surface 1
        \gol[1]\BufferGet(1,1,@alive);<- check if the current cell is alive (BufferGet = read pixel color)
        \gol[2]\DrawTint(\gol[1],0,0,0,0);<- draw the mask over surface 1 so only the pixels needed are retained
        *cell = \buffer;<- grab the pointer to the buffer of surface 1
        count = 0;<- reset the cell counter
        For index = 0 To 15;<- count all neighbouring cells (could be improved)
          count + *cell\a
          *cell + 1
        Next
        If alive;<- evolve the next generation of cells
          If count < 2 Or count > 3
            \gol[3]\BufferSet(X + 1,Y + 1,0);<- set the next generation
          Else
            \gol[3]\BufferSet(X + 1,Y + 1,1)
          EndIf
        Else
          If count = 3
            \gol[3]\BufferSet(X + 1,Y + 1,1)
          Else
            \gol[3]\BufferSet(X + 1,Y + 1,0)
          EndIf
        EndIf
      Next
    Next
    \gol[0]\BufferRead(\gol[3]);<- override the old generation
    \gol[0]\Draw(#Null,-1,-1);<- draw the surface into the backbuffer
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i golInit(Possibility.i)
  Protected x.i
  Protected y.i
  With demo
    \gol[0]\BufferFill()
    For y = 1 To \height
      For x = 1 To \width
        \gol[0]\BufferSet(x,y,Bool(Random(Possibility) = 0));<- set random initial cells
      Next
    Next
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i Main()
  Protected title.s
  Protected mx.i
  Protected my.i
  Protected tick.i
  Protected mode.i
  With demo
    title = "RPIX Demo 5 - GOL" + " / Raindrop Mode: " 
    If rpixWindowOpen(800,600,#False,200,150,#True)
      rpixWindowTitle(title + "0")
      If Init()
        ;golInit(10)
        Repeat
          tick + 1;<- a framcounter
          If tick % 6 = 0;<- every 6 frames generate a new generation
            golEvolve()
          EndIf
          \rpix\InputUpdate();<- update the input
          If \rpix\InputKeyToggle(#VK_F1);<- f1 toggle fullscreen mode
            \rpix\ScreenToggle()
          EndIf
          If \rpix\InputKeyToggle(#VK_F2);<- f2 toggle rainmode
            mode ! 1
            rpixWindowTitle(title + Str(mode))
          EndIf
          If \rpix\InputKeyToggle(#VK_F3);<- clear all cells
            \gol[0]\BufferFill()
          EndIf
          If \rpix\InputMouseTranslate(@mx,@my);<- grab the mouse and check if its inside the screen
            If \rpix\InputKeyDown(#VK_LBUTTON);<- is the left mouse button pressed?
              \rpix\DrawCircle(#Null,mx + 1,my + 1,5,0,1)
              \rpix\DrawCircle(\gol[0],mx + 1,my + 1,5,0,1);<- draw a circle (generate new cells)
              \rpix\DrawBox(\gol[0],0,0,\width + 2,\height + 2,0);<- retain the dead cell border
            EndIf
          EndIf
          If tick % 28 = 0 And mode;<- every 28 frames generate a raindrop effect if raindrop mode is 1
            mx = Random(\width) + 1;<- get a random screen position for the raindrop
            my = Random(\height) + 1
            \rpix\DrawCircle(\gol[0],mx,my,3,0,1);<- draw 3 circles that make up the raindrop effect
            \rpix\DrawCircle(\gol[0],mx,my,6,4,1)
            \rpix\DrawCircle(\gol[0],mx,my,10,4,1)
            \rpix\DrawBox(\gol[0],0,0,\width + 2,\height + 2,0);<- retain the dead cell border
          EndIf  
          \rpix\BufferDrawEvent();<- draw everything
          If \rpix\InputKeyToggle(#VK_ESCAPE)
            Break
          EndIf
        Until rpixWindowExit()
      EndIf
      rpixWindowClose()
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

CompilerIf #PB_Compiler_Debugger
  MessageRequester("RPIX Demo","Please disable the debugger :)",#PB_MessageRequester_Info)  
CompilerElse
  Main()
CompilerEndIf

End
More infos about Conway's Game of Life: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
Have fun
User avatar
Kuron
Addict
Addict
Posts: 1626
Joined: Sat Oct 17, 2009 10:51 pm
Location: Pacific Northwest

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Kuron »

Looks amazing. I need to try and find my login info to redownload PB so I can try this.
Best wishes to the PB community. Thank you for the memories. ♥️
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Thank you @Kuron :)

Update alpha 8:
  • added convenience functions: rpixWindowOpen(), rpixWindowTitle(), rpixWindowExit() and rpixWindowClose()
Quick and easy Window/Screen creation 8)

Demo 6 Snake:
Image

Code:

Code: Select all

EnableExplicit

;Demo 6 (alpha 8) - Snake :>

XIncludeFile "rpix.pbi"

Structure GAME_STRUCT
  *rpix.RPIX
  tick.i
  state.i
  direction.i
  object.i
  points.i
  count.i
  tail.POINT
  head.POINT
EndStructure

Global game.GAME_STRUCT

Procedure.i snakeInit()
  Protected x.i
  Protected y.i
  Protected c.i
  With game
    \rpix\BufferFill()
    \state = 1
    \direction = 1
    \points = 0
    \count = 0
    \head\x = 32
    \head\y = 32
    \tail\x = 32
    \tail\y = 33
    \rpix\BufferSet(\head\x,\head\y,1)
    \rpix\BufferSet(\tail\x,\tail\y,1)
    For y = 0 To 63
      For x = 0 To 63
        If (x <> 32 And y < 32) Or (x <> 32 And y > 32)
          c = Bool(Random(100) = 0)
          \count + c
          \rpix\BufferSet(x,y,5 * c)
        EndIf
      Next
    Next
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i snakeUpdate(*Pos.POINT,Direction.i)
  With game
    Select Direction
      Case 1:*Pos\y - 1
      Case 2:*Pos\x + 1
      Case 3:*Pos\y + 1
      Case 4:*Pos\x - 1
    EndSelect
    ProcedureReturn #Null
  EndWith
EndProcedure

Procedure.i snakeMain()
  With game
    \rpix = rpixWindowOpen(#RPIX_WINDOW_NORMAL,600,600,64,64)
    If \rpix
      rpixWindowTitle(\rpix,"Demo 6 - SNAKE")
      \rpix\PaletteSet(0,$212121)
      \rpix\PaletteSet(1,$00FF00)
      \rpix\PaletteSet(2,$00FF00)
      \rpix\PaletteSet(3,$00FF00)
      \rpix\PaletteSet(4,$00FF00)
      \rpix\PaletteSet(5,$FF0000)
      Repeat
        \tick + 1
        \rpix\InputUpdate()
        Select \state
          Case 0
            \rpix\BufferFill()
            \rpix\DrawText(#Null,32,20,"PLAY",1,#True,#True)
            \rpix\DrawText(#Null,32,28,"SNAKE",1,#True,#True)
            \rpix\DrawText(#Null,32,40,"[RETURN]",100,#True,#True)
            If \rpix\InputKeyDown(#VK_RETURN)
              snakeInit()
            EndIf
          Case 1
            If \rpix\InputKeyDown(#VK_UP)
              \direction = 1
            EndIf
            If \rpix\InputKeyDown(#VK_RIGHT)
              \direction = 2
            EndIf
            If \rpix\InputKeyDown(#VK_DOWN)
              \direction = 3
            EndIf
            If \rpix\InputKeyDown(#VK_LEFT)
              \direction = 4
            EndIf
            If \rpix\InputKeyDown(#VK_ESCAPE)
              \state = 2
            EndIf
            If \tick % 4 = 0
              \rpix\BufferSet(\head\x,\head\y,\direction)
              snakeUpdate(@\head,\direction)
              If \rpix\BufferGet(\head\x,\head\y,@\object)
                If \object = 0 Or \object = 5
                  \rpix\BufferSet(\head\x,\head\y,\direction)
                  If \object
                    \points + 1
                    If \points = \count
                      \state = 2
                    EndIf
                  Else
                    \rpix\BufferGet(\tail\x,\tail\y,@\object)
                    \rpix\BufferSet(\tail\x,\tail\y,0)
                    snakeUpdate(@\tail,\object)
                  EndIf
                Else
                  \state = 2
                EndIf
              Else
                \state = 2
              EndIf
            EndIf
          Case 2
            \rpix\BufferFill()
            If \points = \count
              \rpix\DrawText(#Null,32,20,"YOU",1,#True,#True)
              \rpix\DrawText(#Null,32,28,"WON",1,#True,#True)
            Else
              \rpix\DrawText(#Null,32,20,"GAME",5,#True,#True)
              \rpix\DrawText(#Null,32,28,"OVER",5,#True,#True)
            EndIf
            \rpix\DrawText(#Null,32,40,Str(\points) + "/" + Str(\count),100,#True,#True)
            If \rpix\InputKeyToggle(#VK_RETURN)
              SnakeInit()
            EndIf
            If \rpix\InputKeyToggle(#VK_ESCAPE)
              \state = 0  
            EndIf
        EndSelect    
        \rpix\BufferDrawEvent()
      Until rpixWindowExit(\rpix)
      rpixWindowClose(\rpix);<- will call \rpix\Release() / dont call \rpix\Release() when using the rpixWindow functions!
    EndIf
    ProcedureReturn #Null
  EndWith
EndProcedure

snakeMain()

End
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: [Windows x64] RetroPixel a 2D 8-Bit (256 Color) GFX Library

Post by Mijikai »

Update alpha 9:
  • fixed an problem related to the cursor clipping (relative mouse mode)
Post Reply