r/raylib

🔥 Hot ▲ 123 r/raylib+3 crossposts

I made a Metroidvania where you can build and share your own worlds

Hey, I’ve been working on Silent Citadel, a 2D metroidvania for a while and just released the first playable version on itch.

It includes a full handcrafted world, plus a built-in world editor you can use to create and share your own custom worlds.

There's also local co-op support for up to 4 players.

Would love feedback if anyone wants to try it:

https://ertud2.itch.io/silent-citadel

u/nop4n1c_ — 7 hours ago
▲ 7 r/raylib+1 crossposts

PixelClaw: an LLM agent for image manipulation

I'm making an LLM agent specialized for image processing. It combines:

  • an LLM for conversation, planning, and tool use (supports a variety of LLMs)
  • image generation/AI-based editing via gpt-image
  • background removal via rembg (several specialized models available)
  • pixelization using pyxelate
  • posterization and defringing using custom algorithms
  • speech-to-text (Whisper) and text-to-speech (Kokoro plus HALO)
  • a nice UI based on Raylib, including file drag-and-drop

PixelClaw is free and open-source at https://github.com/JoeStrout/PixelClaw/ . You can find more demo videos there too. While you're there, if you find it interesting, please click the star ⭐️ at the top of the page; that helps me gauge interest.

u/JoeStrout — 2 days ago
🔥 Hot ▲ 121 r/raylib

Really had fun building a custom terminal emulator GUI for my Game in Raylib. DOS anyone? :D The CRT shader also is the cherry on top!

This is for my my game. That will be a shmup game. Think like Tyrian 2000. But because the "universe" where it will play. It's was for me important that it really look old skool. This Terminal simulator works in a 8x8 pixel grid.

I used the free unscii font. Then with Raylib font tools I had a texture + glyph information. Then a custom font renderer. Did this btw in C#.

But you can do so much fun stuff with this. Let me know if people are interesting in the source. Because currently it's in my private git repo. The game it self won't be open source. But when I am sure everything is ok I will move some parts to my custom game engine as additional nuget package which is open source and MIT!

Anyway just wanted to share this. Really proud what it become and yet again Ray, thank you for the amazing library!

u/Dear-Beautiful2243 — 4 days ago
▲ 38 r/raylib+2 crossposts

Nevu UI has been updated to v0.7.5

Changelog: https://github.com/GolemBebrov/nevu-ui/releases/tag/v,

0.7.5

This update focuses on improving quality and shorten length of code! also improving performance

Please star my project, on github, i will really appreciate it!

Code of the program on the video:

import nevu_ui as nv #(0.7.5, tested on: python 3.14.2)
import pygame #(pygame-ce)
import sys
import pyray as rl
pygame.init()


GLOBAL_FONT = "tests/vk_font.ttf" # CHANGE IT!!! if you wnant to change font


def create_border(name):
    return nv.BorderConfig(name = name, font = nv.load_font(GLOBAL_FONT, 20))


class UI:
    def __init__(self, root, res):
        self.root = root
        self.generate_base_constants()
        self.create_menu_base(res)
        self.create_menu_left(res)
        self.create_menu_scr(res)
        self.create_menu_group(res)


    def generate_base_constants(self):
        self.widget_style = nv.default_style(border_radius=5, border_width=0, colortheme=nv.ColorThemeLibrary.dracula, font_name=GLOBAL_FONT)
             
        self.widget_style2 = self.widget_style(border_radius=15*2)
        
        self.widget_size_s = (100, 50)
        self.widget_size_m = (150, 50)
        self.widget_size_l = (200, 50)
        self.widget_size_x = (250, 75) 
        self.widget_size_xl = (300, 50)
        self.widget_size_xxl = (400, 50)
        
        self.widget_kwargs = {"size": self.widget_size_l, "style": self.widget_style, "single_instance": True}
        self.widget_kwargs2 = {"size": self.widget_size_m, "style": self.widget_style2, "single_instance": True}
    
    def _on_style_click(self, *args, **kwargs):
        colotheme = args[1]
        self.menu.apply_style_patch_to_layout(colortheme=colotheme) 
        self.menu_left.apply_style_patch_to_layout(colortheme=colotheme)
    
    def create_menu_base(self, res):
        nv.nevu_object_globals.modify(**self.widget_kwargs)
        with nv.widget_globals.modify_temp(size = self.widget_size_s, style = self.widget_style(font_size = 25), subtheme_role=nv.SubThemeRole.TERTIARY):
            self.x = nv.Label("X:NAN", self.widget_size_s, self.widget_style(font_size=25))
            self.y = nv.Label("Y:NAN", self.widget_size_s, self.widget_style(font_size=25))
        
        coords_lay = nv.StackColumn(
            content = [
                (nv.Align.CENTER, self.x),
                (nv.Align.CENTER, self.y)
            ])
        
        self.mode = nv.ElementSwitcher(elements = ["Tile","Script","Group"], on_content_change = self.root.on_change_mode)
        mode_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, nv.Label("Mode:", self.widget_size_l, subtheme_role = nv.SubThemeRole.TERTIARY)),
                (nv.Align.CENTER, self.mode)
            ])
        
        with nv.widget_globals.modify_temp(subtheme_role = nv.SubThemeRole.PRIMARY, style = self.widget_style2, size=50, active_rect_factor=0.8): #type: ignore
            self.wall = nv.RectCheckBox(on_toggle = self.root.stub)
            is_wall_layout = nv.StackRow(
                content = [
                    (nv.Align.CENTER, nv.Label("Wall:", self.widget_size_l)),
                    (nv.Align.CENTER, self.wall)
                ]
            )


            self.passable =  nv.RectCheckBox(on_toggle = self.root.stub, toggled=True)
            is_passable_layout = nv.StackRow(
                content = [
                    (nv.Align.CENTER, nv.Label("Passable:", self.widget_size_l)),
                    (nv.Align.CENTER, self.passable)
                ]
            )


        tile_attrs_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, is_wall_layout),
                (nv.Align.CENTER, is_passable_layout)
            ], borders = create_border(name = "Traits")
        )
        
        file_layout = nv.StackRow(
            content = [
                (nv.Align.CENTER, nv.Label("TBD...", subtheme_role = nv.SubThemeRole.TERTIARY))
            ], borders = create_border(name = "Files")
        )
        
        left_panel_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, tile_attrs_layout),
                (nv.Align.CENTER, file_layout)
            ]
        ) 
        white_line = nv.Widget((300, 3), self.widget_style(border_radius=5, border_width=0), single_instance = False,)
        with nv.widget_globals.modify_temp(**self.widget_kwargs2):
            scene_layout = nv.StackColumn(
                content = [
                    (nv.Align.CENTER, nv.Button(self.root.stub,"Save")),
                    (nv.Align.CENTER, white_line),
                    (nv.Align.CENTER, nv.Button(self.root.stub,"Load")),
                    (nv.Align.CENTER, white_line),
                    (nv.Align.CENTER, nv.Button(self.root.stub,"New")),
                ], borders = create_border(name = "Scene")
            )


            self.obj_lbl = nv.Label("Object", style=self.widget_style(border_radius=(0,15,15,0)))
            self.obj_btn = nv.Button(self.root.stub, "+", size=(50,50), style=self.widget_style(border_radius=(15,0,0,15)))
            pl_object_stack = nv.StackRow(
                content = [
                    (nv.Align.CENTER, self.obj_btn), (nv.Align.CENTER, self.obj_lbl)
                ], spacing = 2
            )
            
            self.door_lbl = nv.Label("+ Door", style=self.widget_style(border_radius=(0,15,15,0)))
            self.door_btn = nv.Button(self.root.stub, "+", size=(50,50), style=self.widget_style(border_radius=(15,0,0,15)))
            pl_door_stack = nv.StackRow(
                content = [
                    (nv.Align.CENTER, self.door_btn), (nv.Align.CENTER, self.door_lbl)
                ], spacing = 2
            )
            
            objects_layout = nv.StackColumn(
                content=[(nv.Align.CENTER, pl_object_stack),
                        (nv.Align.CENTER, pl_door_stack),
                ], borders = create_border(name = "Objects")
            )
        
        self.stylechk = nv.ElementSwitcher(self.widget_size_m, [["Material", nv.ColorThemeLibrary.material3_green], ["MaterialAlt", nv.ColorThemeLibrary.material3_dark],
                                                                ["Cat Dark", nv.ColorThemeLibrary.catppuccin_mocha], ["Cat Light", nv.ColorThemeLibrary.catppuccin_latte],
                                                                ["Box Dark", nv.ColorThemeLibrary.gruvbox_dark], ["Box Light", nv.ColorThemeLibrary.gruvbox_light],
                                                                ["Guthib", nv.ColorThemeLibrary.github_dark], ["Pastel", nv.ColorThemeLibrary.pastel_rose_light],
                                                                [1,nv.ColorThemeLibrary.dracula]
                                                                ], self.widget_style2, on_content_change = self._on_style_click)
        self.stylechklbl = nv.Label("Style:", self.widget_size_s, self.widget_style, subtheme_role = nv.SubThemeRole.TERTIARY)
        
        style_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, self.stylechklbl),
                (nv.Align.CENTER, self.stylechk)
            ]
        )
        
        self.layer = nv.ElementSwitcher(self.widget_size_s, ["0","1","2","3","4","5","6","7","8","9","10"], self.widget_style2, on_content_change = self.root.stub)
        self.layerlbl = nv.Label("layer:", self.widget_size_s, self.widget_style, subtheme_role = nv.SubThemeRole.TERTIARY)
        
        layer_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, self.layerlbl),
                (nv.Align.CENTER, self.layer)
            ]
        )
        self.layer.disactivate()
        self.layer.hide()
        self.layerlbl.hide()
        layer_style_layout = nv.StackColumn(
            content = [
                (nv.Align.CENTER, style_layout),
                (nv.Align.CENTER, layer_layout)
            ]
        )
        
        main_layout = nv.Grid([nv.fill%100, nv.fill%100], x = 6, y = 1,
                            content = {
                                (1, 1): coords_lay,
                                (2, 1): left_panel_layout,
                                (3.3, 1): scene_layout,
                                (4.33, 1): objects_layout,
                                (5.3, 1): layer_style_layout,
                                (5, 1): nv.Widget([5,nv.fill%100], nv.default_style(border_width=0)),
                                (6, 1): mode_layout
                            }, borders = create_border(name = "REDATOR"))
                            
        self.menu = nv.Menu(window, [res[0], 300], layout=main_layout, style=self.widget_style(border_radius=0, border_width=0,))
        self.menu.set_coordinates(0, res[1]-300) #pls dont watch at this bad code aaaaaaaahh
        
    def create_menu_scr(self, res):
        self.menuscr = nv.Menu(window, [400,300])
        self.menuscr.set_coordinates(res[0]-400, res[1]-600)
        
        self.scriptE = nv.Input(self.widget_size_xl, self.widget_style2, "Not selected", whitelist = nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS, on_change_function=self.root.stub) 
        self.scriptL = nv.Input(self.widget_size_xl, self.widget_style2, "Not selected", whitelist = nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS, on_change_function=self.root.stub) 
        
        enter_layout = nv.StackColumn(spacing = 5,
                                content = [(nv.Align.CENTER, nv.Label("Enter:", [110,35], self.widget_style(border_radius=4, font_size=20))),
                                        (nv.Align.CENTER, self.scriptE)])
        exit_layout = nv.StackColumn(spacing = 5,
                                content = [(nv.Align.CENTER, nv.Label("Exit:", [110,35], self.widget_style(border_radius=4, font_size=20))),
                                        (nv.Align.CENTER, self.scriptL)])
        
        script_layout = nv.ScrollableColumn([400,300], spacing = 12,
                                content = [(nv.Align.CENTER, nv.Label("Script:", [nv.fill%60, 35], self.widget_style(font_size=20))),
                                        (nv.Align.CENTER, enter_layout),
                                        (nv.Align.CENTER, exit_layout)])
        
        self.menuscr.layout = script_layout
        
    def create_menu_group(self, res):
        self.menugroup = nv.Menu(window, [400,300], self.widget_style(colortheme=nv.ColorThemeLibrary.catppuccin_mocha))
        self.menugroup.set_coordinates(res[0]-400, res[1]-600)
        
        self.scr_name = nv.Input(self.widget_size_xxl, self.widget_style2(border_radius=0), "Not selected", whitelist = list(nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS), on_change_function=self.root.stub)
        self.groupE = nv.Input(self.widget_size_xl, self.widget_style2, "Entry", whitelist = list(nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS+" "+nv.InputType.NUMBERS), on_change_function=self.root.stub)
        self.groupL = nv.Input(self.widget_size_xl, self.widget_style2, "Exit", whitelist = list(nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS+" "+nv.InputType.NUMBERS), on_change_function=self.root.stub)
        self.groupC = nv.Input(self.widget_size_xl, self.widget_style2, "Inside", whitelist = list(nv.InputType.ALL_LETTERS+nv.InputType.ALL_SYMBOLS+" "+nv.InputType.NUMBERS), on_change_function=self.root.stub)


        name_lay = nv.StackColumn(
                            content = [(nv.Align.LEFT, nv.Label("Name(Important):", [200,35], self.widget_style(border_radius=(0,15,15,0), font_size=20))),
                                    (nv.Align.LEFT, self.scr_name)]
        )


        enter_lay = nv.StackColumn(spacing=5,
                            content = [(nv.Align.CENTER, nv.Label("Entry:", [110,35], self.widget_style(border_radius=4, font_size=20))),
                                    (nv.Align.CENTER, self.groupE)])


        exit_lay = nv.StackColumn(spacing=5,
                            content = [(nv.Align.CENTER, nv.Label("Exit:", [110,35], self.widget_style(border_radius=4, font_size=20))),
                                    (nv.Align.CENTER, self.groupL)])


        inside_lay = nv.StackColumn(spacing=5,
                            content = [(nv.Align.CENTER, nv.Label("Inside:", [110,35], self.widget_style(border_radius=4, font_size=20))),
                                    (nv.Align.CENTER, self.groupC)])



        script_group_panel = nv.ScrollableColumn([100%nv.fill, 100%nv.fill], spacing = 5, id = "scr",
            content=[
                (nv.Align.CENTER, nv.Label("Script Group:", self.widget_size_xl, self.widget_style(font_size=20))),
                (nv.Align.CENTER, name_lay),
                (nv.Align.CENTER, enter_lay),
                (nv.Align.CENTER, exit_lay),
                (nv.Align.CENTER, inside_lay)
            ])
        
        self.menugroup.layout = script_group_panel
        
    def create_menu_left(self, res):
        self.menu_left = nv.Menu(window, [300,1080-300], style=self.widget_style2(border_radius=0, border_width=0))
        lay2 = nv.ScrollableColumn([300,1080-300],)
        with nv.widget_globals.modify_temp(subtheme_role = nv.SubThemeRole.PRIMARY, style = self.widget_style2()):
            self.name = nv.Input((250,50),None,"Name","Name",whitelist=list(nv.InputType.ALL_SYMBOLS+nv.InputType.NUMBERS),on_change_function=self.root.stub, single_instance=True)
            self.desc = nv.Input((250,50),None,"Description",on_change_function=self.root.stub, single_instance=True)
        lay2.add_item(self.name,nv.Align.CENTER)
        lay2.add_item(self.desc,nv.Align.CENTER)
        
        self.graphity = nv.RectCheckBox(35,self.widget_style2, on_toggle =self.root.stub, active_rect_factor=0.85)
        self.graphity_slider = nv.Slider((200,30),self.widget_style2(font_size=10), current_value = 50, start = 50, end = 100,)
        
        lay3 = nv.StackColumn(
            content=[
                (nv.Align.CENTER, nv.Label("Graphity mode:",(200,50),self.widget_style2, subtheme_role = nv.SubThemeRole.TERTIARY)),
                (nv.Align.CENTER, nv.StackColumn(content=[(nv.Align.CENTER, self.graphity_slider),(nv.Align.CENTER, self.graphity)]))
            ])
            
        lay2.add_item(lay3,nv.Align.CENTER)
        self.menu_left.layout = lay2
        nv.Widget()
        
    def draw(self):
        
        self.menu.draw()
        self.menu_left.draw()
        if self.root.mode =="Script": self.menuscr.draw()
        if self.root.mode =="Group": self.menugroup.draw()


    def update(self):
        self.menu.update()
        self.menu_left.update()
        if self.root.mode =="Script": self.menuscr.update()
        if self.root.mode =="Group": self.menugroup.update()



class App():
    def __init__(self,res):
        global window #Antipattern detected Ow<
        self.size = (res[0],res[1])
        window = nv.Window(self.size, resizable=True, backend=nv.Backend.Pygame) 
        self.window = window
        self.mode = "Tile"
        self.ui = UI(self,[res[0],res[1]])
        
        pygame.display.set_caption("Nevu UI Demo")


    def stub(self, *args, **kwargs):
        print("Action triggered:", args, kwargs)


    def on_change_mode(self, val, id):
        self.mode = val
        print(f"Mode changed to: {self.mode}")
        
        if self.mode != "Group":
            self.ui.layer.disactivate()
            self.ui.layer.hide()
            self.ui.layerlbl.hide()
        else:
            self.ui.layer.activate()
            self.ui.layer.show()
            self.ui.layerlbl.show()
            
    def update(self):
        self.window.update(pygame.event.get(), 999999999) #no limitz
        self.ui.update()
        
    def draw(self):
        self.window.display.fill((20, 20, 25, 255))
        self.ui.draw()
        #window.draw_overlay() #to draw layout borders!! uOu
        
    def run(self):
        font = pygame.Font(GLOBAL_FONT, 20)
        draw_fps = True
        while True:
            window.begin_frame()
            self.update()
            self.draw()
            if draw_fps:
                if nv.nevu_state.window.is_dtype.raylib:
                    rl.draw_fps(0,0)
                else:
                    self.window.display.blit(font.render(f"FPS: {str(nv.time.fps)}", True, (255, 255, 255)), (10,10))
            window.end_frame()
        
if __name__ == "__main__":
    app = App((1900, 1080))
    app.run()
u/Due_Engineer_7647 — 3 days ago
▲ 2 r/raylib

LCARS app progress: text editor

This is the continuation in the series of LCARS app development:

This time I focused on implementing a text editor from scratch in Raylib.

This is for something like the personal log / Captain’s log use-case.

Works better in the full screen version available here

Features

  • Mouse over text area to allow editing text
  • Insert text - only at the end for now… Will work on a gab buffer or something in the future to allow moving the cursor and edit text anywhere.
  • Text selection forwards and backwards with mouse click and drag
  • Delete from the end with backspace key - holding down backspace will delete text with a delay
  • Delete selected text with backspace or delete key
  • Copy paste with Ctrl+C and Ctrl+V
  • Using the native app (not for web yet), saving text into a local DB (sqlite)
    • restarting the app, retains the saved text
  • Rotating Earth, before it was just a sphere wireframe.

These feature may seem basic (for the text editor for instance), but remember I’m implementing this from scratch in Raylib, so it’s a start…

Works best on desktop. Did not look into making it mobile friendly yet.

mihai-safta.dev
u/CatalinMihaiSafta — 8 hours ago
▲ 14 r/raylib

Adding graphics for my Grown Up addaptation videogame

Now the character appears more like Adan Sandler than your last version hahaha.

u/Shift_Character — 4 days ago