High-performance creative engine (Win32/OpenGL/Python)
Project description
jwarol
Движок на C++ для 2D-игр на Python. Win32 API + сырой OpenGL (и X11 на Linux). Включено: рендеринг, шейдеры, текстуры, физика, звук .wav, игровые объекты, шифрование ассетов, скелетная анимация из Blender, 3D → 2D проекция, сборка в .exe через PyInstaller.
Возможности
| Функция | Описание |
|---|---|
| Window | Создание окна с OpenGL-контекстом (Win32 / X11) |
| Shader | Компиляция GLSL-шейдеров из строк или отдельных .glsl файлов |
| Renderer | Рендеринг прямоугольников и текстур через GPU |
| Texture | Загрузка PNG/JPG/BMP, процедурные текстуры, наложение |
| Input | Клавиатурный ввод (GetAsyncKeyState) |
| Camera | Следование за игроком, скроллинг мира, smooth-камера |
| Sprite/Animation | Спрайт-листы, анимация по кадрам, генерация анимаций |
| DetailGroup | Группа деталей — батч-рендеринг тысяч объектов без просадки FPS |
| AnimatedSprite2D | Скелетная 2D-анимация из Blender (bones, keyframes, IK) |
| SkeletonData | Иерархия костей для скелетной анимации |
| SkeletalAnimation | Анимация с интерполяцией ключевых кадров |
| Blender Export | Экспорт 2D/3D моделей и анимаций из Blender в движок |
| 3D → 2D Pipeline | 3D-модели из Blender рендерятся как 2D-спрайты |
| Physics | Verlet-интеграция, гравитация, коллизии, типы тел |
| Sound | Воспроизведение .wav (winmm) |
| Object/Player/Character/GUI/Detail | Система игровых объектов |
| Texture.encrypt | Шифрование текстур для скомпилированных игр |
| PyInstaller Build | Сборка игры в .exe одной командой |
| Clock | Замер дельта-тайма |
| Vec2/Rect/Mat3 | 2D-математика |
Установка
pip install jwarol
Сборка из исходников
# Windows (MSVC)
python setup.py build
# Linux (GCC)
python setup.py build
Быстрый старт
import jwarol as jl
jl.init_graphics()
win = jl.Window(800, 600, "Hello jwarol")
renderer = jl.Renderer(800, 600)
while win.is_open:
win.poll_events()
win.clear(0.1, 0.1, 0.2)
renderer.draw_rect(100, 100, 200, 150, 1.0, 0.3, 0.5)
renderer.draw_rect(350, 200, 100, 100, 0.2, 0.7, 1.0)
win.swap_buffers()
Текстуры
# Загрузка из файла (PNG/JPG/BMP на Windows, BMP на Linux)
tex = jl.Texture("sprite.png")
# Процедурная текстура из пикселей (RGBA)
import struct
w, h = 64, 64
pixels = bytearray()
for y in range(h):
for x in range(w):
pixels.extend([255, 0, 0, 255]) # красный пиксель
tex = jl.Texture(w, h, bytes(pixels), 4)
# Рисование с текстурой
renderer.draw_texture(tex, 100, 100, 200, 200)
# Текстура с прозрачностью
renderer.draw_texture(tex, 100, 100, 200, 200, alpha=0.5)
# Спрайт из tileset'а (draw_texture_ex)
renderer.draw_texture_ex(tileset, 0, 0, 32, 32, 0, 0, 32, 32)
# Умная загрузка всех текстур из папки
textures = jl.Texture.load_all("assets/textures/")
# Наложение текстур
combined = tex.overlay(other_tex, 10, 10, alpha=0.7)
Физика
world = jl.World()
world.gravity = 980.0
# Verlet-точки
p = world.add_point(400, 100) # падает вниз
# Физические тела (прямоугольники)
body = world.add_body(300, 0, 50, 50, jl.BodyType.DYNAMIC)
static = world.add_body(0, 650, 800, 50, jl.BodyType.STATIC)
# Исключение из физики
body.enabled = False # не участвует в симуляции
# Шаг физики
dt = clock.tick()
world.step(dt)
# Чтение позиции
x, y = body.pos.x, body.pos.y
Ввод и время
input = jl.Input()
clock = jl.Clock()
while win.is_open:
dt = clock.tick()
input.update()
if input.is_down(ord('W')): pass # клавиша зажата
if input.is_pressed(ord(' ')): pass # только что нажали
if input.is_released(ord('A')): pass # только что отпустили
Шейдеры
# Из строк
shader = jl.Shader("""
#version 330 core
layout(location = 0) in vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
""", """
#version 330 core
uniform vec4 uColor;
out vec4 FragColor;
void main() { FragColor = uColor; }
""")
shader.use()
# Из отдельных файлов (любые названия)
shader = jl.Shader.from_file("shaders/vertex.glsl", "shaders/fragment.glsl")
shader = jl.Shader.from_file("custom.vert", "custom.frag")
# Из одного файла с маркерами (#shader vertex / #shader fragment)
shader = jl.Shader.from_single_file("shader.glsl")
# Ошибки обрабатываются без краша:
# - файл не найден → RuntimeError
# - файл пустой → RuntimeError
# - ошибка компиляции → RuntimeError с логом
try:
s = jl.Shader.from_file("bad.glsl", "empty.glsl")
except RuntimeError as e:
print("Shader error:", e)
Звук
# Из папки
sound = jl.Sound("assets/sounds/effect.wav")
sound.play()
sound.set_volume(500) # 0-1000
sound.stop()
# Быстрое воспроизведение без создания объекта
jl.Sound.play_file("assets/sounds/click.wav")
Камера
cam = jl.Camera(800, 600)
# Следование за игроком (с плавностью)
cam.follow(player)
cam.smooth = 0.1 # 0 = мгновенно, 1 = очень плавно
# Применить к рендереру перед отрисовкой
cam.update(dt)
cam.apply_to(renderer)
# Все draw_rect/draw_texture теперь сдвинуты камерой
# Объекты за экраном не рисуются (FPS не проседает)
renderer.draw_rect(0, 0, 2000, 2000, 0.2, 0.2, 0.3) # большой мир
Спрайты и анимация
# Загрузка спрайт-листа (одна картинка с кадрами)
sheet = jl.Texture("player_walk.png") # 4 колонки x 2 строки = 8 кадров
sprite = jl.Sprite()
sprite.set_texture(sheet, 4, 2) # 4 колонки, 2 строки
# Анимация из последовательности кадров
anim = jl.Animation()
anim.from_range(0, 3) # кадры 0,1,2,3
anim.speed = 0.1 # сек/кадр
anim.loop = True
sprite.set_animation(anim)
# В игровом цикле
sprite.update(dt)
sprite.draw_at(renderer, player.x, player.y, 64, 64)
# Генерация простых анимаций
idle = jl.Animation.generate("idle", 8)
walk = jl.Animation.generate("walk", 8)
run = jl.Animation.generate("run", 8)
# Рендерер сам обрезает невидимые объекты
renderer.set_camera(cam.x, cam.y)
renderer.is_visible(x, y, w, h) # проверка видимости вручную
Группа деталей (DetailGroup)
Тысячи мелких объектов без просадки FPS — все рендерятся за один draw call.
details = jl.DetailGroup()
# Цветные прямоугольники
details.add(100, 200, 32, 32, 1.0, 0.5, 0.0, 0.8)
# С текстурой (атлас: одна картинка с кадрами)
tileset = jl.Texture("tileset.png")
details.set_texture(tileset, 4, 4) # 4x4 сетка кадров
# Фрейм из атласа (col=0, row=0 — первый кадр)
details.add_frame(100, 100, 32, 32, 0, 0)
details.add_frame(140, 100, 32, 32, 1, 0)
# С произвольными UV-координатами
details.add_uv(200, 200, 64, 64, 0.0, 0.0, 0.25, 0.5)
# Дистанция — объекты дальше не рисуются
details.set_max_distance(500)
# Всё одним draw call
details.draw(cam.x, cam.y, 800, 600)
# Culling ON → рисует только видимое (FPS стабильный)
# Culling OFF → рисует всё подряд (можно сделать просадку FPS)
details.set_culling(False)
Скелетная анимация из Blender (AnimatedSprite2D)
Создайте 2D персонажа в Blender с арматурой, анимируйте — и экспортируйте в движок. Поддерживаются:
- 2D-скелеты из Blender (bones, parent hierarchy)
- Ключевые кадры с интерполяцией (position, rotation, scale)
- Множественные анимации (idle, walk, run, jump, attack...)
- Аттачмент спрайтов к костям
- Зацикленные и одноразовые анимации
- 3D-модели, спроецированные в 2D (Orthographic render)
Экспорт из Blender
# Установка: pip install Pillow
blender --background character.blend --python tools/blender_export.py -- --output ./export
Результат:
character.json— скелет, анимации, аттачментыcharacter.png— спрайт-лист со всеми кадрами анимаций
Использование в движке
# Загрузка из Blender-экспорта
hero = jl.AnimatedSprite2D.from_blender(
"export/character.json",
"export/character.png"
)
# Проигрывание анимации
hero.play("idle") # название анимации — имя Action в Blender
# В игровом цикле:
while win.is_open:
dt = clock.tick()
hero.update(dt)
hero.draw(renderer, player.x, player.y, scale=1.0)
Ручное создание скелета
# Создание скелета программно
skel = jl.SkeletonData()
skel.add_bone("root") # индекс 0
skel.add_bone("body", 0) # индекс 1, parent = root
skel.add_bone("head", 1) # индекс 2, parent = body
skel.add_bone("arm_l", 1) # индекс 3, parent = body
skel.add_bone("arm_r", 1) # индекс 4, parent = body
# Позиционирование костей
skel.bones[1].y = -30 # body ниже root
skel.bones[2].y = -30 # head выше body
skel.bones[3].x = -20 # arm_l левее body
skel.bones[4].x = 20 # arm_r правее body
# Создание анимации
walk = jl.SkeletalAnimation()
walk.name = "walk"
walk.duration = 1.0
walk.speed = 1.0
walk.loop = True
# Создание трека для bone[3] (arm_l)
track = jl.BoneTrack()
track.bone_index = 3
# Ключевые кадры вращения
kf1 = jl.BoneKeyframe()
kf1.time = 0.0; kf1.rotation = -30
kf2 = jl.BoneKeyframe()
kf2.time = 0.5; kf2.rotation = 30
kf3 = jl.BoneKeyframe()
kf3.time = 1.0; kf3.rotation = -30
track.keyframes = [kf1, kf2, kf3]
walk.tracks = [track]
# Прикрепление спрайтов к костям
att = jl.BoneAttachment()
att.bone_index = 1 # body
att.ox = 0; att.oy = 0
att.w = 64; att.h = 64
att.u0 = 0; att.v0 = 0 # UV в текстуре
att.u1 = 0.5; att.v1 = 0.5
# Создание анимированного спрайта
sprite = jl.AnimatedSprite2D()
sprite.skeleton = skel
sprite.add_animation(walk)
sprite.attachments = [att]
sprite.set_texture(jl.Texture("character.png"))
sprite.play("walk")
# В цикле:
sprite.update(dt)
sprite.draw(renderer, x, y)
3D → 2D Pipeline
3D-модели из Blender автоматически проецируются в 2D:
# Та же функция from_blender(), но модель 3D
# Blender экспортирует ортографические рендеры как спрайт-лист
mech = jl.AnimatedSprite2D.from_blender("mech.json", "mech.png")
mech.play("walk")
mech.draw(renderer, 400, 300, scale=2.0)
Игровые объекты
# Базовый объект
obj = jl.Object(100, 100, 64, 64)
obj.active = True
obj.set_texture(texture)
# Игрок
player = jl.Player(400, 300, 32, 32)
player.speed = 250.0
# Персонаж
char = jl.Character(200, 200, 32, 48)
char.hp = 100
# GUI-элемент
gui = jl.GUI(10, 10, 200, 30)
gui.text = "Score: 0"
# Мелкая деталь
detail = jl.Detail(500, 400, 16, 16)
detail.alpha = 0.5
Шифрование текстур
# Зашифровать текстуру (создаёт .enc файл)
jl.Texture.encrypt("sprite.png", "my_secret_key")
# Загрузить зашифрованную текстуру
tex = jl.Texture.load_encrypted("sprite.png.enc", "my_secret_key")
Сборка в .exe (PyInstaller)
Автоматическая сборка игры с C++-расширением jwarol в .exe:
# Базовая сборка (папка с файлами)
python build_exe.py game.py
# Один exe-файл
python build_exe.py game.py --onefile
# С иконкой и именем
python build_exe.py game.py --onefile --icon icon.ico --name "MyGame"
# С папкой ассетов
python build_exe.py game.py --assets assets/
# С консолью (для отладки)
python build_exe.py game.py --console
# Полная очистка
python build_exe.py game.py --clean
Результат в папке dist/.
Структура проекта
jwarol/
├── include/ # C++ заголовки
│ ├── Window.hpp # Окно (Win32 / X11)
│ ├── Shader.hpp # GLSL-шейдеры
│ ├── Renderer.hpp # 2D-рендерер (OpenGL)
│ ├── Texture.hpp # Текстуры (PNG/JPG/BMP/WIC)
│ ├── Input.hpp # Клавиатурный ввод
│ ├── Physics.hpp # Verlet-физика
│ ├── Sound.hpp # WAV-звук (winmm)
│ ├── Camera.hpp # Камера с follow
│ ├── Sprite.hpp # Спрайты и анимация
│ ├── DetailGroup.hpp# Батч-рендеринг
│ ├── Anim2D.hpp # Скелетная 2D-анимация
│ ├── GameObject.hpp # Игровые объекты
│ ├── Math.hpp # Vec2, Rect, Mat3
│ ├── Clock.hpp # Дельта-таймер
│ └── GLFunctions.hpp# OpenGL-функции
├── src/ # C++ исходники
├── tools/ # Инструменты
│ └── blender_export.py # Экспорт из Blender
├── build_exe.py # PyInstaller сборщик
├── setup.py # setup.py для build
└── README.md
Репозиторий
https://github.com/r9441887-collab/jwarol-code-new
Лицензия
MIT License — Руслан (r9441887@gmail.com)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jwarol-0.14.0.tar.gz.
File metadata
- Download URL: jwarol-0.14.0.tar.gz
- Upload date:
- Size: 38.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5b75e0cc0c605fdc4b96f005deee16e0fbdd12766ad346e7effa0c1d38e6915
|
|
| MD5 |
aa7c7849e7a6ed43862f45a0dcac80e1
|
|
| BLAKE2b-256 |
a03de22d804e7b3e4cc4b4abeceafc9adf3db334317c86e2cf7ef943ab9014f6
|
File details
Details for the file jwarol-0.14.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: jwarol-0.14.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 235.7 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6def2d6eabb80c1760874f2208da94765ec3359cfd0f02f9da188efe0afd48cd
|
|
| MD5 |
0e7f9610d92a3a8ab6f8ca5f481440f8
|
|
| BLAKE2b-256 |
7cafb64a63ab38fb3f3d181a16841e08c367d08758c56f4ec43165940ad44061
|