This repo contains example programs for pyray, the Python bindings for the raylib library. All examples have been migrated from the official raylib C examples.
Original C examples:
Note:
A ticket was opened to request integration of these examples with pyray:
electronstudio/raylib-python-cffi#173
Create a virtual environment (for Windows, activation differs on each platform):
python -m venv venv
venv\Scripts\activate
Install the pyray package:
pip install -r requirements.txt
Run the desired example:
python raylib_official_examples/textures/textures_background_scrolling.py
WARNING: most of the code is the result of an automatic migration from C to Python using non-deterministic tools, so it may contain bugs, may not be idiomatic Python, contains odd comments...
Not all the code was reviewed. (I just don't have the time for that)
The license of the original C examples has been preserved. (See the module-level docstring in each.) As far as I know, they all follow the zlib/libpng license.
Note that not all examples work: 140/173 (~81%) examples work without issues.
See migration_issues for the list of examples with known issues, and a brief description of the issue. Note that some examples run, but some actions cause crashes. The action is described next to the example path.
See pyray API oddities for some quick notes on API that seem clumsy to use.
Many examples share common failures, such as failure to create a Texture2D().
The script list_working_examples.py can be used to update:
This is a heavily tool-assisted migration of those 120+ examples, in about 1.5 calendar days.
Tools used (as of 2025-05-14):
- Visual Studio Code (1.100.1), with GitHub Copilot
- Agent mode
- Model: mostly "Claude 3.7 Sonnet", but initially used "Gemini 2.5 Pro (Preview)". Unfortunately, Gemini started returning constant "500 Server Error", so I switched to Claude which felt much slower, but gave good results with some rare Python syntax/indent issues...
The original C examples were copied into the repo directory: the Copilot Agent is only allowed to access files within the project directory. This is the great advantage of using the agent mode: you don't need to add files to the context, just provide some reference to them.
You can find a rough log of the prompts here, though midway I started just updating the last one as it was growing too big, and it seems to accept new guidelines without issues.
Migration prompts started with the first paragraph, and grew as I found recurrent issues. Recommendations:
- Create the target directory beforehand if you don't want to waste 1 min for "I need to create the directory".
- Don't trust it when it says it migrated everything. Compare the original number of .c and .py files. It's common to require 3+ iterations for a directory (but this also gives you the opportunity to adjust the prompt with newly discovered issues).
- In practice, I was testing/tracking down issues on the previous conversion batch while the tool worked on the next batch. To track a given source had been tested, I added it to git (which shows up in a different color in PyCharm).
- For some languages, it may be necessary to provide complete examples of "good" quality code. Add these source files to the context and explain in the prompt how they should be used as a reference.
Prompt:
Let's migrate all examples from raylib_c_examples/textures to raylib_official_examples/textures. Existing files may have already been migrated.
Use "import pyray as rl" to import raylib.
Comments must be written in English.
Don't forget that:
- The correct way to call rlgl_* functions in Python is, for example: "rl.rl_push_matrix()".
- It is rl.rl_color4ub(), not rl.rl_color_4ub()
- It is rl.rl_normal3f(), not rl.rl_normal_3f()
- It is rl.draw_texture_n_patch, not rl.draw_texture_npatch()
- It is "rl.update_camera(camera, rl.CAMERA_FREE)", not "rl.update_camera(rl.byref(camera), rl.CAMERA_FREE)", rl.byref() doesn't exist
- It is rl.rl_scalef(), not rl.rl_scale_f()
- It is rl.rl_rotatef(), not rl.rl_rotate_f()
- It is rl.rl_translatef(), not rl.rl_translate_f()
- It is rl.ffi.new(), not rl.pyray.ffi.new()
- It is rl.gui_get_state(), not rl.get_gui_state()
# Converting file loading
When you see code loading files such as:
music = rl.load_music_stream("raylib_c_examples/audio/resources/country.mp3") # BAD
Convert them using the following approach: THIS_DIR global constant (just after the import) with directory of the script,
and then make the path relative to it:
from pathlib import Path
THIS_DIR = Path(__file__).resolve().parent
#...
music = rl.load_music_stream(str(THIS_DIR/"raylib_c_examples/audio/resources/country.mp3")) # GOOD
# Loading shader
You should never pass None as the first parameter to rl.load_shader. The correct way to load a shader is:
shdr_outline = rl.load_shader("", str(THIS_DIR/f"resources/shaders/glsl{GLSL_VERSION}/outline.fs"))
# Pointer handling in rl.gui_*() functions
In the calls to rl.gui_slider_bar(), the 4th parameter must be a pointer to float created
with: "pyray.ffi.new('float *', 1.0)" for example. The float pointer value is accessed
with pointer[0].
Variables initialized with pyray.ffi.new() must be suffixed with _ptr.
Example of bad code:
start_angle = 0.0
print( f"Angle is {start_angle}" )
start_angle = rl.gui_slider_bar(rl.Rectangle(600, 40, 120, 20), "StartAngle", f"{start_angle:.2f}", start_angle, 0, 720)
Example of correct code:
start_angle_ptr = pyray.ffi.new('float *', 0.0)
print( f"Angle is {start_angle_ptr[0]}" )
rl.gui_slider_bar(rl.Rectangle(600, 40, 120, 20), "StartAngle", f"{start_angle:.2f}", start_angle_ptr, 0, 720)
Similar care is required for rl.gui_check_box(), but concerning the last parameter.
Similar care is required for rl.gui_spinner(), rl.gui_combo_box(), rl.gui_list_view(), rl.gui_list_view()
and rl.gui_value_box(), but for the 3rd parameter.
Similar care is required for rl.gui_list_view_ex(), but concerning the last 3 parameters.
Incorrect: font.base_size, font_sdf.glyph_count
Correct: font.baseSize, font_sdf.glyphCount
Below is an example of prompts used to fix non-trivial issues discovered after migration was done.
Prompt:
Review all the shapes/ migration to Python. The calls to rl.gui_slider_bar(), which have been incorrectly migrated
from C to Python, and adjust the code so that it is correct.
The 4th parameter must be a pointer to float created with: "pyray.ffi.new('float *', 1.0)" for example. The float
pointer value is accessed with pointer[0].
Example of bad code:
start_angle = 0.0
print( f"Angle is {start_angle}" )
start_angle = rl.gui_slider_bar(rl.Rectangle(600, 40, 120, 20), "StartAngle", f"{start_angle:.2f}", start_angle, 0, 720)
Example of correct code:
start_angle_ptr = rl.ffi.new('float *', 0.0)
print( f"Angle is {start_angle_ptr[0]}" )
rl.gui_slider_bar(rl.Rectangle(600, 40, 120, 20), "StartAngle", f"{start_angle_ptr[0]:.2f}", start_angle_ptr, 0, 720)
There is also an identical issue for rl.gui_check_box(), but concerning the last parameter. Correct it too.