High-performance creative engine (Win32/OpenGL/Python)
Project description
jwarol
Движок на C++ для 2D-игр на Python. Win32 API + сырой OpenGL (X11 на Linux). Рендеринг, шейдеры, текстуры, физика, звук .wav, игровые объекты, шифрование ассетов, скелетная анимация из Blender, сборка в .exe через PyInstaller.
Возможности
| Функция | Описание |
|---|---|
| Window | Окно с OpenGL-контекстом (Win32 / X11), init_graphics() встроен |
| Shader | GLSL-шейдеры из строк/файлов, проверка ошибок, move-семантика |
| Renderer | 2D-рендеринг с ортографической проекцией, камерой, culling |
| Texture | PNG/JPG/BMP (WIC), процедурные, наложение, шифрование AES-256-CBC |
| Input | Клавиатурный ввод (Win32 / X11) |
| Camera | Следование за объектом или по координатам, zoom, smooth |
| Sprite/Animation | Спрайт-листы, покадровая анимация |
| DetailGroup | Батч-рендеринг тысяч объектов за 1 draw call |
| AnimatedSprite2D | Скелетная 2D-анимация из Blender |
| Physics | Verlet-интеграция, коллизии, STATIC/DYNAMIC/KINEMATIC |
| Sound | .wav через winmm (Windows), заглушка на Linux |
| GameObjects | Object, Player, Character, GUI, Detail |
| PyInstaller Build | Сборка игры в .exe |
Установка
pip install jwarol
Сборка из исходников
python setup.py build
Быстрый старт
import jwarol as jl
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()
Полный пример с физикой, камерой, управлением
import jwarol as jl, random
win = jl.Window(1280, 720, "Jwarol Demo")
renderer = jl.Renderer(1280, 720)
clock = jl.Clock()
input = jl.Input()
# Мир физики
world = jl.World()
world.gravity = 1200.0
world.add_body(0, 680, 2000, 40, jl.BodyType.STATIC) # земля
player_idx = world.add_body(400, 300, 50, 50, jl.BodyType.DYNAMIC)
player_speed = 400.0
jump_force = 700.0
# Камера
cam = jl.Camera(1280, 720)
cam.smooth = 0.1
# 1000 декоративных частиц
details = jl.DetailGroup()
for i in range(1000):
details.add(random.randint(-1000, 2000),
random.randint(0, 680), 5, 5,
random.random(), random.random(), 1.0, 0.6)
while win.is_open:
dt = clock.tick()
win.poll_events()
input.update()
pb = world.get_body(player_idx)
# Управление
if input.is_down(ord('A')): pb.pos.x -= player_speed * dt
if input.is_down(ord('D')): pb.pos.x += player_speed * dt
if input.is_pressed(ord('W')) and pb.pos.y >= 680 - pb.size.y:
pb.velocity.y = -jump_force
world.step(dt)
# Камера
pb = world.get_body(player_idx)
cam.follow_pos(pb.pos.x, pb.pos.y)
cam.update(dt)
cam.apply_to(renderer)
# Отрисовка
win.clear(0.1, 0.1, 0.15)
details.draw(cam.x, cam.y, 1280, 720)
renderer.draw_rect(0, 680, 2000, 40, 0.2, 0.8, 0.2)
pb = world.get_body(player_idx)
renderer.draw_rect(pb.pos.x, pb.pos.y, 50, 50, 1.0, 0.5, 0.0)
win.swap_buffers()
Текстуры
tex = jl.Texture("sprite.png")
# Процедурная текстура
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, alpha=0.5)
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)
Физика
add_body() возвращает индекс тела. Для изменений используйте get_body(index).
world = jl.World()
world.gravity = 980.0
idx = world.add_body(300, 0, 50, 50, jl.BodyType.DYNAMIC)
world.add_body(0, 650, 800, 50, jl.BodyType.STATIC)
world.step(dt)
body = world.get_body(idx)
x, y = body.pos.x, body.pos.y
body.velocity.y = -500 # прыжок
body.apply_force(jl.Vec2(100, 0))
BodyType
BodyType.DYNAMIC— движется, реагирует на коллизииBodyType.STATIC— неподвижен, не двигается при коллизияхBodyType.KINEMATIC— движется, не реагирует на коллизии
PhysicsBody свойства
pos(Vec2) — позицияsize(Vec2) — размерvelocity(Vec2) — скоростьtype(BodyType) — тип телаenabled(bool) — активно ли телоapply_force(Vec2)— добавить силу к скорости
Всеми телами можно управлять через индексы
idx = world.add_body(100, 100, 32, 32, jl.BodyType.DYNAMIC)
body = world.get_body(idx)
body.pos.x += 10
body.velocity.y = -jump_force
Ввод и время
input = jl.Input()
clock = jl.Clock()
while win.is_open:
dt = clock.tick()
input.update()
if input.is_down(ord('W')): ...
if input.is_pressed(ord(' ')): ...
if input.is_released(ord('A')): ...
is_down— зажатаis_pressed— только что нажатаis_released— только что отпущена
Шейдеры
shader = jl.Shader("#version 330 core\n...", "#version 330 core\n...")
shader.use()
shader = jl.Shader.from_file("vertex.glsl", "fragment.glsl")
shader = jl.Shader.from_single_file("shader.glsl")
try:
s = jl.Shader.from_file("bad.glsl", "empty.glsl")
except RuntimeError as e:
print("Shader error:", e)
Камера
cam = jl.Camera(800, 600)
# Следование за объектом
cam.follow(player)
cam.smooth = 0.1
# Следование за координатами (без привязки к объекту)
cam.follow_pos(player_x, player_y)
cam.zoom = 2.0
cam.update(dt)
cam.apply_to(renderer)
# Ручное управление камерой
cam.x = player_x - 400
cam.y = player_y - 300
Спрайты и анимация
sheet = jl.Texture("player_walk.png")
sprite = jl.Sprite()
sprite.set_texture(sheet, 4, 2)
anim = jl.Animation()
anim.from_range(0, 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)
Группа деталей (DetailGroup)
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)
details.add_frame(100, 100, 32, 32, 0, 0)
details.set_max_distance(500)
details.draw(cam.x, cam.y, 800, 600)
Скелетная анимация (AnimatedSprite2D)
hero = jl.AnimatedSprite2D.from_blender("character.json", "character.png")
hero.play("idle")
hero.anim_loop = True
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")
skel.add_bone("body", 0)
skel.add_bone("head", 1)
skel.add_bone("arm_l", 1)
skel.add_bone("arm_r", 1)
skel.bones[1].y = -30
skel.bones[2].y = -30
skel.bones[3].x = -20
skel.bones[4].x = 20
walk = jl.SkeletalAnimation()
walk.name = "walk"
walk.duration = 1.0
walk.loop = True
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]
sprite = jl.AnimatedSprite2D()
sprite.skeleton = skel
sprite.add_animation(walk)
sprite.set_texture(jl.Texture("character.png"))
sprite.play("walk")
# В цикле:
sprite.update(dt)
sprite.draw(renderer, x, y)
Игровые объекты
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 = jl.GUI(10, 10, 200, 30)
gui.text = "Score: 0"
detail = jl.Detail(500, 400, 16, 16)
detail.alpha = 0.5
Шифрование текстур (AES-256-CBC)
# Зашифровать файл
jl.Texture.encrypt("sprite.png", "my_secret_key")
# → sprite.png.enc
# Загрузить зашифрованный (расшифровка в памяти)
tex = jl.Texture.load_encrypted("sprite.png.enc", "my_secret_key")
Сборка в .exe (PyInstaller)
python build_exe.py game.py
python build_exe.py game.py --onefile --icon icon.ico --name "MyGame"
python build_exe.py game.py --assets assets/ --console
Результат в папке dist/.
Структура проекта
jwarol/
├── include/ # C++ заголовки
│ ├── Window.hpp # Окно (Win32 / X11)
│ ├── Shader.hpp # GLSL-шейдеры (move-семантика, деструктор)
│ ├── Renderer.hpp # 2D-рендерер (ортографическая проекция)
│ ├── Texture.hpp # Текстуры (PNG/JPG/BMP/WIC)
│ ├── Input.hpp # Клавиатурный ввод (Win32 / X11)
│ ├── Physics.hpp # Verlet-физика (STATIC fix, getBody)
│ ├── Sound.hpp # WAV-звук (winmm)
│ ├── Camera.hpp # Камера с follow/follow_pos/zoom
│ ├── Sprite.hpp # Спрайты и анимация
│ ├── DetailGroup.hpp# Батч-рендеринг
│ ├── Anim2D.hpp # Скелетная 2D-анимация
│ ├── GameObject.hpp # Игровые объекты
│ ├── Math.hpp # Vec2, Rect, Mat3
│ ├── Clock.hpp # Дельта-таймер
│ ├── Crypto.hpp # AES-256-CBC шифрование
│ └── GLFunctions.hpp# OpenGL-функции (кросс-платформа)
├── src/ # C++ исходники
├── tools/
│ └── blender_export.py
├── build_exe.py # PyInstaller сборщик
├── setup.py
└── 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.16.0.tar.gz.
File metadata
- Download URL: jwarol-0.16.0.tar.gz
- Upload date:
- Size: 57.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83691379f5d1b9f5e95c69c37fc53e3a8225fb77a56fc08964eae73c3ebf5920
|
|
| MD5 |
fda3bbc5ccfac3d9436636b181d7a98c
|
|
| BLAKE2b-256 |
55a854b9068e6d12ecdd35521be2c6dc0588b3925a160b65524698c022e2c795
|
File details
Details for the file jwarol-0.16.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: jwarol-0.16.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 273.1 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 |
24f9931f27888d172ca612af37718db664617de5487a5009c4df8849aaa41795
|
|
| MD5 |
86f33480c390aa012148e62bbf98e1f6
|
|
| BLAKE2b-256 |
b6b75edb7b3e650d4c0dd9101d4784a850c180cdbee5c9ebdf635b95f2fd78ad
|