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.14.1.tar.gz (38.7 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.14.1-cp312-cp312-win_amd64.whl (235.7 kB view details)

Uploaded CPython 3.12Windows x86-64

File details

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

File metadata

  • Download URL: jwarol-0.14.1.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

Hashes for jwarol-0.14.1.tar.gz
Algorithm Hash digest
SHA256 64e3e1a77208bfe49e0f49fcafbffd748cfe3417450c6f6415136fbb1e3fbef5
MD5 5fc90b0acb35e6616ef978c6df495a9d
BLAKE2b-256 4012688cab2f0812f57f367b77fce5737d867fc4755567bc944504f2874f8f7a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: jwarol-0.14.1-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

Hashes for jwarol-0.14.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 eb4f6e655d32afd2bd6b446d068f901ba0f5b82cf2a9616fb9de377eea984fef
MD5 b2f0457ab0eb885e2d202353c61ddfff
BLAKE2b-256 7abbda6c344a4543ba5a7311e2a09f5058ca573d608ba837b6aa141e67f2605c

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