https://github.com/python-ninja-hebi/wonderworld
Data-oriented Python Game Engine
https://github.com/python-ninja-hebi/wonderworld
game-engine pygame pygame-library python
Last synced: 2 months ago
JSON representation
Data-oriented Python Game Engine
- Host: GitHub
- URL: https://github.com/python-ninja-hebi/wonderworld
- Owner: Python-Ninja-Hebi
- Created: 2023-02-20T16:14:59.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2024-01-23T13:26:30.000Z (almost 2 years ago)
- Last Synced: 2024-11-14T16:16:28.454Z (about 1 year ago)
- Topics: game-engine, pygame, pygame-library, python
- Language: Python
- Homepage:
- Size: 1020 KB
- Stars: 3
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.ipynb
Awesome Lists containing this project
README
{
"cells": [
{
"cell_type": "markdown",
"id": "f3f374ee-78f2-4c76-bd7c-53fe608c9ade",
"metadata": {},
"source": [
"# wonderworld - Data-oriented Python Game Engine"
]
},
{
"cell_type": "markdown",
"id": "fce9d0b5-0b81-4048-ab23-d48138f3601e",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "markdown",
"id": "ae0b1338-2a91-40a3-a5c7-f9aa1ce2638b",
"metadata": {},
"source": [
"* **Game Engine** implemented on **Database Engine (in memory with pandas)**\n",
"* **world** is a **database**\n",
"* **archetype** is a **table**\n",
"* **entity** is a **row(id)**\n",
"* **componenten** is one **column**\n",
"* **system** is a **function**\n",
"* **query** is a **database query**\n",
"* **Now** with **pydantic** https://docs.pydantic.dev/latest/"
]
},
{
"cell_type": "markdown",
"id": "f4e8bd63-0105-43b4-a2e4-47b4388a08e3",
"metadata": {},
"source": [
"*I have long wanted to program games in Pygame using the Entity Component System (ECS) game design pattern. Once when I looked at the Bevy game framework in Rust, I was hooked. I wanted something like that in Python too.\n",
"Since I couldn't find such a framework anywhere, I started developing a Python ECS framework myself -* **wonderworld**"
]
},
{
"cell_type": "markdown",
"id": "ab577b53-d604-4cf3-9a87-d7b41b87a272",
"metadata": {},
"source": [
"## Entity Component System ECS"
]
},
{
"cell_type": "markdown",
"id": "a57006ef-0e7b-4072-8e91-5aabfc739eae",
"metadata": {},
"source": [
"The data-oriented programming (DOP) approach tries to strictly separate data and program from each other."
]
},
{
"cell_type": "markdown",
"id": "bd482601-3598-4e57-a583-13eaf344f983",
"metadata": {},
"source": [
"One way to do this is with the **Entity Component System** (ECS) design pattern.\n",
"\n",
"A **component** in the ECS design pattern contains only data.\n",
"\n",
"Examples:\n",
"\n",
"* Transform(x,y) - TransformComponent\n",
"* Speed (x,y) - SpeedComponent\n",
"* Sprite(image) - ImageComponent\n",
"\n",
"A component does not contain any processing logic.\n",
"\n",
"An **entity** is a game object, such as the knight. It consists of any number of components and does not itself contain any data or program logic. A unique ID linked to the components via an internal data structure is sufficient for the implementation of an entity.\n",
"\n",
"A **system** contains the complete processing logic, but no own data. A game can consist of any number of systems. A system reads components and transforms their current state into another.\n",
"\n",
"For example, the **Movement system** retrieves all speed components (SpeedComponents) and transform components (PositionComponent) in order to calculate the new position from them."
]
},
{
"cell_type": "markdown",
"id": "7034ab26-5c14-4ae2-9ee3-851234071c10",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "markdown",
"id": "20592fa5-ca08-4a80-869b-29af74851808",
"metadata": {},
"source": [
"## Bevy"
]
},
{
"cell_type": "markdown",
"id": "589a0967-1a23-4987-84a0-d8eb00973a60",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "markdown",
"id": "af81b4c4-0f8e-4b9a-a6e1-26b02dbc6982",
"metadata": {},
"source": [
"https://bevyengine.org"
]
},
{
"cell_type": "markdown",
"id": "52f5c455-9b0e-4b53-8598-bde0ef661808",
"metadata": {},
"source": [
"## pygame"
]
},
{
"cell_type": "markdown",
"id": "c9bd7ff7-6124-4061-8601-1a285e5d0aca",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "markdown",
"id": "adc227f6-e595-4fd9-b6e3-0f24dfe5f2a6",
"metadata": {},
"source": [
"https://www.pygame.org"
]
},
{
"cell_type": "markdown",
"id": "8225736a-f412-44ef-917a-d5d42898b435",
"metadata": {},
"source": [
"## Example first.py"
]
},
{
"cell_type": "markdown",
"id": "ce973014-a027-4cf9-886a-dd379b9bc8aa",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c6d9c7c3-b869-494b-b7df-71ede81b51c6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.1.2 (SDL 2.0.18, Python 3.9.16)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [
"from wonderworld import *\n",
"\n",
"PADDLE_VELOCITY: float = 180.0\n",
"\n",
"class Paddle(Component):\n",
" pass\n",
"\n",
"\n",
"def setup():\n",
" commands = get_commands()\n",
" assets = get_resource(Assets)\n",
"\n",
" # asset_server = get_resource(AssetServer)\n",
" # image_handle = asset_server.load(\"box.png\");\n",
"\n",
" image_handle = assets.add(create_image(color=pygame.Color('white'),\n",
" size=Vector2(10.0, 100.0)))\n",
"\n",
" # camera\n",
" commands.spawn(Camera2dBundle(camera=Camera(),\n",
" transform=Transform(translation=Vector2(0,0),\n",
" rotation=0,\n",
" scale=Vector2(0, 0))))\n",
"\n",
" # paddle\n",
" commands.spawn(SpriteBundle(sprite=Sprite(image=image_handle),\n",
" transform=Transform(translation=Vector2(100, 100),\n",
" rotation=0,\n",
" scale=Vector2(0, 0)),\n",
" visibility=Visibility(visible=True))) \\\n",
" .insert(Paddle())\n",
"\n",
"\n",
"def input():\n",
" time = get_resource(Time)\n",
" keyboard_input = get_resource(Keyboard)\n",
"\n",
" for transform, in Query(Transform, With(Paddle)):\n",
" paddle_move = time.delta_seconds * PADDLE_VELOCITY\n",
" if keyboard_input.pressed(K_UP):\n",
" transform.translation.y = transform.translation.y - paddle_move\n",
"\n",
" if keyboard_input.pressed(K_DOWN):\n",
" transform.translation.y = transform.translation.y + paddle_move\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" App().insert_resource(ClearColor(color=pygame.Color('black'))) \\\n",
" .add_plugins(DefaultPlugins()) \\\n",
" .add_startup_system(setup) \\\n",
" .add_system(input) \\\n",
" .run()\n"
]
},
{
"cell_type": "markdown",
"id": "67145359-33b5-41c6-af74-c71681bd9734",
"metadata": {},
"source": [
"## Example ping.py"
]
},
{
"cell_type": "markdown",
"id": "d25c8267-2051-436d-bbae-78fdad3a4702",
"metadata": {},
"source": [
"
"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b7856bd8-3958-497d-bec8-9bbf04e0d884",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.1.2 (SDL 2.0.18, Python 3.9.16)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [
"from wonderworld import *\n",
"\n",
"HEIGHT = 600\n",
"WIDTH = 800\n",
"\n",
"PADDLE_WIDTH: float = 10.0\n",
"PADDLE_HEIGHT: float = 100.0\n",
"PADDLE_VELOCITY: float = 180.0\n",
"PADDLE_MAX_MOVE: float = HEIGHT - PADDLE_HEIGHT\n",
"PADDLE_MIN_MOVE: float = 0\n",
"\n",
"BALL_WIDTH: float = 10.0\n",
"BALL_VELOCITY: Vector2 = Vector2(160.0, 160.0)\n",
"\n",
"LINE_WIDTH: float = 2.0;\n",
"\n",
"\n",
"class Score(Resource):\n",
" left: int\n",
" right: int\n",
"\n",
"\n",
"class Ball(Component):\n",
" pass\n",
"\n",
"\n",
"class Paddle(Component):\n",
" up_key: int\n",
" down_key: int\n",
"\n",
"\n",
"class Velocity(Component):\n",
" value: Vector2\n",
"\n",
"\n",
"class LeftText(Component):\n",
" pass\n",
"\n",
"\n",
"class RightText(Component):\n",
" pass\n",
"\n",
"\n",
"def setup():\n",
" commands = get_commands()\n",
" assets = get_resource(Assets)\n",
"\n",
" # camera\n",
" commands.spawn(Camera2dBundle(camera=Camera(),\n",
" transform=Transform()))\n",
"\n",
" # ball\n",
" ball_handle = assets.add(create_image(color=pygame.Color('white'),\n",
" size=Vector2(BALL_WIDTH, BALL_WIDTH)))\n",
"\n",
" commands.spawn(SpriteBundle(sprite=Sprite(image=ball_handle),\n",
" transform=Transform(translation=Vector2(100, 100)),\n",
" visibility=Visibility(visible=True))) \\\n",
" .insert(Velocity(value=BALL_VELOCITY.copy())) \\\n",
" .insert(Ball())\n",
"\n",
" # line\n",
" line_handle = assets.add(create_image(color=pygame.Color('white'),\n",
" size=Vector2(LINE_WIDTH, HEIGHT)))\n",
" commands.spawn(SpriteBundle(sprite=Sprite(image=line_handle),\n",
" transform=Transform(translation=Vector2(WIDTH / 2, 0)),\n",
" visibility=Visibility(visible=True)))\n",
" # paddle_left\n",
" left_handle = assets.add(create_image(color=pygame.Color('white'),\n",
" size=Vector2(PADDLE_WIDTH, PADDLE_HEIGHT)))\n",
" commands.spawn(SpriteBundle(sprite=Sprite(image=left_handle),\n",
" transform=Transform(translation=Vector2(0.0,\n",
" HEIGHT / 2.0 - PADDLE_HEIGHT / 2)),\n",
" visibility=Visibility(visible=True))) \\\n",
" .insert(Paddle(up_key=K_w, down_key=K_a))\n",
"\n",
" # paddle_right\n",
" right_handle = assets.add(create_image(color=pygame.Color('white'),\n",
" size=Vector2(PADDLE_WIDTH, PADDLE_HEIGHT)))\n",
" commands.spawn(SpriteBundle(sprite=Sprite(image=right_handle),\n",
" transform=Transform(translation=Vector2(WIDTH - PADDLE_WIDTH,\n",
" HEIGHT / 2.0 - PADDLE_HEIGHT / 2)),\n",
" visibility=Visibility(visible=True))) \\\n",
" .insert(Paddle(up_key=K_UP, down_key=K_DOWN))\n",
"\n",
" # score\n",
" asset_server = get_resource(AssetServer)\n",
" font_size = 64\n",
" commands.spawn(Text2dBundle(\n",
" text = Text(text=\"0\",\n",
" font = asset_server.load(\"fonts/FiraSans-Bold.ttf\",AssetType.FONT,size=font_size),\n",
" font_size = font_size,\n",
" color = Color('white')\n",
" ),\n",
" transform = Transform(translation=Vector2(WIDTH/4.0,200.)\n",
" ))).insert(LeftText())\n",
" commands.spawn(Text2dBundle(\n",
" text=Text(text=\"0\",\n",
" font=asset_server.load(\"fonts/FiraSans-Bold.ttf\", AssetType.FONT, size=font_size),\n",
" font_size=font_size,\n",
" color=Color('white')\n",
" ),\n",
" transform=Transform(translation=Vector2(3 *WIDTH / 4.0, 200.)\n",
" ))).insert(RightText())\n",
"\n",
"\n",
"def input():\n",
" time = get_resource(Time)\n",
" keyboard = get_resource(Keyboard)\n",
"\n",
" for (paddle, transform) in Query((Paddle, Transform)):\n",
" paddle_move = time.delta_seconds * PADDLE_VELOCITY\n",
" if keyboard.pressed(paddle.up_key):\n",
" transform.translation.y = max(PADDLE_MIN_MOVE, transform.translation.y - paddle_move)\n",
" if keyboard.pressed(paddle.down_key):\n",
" transform.translation.y = min(PADDLE_MAX_MOVE, transform.translation.y + paddle_move)\n",
"\n",
"\n",
"def move_ball():\n",
" time = get_resource(Time)\n",
" for (transform, velocity) in Query((Transform, Velocity), With(Ball)):\n",
" transform.translation += velocity.value * time.delta_seconds\n",
"\n",
" if transform.translation.y <= 0 or transform.translation.y + BALL_WIDTH >= HEIGHT:\n",
" velocity.value.y = - velocity.value.y\n",
"\n",
"\n",
"def collides():\n",
" for ball_transform, velocity in Query((Transform, Velocity), With(Ball)):\n",
" ball_size = Vector2(BALL_WIDTH, BALL_WIDTH)\n",
" for paddle_transform, in Query(Transform, With(Paddle)):\n",
" paddle_size = Vector2(PADDLE_WIDTH, PADDLE_HEIGHT)\n",
" if collide_aabb(ball_transform.translation, ball_size,\n",
" paddle_transform.translation, paddle_size):\n",
" velocity.value.x = - velocity.value.x\n",
"\n",
"\n",
"def score():\n",
"\n",
" score = get_resource(Score)\n",
"\n",
" for transform, in Query(Transform,With(Ball)):\n",
" if transform.translation.x < 0:\n",
" score.right += 1\n",
" transform.translation = Vector2(WIDTH/2,HEIGHT/2)\n",
" elif transform.translation.x > WIDTH:\n",
" score.left += 1\n",
" transform.translation = Vector2(WIDTH/2,HEIGHT/2)\n",
"\n",
"def show_score():\n",
" score = get_resource(Score)\n",
"\n",
" for text, in Query(Text,With(LeftText)):\n",
" text.text = f\"{score.left}\"\n",
"\n",
" for text, in Query(Text,With(RightText)):\n",
" text.text = f\"{score.right}\"\n",
"\n",
"if __name__ == \"__main__\":\n",
" App() \\\n",
" .insert_resource(ClearColor(color=pygame.Color('black'))) \\\n",
" .insert_resource(WindowDescriptor(\n",
" title=\"wonderworld ping\",\n",
" width=WIDTH,\n",
" height=HEIGHT,\n",
" resizable=False)) \\\n",
" .insert_resource(Score(left=0, right=0)) \\\n",
" .add_plugins(DefaultPlugins()) \\\n",
" .add_startup_system(setup) \\\n",
" .add_system(input) \\\n",
" .add_system(move_ball) \\\n",
" .add_system(collides) \\\n",
" .add_system(score) \\\n",
" .add_system(show_score) \\\n",
" .run()\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d55e968e-f43b-4e60-b5cc-68ea1e615576",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "c9a06c9b-eb2a-42b9-9aed-03659c2321c9",
"metadata": {},
"source": [
"* Let's build an Entity Component System from scratch (part 1) https://devlog.hexops.com/2022/lets-build-ecs-part-1/\n",
"* Let's build an Entity Component System (part 2): databases https://devlog.hexops.com/2022/lets-build-ecs-part-2-databases/"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51de7e74-6cc1-4231-9bfe-7b07acf68ea7",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}