Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jwarol-0.13.0.tar.gz (38.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jwarol-0.13.0-cp312-cp312-win_amd64.whl (234.5 kB view details)

Uploaded CPython 3.12Windows x86-64

File details

Details for the file jwarol-0.13.0.tar.gz.

File metadata

  • Download URL: jwarol-0.13.0.tar.gz
  • Upload date:
  • Size: 38.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for jwarol-0.13.0.tar.gz
Algorithm Hash digest
SHA256 96f8a108df541d65d0eb585f6c248a4c921a5f801af4be73227cbb0cede8eb67
MD5 2fc66b373036bc696feeea4dcd18ae74
BLAKE2b-256 abdb9310a9fb51d59c5979fdab9af172a89d1596edf5b1fcd89586c23f7e8210

See more details on using hashes here.

File details

Details for the file jwarol-0.13.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: jwarol-0.13.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 234.5 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

Hashes for jwarol-0.13.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 46a69cb392411f4ce45235a6d6ba055592a29700a2f137e3739da088b8645685
MD5 97703dbb8ab8730bc79a296001a18010
BLAKE2b-256 42d00b4b4a28f9624d00120c34b5cc2071237c12b249717722c458f216cde60a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page