├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── crunch.wav ├── effect_death.png ├── effect_death.txt ├── effect_vanish.png ├── effect_vanish.txt ├── enemy_eagle.png ├── enemy_eagle.txt ├── enemy_opossum.png ├── enemy_opossum.txt ├── hero.png ├── hero.txt ├── hero_alt.act ├── hurt.wav ├── jump.wav ├── layer_background.png ├── layer_background.tmx ├── layer_background.tsx ├── layer_foreground.png ├── layer_foreground.tmx ├── layer_foreground.tsx ├── objects.png ├── objects.tsx ├── pickup.wav ├── score.png ├── score.txt ├── ui.png ├── ui.tsx └── vulture.wav ├── screenshot.png └── src ├── UI.py ├── actor.py ├── eagle.py ├── effect.py ├── game.py ├── opossum.py ├── platformer.py ├── player.py ├── raster_effect.py ├── rectangle.py ├── score.py ├── sound.py ├── tilengine.py └── world.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Windows image file caches 7 | Thumbs.db 8 | ehthumbs.db 9 | 10 | # Folder config file 11 | Desktop.ini 12 | 13 | # Recycle Bin used on file shares 14 | $RECYCLE.BIN/ 15 | 16 | *.lnk 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.6 5 | install: 6 | - sudo cp lib/libTilengine.so /usr/lib/x86_64-linux-gnu 7 | - sudo chmod a+x /usr/lib/x86_64-linux-gnu/libTilengine.so 8 | - sudo apt install libsdl2-dev libsdl2-mixer-dev 9 | - pip install pysdl2 10 | script: 11 | - python platformer.py 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Marc Palacios (megamarc) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tilengine python platformer 2 | This project aims to teach actual game mechanics using the free, cross-platform [Tilengine retro graphics engine](http://www.tilengine.org) under python. 3 | 4 | ![screenshot](screenshot.png) 5 | 6 | ## Features 7 | The features implemented so far are: 8 | * Two layer parallax scrolling 9 | * Sprite and tileset animations 10 | * Raster effects for sky gradient color, cloud motion, sea water *linescroll* and hills movement on a single background layer 11 | * Basic list of game entities (actors) 12 | * Three character states: idle, running and jumping 13 | * Tileset attributes in Tiled editor: *type* and *priority* 14 | * Player/level interaction: the player can jump on platforms, get blocked by walls and pick gems 15 | * Inertial control and acceleration 16 | * Slopes 17 | * Active game entities management 18 | * Define game entities (enemies, etc) inside tmx object layer and load into a list 19 | * Enemy behavior and spawn active enemies from loaded entities list 20 | * Enemies can hurt player and make it bounce 21 | * Basic sound effects with SDL_Mixer library 22 | 23 | ## Prerequisites 24 | This project depends on three external components that must be installed separately: 25 | 26 | ### Tilengine 27 | http://www.tilengine.org 28 | 29 | Each supported platform has its own methods for build or install binaries, please follow method of your own platform. 30 | 31 | ### SDL2 and SDL2_Mixer 32 | https://www.libsdl.org/ 33 | 34 | SDL2 (Simple DirectMedia Layer) is an open-source, cross-platform library for managing windows, user input, graphics and sound. Both tilengine and this project use SDL2 internally. You must install the runtime binaries into your system. 35 | 36 | **Windows and OSX:** 37 | 38 | Download prebuilt binaries here: 39 | 40 | https://www.libsdl.org/download-2.0.php 41 | 42 | https://www.libsdl.org/projects/SDL_mixer/ 43 | 44 | **Debian-based linux:** 45 | 46 | Open a terminal window and install directly from package manager: 47 | ``` 48 | sudo apt install libsdl2-2.0-0 libsdl2-mixer-2.0-0 49 | ``` 50 | 51 | ### SDL2 python binding 52 | http://pysdl2.readthedocs.io 53 | 54 | You must also install the binding for using SDL2 from python language. From a terminal window type the following command: 55 | ``` 56 | pip install pysdl2 57 | ``` 58 | 59 | ## Source code organisation 60 | This is a breif overview about modules breakdown in `/src` directory 61 | 62 | |Module | Role 63 | |-------------------------------------------|--------------------- 64 | |[`platformer.py`](src/platformer.py) | Main module: init and game loop 65 | |[`tilengine.py`](src/tilengine.py) | Tilengine binding for python 66 | |[`raster_effect.py`](src/raster_effect.py) | Tilengine raster effects for background 67 | |[`game.py`](src/game.py) | Game backbone, global instances 68 | |[`actor.py`](src/actor.py) | Base class for all game entities 69 | |[`player.py`](src/player.py) | Player class 70 | |[`opossum.py`](src/opossum.py) | Terrestrial enemy class 71 | |[`eagle.py`](src/eagle.py) | Flying enemy class 72 | |[`score.py`](src/score.py) | Pop-up animation of score class 73 | |[`effect.py`](src/effect.py) | Generic one-shot animation class 74 | |[`world.py`](src/world.py) | World/level class 75 | |[`UI.py`](src/ui.py) | HUD UI class (score, time...) 76 | |[`rectangle.py`](src/rectangle.py) | Simple helper class for rectangles 77 | |[`sound.py`](src/sound.py) | Sound effects manager 78 | 79 | ## Acknowledge 80 | Graphic assets are copyrighted and owned by their original authors 81 | * Backgrounds created by ansimuz: https://ansimuz.itch.io/magic-cliffs-environment 82 | * Player character created by Jesse M: https://jesse-m.itch.io/jungle-pack 83 | -------------------------------------------------------------------------------- /assets/crunch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/crunch.wav -------------------------------------------------------------------------------- /assets/effect_death.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/effect_death.png -------------------------------------------------------------------------------- /assets/effect_death.txt: -------------------------------------------------------------------------------- 1 | death-1,0,0,40,41 2 | death-2,40,0,40,41 3 | death-3,0,41,40,41 4 | death-4,40,41,40,41 5 | death-5,80,0,40,41 6 | death-6,80,41,40,41 -------------------------------------------------------------------------------- /assets/effect_vanish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/effect_vanish.png -------------------------------------------------------------------------------- /assets/effect_vanish.txt: -------------------------------------------------------------------------------- 1 | vanish1,0,0,16,16 2 | vanish2,16,0,16,16 3 | vanish3,32,0,16,16 4 | vanish4,48,0,16,16 5 | vanish5,64,0,16,16 6 | vanish6,80,0,16,16 7 | vanish7,96,0,16,16 8 | vanish8,112,0,16,16 9 | -------------------------------------------------------------------------------- /assets/enemy_eagle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/enemy_eagle.png -------------------------------------------------------------------------------- /assets/enemy_eagle.txt: -------------------------------------------------------------------------------- 1 | fly1,0,0,40,40 2 | fly2,40,0,40,40 3 | fly3,80,0,40,40 4 | fly4,120,0,40,40 5 | -------------------------------------------------------------------------------- /assets/enemy_opossum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/enemy_opossum.png -------------------------------------------------------------------------------- /assets/enemy_opossum.txt: -------------------------------------------------------------------------------- 1 | opossum-1,0,0,36,24 2 | opossum-2,0,24,36,24 3 | opossum-3,36,0,36,24 4 | opossum-4,36,24,36,24 5 | opossum-5,0,48,36,24 6 | opossum-6,36,48,36,24 -------------------------------------------------------------------------------- /assets/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/hero.png -------------------------------------------------------------------------------- /assets/hero.txt: -------------------------------------------------------------------------------- 1 | idle1,0,0,24,36 2 | idle2,24,0,24,36 3 | idle3,48,0,24,36 4 | idle4,72,0,24,36 5 | idle5,96,0,24,36 6 | idle6,120,0,24,36 7 | idle7,144,0,24,36 8 | idle8,168,0,24,36 9 | idle9,0,36,24,36 10 | idle10,24,36,24,36 11 | idle11,48,36,24,36 12 | jump1,72,36,24,36 13 | jump2,96,36,24,36 14 | run1,0,72,24,36 15 | run2,24,72,24,36 16 | run3,48,72,24,36 17 | run4,72,72,24,36 18 | run5,96,72,24,36 19 | run6,120,72,24,36 20 | run7,144,72,24,36 21 | run8,168,72,24,36 22 | -------------------------------------------------------------------------------- /assets/hero_alt.act: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/hero_alt.act -------------------------------------------------------------------------------- /assets/hurt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/hurt.wav -------------------------------------------------------------------------------- /assets/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/jump.wav -------------------------------------------------------------------------------- /assets/layer_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/layer_background.png -------------------------------------------------------------------------------- /assets/layer_background.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJzt13e0z2UcB/B77XXtzbX33vMipRClVBJaIiOjNJDwU1ERKSOlQQMpVDJKi4bKSFEyyiijaFBCRr0uv3vOzz33d13cc+kc33Ne5zzP5/M8n+f97zci4v/9RZKK1Mk0Lw1pSUf6M5iTgYxkIjNZQnpRZD3FednITg5ykovc5DmNbHnJR34KUJBCFCaaIhRN5H4xilOCksFaKUpThrKUozwVTiFXRSpRmSpUpRrVqUFNalGbOtSlHvVDZjSgITE0ojFNuICmXMhFNONiLklCrua0oCWX0orWXMbltOEKrqQtV3E119COa2nPdXSgI524nhu4kZu4mc7cQhe6cmsCOQhYB2Jr3ehOD3pyG73oTR/6cjt30I87uYu7uYf+DGAg9zKI+xjMEIZGHH9oGPfzAA8ynBE8xMPxchCwDqgFHrEfySgeZTRjeIyxPM4TjGM8E4L9UBN5kkk8xdNMTuDcMzzLczwfUp9CaA4CI49nC8T2p/JCAvPivJhIL85LvMw0poc5M4NXEpkRl4PA1GC2WDOT8P7JvJoMM2aeuD6W7zVmMZs5vM4bvJnCvXD1ubzFPOazgIW8ncK9cPV3WMS7vMf7fMCHKdwLV1983nmJWMJH50COcD7mk3MgR6hPWcpnfM4XLDvLmZazgpV8ySq+4mtWs4Zv+Ja1fMc61rMhmbNs5Ht+YBOb2cJWfuQntrGdHezkZ35hF7v5ld/4nT/Yw17+5C/28Tf7k5jpAAf5h0Mc5ghH+ZfYH5FIUpGaNKQlHenJQEYykZksRJGVbGQnBznJRW7ykJd85KcABSNPzFXIvjDRFKEoxShOCUpSitKUoSzlKE8FKlKJylShKtWoTg1qUova8dShLvWoT4OghsTQKJjzP8so008= 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/layer_background.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/layer_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/layer_foreground.png -------------------------------------------------------------------------------- /assets/layer_foreground.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | eJztmumPFEUYh2t6QL97BuQWhXVRjEYFFj5qwg27IOu1C+L6xZvEAwRtOdZdhJVr1a+emCiCgIpGXQXikfgPeCx+8D5jVBQIiE8x3dmaSnV3dfd0z6zuL3lSnZqqfqve9zfVPSxCDGhAAxrQgLLQJwVzW6vqb+utVQXlMeu8VitunpJbcKAIgzwGw2nVXFQ/03SPGTATZsEchXkwHxqhKeJeJk+F9SWNq9fdr/npEevLS/qer4JJMBmmeDTA1LwXhq4Rwr0aqhA6kdRcPixKC38EVsNahXZ4FDqgM2C+1AbYCF0x+pLEldLr7td8msW+s9Qm2AxbYKvSfwfcCXfB3R73wLIM14IXpSfLtIp0r4QHYQUshwdq2LO6f16D1+EN2AdvKbwN78C70BMwX2o/HICDMfrixvWl1z1uzU01TCL9u/oBfAgfwcdK/7PwHDwPL3hshxcrsAZVeE7gPYEHBV4UeFJ685T24sc9sBtehV2wE16BHTXgVdNzV/fPT/Az/AK/wm8Kv8Mf8CccDpgvdQSOwrEYfXHjqlLrblPzsBrGVdC5eRxOwD9wUun/FD6Dz+ELj144lDC+SXhN4DmB9wQeFHhR4EmBN6VHxY948Qf4Hr6Db+Eb+Bq+qrJPTeeelO6f0Xh5DJwPY+FChfFQBxdBfcE8P6nixlWl1r03Ik5UDaNke27a6IxCiSQK+42J1wSeE3hP4EGBFwWeFHhTelSMKgh3JIyA4TAMzoOhMKSQn09tzs0gLWJuM1wH18ONCi3QCothSYV/e+cVN6qGQYp7bmap4fxwHAmjYDSMgWlO3+d4TeA5gfcEHhR4UeBJgTelR09pIX5cAE3QCPNhHszNyae252aYutjL47AJtihsg254IqN/H8ojrk0NpSp5blZaQ4pC3OT0scxjWLH0OV4TeE7gPYEHBV4UeFJ6s0zr8WQndHjebM/Io2nOzf+rompYS+emSXPw4lDYii+3OWWt20h/k+fVdm8/eFDgRelJozozPj8rcW7+13QOtTjX4szVa6iqmudm2PpnF0senQu3huB7dUGxb66+R1VBPrX9m0tUG+fc9PdfqdimNo8Yfqu/m42D8TCLz2bDWK4v8Pqi6CmU2gaY6qQ/N+W74wjD+2NQfD9unWH96j7W4Lu1sK6Eu6782u3gusNrO2F9sXyPapx6mODljc/cOm0t6rtuWqnnpikv45R96vv36xqGXPvEAPz5ecQwjVkI18Iip/w9bTXx18jfYVzfAC08C+l3l3B9szemRaMVmhnTRnsv3Af3w3JYEbNdCavgIW2NzUrs1pB16Ov3W9v5tuP0fITlIGku9DYsL/4+9f2b1m/aT1sA/vw8YpjGPAYbYCN0OX3vaPuI/6b8Hcb15lKfK9/bnuT6KWWcSnfpHu522pfgZdgBO2FXzHY37IG9IWvsDlmHvn6/tZ1vO07PR1gOkuZCb8Py4u9T379p/ab9BL0Dqe/wWccwjXmfvv1wAA6G3MMW7uH2cp9D8KWT7jnXw/z3nMqu0XZ+mjiVzEFeeal1/mZ/R+AoHPP2epz2hMW+HSjCIBjs9XEP90zas+DsYrp6HGYNfznmNZ6U9U+w37jzo8ZlnYO4ebHBtr5xx2YZ42K4BCbCpV7fZXC5xf0nwWSYAg1eH/dwp9POgJkpa1TP/AkBa7wCrrT0jUrYfNNcfZw+JuscxM2LDbb1jTs2yxhL4RZoC/g8yfepks88+Xe1pYYYt8Hthn6Tb0yY5pvm6uNs75/lcz8sL2lql6TWWcQwjYn6Tib5Psn/P7C4gn9nkzXRYzwNz8RcV5L5SeNUOge2eUlTuyS1ziKGaUzSZ0fe9YmqSS2Rh0f7Y17SEPXcr6X69Jea5OXR/paXNPwLYxOxVA== 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/layer_foreground.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /assets/objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/objects.png -------------------------------------------------------------------------------- /assets/objects.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/pickup.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/pickup.wav -------------------------------------------------------------------------------- /assets/score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/score.png -------------------------------------------------------------------------------- /assets/score.txt: -------------------------------------------------------------------------------- 1 | inc_5,0,0,8,8 2 | dec_5,8,0,8,8 3 | add_1,16,0,8,8 4 | -------------------------------------------------------------------------------- /assets/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/ui.png -------------------------------------------------------------------------------- /assets/ui.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/vulture.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/assets/vulture.wav -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megamarc/TilenginePythonPlatformer/7e031e5ffd59006944dcc9a3d8719db9a5d8e82a/screenshot.png -------------------------------------------------------------------------------- /src/UI.py: -------------------------------------------------------------------------------- 1 | """ UI HUD elements (time, score...) """ 2 | 3 | from tilengine import Tileset, Tilemap, Tile 4 | import game 5 | 6 | class UI(object): 7 | """ UI elements """ 8 | def __init__(self): 9 | self.cols = game.WIDTH//8 10 | self.layer = game.engine.layers[0] 11 | self.layer.setup(Tilemap.create(4, self.cols, None), Tileset.fromfile("ui.tsx")) 12 | self.layer.set_clip(0, 0, game.WIDTH, 32) 13 | 14 | def update_time(self, time): 15 | text = "{:03}".format(time) 16 | col = (self.cols - len(text)) // 2 17 | tile = Tile() 18 | for digit in text: 19 | base_index = int(digit) 20 | tile.index = base_index + 11 21 | self.layer.tilemap.set_tile(1, col, tile) 22 | tile.index = base_index + 21 23 | self.layer.tilemap.set_tile(2, col, tile) 24 | col += 1 25 | -------------------------------------------------------------------------------- /src/actor.py: -------------------------------------------------------------------------------- 1 | """ Base class for all game entities (actors) """ 2 | 3 | import game 4 | 5 | class Direction: 6 | """ player orientations """ 7 | Right, Left = range(2) 8 | 9 | class Actor(object): 10 | """ Generic active game entity base class """ 11 | spriteset = None 12 | def __init__(self, item_ref, x, y): 13 | self.x = x 14 | self.y = y 15 | self.sprite = game.engine.sprites[game.engine.get_available_sprite()] 16 | self.sprite.setup(self.spriteset) 17 | self.item = item_ref 18 | game.actors.append(self) 19 | 20 | def __del__(self): 21 | self.sprite.disable() 22 | if self.item is not None: 23 | self.item.alive = False 24 | 25 | def kill(self): 26 | """ definitive kill of active game entity, removing from spawn-able item list too """ 27 | game.world.objects.remove(self.item) 28 | self.item = None 29 | game.actors.remove(self) 30 | -------------------------------------------------------------------------------- /src/eagle.py: -------------------------------------------------------------------------------- 1 | """ Flying enemy, waves across screen """ 2 | 3 | from math import sin, radians 4 | from tilengine import Spriteset, Sequence, Flags 5 | from actor import Actor, Direction 6 | import game 7 | 8 | class Eagle(Actor): 9 | """ Flying enemy """ 10 | size = (40, 40) 11 | seq_fly = None 12 | 13 | def __init__(self, item_ref, x, y): 14 | 15 | # init class members once 16 | if Eagle.spriteset is None: 17 | Eagle.spriteset = Spriteset.fromfile("enemy_eagle") 18 | Eagle.seq_fly = Sequence.create_sprite_sequence(Eagle.spriteset, "fly", 6) 19 | 20 | Actor.__init__(self, item_ref, x, y) 21 | self.frame = 0 22 | self.base_y = y 23 | self.xspeed = -3 24 | self.direction = Direction.Left 25 | self.sprite.set_animation(Eagle.seq_fly, 0) 26 | self.collision_points = (4, 20, 36) 27 | 28 | def update(self): 29 | """ Update once per frame """ 30 | self.x += self.xspeed 31 | self.y = self.base_y + int(sin(radians(self.frame*4))*15) 32 | self.frame += 1 33 | if self.frame is 10: 34 | game.sounds.play("eagle", 3) 35 | screen_x = self.x - game.world.x 36 | 37 | if self.direction is Direction.Left: 38 | if screen_x < 10: 39 | self.direction = Direction.Right 40 | self.xspeed = -self.xspeed 41 | self.sprite.set_flags(Flags.FLIPX) 42 | game.sounds.play("eagle", 3) 43 | else: 44 | for point in self.collision_points: 45 | game.player.check_hit(self.x, self.y + point, self.direction) 46 | else: 47 | if screen_x > 590: 48 | self.direction = Direction.Left 49 | self.xspeed = -self.xspeed 50 | self.sprite.set_flags(0) 51 | game.sounds.play("eagle", 3) 52 | else: 53 | for point in self.collision_points: 54 | game.player.check_hit(self.x + self.size[0], self.y + point, self.direction) 55 | self.sprite.set_position(screen_x, self.y) 56 | return True 57 | -------------------------------------------------------------------------------- /src/effect.py: -------------------------------------------------------------------------------- 1 | """ Generic, reusable one-shot animation (explosions, vanish, smoke...) """ 2 | 3 | from actor import Actor 4 | import game 5 | 6 | class Effect(Actor): 7 | """ placeholder for simple sprite effects """ 8 | def __init__(self, x, y, spriteset, sequence): 9 | self.spriteset = spriteset 10 | Actor.__init__(self, None, x, y) 11 | self.sprite.set_animation(sequence, 1) 12 | 13 | def update(self): 14 | """ updates effect state once per frame """ 15 | self.sprite.set_position(self.x - game.world.x, self.y) 16 | if self.sprite.get_animation_state() is False: 17 | return False 18 | return True 19 | -------------------------------------------------------------------------------- /src/game.py: -------------------------------------------------------------------------------- 1 | """ Game backbone, shared instances used in other modules """ 2 | 3 | # global constants 4 | WIDTH = 640 # framebuffer width 5 | HEIGHT = 360 # framebuffer height 6 | MAX_LAYERS = 3 # backgroudn layers 7 | MAX_SPRITES = 32 # max sprites 8 | ASSETS_PATH = "../assets" 9 | 10 | # global game objects, delayed creation 11 | 12 | engine = () # tilengine main instance 13 | window = () # tilengine window instance 14 | actors = () # list that contains every active game entity 15 | ui = () # UI items 16 | world = () # world/level instance 17 | player = () # player instance 18 | sounds = () # sound effects handler 19 | -------------------------------------------------------------------------------- /src/opossum.py: -------------------------------------------------------------------------------- 1 | """ Terrestrial enemy, chases player on floor """ 2 | 3 | from tilengine import Spriteset, Sequence, Flags 4 | from actor import Actor, Direction 5 | import game 6 | 7 | class Opossum(Actor): 8 | """ Floor enemy. Chases player in a 80 pixel radius """ 9 | size = (36, 24) 10 | seq_walk = None 11 | 12 | def __init__(self, item_ref, x, y): 13 | 14 | # init class members once 15 | if Opossum.spriteset is None: 16 | Opossum.spriteset = Spriteset.fromfile("enemy_opossum") 17 | Opossum.seq_walk = Sequence.create_sprite_sequence(Opossum.spriteset, "opossum-", 6) 18 | 19 | Actor.__init__(self, item_ref, x, y) 20 | self.xspeed = -2 21 | self.direction = Direction.Left 22 | self.sprite.set_animation(Opossum.seq_walk, 0) 23 | 24 | def update(self): 25 | """ Update once per frame """ 26 | self.x += self.xspeed 27 | if self.direction is Direction.Left: 28 | if self.x - game.player.x < -80: 29 | self.direction = Direction.Right 30 | self.xspeed = -self.xspeed 31 | self.sprite.set_flags(Flags.FLIPX) 32 | else: 33 | game.player.check_hit(self.x, self.y + self.size[1]//2, self.direction) 34 | else: 35 | if self.x - game.player.x > 80 and self.direction is Direction.Right: 36 | self.direction = Direction.Left 37 | self.xspeed = -self.xspeed 38 | self.sprite.set_flags(0) 39 | else: 40 | game.player.check_hit(self.x + self.size[0], self.y + self.size[1]//2, self.direction) 41 | 42 | self.sprite.set_position(self.x - game.world.x, self.y) 43 | return True 44 | -------------------------------------------------------------------------------- /src/platformer.py: -------------------------------------------------------------------------------- 1 | """ Tilengine python platformer demo """ 2 | 3 | from tilengine import Engine, Window 4 | from raster_effect import raster_effect 5 | from world import World 6 | from player import Player 7 | from UI import UI 8 | from sound import Sound 9 | import game 10 | 11 | # init tilengine 12 | game.engine = Engine.create(game.WIDTH, game.HEIGHT, game.MAX_LAYERS, game.MAX_SPRITES, 0) 13 | game.engine.set_load_path(game.ASSETS_PATH) 14 | 15 | # set raster callback 16 | game.engine.set_raster_callback(raster_effect) 17 | 18 | # init global game entities 19 | game.actors = list() 20 | game.world = World() 21 | game.player = Player() 22 | game.ui = UI() 23 | 24 | # load sound effects 25 | game.sounds = Sound(4, game.ASSETS_PATH) 26 | game.sounds.load("jump", "jump.wav") 27 | game.sounds.load("crush", "crunch.wav") 28 | game.sounds.load("pickup", "pickup.wav") 29 | game.sounds.load("hurt", "hurt.wav") 30 | game.sounds.load("eagle", "vulture.wav") 31 | 32 | # create window & start main loop 33 | game.window = Window.create() 34 | game.world.start() 35 | while game.window.process(): 36 | for actor in game.actors: 37 | if not actor.update(): 38 | game.actors.remove(actor) 39 | -------------------------------------------------------------------------------- /src/player.py: -------------------------------------------------------------------------------- 1 | """ Main player game entity """ 2 | 3 | from tilengine import Spriteset, Sequence, Palette, Input, Flags, TileInfo 4 | from actor import Actor, Direction 5 | from world import Medium, Tiles 6 | from eagle import Eagle 7 | from opossum import Opossum 8 | from rectangle import Rectangle 9 | from effect import Effect 10 | from score import Score 11 | import game 12 | 13 | tiles_info = (TileInfo(), TileInfo(), TileInfo(), TileInfo()) 14 | 15 | class State: 16 | """ player states """ 17 | Undefined, Idle, Run, Jump, Hit = range(5) 18 | 19 | 20 | class Player(Actor): 21 | """ main player entity """ 22 | size = (24, 36) 23 | xspeed_delta = 12 24 | xspeed_limit = 200 25 | yspeed_delta = 10 26 | yspeed_limit = 350 27 | jspeed_delta = 5 28 | 29 | seq_idle = None 30 | seq_jump = None 31 | seq_run = None 32 | spriteset_death = None 33 | seq_death = None 34 | 35 | def __init__(self): 36 | 37 | # init class members once 38 | if Player.spriteset is None: 39 | Player.spriteset = Spriteset.fromfile("hero") 40 | Player.seq_idle = Sequence.create_sprite_sequence(Player.spriteset, "idle", 4) 41 | Player.seq_jump = Sequence.create_sprite_sequence(Player.spriteset, "jump", 24) 42 | Player.seq_run = Sequence.create_sprite_sequence(Player.spriteset, "run", 5) 43 | Player.spriteset_death = Spriteset.fromfile("effect_death") 44 | Player.seq_death = Sequence.create_sprite_sequence(Player.spriteset_death, "death-", 5) 45 | 46 | Actor.__init__(self, None, 60, 188) 47 | self.state = State.Undefined 48 | self.direction = Direction.Right 49 | self.xspeed = 0 50 | self.yspeed = 0 51 | self.set_idle() 52 | self.sprite.set_position(self.x, self.y) 53 | self.width = self.size[0] 54 | self.height = self.size[1] 55 | self.medium = Medium.Floor 56 | self.jump = False 57 | self.immunity = 0 58 | self.rectangle = Rectangle(0, 0, self.width, self.height) 59 | 60 | 61 | self.palettes = (self.spriteset.palette, Palette.fromfile("hero_alt.act")) 62 | 63 | def set_idle(self): 64 | """ sets idle state, idempotent """ 65 | if self.state is not State.Idle: 66 | self.sprite.set_animation(Player.seq_idle, 0) 67 | self.state = State.Idle 68 | self.xspeed = 0 69 | 70 | def set_running(self): 71 | """ sets running state, idempotent """ 72 | if self.state is not State.Run: 73 | self.sprite.set_animation(Player.seq_run, 0) 74 | self.state = State.Run 75 | 76 | def set_jump(self): 77 | """ sets jump state, idempotent """ 78 | if self.state is not State.Jump: 79 | self.yspeed = -280 80 | self.sprite.set_animation(Player.seq_jump, 0) 81 | self.state = State.Jump 82 | self.medium = Medium.Air 83 | game.sounds.play("jump", 0) 84 | 85 | def set_bounce(self): 86 | """ bounces on top of an enemy """ 87 | self.yspeed = -150 88 | self.state = State.Jump 89 | self.medium = Medium.Air 90 | 91 | def set_hit(self, enemy_direction): 92 | """ sets hit animation by an enemy """ 93 | self.direction = enemy_direction 94 | if self.direction is Direction.Left: 95 | self.xspeed = -self.xspeed_limit 96 | self.sprite.set_flags(0) 97 | else: 98 | self.xspeed = self.xspeed_limit 99 | self.sprite.set_flags(Flags.FLIPX) 100 | self.yspeed = -150 101 | self.state = State.Hit 102 | self.medium = Medium.Air 103 | self.sprite.disable_animation() 104 | self.sprite.set_picture(12) 105 | self.immunity = 90 106 | game.sounds.play("hurt", 0) 107 | game.world.add_timer(-5) 108 | Score(-5, self.x, self.y) 109 | 110 | def update_direction(self): 111 | """ updates sprite facing depending on direction """ 112 | if game.window.get_input(Input.RIGHT): 113 | direction = Direction.Right 114 | elif game.window.get_input(Input.LEFT): 115 | direction = Direction.Left 116 | else: 117 | direction = self.direction 118 | if self.direction is not direction: 119 | self.direction = direction 120 | if self.direction is Direction.Right: 121 | self.sprite.set_flags(0) 122 | else: 123 | self.sprite.set_flags(Flags.FLIPX) 124 | 125 | def update_floor(self): 126 | """ process input when player is in floor medium """ 127 | if game.window.get_input(Input.RIGHT) and self.xspeed < Player.xspeed_limit: 128 | self.xspeed += self.xspeed_delta 129 | self.set_running() 130 | elif game.window.get_input(Input.LEFT) and self.xspeed > -Player.xspeed_limit: 131 | self.xspeed -= Player.xspeed_delta 132 | self.set_running() 133 | elif abs(self.xspeed) < Player.xspeed_delta: 134 | self.xspeed = 0 135 | elif self.xspeed > 0: 136 | self.xspeed -= Player.xspeed_delta 137 | elif self.xspeed < 0: 138 | self.xspeed += Player.xspeed_delta 139 | if self.xspeed == 0: 140 | self.set_idle() 141 | if game.window.get_input(Input.A): 142 | if self.jump is not True: 143 | self.set_jump() 144 | self.jump = True 145 | else: 146 | self.jump = False 147 | 148 | def update_air(self): 149 | """ process input when player is in air medium """ 150 | if game.window.get_input(Input.RIGHT) and self.xspeed < Player.xspeed_limit: 151 | self.xspeed += self.jspeed_delta 152 | elif game.window.get_input(Input.LEFT) and self.xspeed > -Player.xspeed_limit: 153 | self.xspeed -= self.jspeed_delta 154 | 155 | def check_left(self, x, y): 156 | """ checks/adjusts environment collision when player is moving to the left """ 157 | game.world.foreground.get_tile(x, y + 4, tiles_info[0]) 158 | game.world.foreground.get_tile(x, y + 18, tiles_info[1]) 159 | game.world.foreground.get_tile(x, y + 34, tiles_info[2]) 160 | if Tiles.Wall in (tiles_info[0].type, tiles_info[1].type, tiles_info[2].type): 161 | self.x = (tiles_info[0].col + 1) * 16 162 | self.xspeed = 0 163 | game.world.pick_gem(tiles_info) 164 | 165 | def check_right(self, x, y): 166 | """ checks/adjusts environment collision when player is moving to the right """ 167 | game.world.foreground.get_tile(x + self.width, y + 4, tiles_info[0]) 168 | game.world.foreground.get_tile(x + self.width, y + 18, tiles_info[1]) 169 | game.world.foreground.get_tile(x + self.width, y + 34, tiles_info[2]) 170 | if Tiles.Wall in (tiles_info[0].type, tiles_info[1].type, tiles_info[2].type): 171 | self.x = (tiles_info[0].col * 16) - self.width 172 | self.xspeed = 0 173 | game.world.pick_gem(tiles_info) 174 | 175 | def check_top(self, x, y): 176 | """ checks/adjusts environment collision when player is jumping """ 177 | game.world.foreground.get_tile(x + 0, y, tiles_info[0]) 178 | game.world.foreground.get_tile(x + 12, y, tiles_info[1]) 179 | game.world.foreground.get_tile(x + 24, y, tiles_info[2]) 180 | if Tiles.Wall in (tiles_info[0].type, tiles_info[1].type, tiles_info[2].type): 181 | self.y = (tiles_info[0].row + 1) * 16 182 | self.yspeed = 0 183 | game.world.pick_gem(tiles_info) 184 | 185 | def check_bottom(self, x, y): 186 | """ checks/adjusts environment collision when player is falling or running """ 187 | ground = False 188 | 189 | game.world.foreground.get_tile(x + 0, y + self.height, tiles_info[0]) 190 | game.world.foreground.get_tile(x + 12, y + self.height, tiles_info[1]) 191 | game.world.foreground.get_tile(x + 24, y + self.height, tiles_info[2]) 192 | game.world.foreground.get_tile(x + 12, y + self.height - 1, tiles_info[3]) 193 | 194 | # check up slope 195 | if tiles_info[3].type is Tiles.SlopeUp: 196 | slope_height = 16 - tiles_info[3].xoffset 197 | if self.yspeed >= 0 and tiles_info[3].yoffset > slope_height: 198 | self.y -= (tiles_info[3].yoffset - slope_height) 199 | ground = True 200 | 201 | # check down slope 202 | elif tiles_info[3].type is Tiles.SlopeDown: 203 | slope_height = tiles_info[3].xoffset + 1 204 | if self.yspeed >= 0 and tiles_info[3].yoffset > slope_height: 205 | self.y -= (tiles_info[3].yoffset - slope_height) 206 | ground = True 207 | 208 | # check inner slope (avoid falling between staircased slopes) 209 | elif tiles_info[1].type is Tiles.InnerSlopeUp: 210 | if self.xspeed > 0: 211 | self.y = (tiles_info[1].row * 16) - self.height - 1 212 | else: 213 | self.x -= 1 214 | ground = True 215 | 216 | elif tiles_info[1].type is Tiles.InnerSlopeDown: 217 | if self.xspeed > 0: 218 | self.x += 1 219 | else: 220 | self.y = (tiles_info[1].row * 16) - self.height - 1 221 | ground = True 222 | 223 | # check regular floor 224 | elif Tiles.Floor in (tiles_info[0].type, tiles_info[1].type, tiles_info[2].type): 225 | self.y = (tiles_info[0].row * 16) - self.height 226 | ground = True 227 | 228 | # adjust to ground 229 | if ground is True: 230 | self.yspeed = 0 231 | if self.medium is Medium.Air: 232 | self.medium = Medium.Floor 233 | if self.xspeed == 0: 234 | self.set_idle() 235 | else: 236 | self.set_running() 237 | else: 238 | self.medium = Medium.Air 239 | game.world.pick_gem(tiles_info) 240 | 241 | def check_jump_on_enemies(self, x, y): 242 | """ checks jumping above an enemy. If so, kills it, bounces and spawns a death animation """ 243 | px, py = x+self.width/2, y+self.height 244 | for actor in game.actors: 245 | actor_type = type(actor) 246 | if actor_type in (Eagle, Opossum): 247 | ex, ey = actor.x + actor.size[0]/2, actor.y 248 | if abs(px - ex) < 25 and 5 < py - ey < 20: 249 | game.world.add_timer(5) 250 | actor.kill() 251 | self.set_bounce() 252 | Effect(actor.x, actor.y - 10, self.spriteset_death, self.seq_death) 253 | game.sounds.play("crush", 2) 254 | return 255 | 256 | def check_hit(self, x, y, direction): 257 | """ returns if get hurt by enemy at select position and direction""" 258 | if self.immunity is 0 and self.rectangle.check_point(x, y): 259 | self.set_hit(direction) 260 | 261 | def update(self): 262 | """ process input and updates state once per frame """ 263 | oldx = self.x 264 | oldy = self.y 265 | 266 | # update immunity 267 | if self.immunity is not 0: 268 | pal_index0 = (self.immunity >> 2) & 1 269 | self.immunity -= 1 270 | pal_index1 = (self.immunity >> 2) & 1 271 | if self.immunity is 0: 272 | pal_index1 = 0 273 | if pal_index0 != pal_index1: 274 | self.sprite.set_palette(self.palettes[pal_index1]) 275 | 276 | # update sprite facing 277 | self.update_direction() 278 | 279 | # user input: move character depending on medium 280 | if self.medium is Medium.Floor: 281 | self.update_floor() 282 | elif self.medium is Medium.Air: 283 | if self.state is not State.Hit: 284 | self.update_air() 285 | if self.yspeed < Player.yspeed_limit: 286 | self.yspeed += Player.yspeed_delta 287 | 288 | self.x += (self.xspeed / 100.0) 289 | self.y += (self.yspeed / 100.0) 290 | 291 | # clip to game.world limits 292 | if self.x < 0.0: 293 | self.x = 0.0 294 | elif self.x > game.world.foreground.width - self.width: 295 | self.x = game.world.foreground.width - self.width 296 | 297 | # check and fix 4-way collisions depending on motion direction 298 | intx = int(self.x) 299 | inty = int(self.y) 300 | if self.yspeed < 0: 301 | self.check_top(intx, inty) 302 | elif self.yspeed >= 0: 303 | self.check_bottom(intx, inty) 304 | if self.xspeed < 0: 305 | self.check_left(intx, inty) 306 | elif self.xspeed > 0: 307 | self.check_right(intx, inty) 308 | if self.yspeed > 0: 309 | self.check_jump_on_enemies(intx, inty) 310 | 311 | if self.x != oldx or self.y != oldy: 312 | self.rectangle.update_position(int(self.x), int(self.y)) 313 | self.sprite.set_position(int(self.x) - game.world.x, int(self.y)) 314 | return True 315 | -------------------------------------------------------------------------------- /src/raster_effect.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstrates raster effects for tilengine: 3 | - linescroll for medium layer 4 | - gradient color for sky 5 | """ 6 | 7 | from tilengine import Color 8 | import game 9 | 10 | # sky gradient color 11 | SKY_COLORS = (Color.fromstring("#78D7F2"), Color.fromstring("#E2ECF2")) 12 | 13 | def lerp(pos_x, x0, x1, fx0, fx1): 14 | """ integer linear interpolation """ 15 | return fx0 + (fx1 - fx0) * (pos_x - x0) // (x1 - x0) 16 | 17 | def interpolate_color(x, x1, x2, color1, color2): 18 | """ linear interpolation between two Color objects """ 19 | r = lerp(x, x1, x2, color1.r, color2.r) 20 | g = lerp(x, x1, x2, color1.g, color2.g) 21 | b = lerp(x, x1, x2, color1.b, color2.b) 22 | return Color(r, g, b) 23 | 24 | def raster_effect(line): 25 | """ raster effect callback, called every rendered scanline """ 26 | 27 | # sky color gradient 28 | if 0 <= line <= 128: 29 | color = interpolate_color(line, 0, 128, SKY_COLORS[0], SKY_COLORS[1]) 30 | game.engine.set_background_color(color) 31 | 32 | # sets cloud position at frame start 33 | if line == 0: 34 | game.world.background.set_position(int(game.world.clouds), 0) 35 | 36 | # linescroll on main background 37 | elif 160 <= line <= 208: 38 | pos1 = game.world.x//10 39 | pos2 = game.world.x//3 40 | xpos = lerp(line, 160, 208, pos1, pos2) 41 | game.world.background.set_position(xpos, 0) 42 | 43 | # bottom background 44 | elif line == 256: 45 | game.world.background.set_position(game.world.x//2, 0) 46 | -------------------------------------------------------------------------------- /src/rectangle.py: -------------------------------------------------------------------------------- 1 | """ Basic rectangle helper for hitboxes """ 2 | 3 | class Rectangle(object): 4 | """ aux rectangle """ 5 | def __init__(self, x, y, w, h): 6 | self.width = w 7 | self.height = h 8 | self.update_position(x, y) 9 | 10 | def update_position(self, x, y): 11 | self.x1 = x 12 | self.y1 = y 13 | self.x2 = x + self.width 14 | self.y2 = y + self.height 15 | 16 | def check_point(self, x, y): 17 | """ returns if point is contained in rectangle """ 18 | return self.x1 <= x <= self.x2 and self.y1 <= y <= self.y2 19 | -------------------------------------------------------------------------------- /src/score.py: -------------------------------------------------------------------------------- 1 | """ Effect that shows pop-up score on player actions """ 2 | 3 | from tilengine import Spriteset 4 | from actor import Actor 5 | import game 6 | 7 | class Score(Actor): 8 | def __init__(self, value, x, y): 9 | 10 | # init class members once 11 | if Score.spriteset is None: 12 | Score.spriteset = Spriteset.fromfile("score") 13 | 14 | Actor.__init__(self, None, int(x), int(y)) 15 | if value is 5: 16 | self.sprite.set_picture(0) 17 | elif value is -5: 18 | self.sprite.set_picture(1) 19 | elif value is 1: 20 | self.sprite.set_picture(2) 21 | 22 | self.t0 = game.window.get_ticks() 23 | self.t1 = self.t0 + 1000 24 | 25 | def update(self): 26 | now = game.window.get_ticks() 27 | p = (now - self.t0) / (self.t1 - self.t0) 28 | p = -(p * (p - 2)) 29 | self.sprite.set_position(int(self.x - game.world.x), int(self.y - p*16)) 30 | return now < self.t1 31 | -------------------------------------------------------------------------------- /src/sound.py: -------------------------------------------------------------------------------- 1 | """ Sound effect helper """ 2 | 3 | from sdl2 import * 4 | from sdl2.sdlmixer import * 5 | 6 | class Sound(object): 7 | """ Manages sound effects """ 8 | def __init__(self, num_channels, path): 9 | SDL_Init(SDL_INIT_AUDIO) 10 | Mix_Init(0) 11 | Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 2048) 12 | Mix_AllocateChannels(num_channels) 13 | self._sounds = dict() 14 | if path is None: 15 | self.path = "./" 16 | else: 17 | self.path = path + "/" 18 | 19 | def __del__(self): 20 | for s in self._sounds: 21 | Mix_FreeChunk(s) 22 | 23 | def load(self, name, file): 24 | self._sounds[name] = Mix_LoadWAV((self.path + file).encode()) 25 | 26 | def play(self, name, channel): 27 | Mix_PlayChannel(channel, self._sounds[name], 0) 28 | -------------------------------------------------------------------------------- /src/tilengine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python wrapper for Tilengine retro graphics engine 3 | Updated to library version 2.11.0 4 | http://www.tilengine.org 5 | """ 6 | 7 | """ 8 | Copyright (c) 2017-2022 Marc Palacios Domenech 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | """ 28 | 29 | # pylint: disable=C0103 30 | # pylint: disable=W0614 31 | # pylint: disable=W0312 32 | # pylint: disable=R0201 33 | from sys import platform as _platform 34 | from ctypes import * 35 | from os import path 36 | from typing import List, Union, Optional 37 | 38 | 39 | # constants -------------------------------------------------------------------- 40 | 41 | class WindowFlags: 42 | """ 43 | List of flag values for window creation 44 | """ 45 | FULLSCREEN = (1 << 0) 46 | VSYNC = (1 << 1) 47 | S1 = (1 << 2) 48 | S2 = (2 << 2) 49 | S3 = (3 << 2) 50 | S4 = (4 << 2) 51 | S5 = (5 << 2) 52 | NEAREST = (1 << 6) 53 | 54 | 55 | class Flags: 56 | """ 57 | List of flags for tiles and sprites 58 | """ 59 | FLIPX = (1 << 15) # horizontal flip 60 | FLIPY = (1 << 14) # vertical flip 61 | ROTATE = (1 << 13) # row/column flip (unsupported, Tiled compatibility) 62 | PRIORITY = (1 << 12) # tile goes in front of sprite layer 63 | MASKED = (1 << 11) # sprite won't be drawn inside masked region 64 | TILESET = (7 << 8) # tileset index 65 | 66 | 67 | class Error: 68 | """ 69 | List of possible error codes returned by :meth:`Engine.get_last_error()` 70 | """ 71 | OK = 0 # No error 72 | OUT_OF_MEMORY = 1 # Not enough memory 73 | IDX_LAYER = 2 # Layer index out of range 74 | IDX_SPRITE = 3 # Sprite index out of range 75 | IDX_ANIMATION = 4 # Animation index out of range 76 | IDX_PICTURE = 5 # Picture or tile index out of range 77 | REF_TILESET = 6 # Invalid Tileset reference 78 | REF_TILEMAP = 7 # Invalid Tilemap reference 79 | REF_SPRITESET = 8 # Invalid Spriteset reference 80 | REF_PALETTE = 9 # Invalid Palette reference 81 | REF_SEQUENCE = 10 # Invalid SequencePack reference 82 | REF_SEQPACK = 11 # Invalid Sequence reference 83 | REF_BITMAP = 12 # Invalid Bitmap reference 84 | NULL_POINTER = 13 # Null pointer as argument 85 | FILE_NOT_FOUND = 14 # Resource file not found 86 | WRONG_FORMAT = 15 # Resource file has invalid format 87 | WRONG_SIZE = 16 # A width or height parameter is invalid 88 | UNSUPPORTED = 17 # Unsupported function 89 | REF_LIST = 18 # Invalid ObjectList reference 90 | 91 | 92 | class LogLevel: 93 | """ 94 | Log levels for :meth:`Engine.set_log_level()` 95 | """ 96 | NONE, ERRORS, VERBOSE = range(3) 97 | 98 | 99 | class Blend: 100 | """ 101 | Available blending modes 102 | """ 103 | NONE, MIX25, MIX50, MIX75, ADD, SUB, MOD, CUSTOM = range(8) 104 | MIX = MIX50 105 | 106 | 107 | class Input: 108 | """ 109 | Available inputs to query in :meth:`Window.get_input` 110 | """ 111 | NONE, UP, DOWN, LEFT, RIGHT, A, B, C, D, E, F, START, QUIT, CRT = range(14) 112 | BUTTON1, BUTTON2, BUTTON3, BUTTON4, BUTTON5, BUTTON6 = range(A, START) 113 | P1, P2, P3, P4 = range(0, 128, 32) 114 | 115 | 116 | PLAYER1, PLAYER2, PLAYER3, PLAYER4 = range(4) 117 | 118 | 119 | class Overlay: 120 | """ 121 | Unised, kept for backwards compatibility with pre-2.10 release 122 | """ 123 | NONE, SHADOWMASK, APERTURE, SCANLINES, CUSTOM = range(5) 124 | 125 | 126 | class CRT: 127 | """ 128 | Types of crt effect for release 2.10 129 | """ 130 | SLOT, APERTURE, SHADOW = range(3) 131 | 132 | 133 | class TilengineException(Exception): 134 | """ 135 | Tilengine exception class 136 | """ 137 | def __init__(self, value): 138 | self.value = value 139 | 140 | def __str__(self): 141 | return repr(self.value) 142 | 143 | 144 | # structures ------------------------------------------------------------------ 145 | class Tile(Structure): 146 | """ 147 | Tile data contained in each cell of a :class:`Tilemap` object 148 | 149 | :attr:`index`: tile index 150 | :attr:`flags`: sum of :class:`Flags` values 151 | """ 152 | _fields_ = [ 153 | ("index", c_ushort), 154 | ("flags", c_ushort) 155 | ] 156 | 157 | 158 | class ColorStrip(Structure): 159 | """ 160 | Data used to define each frame of a color cycle for :class:`Sequence` objects 161 | """ 162 | _fields_ = [ 163 | ("delay", c_int), 164 | ("first", c_ubyte), 165 | ("count", c_ubyte), 166 | ("dir", c_ubyte) 167 | ] 168 | 169 | def __init__(self, delay: int, first: int, count: int, direction: int): 170 | self.delay = delay 171 | self.first = first 172 | self.count = count 173 | self.dir = direction 174 | 175 | 176 | class SequenceInfo(Structure): 177 | """Sequence info returned by :meth:`Sequence.get_info`""" 178 | _fields_ = [ 179 | ("name", c_char * 32), 180 | ("num_frames", c_int) 181 | ] 182 | 183 | 184 | class SequenceFrame(Structure): 185 | """ 186 | Data used to define each frame of an animation for :class:`Sequence` objects 187 | """ 188 | _fields_ = [ 189 | ("index", c_int), 190 | ("delay", c_int) 191 | ] 192 | 193 | def __init__(self, index: int, delay: int): 194 | self.index = index 195 | self.delay = delay 196 | 197 | 198 | class SpriteInfo(Structure): 199 | """ 200 | Data returned by :meth:`Spriteset.get_sprite_info` with dimensions of the required sprite 201 | """ 202 | _fields_ = [ 203 | ("w", c_int), 204 | ("h", c_int) 205 | ] 206 | 207 | 208 | class TileInfo(Structure): 209 | """ 210 | Data returned by :meth:`Layer.get_tile` about a given tile inside a background layer 211 | """ 212 | _fields_ = [ 213 | ("index", c_ushort), 214 | ("flags", c_ushort), 215 | ("row", c_int), 216 | ("col", c_int), 217 | ("xoffset", c_int), 218 | ("yoffset", c_int), 219 | ("color", c_ubyte), 220 | ("type", c_ubyte), 221 | ("empty", c_bool) 222 | ] 223 | 224 | 225 | class SpriteData(Structure): 226 | """ 227 | Data used to create :class:`Spriteset` objects 228 | """ 229 | _fields_ = [ 230 | ("name", c_char * 64), 231 | ("x", c_int), 232 | ("y", c_int), 233 | ("w", c_int), 234 | ("h", c_int) 235 | ] 236 | 237 | def __init__(self, name: str, x: int, y: int, width: int, height: int): 238 | self.name = _encode_string(name) 239 | self.x = x 240 | self.y = y 241 | self.w = width 242 | self.h = height 243 | 244 | 245 | class TileAttributes(Structure): 246 | """ 247 | Data used to create :class:`Tileset` objects 248 | """ 249 | _fields_ = [ 250 | ("type", c_ubyte), 251 | ("priority", c_bool) 252 | ] 253 | 254 | def __init__(self, tile_type: int, priority: bool): 255 | self.type = tile_type 256 | self.priority = priority 257 | 258 | 259 | class PixelMap(Structure): 260 | """ 261 | Data passed to :meth:`Layer.set_pixel_mapping` in a list 262 | """ 263 | _fields_ = [ 264 | ("dx", c_short), 265 | ("dy", c_short) 266 | ] 267 | 268 | 269 | class ObjectInfo(Structure): 270 | """ 271 | Object item info returned by :meth:`ObjectInfo.get_info()` 272 | """ 273 | _fields_ = [ 274 | ("id", c_ushort), 275 | ("gid", c_ushort), 276 | ("flags", c_ushort), 277 | ("x", c_int), 278 | ("y", c_int), 279 | ("width", c_int), 280 | ("height", c_int), 281 | ("type", c_ubyte), 282 | ("visible", c_bool), 283 | ("name", c_char * 64) 284 | ] 285 | 286 | 287 | class TileImage(Structure): 288 | """ 289 | Image Tile items for TLN_CreateImageTileset() 290 | """ 291 | _fields_ = [ 292 | ("bitmap", c_void_p), 293 | ("id", c_ushort), 294 | ("type", c_ubyte) 295 | ] 296 | 297 | 298 | class SpriteState(Structure): 299 | """ 300 | Sprite state for :meth:`Sprite.get_state()` 301 | """ 302 | _fields_ = [ 303 | ("x", c_int), 304 | ("y", c_int), 305 | ("w", c_int), 306 | ("h", c_int), 307 | ("flags", c_ushort), 308 | ("palette", c_void_p), 309 | ("spriteset", c_void_p), 310 | ("index", c_int), 311 | ("enabled", c_bool), 312 | ("collision", c_bool) 313 | ] 314 | 315 | 316 | class Color(object): 317 | """ 318 | Represents a color value in RGB format 319 | """ 320 | def __init__(self, r: int, g: int, b: int): 321 | self.r = r 322 | self.g = g 323 | self.b = b 324 | 325 | @classmethod 326 | def fromstring(cls, string: str): 327 | """ creates a color from a ccs-style #rrggbb string """ 328 | r = int(string[1:3], 16) 329 | g = int(string[3:5], 16) 330 | b = int(string[5:7], 16) 331 | return Color(r, g, b) 332 | 333 | 334 | # module internal variables 335 | _tln: "Engine" # handle to shared native library 336 | _window: "Window" # singleton window 337 | _window_created = False # singleton window is created 338 | 339 | # select library depending on OS 340 | _library = "" 341 | if _platform == "linux" or _platform == "linux2": 342 | _library = "libTilengine.so" 343 | elif _platform == "win32": 344 | _library = "Tilengine.dll" 345 | elif _platform == "darwin": 346 | _library = "Tilengine.dylib" 347 | else: 348 | raise OSError("Unsupported platform: must be Windows, Linux or Mac OS") 349 | 350 | # load native library. Try local path, if not, system path 351 | if path.isfile(_library): 352 | _tln = cdll.LoadLibrary(f"./{_library}") 353 | else: 354 | _tln = cdll.LoadLibrary(_library) 355 | 356 | # callback types for user functions 357 | _video_callback_function = CFUNCTYPE(None, c_int) 358 | _blend_function = CFUNCTYPE(c_ubyte, c_ubyte, c_ubyte) 359 | 360 | 361 | # convert string to c_char_p 362 | def _encode_string(string: Optional[str]): 363 | if string is not None: 364 | return string.encode() 365 | return None 366 | 367 | # convert c_char_p to string 368 | def _decode_string(byte_array: Optional[bytearray]): 369 | if byte_array is not None: 370 | return byte_array.decode() 371 | return None 372 | 373 | 374 | # error handling -------------------------------------------------------------- 375 | _tln.TLN_GetLastError.restype = c_int 376 | _tln.TLN_GetErrorString.argtypes = [c_int] 377 | _tln.TLN_GetErrorString.restype = c_char_p 378 | 379 | 380 | # raises exception depending on error code 381 | def _raise_exception(result: bool=False): 382 | if result is not True: 383 | error = _tln.TLN_GetLastError() 384 | error_string = _tln.TLN_GetErrorString(error) 385 | raise TilengineException(error_string.decode()) 386 | 387 | # World management 388 | _tln.TLN_LoadWorld.argtypes = [c_char_p, c_int] 389 | _tln.TLN_LoadWorld.restype = c_bool 390 | _tln.TLN_SetWorldPosition.argtypes = [c_int, c_int] 391 | _tln.TLN_SetWorldPosition.restype = None 392 | _tln.TLN_SetLayerParallaxFactor.argtypes = [c_int, c_float, c_float] 393 | _tln.TLN_SetLayerParallaxFactor.restype = c_bool 394 | _tln.TLN_SetSpriteWorldPosition.argtypes = [c_int, c_int, c_int] 395 | _tln.TLN_SetSpriteWorldPosition.restype = c_bool 396 | _tln.TLN_ReleaseWorld.argtypes = None 397 | _tln.TLN_ReleaseWorld.restype = None 398 | 399 | # basic management ------------------------------------------------------------ 400 | _tln.TLN_Init.argtypes = [c_int, c_int, c_int, c_int, c_int] 401 | _tln.TLN_Init.restype = c_void_p 402 | _tln.TLN_DeleteContext.argtypes = [c_void_p] 403 | _tln.TLN_DeleteContext.restype = c_bool 404 | _tln.TLN_SetContext.argtypes = [c_void_p] 405 | _tln.TLN_SetContext.restype = c_bool 406 | _tln.TLN_GetContext.restype = c_void_p 407 | _tln.TLN_GetNumObjects.restype = c_int 408 | _tln.TLN_GetVersion.restype = c_int 409 | _tln.TLN_GetUsedMemory.restype = c_int 410 | _tln.TLN_SetBGColor.argtypes = [c_ubyte, c_ubyte, c_ubyte] 411 | _tln.TLN_SetBGColorFromTilemap.argtypes = [c_void_p] 412 | _tln.TLN_SetBGColorFromTilemap.restype = c_bool 413 | _tln.TLN_SetBGBitmap.argtypes = [c_void_p] 414 | _tln.TLN_SetBGBitmap.restype = c_bool 415 | _tln.TLN_SetBGPalette.argtypes = [c_void_p] 416 | _tln.TLN_SetBGPalette.restype = c_bool 417 | _tln.TLN_SetRenderTarget.argtypes = [c_void_p, c_int] 418 | _tln.TLN_UpdateFrame.argtypes = [c_int] 419 | _tln.TLN_SetLoadPath.argtypes = [c_char_p] 420 | _tln.TLN_SetLogLevel.argtypes = [c_int] 421 | _tln.TLN_OpenResourcePack.argtypes = [c_char_p, c_char_p] 422 | _tln.TLN_OpenResourcePack.restype = c_bool 423 | _tln.TLN_SetSpritesMaskRegion.argtypes = [c_int, c_int] 424 | 425 | class Engine(object): 426 | """ 427 | Main object for engine creation and rendering 428 | 429 | :ivar layers: tuple of Layer objects, one entry per layer 430 | :ivar sprites: tuple of Sprite objects, one entry per sprite 431 | :ivar animations: tuple of Animation objects, one entry per animation 432 | :ivar version: library version number 433 | """ 434 | def __init__(self, handle: c_void_p, num_layers: int, num_sprites: int, num_animations: int): 435 | self._as_parameter_ = handle 436 | self.layers = tuple([Layer(n) for n in range(num_layers)]) 437 | self.sprites = tuple([Sprite(n) for n in range(num_sprites)]) 438 | self.animations = tuple([Animation(n) for n in range(num_animations)]) 439 | self.version = _tln.TLN_GetVersion() 440 | self.cb_raster_func = None 441 | self.cb_frame_func = None 442 | self.cb_blend_func = None 443 | self.library = _tln 444 | 445 | version = [2,11,0] # expected library version 446 | req_version = (version[0] << 16) + (version[1] << 8) + version[2] 447 | if self.version < req_version: 448 | maj_version = self.version >> 16 449 | min_version = (self.version >> 8) & 255 450 | bug_version = self.version & 255 451 | print("WARNING: Library version is %d.%d.%d, expected at least %d.%d.%d!" % \ 452 | (maj_version, min_version, bug_version, version[0], version[1], version[2])) 453 | 454 | @classmethod 455 | def create(cls, width: int, height: int, num_layers: int, num_sprites: int, num_animations: int) -> "Engine": 456 | """ 457 | Static method that creates a new instance of the engine 458 | 459 | :param width: horizontal resolution in pixels 460 | :param height: vertical resolution in pixels 461 | :param num_layers: max number of background layers 462 | :param num_sprites: max number of sprites 463 | :param num_animations: number of color-cycle animations 464 | :return: new instance 465 | """ 466 | handle = _tln.TLN_Init(width, height, num_layers, num_sprites, num_animations) 467 | if handle is not None: 468 | _engine = Engine(handle, num_layers, num_sprites, num_animations) 469 | return _engine 470 | else: 471 | _raise_exception() 472 | 473 | def __del__(self): 474 | _tln.TLN_DeleteContext(self) 475 | 476 | def get_num_objects(self) -> int: 477 | """ 478 | :return: the number of objets used by the engine so far 479 | """ 480 | return _tln.TLN_GetNumObjects() 481 | 482 | def get_used_memory(self) -> int: 483 | """ 484 | :return: the total amount of memory used by the objects 485 | """ 486 | return _tln.TLN_GetUsedMemory() 487 | 488 | def set_background_color(self, param): 489 | """ 490 | Sets the background color 491 | 492 | :param param: can be a Color object or a Tilemap object. In this case, \ 493 | it assigns de background color as defined inside the tilemap 494 | """ 495 | param_type = type(param) 496 | if param_type is Color: 497 | _tln.TLN_SetBGColor(param.r, param.g, param.b) 498 | elif param_type is Tilemap: 499 | _tln.TLN_SetBGColorFromTilemap(param) 500 | 501 | def disable_background_color(self): 502 | """ 503 | Disales background color rendering. If you know that the last background layer will always 504 | cover the entire screen, you can disable it to gain some performance 505 | """ 506 | _tln.TLN_DisableBGColor() 507 | 508 | def set_background_bitmap(self, bitmap: "Bitmap"): 509 | """ 510 | Sets a static bitmap as background 511 | 512 | :param bitmap: Bitmap object to set as background. Set None to disable it. 513 | """ 514 | ok = _tln.TLN_SetBGBitmap(bitmap) 515 | _raise_exception(ok) 516 | 517 | def set_background_palette(self, palette: "Palette"): 518 | """ 519 | Sets the palette for the background bitmap. By default it is assigned the palette 520 | of the bitmap passed in :meth:`Engine.set_background_bitmap` 521 | 522 | :param palette: Palette object to set 523 | """ 524 | ok = _tln.TLN_SetBGPalette(palette) 525 | _raise_exception(ok) 526 | 527 | def set_raster_callback(self, raster_callback): 528 | """ 529 | Enables raster effects processing, like a virtual HBLANK interrupt where any render parameter can be modified between scanlines. 530 | 531 | :param raster_callback: name of the user-defined function to call for each scanline. Set None to disable. \ 532 | This function takes one integer parameter that indicates the current scanline, between 0 and vertical resolution. 533 | 534 | Example:: 535 | 536 | def my_raster_callback(num_scanline): 537 | if num_scanline is 32: 538 | engine.set_background_color(Color(0,0,0)) 539 | 540 | engine.set_raster_callback(my_raster_callback) 541 | """ 542 | if raster_callback is None: 543 | self.cb_raster_func = None 544 | else: 545 | self.cb_raster_func = _video_callback_function(raster_callback) 546 | _tln.TLN_SetRasterCallback(self.cb_raster_func) 547 | 548 | def set_frame_callback(self, frame_callback): 549 | """ 550 | Enables user callback for each drawn frame, like a virtual VBLANK interrupt 551 | 552 | :param frame_callback: name of the user-defined function to call for each frame. Set None to disable. \ 553 | This function takes one integer parameter that indicates the current frame. 554 | 555 | Example:: 556 | 557 | def my_frame_callback(num_frame): 558 | engine.set_background_color(Color(0,0,0)) 559 | 560 | engine.set_frame_callback(my_frame_callback) 561 | """ 562 | if frame_callback is None: 563 | self.cb_frame_func = None 564 | else: 565 | self.cb_frame_func = _video_callback_function(frame_callback) 566 | _tln.TLN_SetFrameCallback(self.cb_frame_func) 567 | 568 | def set_render_target(self, pixels, pitch): 569 | """ 570 | Sets the output surface for rendering 571 | 572 | :param pixels: Pointer to the start of the target framebuffer 573 | :param pitch: Number of bytes per each scanline of the framebuffer 574 | """ 575 | _tln.TLN_SetRenderTarget(pixels, pitch) 576 | 577 | def update_frame(self, num_frame=0): 578 | """ 579 | Draws the frame to the previously specified render target 580 | 581 | :param num_frame: optional frame number for animation control 582 | """ 583 | _tln.TLN_UpdateFrame(num_frame) 584 | 585 | def set_load_path(self, path: str): 586 | """ 587 | Sets base path for all data loading static methods `fromfile` 588 | 589 | :param path: Base path. Files will load at path/filename. Set None to use current directory 590 | """ 591 | _tln.TLN_SetLoadPath(_encode_string(path)) 592 | 593 | def set_custom_blend_function(self, blend_function): 594 | """ 595 | Sets custom blend function to use in sprites or background layers when `BLEND_CUSTOM` mode 596 | is selected with the :meth:`Layer.set_blend_mode` and :meth:`Sprite.set_blend_mode` methods. 597 | 598 | :param blend_function: name of the user-defined function to call when blending that takes \ 599 | two integer arguments: source component intensity, destination component intensity, and returns \ 600 | the desired intensity. 601 | 602 | Example:: 603 | 604 | # do 50%/50% blending 605 | def blend_50(src, dst): 606 | return (src + dst) / 2 607 | 608 | engine.set_custom_blend_function(blend_50) 609 | """ 610 | self.cb_blend_func = _blend_function(blend_function) 611 | _tln.TLN_SetCustomBlendFunction(self.cb_blend_func) 612 | 613 | def set_log_level(self, log_level: int): 614 | """ 615 | Sets output messages 616 | """ 617 | _tln.TLN_SetLogLevel(log_level) 618 | 619 | def get_available_sprite(self) -> int: 620 | """ 621 | :return: Index of the first unused sprite (starting from 0) or -1 if none found 622 | """ 623 | index = _tln.TLN_GetAvailableSprite() 624 | return index 625 | 626 | def get_available_animation(self) -> int: 627 | """ 628 | :return: Index of the first unused animation (starting from 0) or -1 if none found 629 | """ 630 | index = _tln.TLN_GetAvailableAnimation() 631 | return index 632 | 633 | def open_resource_pack(self, filename: str, key: str=''): 634 | """ 635 | Opens the resource package with optional aes-128 key and binds it 636 | :param filename: file with the resource package (.dat extension) 637 | :param key: optional null-terminated ASCII string with aes decryption key 638 | """ 639 | ok = _tln.TLN_OpenResourcePack(_encode_string(str(filename)), _encode_string(str(key))) 640 | _raise_exception(ok) 641 | 642 | def close_resource_pack(self): 643 | """ 644 | Closes currently opened resource package and unbinds it 645 | """ 646 | return _tln.TLN_CloseResourcePack() 647 | 648 | def set_sprites_mask_region(self, top: int, bottom: int): 649 | """ 650 | Defines a sprite masking region between the two scanlines. Sprites masked with Sprite.enable_mask_region() won't be drawn inside this region 651 | 652 | :param top: upper scaline of the exclusion region 653 | :param bottom: lower scanline of the exclusion region 654 | """ 655 | ok = _tln.TLN_SetSpritesMaskRegion(top, bottom) 656 | _raise_exception(ok) 657 | 658 | def load_world(self, filename: str, first_layer: int=0): 659 | """ 660 | Loads and assigns complete TMX file 661 | 662 | :param filename: main .tmx file to load 663 | :first_layer: optional layer index to start to assign, by default 0 664 | """ 665 | ok =_tln.TLN_LoadWorld(filename, first_layer) 666 | _raise_exception(ok) 667 | 668 | def set_world_position(self, x: int, y: int): 669 | """ 670 | Sets global world position, moving all layers in sync according to their parallax factor 671 | 672 | :param x: horizontal position in world space 673 | :param y: vertical position in world space 674 | """ 675 | ok = _tln.TLN_SetWorldPosition(x, y) 676 | _raise_exception(ok) 677 | 678 | def release_world(self): 679 | """ 680 | Releases world resources loaded with Engine.load_world 681 | """ 682 | _tln.TLN_ReleaseWorld() 683 | 684 | 685 | # window management ----------------------------------------------------------- 686 | _tln.TLN_CreateWindow.argtypes = [c_char_p, c_int] 687 | _tln.TLN_CreateWindow.restype = c_bool 688 | _tln.TLN_CreateWindowThread.argtypes = [c_char_p, c_int] 689 | _tln.TLN_CreateWindowThread.restype = c_bool 690 | _tln.TLN_ProcessWindow.restype = c_bool 691 | _tln.TLN_IsWindowActive.restype = c_bool 692 | _tln.TLN_GetInput.argtypes = [c_int] 693 | _tln.TLN_GetInput.restype = c_bool 694 | _tln.TLN_EnableInput.argtypes = [c_int, c_bool] 695 | _tln.TLN_AssignInputJoystick.argtypes = [c_int, c_int] 696 | _tln.TLN_DefineInputKey.argtypes = [c_int, c_int, c_uint] 697 | _tln.TLN_DefineInputButton.argtypes = [c_int, c_int, c_ubyte] 698 | _tln.TLN_DrawFrame.argtypes = [c_int] 699 | _tln.TLN_EnableCRTEffect.argtypes = [c_int, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_bool, c_ubyte] 700 | _tln.TLN_ConfigCRTEffect.argtypes = [c_int, c_bool] 701 | _tln.TLN_GetTicks.restype = c_int 702 | _tln.TLN_Delay.argtypes = [c_int] 703 | _tln.TLN_GetWindowWidth.restype = c_int 704 | _tln.TLN_GetWindowHeight.restype = c_int 705 | 706 | class Window(object): 707 | """ 708 | Built-in window manager for easy setup and testing 709 | 710 | :ivar num_frame: current frame being drawn, starting from 0 711 | :ivar width: actual window width (after scaling) 712 | :ivar height: actual window height (after scaling) 713 | """ 714 | def __init__(self): 715 | self.cb_sdl_func = None 716 | 717 | @classmethod 718 | def create(cls, title:str='Tilengine window', overlay=None, flags:int=WindowFlags.VSYNC) -> "Window": 719 | """ 720 | Static method that creates a single-threaded window that must be used in conjunction with 721 | :meth:`Window.process` in a loop 722 | 723 | :param overlay: obsolete, kept for compatibility with pre 2.10 release with old CRT effect 724 | :param flags: optional flags combination of :class:`WindowFlags` values 725 | :return: instance of the created window 726 | """ 727 | global _window, _window_created 728 | 729 | """Added the ability to choose the window title ~AleK3y""" 730 | _tln.TLN_SetWindowTitle(_encode_string(str(title))) 731 | 732 | if _window_created: 733 | return _window 734 | ok = _tln.TLN_CreateWindow(_encode_string(overlay), flags) 735 | if ok is True: 736 | _window = Window() 737 | _window.num_frame = 0 738 | _window.width = _tln.TLN_GetWindowWidth() 739 | _window.height = _tln.TLN_GetWindowHeight() 740 | _window_created = True 741 | return _window 742 | else: 743 | _raise_exception(ok) 744 | 745 | @classmethod 746 | def create_threaded(cls, overlay=None, flags:int=WindowFlags.VSYNC) -> "Window": 747 | """ 748 | Static method that creates a multi-threaded window that runs in its own thread without user loop. 749 | Used mainly in python interactive console 750 | 751 | :param overlay: obsolete, kept for compatibility with pre 2.10 release with old CRT effect 752 | :param flags: optional flags combination of :class:`WindowFlags` values 753 | """ 754 | global _window 755 | if _window is not None: 756 | return _window 757 | ok = _tln.TLN_CreateWindowThread(_encode_string(overlay), flags) 758 | if ok is True: 759 | _window = Window() 760 | _window.width = _tln.TLN_GetWindowWidth() 761 | _window.height = _tln.TLN_GetWindowHeight() 762 | return _window 763 | else: 764 | _raise_exception(ok) 765 | 766 | def process(self): 767 | """ 768 | Does basic window housekeeping in signgle-threaded window, created with :meth:`Window.create`. 769 | This method must be called in a loop by the main thread. 770 | :return: True if window is active or False if the user has requested to end the application 771 | (by pressing Esc key or clicking the close button) 772 | """ 773 | _tln.TLN_DrawFrame(self.num_frame) 774 | self.num_frame += 1 775 | return _tln.TLN_ProcessWindow() 776 | 777 | def is_active(self) -> bool: 778 | """ 779 | :return: True if window is active or False if the user has requested to end the application \ 780 | (by pressing Esc key or clicking the close button) 781 | """ 782 | return _tln.TLN_IsWindowActive() 783 | 784 | def get_input(self, input_id: int) -> bool: 785 | """ 786 | Returns the state of a given input 787 | 788 | :param input_id: one of the :class:`Input` defined values. By default it requests input of player 1. \ 789 | To request input of a given player, add one of the possible P1 - P4 values. 790 | 791 | :return: True if that input is pressed or False if not 792 | 793 | Example:: 794 | 795 | # check if player 1 is pressing right: 796 | value = window.get_input(Input.RIGHT) 797 | 798 | # check if player 2 is pressing action button 1: 799 | value = window.get_input(Input.P2 + Input.BUTTON1) 800 | """ 801 | return _tln.TLN_GetInput(input_id) 802 | 803 | def enable_input(self, player: int, state: bool): 804 | """ 805 | Enables or disables input for specified player 806 | 807 | :param player: player identifier to configure (PLAYER1 - PLAYER4) 808 | :param state: True to enable, False to disable 809 | """ 810 | _tln.TLN_EnableInput(player, state) 811 | 812 | def assign_joystick(self, player: int, joystick_index: int): 813 | """ 814 | 815 | :param player: player identifier to configure (PLAYER1 - PLAYER4) 816 | :param joystick_index: zero-based joystick index to assign. 0 = first, 1 = second.... Disable with -1 817 | """ 818 | _tln.TLN_AssignInputJoystick(player, joystick_index) 819 | 820 | def define_input_key(self, player: int, input: int, key: int): 821 | """ 822 | Assigns a keyboard input to a player 823 | 824 | :param player: player identifier to configure (PLAYER1 - PLAYER4) 825 | :param input: input to assign, member of :class:`Input` 826 | :param key: ASCII key value or scancode as defined in SDL.h 827 | """ 828 | _tln.TLN_DefineInputKey(player, input, key) 829 | 830 | def define_input_button(self, player: int, input: int, button: int): 831 | """ 832 | Assigns a joystick button input to a player 833 | 834 | :param player: player identifier to configure (PLAYER1 - PLAYER4) 835 | :param input: input to assign, member of :class:`Input` 836 | :param button: button index 837 | """ 838 | _tln.TLN_DefineInputButton(player, input, button) 839 | 840 | def draw_frame(self, num_frame=0): 841 | """ 842 | Deprecated, kept for old source code compatibility. Subsumed by :meth:`Window.process`. 843 | """ 844 | 845 | def wait_redraw(self): 846 | """ 847 | In multi-threaded windows, it waits until the current frame has finished rendering. 848 | """ 849 | _tln.TLN_WaitRedraw() 850 | 851 | def config_crt_effect(self, crt_type: int, rf_blur: bool): 852 | """ 853 | Enables CRT simulation post-processing effect to give true retro appeareance 854 | 855 | :param crt_type: One possible value of \ref CRT class 856 | :param rf_blur: Optional RF (horizontal) blur, increases CPU usage 857 | """ 858 | _tln.TLN_ConfigCRTEffect(crt_type, rf_blur) 859 | 860 | def enable_crt_effect(self, overlay_id, overlay_blend, threshold, v0, v1, v2, v3, blur, glow_factor): 861 | """ 862 | Obsolete, kept for backward compatibility with pre- 2.10 release. Use config_crt_effect() instead. 863 | """ 864 | _tln.TLN_EnableCRTEffect(overlay_id, overlay_blend, threshold, v0, v1, v2, v3, blur, glow_factor) 865 | 866 | def disable_crt_effect(self): 867 | """ 868 | Disables the CRT post-processing effect enabled with :meth:`Window.enable_crt_effect` 869 | """ 870 | _tln.TLN_DisableCRTEffect() 871 | 872 | def set_sdl_callback(self, sdl_callback): 873 | """ 874 | Sets callback to process other SDL2 events inside the window 875 | """ 876 | 877 | if sdl_callback is None: 878 | self.cb_sdl_func = None 879 | else: 880 | from sdl2 import SDL_Event 881 | _sdl_callback_function = CFUNCTYPE(None, SDL_Event) 882 | self.cb_sdl_func = _sdl_callback_function(sdl_callback) 883 | _tln.TLN_SetSDLCallback(self.cb_sdl_func) 884 | 885 | 886 | def get_ticks(self) -> int: 887 | """ 888 | :return: the number of milliseconds since application start 889 | """ 890 | return _tln.TLN_GetTicks() 891 | 892 | def delay(self, msecs: int): 893 | """ 894 | Suspends execition for a fixed time 895 | 896 | :param msecs: number of milliseconds to pause 897 | """ 898 | _tln.TLN_Delay(msecs) 899 | 900 | 901 | # spritesets management ----------------------------------------------------------- 902 | _tln.TLN_CreateSpriteset.argtypes = [c_void_p, POINTER(SpriteData), c_int] 903 | _tln.TLN_CreateSpriteset.restype = c_void_p 904 | _tln.TLN_LoadSpriteset.argtypes = [c_char_p] 905 | _tln.TLN_LoadSpriteset.restype = c_void_p 906 | _tln.TLN_CloneSpriteset.argtypes = [c_void_p] 907 | _tln.TLN_CloneSpriteset.restype = c_void_p 908 | _tln.TLN_GetSpriteInfo.argtypes = [c_void_p, c_int, POINTER(SpriteInfo)] 909 | _tln.TLN_GetSpriteInfo.restype = c_bool 910 | _tln.TLN_GetSpritesetPalette.argtypes = [c_void_p] 911 | _tln.TLN_GetSpritesetPalette.restype = c_void_p 912 | _tln.TLN_SetSpritesetData.argtypes = [c_void_p, c_int, POINTER(SpriteData), POINTER(c_ubyte), c_int] 913 | _tln.TLN_SetSpritesetData.restype = c_bool 914 | _tln.TLN_DeleteSpriteset.argtypes = [c_void_p] 915 | _tln.TLN_DeleteSpriteset.restype = c_bool 916 | 917 | 918 | class Spriteset(object): 919 | """ 920 | The Spriteset object holds the graphic data used to render moving objects (sprites) 921 | 922 | :ivar palette: original palette attached inside the resource file 923 | """ 924 | def __init__(self, handle: c_void_p, owner: bool=True): 925 | self._as_parameter_ = handle 926 | self.owner = owner 927 | self.library = _tln 928 | self.palette = Palette(_tln.TLN_GetSpritesetPalette(handle), False) 929 | 930 | @classmethod 931 | def create(cls, bitmap: "Bitmap", sprite_data: POINTER(SpriteData)) -> "Spriteset": 932 | """ 933 | Static method that creates an empty spriteset 934 | 935 | :param bitmap: Bitmap object containing the packaged sprite pictures 936 | :param sprite_data: list of SpriteData tuples describing each sprite pictures 937 | :return: instance of the created object 938 | """ 939 | handle = _tln.TLN_CreateSpriteset(bitmap, sprite_data, len(sprite_data)) 940 | if handle is not None: 941 | return Spriteset(handle) 942 | else: 943 | _raise_exception() 944 | 945 | @classmethod 946 | def fromfile(cls, filename: str) -> "Spriteset": 947 | """ 948 | Static method that loads a spriteset from a pair of png/txt files 949 | 950 | :param filename: png filename with bitmap data 951 | :return: instance of the created object 952 | """ 953 | handle = _tln.TLN_LoadSpriteset(_encode_string(filename)) 954 | if handle is not None: 955 | return Spriteset(handle) 956 | else: 957 | _raise_exception() 958 | 959 | def clone(self) -> "Spriteset": 960 | """ 961 | Creates a copy of the object 962 | 963 | :return: instance of the copy 964 | """ 965 | handle = _tln.TLN_CloneSpriteset(self) 966 | if handle is not None: 967 | return Spriteset(handle) 968 | else: 969 | _raise_exception() 970 | 971 | def set_sprite_data(self, entry: int, data: POINTER(SpriteData), pixels: POINTER(c_byte), pitch: int): 972 | """ 973 | Sets attributes and pixels of a given sprite inside a spriteset 974 | 975 | :param entry: The entry index inside the spriteset to modify [0, num_sprites - 1] 976 | :param data: Pointer to a user-provided SpriteData structure with sprite description 977 | :param pixels: Pointer to user-provided pixel data block 978 | :param pitch: Number of bytes per scanline of the source pixel data 979 | """ 980 | ok = _tln.TLN_SetSpritesetData(self, entry, data, pixels, pitch) 981 | _raise_exception(ok) 982 | 983 | def get_sprite_info(self, entry: int, info: POINTER(SpriteInfo)): 984 | """ 985 | Gets info about a given sprite into an user-provided SpriteInfo tuple 986 | 987 | :param entry: sprite index to query 988 | :param info: SpriteInfo to get the data 989 | """ 990 | ok = _tln.TLN_GetSpriteInfo(self, entry, info) 991 | _raise_exception(ok) 992 | 993 | def __del__(self): 994 | if self.owner: 995 | ok = self.library.TLN_DeleteSpriteset(self) 996 | _raise_exception(ok) 997 | 998 | 999 | # tilesets management --------------------------------------------------------- 1000 | _tln.TLN_CreateTileset.argtypes = [c_int, c_int, c_int, c_void_p, c_void_p, POINTER(TileAttributes)] 1001 | _tln.TLN_CreateTileset.restype = c_void_p 1002 | _tln.TLN_LoadTileset.argtypes = [c_char_p] 1003 | _tln.TLN_LoadTileset.restype = c_void_p 1004 | _tln.TLN_CloneTileset.argtypes = [c_void_p] 1005 | _tln.TLN_CloneTileset.restype = c_void_p 1006 | _tln.TLN_SetTilesetPixels.argtypes = [c_void_p, c_int, POINTER(c_byte), c_int] 1007 | _tln.TLN_SetTilesetPixels.restype = c_bool 1008 | _tln.TLN_GetTileWidth.argtypes = [c_void_p] 1009 | _tln.TLN_GetTileWidth.restype = c_int 1010 | _tln.TLN_GetTileHeight.argtypes = [c_void_p] 1011 | _tln.TLN_GetTileHeight.restype = c_int 1012 | _tln.TLN_GetTilesetNumTiles.argtypes = [c_void_p] 1013 | _tln.TLN_GetTilesetNumTiles.restype = c_int 1014 | _tln.TLN_GetTilesetPalette.argtypes = [c_void_p] 1015 | _tln.TLN_GetTilesetPalette.restype = c_void_p 1016 | _tln.TLN_GetTilesetSequencePack.argtypes = [c_void_p] 1017 | _tln.TLN_GetTilesetSequencePack.restype = c_void_p 1018 | _tln.TLN_FindSpritesetSprite.argtypes = [c_void_p, c_char_p] 1019 | _tln.TLN_FindSpritesetSprite.restype = c_int 1020 | _tln.TLN_DeleteTileset.argtypes = [c_void_p] 1021 | _tln.TLN_DeleteTileset.restype = c_bool 1022 | 1023 | 1024 | class Tileset(object): 1025 | """ 1026 | The Tileset object holds the graphic tiles used to render background layers from a Tilemap 1027 | 1028 | :ivar tile_width: width of each tile 1029 | :ivar tile_height: height of each tile 1030 | :ivar num_tiles: number of unique tiles 1031 | :ivar palette: original palette attached inside the resource file 1032 | :ivar sequence_pack: optional SequencePack embedded inside the Tileset for tileset animation 1033 | """ 1034 | def __init__(self, handle: c_void_p, owner: bool=True): 1035 | self._as_parameter_ = handle 1036 | self.owner = owner 1037 | self.library = _tln 1038 | self.tile_width = _tln.TLN_GetTileWidth(handle) 1039 | self.tile_height = _tln.TLN_GetTileHeight(handle) 1040 | self.num_tiles = _tln.TLN_GetTilesetNumTiles(handle) 1041 | self.palette = Palette(_tln.TLN_GetTilesetPalette(handle), False) 1042 | self.sequence_pack = SequencePack(_tln.TLN_GetTilesetSequencePack(handle), False) 1043 | 1044 | @classmethod 1045 | def create(cls, num_tiles: int, width: int, height: int, palette: "Palette", sequence_pack: Optional["SequencePack"]=None, attributes: Optional[POINTER(TileAttributes)]=None) -> "Tileset": 1046 | """ 1047 | Static method that creates an empty Tileset at runtime 1048 | 1049 | :param num_tiles: number of unique tiles 1050 | :param width: Width of each tile (must be multiple of 8) 1051 | :param height: Height of each tile (must be multiple of 8) 1052 | :param palette: Palette object 1053 | :param sequence_pack: Optional SequencePack with associated Tileset animations 1054 | :param attributes: Optional list of TileAttributes, one element per tile in the tileset 1055 | :return: instance of the created object 1056 | """ 1057 | handle = _tln.TLN_CreateTileset(num_tiles, width, height, palette, sequence_pack, attributes) 1058 | if handle is not None: 1059 | return Tileset(handle) 1060 | else: 1061 | _raise_exception() 1062 | 1063 | @classmethod 1064 | def fromfile(cls, filename: str) -> "Tileset": 1065 | """ 1066 | Static method that loads a Tiled TSX tileset from file 1067 | 1068 | :param filename: TSX file with the tileset 1069 | :return: 1070 | """ 1071 | handle = _tln.TLN_LoadTileset(_encode_string(filename)) 1072 | if handle is not None: 1073 | return Tileset(handle) 1074 | else: 1075 | _raise_exception() 1076 | 1077 | def clone(self) -> "Tileset": 1078 | """ 1079 | Creates a copy of the object 1080 | 1081 | :return: instance of the copy 1082 | """ 1083 | handle = _tln.TLN_CloneTileset(self) 1084 | if handle is not None: 1085 | return Tileset(handle) 1086 | else: 1087 | _raise_exception() 1088 | 1089 | def set_pixels(self, entry: int, data: POINTER(c_byte), pitch: int): 1090 | """ 1091 | Sets pixel data for a single tile 1092 | 1093 | :param entry: Number of tile to set [0, num_tiles - 1] 1094 | :param data: List of bytes with pixel data, one byte per pixel 1095 | :param pitch: Number of bytes per line in source data 1096 | """ 1097 | ok = _tln.TLN_SetTilesetPixels(self, entry, data, pitch) 1098 | _raise_exception(ok) 1099 | 1100 | def __del__(self): 1101 | if self.owner: 1102 | ok = self.library.TLN_DeleteTileset(self) 1103 | _raise_exception(ok) 1104 | 1105 | 1106 | # tilemaps management --------------------------------------------------------- 1107 | _tln.TLN_CreateTilemap.argtypes = [c_int, c_int, POINTER(Tile), c_int, c_void_p] 1108 | _tln.TLN_CreateTilemap.restype = c_void_p 1109 | _tln.TLN_LoadTilemap.argtypes = [c_char_p] 1110 | _tln.TLN_LoadTilemap.restype = c_void_p 1111 | _tln.TLN_CloneTilemap.argtypes = [c_void_p] 1112 | _tln.TLN_CloneTilemap.restype = c_void_p 1113 | _tln.TLN_GetTilemapRows.argtypes = [c_void_p] 1114 | _tln.TLN_GetTilemapRows.restype = c_int 1115 | _tln.TLN_GetTilemapCols.argtypes = [c_void_p] 1116 | _tln.TLN_GetTilemapCols.restype = c_int 1117 | _tln.TLN_SetTilemapTileset2.argtypes = [c_void_p, c_void_p, c_int] 1118 | _tln.TLN_SetTilemapTileset2.restype = c_bool 1119 | _tln.TLN_GetTilemapTileset2.argtypes = [c_void_p, c_int] 1120 | _tln.TLN_GetTilemapTileset2.restype = c_void_p 1121 | _tln.TLN_GetTilemapTile.argtypes = [c_void_p, c_int, c_int, POINTER(Tile)] 1122 | _tln.TLN_GetTilemapTile.restype = c_bool 1123 | _tln.TLN_SetTilemapTile.argtypes = [c_void_p, c_int, c_int, POINTER(Tile)] 1124 | _tln.TLN_SetTilemapTile.restype = c_bool 1125 | _tln.TLN_CopyTiles.argtypes = [c_void_p, c_int, c_int, c_int, c_int, c_void_p, c_int, c_int] 1126 | _tln.TLN_CopyTiles.restype = c_bool 1127 | _tln.TLN_DeleteTilemap.argtypes = [c_void_p] 1128 | _tln.TLN_DeleteTilemap.restype = c_bool 1129 | 1130 | 1131 | class Tilemap(object): 1132 | """ 1133 | The Tilemap object holds the grid of tiles that define the background layout 1134 | 1135 | :ivar rows: number of rows (vertical cells) 1136 | :ivar cols: number of columns (horizontal cells) 1137 | :ivar tileset: Tileset object attached inside the resource file 1138 | """ 1139 | def __init__(self, handle: c_void_p, owner: bool=True): 1140 | self._as_parameter_ = handle 1141 | self.owner = owner 1142 | self.library = _tln 1143 | self.rows = _tln.TLN_GetTilemapRows(handle) 1144 | self.cols = _tln.TLN_GetTilemapCols(handle) 1145 | tileset_handle = _tln.TLN_GetTilemapTileset2(handle, 0) 1146 | if tileset_handle is not None: 1147 | self.tileset = Tileset(tileset_handle, False) 1148 | else: 1149 | self.tileset = None 1150 | 1151 | @classmethod 1152 | def create(cls, rows: int, cols: int, tiles: POINTER(Tile), background_color: int=0, tileset: Optional[Tileset]=None) -> "Tilemap": 1153 | """ 1154 | Static method that creates an empty tilemap 1155 | 1156 | :param rows: Number of rows (vertical dimension) 1157 | :param cols: Number of cols (horizontal dimension) 1158 | :param tiles: List of Tile objects with tile data 1159 | :param background_color: optional Color object with default background color 1160 | :param tileset: Optional reference to associated tileset 1161 | :return: instance of the created object 1162 | """ 1163 | handle = _tln.TLN_CreateTilemap(rows, cols, tiles, background_color, tileset) 1164 | if handle is not None: 1165 | return Tilemap(handle) 1166 | else: 1167 | _raise_exception() 1168 | 1169 | @classmethod 1170 | def fromfile(cls, filename: str, layer_name: Optional[str]=None) -> "Tilemap": 1171 | """ 1172 | Static method that loads a Tiled TMX tilemap from file 1173 | 1174 | :param filename: TMX file with the tilemap 1175 | :param layer_name: Optional name of the layer to load when the TMX file has more than one layer. \ 1176 | By default it loads the first layer inside the TMX 1177 | :return: instance of the created object 1178 | """ 1179 | handle = _tln.TLN_LoadTilemap(_encode_string(filename), _encode_string(layer_name)) 1180 | if handle is not None: 1181 | return Tilemap(handle) 1182 | else: 1183 | _raise_exception() 1184 | 1185 | def clone(self) -> "Tilemap": 1186 | """ 1187 | Creates a copy of the object 1188 | 1189 | :return: instance of the copy 1190 | """ 1191 | handle = _tln.TLN_CloneTilemap(self) 1192 | if handle is not None: 1193 | return Tilemap(handle) 1194 | else: 1195 | _raise_exception() 1196 | 1197 | def get_tileset(self, index: int=0) -> Tileset: 1198 | """ 1199 | Returns the nth tileset associated tileset to the specified tilemap 1200 | :param index: Tileset index (0 - 7), 0 by default 1201 | """ 1202 | return Tileset(_tln.TLN_GetTilemapTileset2(self, index), False) 1203 | 1204 | def set_tileset(self, tileset: Tileset, index:int = 0): 1205 | """ 1206 | Sets the nth tileset associated tileset to the specified tilemap 1207 | :param tileset: Reference to tileset object to associate 1208 | :param index: Tileset index (0 - 7), 0 by default 1209 | """ 1210 | ok = _tln.TLN_SetTilemapTileset2(self, tileset, index) 1211 | _raise_exception(ok) 1212 | 1213 | def get_tile(self, row: int, col: int, tile_info: POINTER(TileInfo)): 1214 | """ 1215 | Gets data about a given tile 1216 | 1217 | :param row: Vertical position of the tile (0 <= row < rows) 1218 | :param col: Horizontal position of the tile (0 <= col < cols) 1219 | :param tile_info: pointer to user-provided :class:`TileInfo` object where to get the data 1220 | """ 1221 | ok = _tln.TLN_GetTilemapTile(self, row, col, tile_info) 1222 | _raise_exception(ok) 1223 | 1224 | def set_tile(self, row: int, col: int, tile_info: Optional[POINTER(TileInfo)]): 1225 | """ 1226 | Sets a tile inside the tilemap 1227 | 1228 | :param row: Vertical position of the tile (0 <= row < rows) 1229 | :param col: Horizontal position of the tile (0 <= col < cols) 1230 | :param tile_info: pointer to user-provided :class:`Tile` object, or None to erase 1231 | """ 1232 | if tile_info is not None: 1233 | ok = _tln.TLN_SetTilemapTile(self, row, col, tile_info) 1234 | else: 1235 | tile_info = Tile() 1236 | tile_info.index = 0 1237 | ok = _tln.TLN_SetTilemapTile(self, row, col, tile_info) 1238 | del tile_info 1239 | _raise_exception(ok) 1240 | 1241 | def copy_tiles(self, src_row: int, src_col: int, num_rows: int, num_cols: int, dst_tilemap: "Tilemap", dst_row: int, dst_col: int): 1242 | """ 1243 | Copies blocks of tiles between two tilemaps 1244 | 1245 | :param src_row: Starting row (vertical position) inside the source tilemap 1246 | :param src_col: Starting column (horizontal position) inside the source tilemap 1247 | :param num_rows: Number of rows to copy 1248 | :param num_cols: Number of columns to copy 1249 | :param dst_tilemap: Target tilemap 1250 | :param dst_row: Starting row (vertical position) inside the target tilemap 1251 | :param dst_col: Starting column (horizontal position) inside the target tilemap 1252 | """ 1253 | ok = _tln.TLN_CopyTiles(self, src_row, src_col, num_rows, num_cols, dst_tilemap, dst_row, dst_col) 1254 | _raise_exception(ok) 1255 | 1256 | def __del__(self): 1257 | if self.owner: 1258 | ok = self.library.TLN_DeleteTilemap(self) 1259 | _raise_exception(ok) 1260 | 1261 | 1262 | # color tables management ----------------------------------------------------- 1263 | _tln.TLN_CreatePalette.argtypes = [c_int] 1264 | _tln.TLN_CreatePalette.restype = c_void_p 1265 | _tln.TLN_LoadPalette.argtypes = [c_char_p] 1266 | _tln.TLN_LoadPalette.restype = c_void_p 1267 | _tln.TLN_ClonePalette.argtypes = [c_void_p] 1268 | _tln.TLN_ClonePalette.restype = c_void_p 1269 | _tln.TLN_SetPaletteColor.argtypes = [c_void_p, c_int, c_ubyte, c_ubyte, c_ubyte] 1270 | _tln.TLN_SetPaletteColor.restype = c_bool 1271 | _tln.TLN_MixPalettes.argtypes = [c_void_p, c_void_p, c_void_p, c_ubyte] 1272 | _tln.TLN_MixPalettes.restype = c_bool 1273 | _tln.TLN_AddPaletteColor.argtypes = [c_void_p, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_ubyte] 1274 | _tln.TLN_AddPaletteColor.restype = c_bool 1275 | _tln.TLN_SubPaletteColor.argtypes = [c_void_p, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_ubyte] 1276 | _tln.TLN_SubPaletteColor.restype = c_bool 1277 | _tln.TLN_ModPaletteColor.argtypes = [c_void_p, c_ubyte, c_ubyte, c_ubyte, c_ubyte, c_ubyte] 1278 | _tln.TLN_ModPaletteColor.restype = c_bool 1279 | _tln.TLN_GetPaletteData.argtypes = [c_void_p, c_int] 1280 | _tln.TLN_GetPaletteData.restype = POINTER(c_ubyte) 1281 | _tln.TLN_DeletePalette.argtypes = [c_void_p] 1282 | _tln.TLN_DeletePalette.restype = c_bool 1283 | 1284 | 1285 | class Palette(object): 1286 | """ 1287 | The Palette object holds the color tables used by tileesets and spritesets to render sprites and backgrounds 1288 | """ 1289 | def __init__(self, handle: c_void_p, owner:bool = True): 1290 | self._as_parameter_ = handle 1291 | self.owner = owner 1292 | self.library = _tln 1293 | 1294 | @classmethod 1295 | def create(cls, num_entries: int=256) -> "Palette": 1296 | """ 1297 | Static method that creates an empty palette 1298 | 1299 | :param num_entries: optional number of colors to hold (up to 256, default value) 1300 | :return: instance of the created object 1301 | """ 1302 | handle = _tln.TLN_CreatePalette(num_entries) 1303 | if handle is not None: 1304 | return Palette(handle) 1305 | else: 1306 | _raise_exception() 1307 | 1308 | @classmethod 1309 | def fromfile(cls, filename: str) -> "Palette": 1310 | """ 1311 | Static method that loads a palette from an Adobe Color Table (.act) file 1312 | 1313 | :param filename: name of the .act file to load 1314 | :return: instance of the created object 1315 | """ 1316 | handle = _tln.TLN_LoadPalette(_encode_string(filename)) 1317 | if handle is not None: 1318 | return Palette(handle) 1319 | else: 1320 | _raise_exception() 1321 | 1322 | def clone(self) -> "Palette": 1323 | """ 1324 | Creates a copy of the object 1325 | 1326 | :return: instance of the copy 1327 | """ 1328 | handle = _tln.TLN_ClonePalette(self) 1329 | if handle is not None: 1330 | return Palette(handle) 1331 | else: 1332 | _raise_exception() 1333 | 1334 | def set_color(self, entry: int, color: Color): 1335 | """ 1336 | Sets the RGB color value of a palette entry 1337 | 1338 | :param entry: Index of the palette entry to modify (0-255) 1339 | :param color: Color object with the r,g,b components of the color 1340 | """ 1341 | ok = _tln.TLN_SetPaletteColor(self, entry, color.r, color.g, color.b) 1342 | _raise_exception(ok) 1343 | 1344 | def mix(self, src_palette1: "Palette", src_palette2: "Palette", factor: int): 1345 | """ 1346 | Mixes two palettes 1347 | 1348 | :param src_palette1: First palette to mix 1349 | :param src_palette2: Second palette to mix 1350 | :param factor: Integer value with percentage of mix (0-100) 1351 | """ 1352 | ok = _tln.TLN_MixPalettes(src_palette1, src_palette2, self, factor) 1353 | _raise_exception(ok) 1354 | 1355 | def add_color(self, first: int, count: int, color: Color): 1356 | """ 1357 | Modifies a range of colors by adding the provided color value to the selected range. 1358 | The result is always a brighter color. 1359 | 1360 | :param first: index of the first color entry to modify 1361 | :param count: number of colors from start to modify 1362 | :param color: Color object to add 1363 | """ 1364 | ok = _tln.TLN_AddPaletteColor(self, first, count, color.r, color.g, color.b) 1365 | _raise_exception(ok) 1366 | 1367 | def sub_color(self, first: int, count: int, color: Color): 1368 | """ 1369 | Modifies a range of colors by subtracting the provided color value to the selected range. 1370 | The result is always a darker color. 1371 | 1372 | :param first: index of the first color entry to modify 1373 | :param count: number of colors from start to modify 1374 | :param color: Color object to subtract 1375 | """ 1376 | ok = _tln.TLN_SubPaletteColor(self, first, count, color.r, color.g, color.b) 1377 | _raise_exception(ok) 1378 | 1379 | def mod_color(self, first: int, count: int, color: Color): 1380 | """ 1381 | Modifies a range of colors by modulating (normalized product) the provided color value to the selected range. 1382 | The result is always a darker color. 1383 | 1384 | :param first: index of the first color entry to modify 1385 | :param count: number of colors from start to modify 1386 | :param color: Color object to modulate 1387 | """ 1388 | ok = _tln.TLN_ModPaletteColor(self, first, count, color.r, color.g, color.b) 1389 | _raise_exception(ok) 1390 | 1391 | def __del__(self): 1392 | if self.owner: 1393 | ok = self.library.TLN_DeletePalette(self) 1394 | _raise_exception(ok) 1395 | 1396 | 1397 | # bitmaps --------------------------------------------------------------------- 1398 | _tln.TLN_CreateBitmap.argtypes = [c_int, c_int, c_int] 1399 | _tln.TLN_CreateBitmap.restype = c_void_p 1400 | _tln.TLN_LoadBitmap.argtypes = [c_char_p] 1401 | _tln.TLN_LoadBitmap.restype = c_void_p 1402 | _tln.TLN_CloneBitmap.argtypes = [c_void_p] 1403 | _tln.TLN_CloneBitmap.restype = c_void_p 1404 | _tln.TLN_GetBitmapPtr.argtypes = [c_void_p, c_int, c_int] 1405 | _tln.TLN_GetBitmapPtr.restype = POINTER(c_ubyte) 1406 | _tln.TLN_GetBitmapWidth.argtypes = [c_void_p] 1407 | _tln.TLN_GetBitmapWidth.restype = c_int 1408 | _tln.TLN_GetBitmapHeight.argtypes = [c_void_p] 1409 | _tln.TLN_GetBitmapHeight.restype = c_int 1410 | _tln.TLN_GetBitmapDepth.argtypes = [c_void_p] 1411 | _tln.TLN_GetBitmapDepth.restype = c_int 1412 | _tln.TLN_GetBitmapPitch.argtypes = [c_void_p] 1413 | _tln.TLN_GetBitmapPitch.restype = c_int 1414 | _tln.TLN_GetBitmapPalette.argtypes = [c_void_p] 1415 | _tln.TLN_GetBitmapPalette.restype = c_void_p 1416 | _tln.TLN_DeleteBitmap.argtypes = [c_void_p] 1417 | _tln.TLN_DeleteBitmap.restype = c_bool 1418 | 1419 | 1420 | class Bitmap(object): 1421 | """ 1422 | The Bitmap object holds graphic data used to build in backgrounds, Tileset and Spriteset objects 1423 | 1424 | :ivar width: number of horizontal pixels 1425 | :ivar height: number of vertical pixels 1426 | :ivar depth: number of bits per pixel 1427 | :ivar pitch: number of bytes per each scanline 1428 | :ivar palette: Palette object attached inside the bitmap 1429 | """ 1430 | def __init__(self, handle: c_void_p, owner=True): 1431 | self._as_parameter_ = handle 1432 | self.owner = owner 1433 | self.library = _tln 1434 | self.width = _tln.TLN_GetBitmapWidth(handle) 1435 | self.height = _tln.TLN_GetBitmapHeight(handle) 1436 | self.depth = _tln.TLN_GetBitmapDepth(handle) 1437 | self.pitch = _tln.TLN_GetBitmapPitch(handle) 1438 | self.palette = Palette(_tln.TLN_GetBitmapPalette(handle), False) 1439 | 1440 | @classmethod 1441 | def create(cls, width: int, height: int, bpp: int=8) -> "Bitmap": 1442 | """ 1443 | Static method that creates an empty bitmap 1444 | 1445 | :param width: Width in pixels 1446 | :param height: Height in pixels 1447 | :param bpp: Optional bits per pixel (8 by default) 1448 | :return: instance of the created object 1449 | """ 1450 | handle = _tln.TLN_CreateBitmap(width, height, bpp) 1451 | if handle is not None: 1452 | return Bitmap(handle) 1453 | else: 1454 | _raise_exception() 1455 | 1456 | @classmethod 1457 | def fromfile(cls, filename: str) -> "Bitmap": 1458 | """ 1459 | Static method that loads a BMP or PNG file 1460 | 1461 | :param filename: name of the file to load (.bmp or .png) 1462 | :return: instance of the created object 1463 | """ 1464 | handle = _tln.TLN_LoadBitmap(_encode_string(filename)) 1465 | if handle is not None: 1466 | return Bitmap(handle) 1467 | else: 1468 | _raise_exception() 1469 | 1470 | def clone(self) -> "Bitmap": 1471 | """ 1472 | Creates a copy of the object 1473 | 1474 | :return: instance of the copy 1475 | """ 1476 | handle = _tln.TLN_CloneBitmap(self) 1477 | if handle is not None: 1478 | return Bitmap(handle) 1479 | else: 1480 | _raise_exception() 1481 | 1482 | def get_data(self, x: int, y: int) -> c_void_p: 1483 | """ 1484 | Returns a pointer to the starting memory address 1485 | 1486 | :param x: Starting x position [0, width - 1] 1487 | :param y: Starting y position [0, height - 1] 1488 | :return: pointer 1489 | """ 1490 | return _tln.TLN_GetBitmapPtr(self, x, y) 1491 | 1492 | def __del__(self): 1493 | if self.owner: 1494 | ok = self.library.TLN_DeleteBitmap(self) 1495 | _raise_exception(ok) 1496 | 1497 | class ObjectList(object): 1498 | """ 1499 | TODO: ObjectList reference 1500 | """ 1501 | 1502 | 1503 | # sequences management -------------------------------------------------------- 1504 | _tln.TLN_CreateSequence.argtypes = [c_char_p, c_int, c_int, POINTER(SequenceFrame)] 1505 | _tln.TLN_CreateSequence.restype = c_void_p 1506 | _tln.TLN_CreateCycle.argtypes = [c_char_p, c_int, POINTER(ColorStrip)] 1507 | _tln.TLN_CreateCycle.restype = c_void_p 1508 | _tln.TLN_CreateSpriteSequence.argtypes = [c_char_p, c_void_p, c_char_p, c_int] 1509 | _tln.TLN_CreateSpriteSequence.restype = c_void_p 1510 | _tln.TLN_CloneSequence.argtypes = [c_void_p] 1511 | _tln.TLN_CloneSequence.restype = c_void_p 1512 | _tln.TLN_GetSequenceInfo.argtypes = [c_void_p, POINTER(SequenceInfo)] 1513 | _tln.TLN_GetSequenceInfo.restype = c_bool 1514 | _tln.TLN_DeleteSequence.argtypes = [c_void_p] 1515 | _tln.TLN_DeleteSequence.restype = c_bool 1516 | 1517 | 1518 | class Sequence(object): 1519 | """ 1520 | The Sequence object holds the sequences to feed the animation engine 1521 | """ 1522 | def __init__(self, handle: c_void_p, owner: bool=True): 1523 | self._as_parameter_ = handle 1524 | self.owner = owner 1525 | self.library = _tln 1526 | 1527 | @classmethod 1528 | def create_sequence(cls, name: str, target: int, frames: List[SequenceFrame]) -> "Sequence": 1529 | """ 1530 | Static method that creates an empty Sequence for Sprite and Tileset animations 1531 | 1532 | :param name: String with an unique name to identify the sequence inside a SequencePack object 1533 | :param target: For Tileset animations, the tile index to animate 1534 | :param frames: List with SequenceFrame objects, one for each frame of animation 1535 | :return: instance of the created object 1536 | """ 1537 | handle = _tln.TLN_CreateSequence(_encode_string(name), target, len(frames), frames) 1538 | if handle is not None: 1539 | return Sequence(handle) 1540 | else: 1541 | _raise_exception() 1542 | 1543 | @classmethod 1544 | def create_cycle(cls, name: str, strips: List[ColorStrip]) -> "Sequence": 1545 | """ 1546 | Static method that creates an empty Sequence for Palette animations 1547 | 1548 | :param name: String with an unique name to identify the sequence inside a SequencePack object 1549 | :param strips: List with ColorStrip objects, one for each frame of animation 1550 | :return: instance of the created object 1551 | """ 1552 | handle = _tln.TLN_CreateCycle(_encode_string(name), len(strips), strips) 1553 | if handle is not None: 1554 | return Sequence(handle) 1555 | else: 1556 | _raise_exception() 1557 | 1558 | @classmethod 1559 | def create_sprite_sequence(cls, spriteset: Spriteset, basename: str, delay: int) -> "Sequence": 1560 | """ 1561 | Static method that creates a sprite sequence based on names inside a spriteset 1562 | 1563 | :param spriteset: Reference to the spriteset with frames to animate 1564 | :param basename: Base of the sprite name for the numbered sequence 1565 | :param delay: Number of frames to hold each animation frame 1566 | :return: created Sequence object or None if error 1567 | """ 1568 | handle = _tln.TLN_CreateSpriteSequence(None, spriteset, _encode_string(basename), delay) 1569 | if handle is not None: 1570 | return Sequence(handle) 1571 | else: 1572 | _raise_exception() 1573 | 1574 | def clone(self) -> "Sequence": 1575 | """ 1576 | Creates a copy of the object 1577 | 1578 | :return: instance of the copy 1579 | """ 1580 | handle = _tln.TLN_CloneSequence(self) 1581 | if handle is not None: 1582 | return Sequence(handle) 1583 | else: 1584 | _raise_exception() 1585 | 1586 | def get_info(self, info: POINTER(SequenceInfo)): 1587 | """ 1588 | Returns runtime info about a given sequence 1589 | 1590 | :param info: user-provided SequenceInfo structure to hold the returned data 1591 | """ 1592 | return _tln.TLN_GetSequenceInfo(self, info) 1593 | 1594 | def __del__(self): 1595 | if self.owner: 1596 | ok = self.library.TLN_DeleteSequence(self) 1597 | _raise_exception(ok) 1598 | 1599 | 1600 | # sequence pack management -------------------------------------------------------- 1601 | _tln.TLN_CreateSequencePack.restype = c_void_p 1602 | _tln.TLN_LoadSequencePack.argtypes = [c_char_p] 1603 | _tln.TLN_LoadSequencePack.restype = c_void_p 1604 | _tln.TLN_FindSequence.argtypes = [c_void_p, c_char_p] 1605 | _tln.TLN_FindSequence.restype = c_void_p 1606 | _tln.TLN_GetSequence.argtypes = [c_void_p] 1607 | _tln.TLN_GetSequence.restype = c_void_p 1608 | _tln.TLN_GetSequencePackCount.argtypes = [c_void_p] 1609 | _tln.TLN_GetSequencePackCount.restype = c_int 1610 | _tln.TLN_AddSequenceToPack.argtypes = [c_void_p, c_void_p] 1611 | _tln.TLN_AddSequenceToPack.restype = c_bool 1612 | _tln.TLN_DeleteSequencePack.argtypes = [c_void_p] 1613 | _tln.TLN_DeleteSequencePack.restype = c_bool 1614 | 1615 | 1616 | class SequencePack(object): 1617 | """ 1618 | The SequencePack object holds a collection of Sequence objects 1619 | 1620 | :ivar count: number of sequences inside the pack 1621 | :ivar sequences: dictionary of contained sequences indexed by name 1622 | """ 1623 | def __init__(self, handle: c_void_p, owner: bool=True): 1624 | self._as_parameter_ = handle 1625 | self.owner = owner 1626 | self.library = _tln 1627 | self.count = _tln.TLN_GetSequencePackCount(self) 1628 | self.sequences = dict() 1629 | sequence_info = SequenceInfo() 1630 | for s in range(self.count): 1631 | sequence = self.get_sequence(s) 1632 | sequence.get_info(sequence_info) 1633 | self.sequences[_decode_string(sequence_info.name)] = sequence 1634 | 1635 | @classmethod 1636 | def create(cls) -> "SequencePack": 1637 | """ 1638 | Static method that creates an empty SequencePack object 1639 | 1640 | :return: instance of the created object 1641 | """ 1642 | handle = _tln.TLN_CreateSequencePack() 1643 | if handle is not None: 1644 | return SequencePack(handle) 1645 | else: 1646 | _raise_exception() 1647 | 1648 | @classmethod 1649 | def fromfile(cls, filename: str) -> "SequencePack": 1650 | """ 1651 | Static method that loads a SQX file with sequence data (XML-based) 1652 | 1653 | :param filename: Name of the SQX file to load 1654 | :return: instance of the created object 1655 | """ 1656 | handle = _tln.TLN_LoadSequencePack(_encode_string(filename)) 1657 | if handle is not None: 1658 | return SequencePack(handle) 1659 | else: 1660 | _raise_exception() 1661 | 1662 | def get_sequence(self, index: int) -> Sequence: 1663 | """ 1664 | Returns the nth sequence inside a sequence pack 1665 | 1666 | :param index: zero-based index number 1667 | :return: Sequence object 1668 | """ 1669 | handle = _tln.TLN_GetSequence(self, index) 1670 | if handle is not None: 1671 | return Sequence(handle, False) 1672 | else: 1673 | _raise_exception() 1674 | 1675 | def find_sequence(self, name: str) -> Sequence: 1676 | """ 1677 | Finds a Sequence by its name 1678 | 1679 | :param name: name of the Sequence to find 1680 | :return: Sequence object if found, or None if error 1681 | """ 1682 | handle = _tln.TLN_FindSequence(self, _encode_string(name)) 1683 | if handle is not None: 1684 | return Sequence(handle) 1685 | else: 1686 | _raise_exception() 1687 | 1688 | def add_sequence(self, sequence: Sequence): 1689 | """ 1690 | Adds a Sequence to a SequencePack 1691 | 1692 | :param sequence: Sequence object to add 1693 | """ 1694 | sequence_info = SequenceInfo() 1695 | sequence.get_info(sequence_info) 1696 | ok = _tln.TLN_AddSequenceToPack(self, sequence) 1697 | if ok: 1698 | self.sequences[_decode_string(sequence_info.name)] = sequence 1699 | _raise_exception(ok) 1700 | 1701 | def __del__(self): 1702 | if self.owner: 1703 | ok = self.library.TLN_DeleteSequencePack(self) 1704 | _raise_exception(ok) 1705 | 1706 | 1707 | # layer management ------------------------------------------------------------ 1708 | _tln.TLN_SetLayer.argtypes = [c_int, c_void_p, c_void_p] 1709 | _tln.TLN_SetLayer.restype = c_bool 1710 | 1711 | _tln.TLN_SetLayerTilemap.argtypes = [c_int, c_void_p] 1712 | _tln.TLN_SetLayerTilemap.restype = c_bool 1713 | _tln.TLN_SetLayerBitmap.argtypes = [c_int, c_void_p] 1714 | _tln.TLN_SetLayerBitmap.restype = c_bool 1715 | _tln.TLN_SetLayerObjects.argtypes = [c_int, c_void_p, c_void_p] 1716 | _tln.TLN_SetLayerObjects.restype = c_bool 1717 | _tln.TLN_SetLayerPalette.argtypes = [c_int, c_void_p] 1718 | _tln.TLN_SetLayerPalette.restype = c_bool 1719 | _tln.TLN_SetLayerPosition.argtypes = [c_int, c_int, c_int] 1720 | _tln.TLN_SetLayerPosition.restype = c_bool 1721 | _tln.TLN_SetLayerScaling.argtypes = [c_int, c_float, c_float] 1722 | _tln.TLN_SetLayerScaling.restype = c_bool 1723 | _tln.TLN_SetLayerTransform.argtypes = [c_int, c_float, c_float, c_float, c_float, c_float] 1724 | _tln.TLN_SetLayerTransform.restype = c_bool 1725 | _tln.TLN_SetLayerPixelMapping.argtypes = [c_int, POINTER(PixelMap)] 1726 | _tln.TLN_SetLayerPixelMapping.restype = c_bool 1727 | _tln.TLN_ResetLayerMode.argtypes = [c_int] 1728 | _tln.TLN_ResetLayerMode.restype = c_bool 1729 | _tln.TLN_SetLayerBlendMode.argtypes = [c_int, c_int, c_ubyte] 1730 | _tln.TLN_SetLayerBlendMode.restype = c_bool 1731 | _tln.TLN_SetLayerColumnOffset.argtypes = [c_int, POINTER(c_int)] 1732 | _tln.TLN_SetLayerColumnOffset.restype = c_bool 1733 | _tln.TLN_SetLayerClip.argtypes = [c_int, c_int, c_int, c_int, c_int] 1734 | _tln.TLN_SetLayerClip.restype = c_bool 1735 | _tln.TLN_DisableLayerClip.argtypes = [c_int] 1736 | _tln.TLN_DisableLayerClip.restype = c_bool 1737 | _tln.TLN_SetLayerMosaic.argtypes = [c_int, c_int, c_int] 1738 | _tln.TLN_SetLayerMosaic.restype = c_bool 1739 | _tln.TLN_DisableLayerMosaic.argtypes = [c_int] 1740 | _tln.TLN_DisableLayerMosaic.restype = c_bool 1741 | _tln.TLN_DisableLayer.argtypes = [c_int] 1742 | _tln.TLN_DisableLayer.restype = c_bool 1743 | _tln.TLN_EnableLayer.argtypes = [c_int] 1744 | _tln.TLN_EnableLayer.restype = c_bool 1745 | _tln.TLN_GetLayerPalette.argtypes = [c_int] 1746 | _tln.TLN_GetLayerPalette.restype = c_void_p 1747 | _tln.TLN_GetLayerTile.argtypes = [c_int, c_int, c_int, POINTER(TileInfo)] 1748 | _tln.TLN_GetLayerTile.restype = c_bool 1749 | _tln.TLN_GetLayerWidth.argtypes = [c_int] 1750 | _tln.TLN_GetLayerWidth.restype = c_int 1751 | _tln.TLN_GetLayerHeight.argtypes = [c_int] 1752 | _tln.TLN_GetLayerHeight.restype = c_int 1753 | _tln.TLN_SetLayerPriority.argtypes = [c_int, c_bool] 1754 | _tln.TLN_SetLayerPriority.restype = c_bool 1755 | 1756 | class Layer(object): 1757 | """ 1758 | The Layer object manages each tiled background plane 1759 | 1760 | :ivar index: layer index, from 0 to num_layers - 1 1761 | :ivar width: width in pixels of the assigned tilemap 1762 | :ivar height: height in pixels of the assigned tilemap 1763 | :ivar tilemap: assigned Tilemap object, for tilemap layers 1764 | :ivar bitmap: assigned Bitmap object, for bitmap layers 1765 | :ivar objectlist: assigned ObjectList object, for object list layers 1766 | """ 1767 | def __init__(self, index): 1768 | self.index = index 1769 | self._as_parameter_ = index 1770 | self.width = 0 1771 | self.height = 0 1772 | self.tilemap = None 1773 | self.bitmap = None 1774 | self.objectlist = None 1775 | 1776 | def setup(self, tilemap: Tilemap, tileset: Optional[Tileset]=None): 1777 | """ 1778 | Enables a background layer by setting the specified tilemap and optional tileset 1779 | 1780 | :param tilemap: Tilemap object with background layout 1781 | :param tileset: Optional Tileset object. If not set, the Tilemap's own Tileset is selected 1782 | """ 1783 | ok = _tln.TLN_SetLayer(self, tileset, tilemap) 1784 | if ok is True: 1785 | self.width = _tln.TLN_GetLayerWidth(self) 1786 | self.height = _tln.TLN_GetLayerHeight(self) 1787 | self.tilemap = tilemap 1788 | if tileset is not None: 1789 | tilemap.set_tileset(tileset) 1790 | return ok 1791 | else: 1792 | _raise_exception() 1793 | 1794 | def set_tilemap(self, tilemap: Tilemap): 1795 | """ 1796 | Configures a tiled background layer with the specified tilemap 1797 | 1798 | :param tilemap: Tilemap object with background layout 1799 | """ 1800 | ok = _tln.TLN_SetLayerTilemap(self, tilemap) 1801 | if ok is True: 1802 | self.width = _tln.TLN_GetLayerWidth(self) 1803 | self.height = _tln.TLN_GetLayerHeight(self) 1804 | self.tilemap = tilemap 1805 | self.bitmap = None 1806 | self.objectlist = None 1807 | return ok 1808 | else: 1809 | _raise_exception() 1810 | 1811 | def set_bitmap(self, bitmap: Bitmap): 1812 | """ 1813 | Configures a background layer with the specified full bitmap 1814 | 1815 | :param bitmap: Bitmap object with full bitmap background 1816 | """ 1817 | ok = _tln.TLN_SetLayerBitmap(self, bitmap) 1818 | if ok is True: 1819 | self.width = _tln.TLN_GetLayerWidth(self) 1820 | self.height = _tln.TLN_GetLayerHeight(self) 1821 | self.bitmap = bitmap 1822 | self.tilemap = None 1823 | self.objectlist = None 1824 | return ok 1825 | else: 1826 | _raise_exception() 1827 | 1828 | def set_palette(self, palette: Palette): 1829 | """ 1830 | Sets the color palette to the layer 1831 | 1832 | :param palette: Palette object to assign. By default the Tileset's own palette is used 1833 | """ 1834 | ok = _tln.TLN_SetLayerPalette(self, palette) 1835 | _raise_exception(ok) 1836 | 1837 | def set_position(self, x: int, y: int): 1838 | """ 1839 | Sets the position of the tileset that corresponds to the upper left corner of the viewport 1840 | 1841 | :param x: horizontal position 1842 | :param y: vertical position 1843 | """ 1844 | ok = _tln.TLN_SetLayerPosition(self, int(x), int(y)) 1845 | _raise_exception(ok) 1846 | 1847 | def set_scaling(self, sx: float, sy: float): 1848 | """ 1849 | Enables layer scaling 1850 | 1851 | :param sx: floating-point value with horizontal scaling factor 1852 | :param sy: floating-point value with vertical scaling factor 1853 | """ 1854 | ok = _tln.TLN_SetLayerScaling(self, sx, sy) 1855 | _raise_exception(ok) 1856 | 1857 | def set_transform(self, angle: float, x: float, y: float, sx: float, sy: float): 1858 | """ 1859 | Enables layer affine transformation (rotation and scaling). All parameters are floating point values 1860 | 1861 | :param angle: rotation angle in degrees 1862 | :param x: horizontal displacement in screen space where the rotation center is located 1863 | :param y: vertical displacement in screen space where the rotation center is located 1864 | :param sx: horizontal scaling factor 1865 | :param sy: vertical scaling factor 1866 | """ 1867 | ok = _tln.TLN_SetLayerTransform(self, angle, x, y, sx, sy) 1868 | _raise_exception(ok) 1869 | 1870 | def set_pixel_mapping(self, pixel_map: POINTER(PixelMap)): 1871 | """ 1872 | Enables pixel mapping displacement table 1873 | 1874 | :param pixel_map: user-provided list of PixelMap objects of hres*vres size: one item per screen pixel 1875 | """ 1876 | ok = _tln.TLN_SetLayerPixelMapping(self, pixel_map) 1877 | _raise_exception(ok) 1878 | 1879 | def reset_mode(self): 1880 | """ 1881 | Disables all special effects: scaling, affine transform and pixel mapping, and returns to default render mode. 1882 | """ 1883 | ok = _tln.TLN_ResetLayerMode(self) 1884 | _raise_exception(ok) 1885 | 1886 | def set_blend_mode(self, mode: int): 1887 | """ 1888 | Enables blending mode with background objects 1889 | 1890 | :param mode: One of the :class:`Blend` defined values 1891 | """ 1892 | ok = _tln.TLN_SetLayerBlendMode(self, mode, 0) 1893 | _raise_exception(ok) 1894 | 1895 | def set_column_offset(self, offsets: Optional[POINTER(c_int)]=None): 1896 | """ 1897 | Enables column offset mode for tiles 1898 | 1899 | :param offsets: User-provided tuple of integers with offsets, one per column of tiles. Pass None to disable 1900 | """ 1901 | ok = _tln.TLN_SetLayerColumnOffset(self, offsets) 1902 | _raise_exception(ok) 1903 | 1904 | def set_clip(self, x1: int, y1: int, x2: int, y2: int): 1905 | """ 1906 | Enables clipping rectangle 1907 | 1908 | :param x1: left coordinate 1909 | :param y1: top coordinate 1910 | :param x2: right coordinate 1911 | :param y2: bottom coordinate 1912 | """ 1913 | ok = _tln.TLN_SetLayerClip(self, x1, y1, x2, y2) 1914 | _raise_exception(ok) 1915 | 1916 | def disable_clip(self): 1917 | """ 1918 | Disables clipping rectangle 1919 | """ 1920 | ok = _tln.TLN_DisableLayerClip(self) 1921 | _raise_exception(ok) 1922 | 1923 | def set_mosaic(self, pixel_w: int, pixel_h: int): 1924 | """ 1925 | Enables mosaic effect (pixelation) 1926 | 1927 | :param pixel_w: horizontal pixel size 1928 | :param pixel_h: vertical pixel size 1929 | """ 1930 | ok = _tln.TLN_SetLayerMosaic(self, pixel_w, pixel_h) 1931 | _raise_exception(ok) 1932 | 1933 | def disable_mosaic(self): 1934 | """ 1935 | Disables mosaic effect 1936 | """ 1937 | ok = _tln.TLN_DisableLayerMosaic(self) 1938 | _raise_exception(ok) 1939 | 1940 | def disable(self): 1941 | """ 1942 | Disables the layer so it is not drawn 1943 | """ 1944 | ok = _tln.TLN_DisableLayer(self) 1945 | _raise_exception(ok) 1946 | 1947 | def enable(self): 1948 | """ 1949 | Re-enables previously disabled layer 1950 | """ 1951 | ok = _tln.TLN_EnableLayer(self) 1952 | _raise_exception(ok) 1953 | 1954 | def get_palette(self) -> Palette: 1955 | """ 1956 | Gets the layer active palette 1957 | 1958 | :return: Palette object of the Layer 1959 | """ 1960 | handle = _tln.TLN_GetLayerPalette(self) 1961 | if handle is not None: 1962 | return Palette(handle) 1963 | else: 1964 | _raise_exception() 1965 | 1966 | def get_tile(self, x: int, y: int, tile_info: POINTER(TileInfo)): 1967 | """ 1968 | Gets detailed info about the tile located in Tilemap space 1969 | 1970 | :param x: x position inside the Tilemap 1971 | :param y: y position inside the Tilemap 1972 | :param tile_info: User-provided TileInfo object where to get the data 1973 | """ 1974 | ok = _tln.TLN_GetLayerTile(self, x, y, tile_info) 1975 | _raise_exception(ok) 1976 | 1977 | def set_priority(self, enable: bool): 1978 | """ 1979 | Sets full layer priority, appearing in front of regular sprites 1980 | 1981 | :param enable: True for enable, False for disable 1982 | """ 1983 | ok = _tln.TLN_SetLayerPriority(self, enable) 1984 | _raise_exception(ok) 1985 | 1986 | def set_parallax_factor(self, x: int, y: int): 1987 | """ 1988 | Sets layer parallax factor to use in conjunction with Engine.set_world_position() 1989 | 1990 | :param x: Horizontal parallax factor 1991 | :param y: Vertical parallax factor 1992 | """ 1993 | ok = _tln.TLN_SetLayerParallaxFactor(self, x, y) 1994 | _raise_exception(ok) 1995 | 1996 | 1997 | # sprite management ----------------------------------------------------------- 1998 | _tln.TLN_ConfigSprite.argtypes = [c_int, c_void_p, c_ushort] 1999 | _tln.TLN_ConfigSprite.restype = c_bool 2000 | _tln.TLN_SetSpriteSet.argtypes = [c_int, c_void_p] 2001 | _tln.TLN_SetSpriteSet.restype = c_bool 2002 | _tln.TLN_SetSpriteFlags.argtypes = [c_int, c_ushort] 2003 | _tln.TLN_SetSpriteFlags.restype = c_bool 2004 | _tln.TLN_EnableSpriteFlag.argtypes = [c_int, c_uint, c_bool] 2005 | _tln.TLN_EnableSpriteFlag.restype = c_bool 2006 | _tln.TLN_SetSpritePivot.argtypes = [c_int, c_float, c_float] 2007 | _tln.TLN_SetSpritePivot.restype = c_bool 2008 | _tln.TLN_SetSpritePosition.argtypes = [c_int, c_int, c_int] 2009 | _tln.TLN_SetSpritePosition.restype = c_bool 2010 | _tln.TLN_SetSpritePicture.argtypes = [c_int, c_int] 2011 | _tln.TLN_SetSpritePicture.restype = c_bool 2012 | _tln.TLN_SetSpritePalette.argtypes = [c_int, c_void_p] 2013 | _tln.TLN_SetSpritePalette.restype = c_bool 2014 | _tln.TLN_SetSpriteBlendMode.argtypes = [c_int, c_int, c_ubyte] 2015 | _tln.TLN_SetSpriteBlendMode.restype = c_bool 2016 | _tln.TLN_SetSpriteScaling.argtypes = [c_int, c_float, c_float] 2017 | _tln.TLN_SetSpriteScaling.restype = c_bool 2018 | _tln.TLN_ResetSpriteScaling.argtypes = [c_int] 2019 | _tln.TLN_ResetSpriteScaling.restype = c_bool 2020 | _tln.TLN_GetSpritePicture.argtypes = [c_int] 2021 | _tln.TLN_GetSpritePicture.restype = c_int 2022 | _tln.TLN_GetAvailableSprite.restype = c_int 2023 | _tln.TLN_EnableSpriteCollision.argtypes = [c_int, c_bool] 2024 | _tln.TLN_EnableSpriteCollision.restype = c_bool 2025 | _tln.TLN_GetSpriteCollision.argtypes = [c_int] 2026 | _tln.TLN_GetSpriteCollision.restype = c_bool 2027 | _tln.TLN_DisableSprite.argtypes = [c_int] 2028 | _tln.TLN_DisableSprite.restype = c_bool 2029 | _tln.TLN_GetSpritePalette.argtypes = [c_int] 2030 | _tln.TLN_GetSpritePalette.restype = c_void_p 2031 | _tln.TLN_SetSpriteAnimation.argtypes = [c_int, c_void_p, c_int] 2032 | _tln.TLN_SetSpriteAnimation.restype = c_bool 2033 | _tln.TLN_PauseSpriteAnimation.argtypes = [c_int] 2034 | _tln.TLN_PauseSpriteAnimation.restype = c_bool 2035 | _tln.TLN_ResumeSpriteAnimation.argtypes = [c_int] 2036 | _tln.TLN_ResumeSpriteAnimation.restype = c_bool 2037 | _tln.TLN_GetAnimationState.argtypes = [c_int] 2038 | _tln.TLN_GetAnimationState.restype = c_bool 2039 | _tln.TLN_DisableSpriteAnimation.argtypes = [c_int] 2040 | _tln.TLN_DisableSpriteAnimation.restype = c_bool 2041 | _tln.TLN_GetSpriteState.argtypes = [c_int, POINTER(SpriteState)] 2042 | _tln.TLN_GetSpriteState.restype = c_bool 2043 | _tln.TLN_SetFirstSprite.argtypes = [c_int] 2044 | _tln.TLN_SetFirstSprite.restype = c_bool 2045 | _tln.TLN_SetNextSprite.argtypes = [c_int, c_int] 2046 | _tln.TLN_SetNextSprite.restype = c_bool 2047 | _tln.TLN_EnableSpriteMasking.argtypes = [c_int, c_bool] 2048 | _tln.TLN_EnableSpriteMasking.restype = c_bool 2049 | 2050 | class Sprite(object): 2051 | """ 2052 | The Sprite object manages each moving character onscreen 2053 | 2054 | :ivar index: sprite index, from 0 to num_sprites - 1 2055 | :ivar spriteset: assigned Spriteset object 2056 | """ 2057 | def __init__(self, index: int): 2058 | self.index = index 2059 | self._as_parameter_ = index 2060 | self.spriteset = None 2061 | 2062 | def setup(self, spriteset: Spriteset, flags: int=0): 2063 | """ 2064 | Enables a sprite by setting its Spriteset and optional flags 2065 | 2066 | :param spriteset: Spriteset object with the graphic data of the sprites 2067 | :param flags: Optional combination of defined :class:`Flag` values, 0 by default 2068 | """ 2069 | ok = _tln.TLN_ConfigSprite(self, spriteset, flags) 2070 | self.spriteset = spriteset 2071 | _raise_exception(ok) 2072 | 2073 | def set_spriteset(self, spriteset: Spriteset): 2074 | """ 2075 | Enables a sprite by setting its Spriteset 2076 | 2077 | :param spriteset: Spriteset object with the graphic data of the sprites 2078 | """ 2079 | ok = _tln.TLN_SetSpriteSet(self, spriteset) 2080 | self.spriteset = spriteset 2081 | _raise_exception(ok) 2082 | 2083 | def set_flags(self, flags: int=0): 2084 | """ 2085 | Sets modification flags 2086 | 2087 | :param flags: Combination of defined :class:`Flag` values 2088 | """ 2089 | ok = _tln.TLN_SetSpriteFlags(self, flags) 2090 | _raise_exception(ok) 2091 | 2092 | def enable_flag(self, flag: int, value: bool=True): 2093 | """ 2094 | Enables (or disables) specified flag for a sprite 2095 | 2096 | :param flag: Combination of defined :class:`Flag` values 2097 | :param value: True to enable (default) or False to disable 2098 | """ 2099 | ok = _tln.TLN_EnableSpriteFlag(self, flag, value) 2100 | _raise_exception(ok) 2101 | 2102 | def set_pivot(self, u: float, v: float): 2103 | """ 2104 | Sets sprite pivot point for placement and scaling source. By default is at (0,0) = top left corner. Uses normalized coordinates in [ 0.0 - 1.0] range. 2105 | 2106 | :param u: Horizontal position (0.0 = full left, 1.0 = full right) 2107 | :param v: Vertical position (0.0 = top, 1.0 = bottom) 2108 | """ 2109 | ok = _tln.TLN_SetSpritePivot(self, u, v) 2110 | _raise_exception(ok) 2111 | 2112 | def set_position(self, x: int, y: int): 2113 | """ 2114 | Sets the sprite position in screen coordinates 2115 | 2116 | :param x: Horizontal position 2117 | :param y: Vertical position 2118 | """ 2119 | ok = _tln.TLN_SetSpritePosition(self, x, y) 2120 | _raise_exception(ok) 2121 | 2122 | def set_world_position(self, x: int, y: int): 2123 | """ 2124 | Sets the sprite position in world space coordinates 2125 | 2126 | :param x: Horizontal world position of pivot (0 = left margin) 2127 | :param y: Vertical world position of pivot (0 = top margin) 2128 | """ 2129 | ok = _tln.TLN_SetSpriteWorldPosition(self, x, y) 2130 | _raise_exception(ok) 2131 | 2132 | def set_picture(self, picture: Union[int,str]): 2133 | """ 2134 | Sets the actual graphic contained in the Spriteset to the sprite 2135 | 2136 | :param picture: can be an integer with the index inside the Spriteset, or a string with its name 2137 | """ 2138 | ok = False 2139 | param_type = type(picture) 2140 | if param_type is int: 2141 | ok = _tln.TLN_SetSpritePicture(self, picture) 2142 | elif param_type is str: 2143 | entry = _tln.TLN_FindSpritesetSprite(self.spriteset, picture) 2144 | if entry != -1: 2145 | ok = _tln.TLN_SetSpritePicture(self, entry) 2146 | else: 2147 | return 2148 | else: 2149 | return 2150 | _raise_exception(ok) 2151 | 2152 | def set_palette(self, palette: Palette): 2153 | """ 2154 | Assigns a Palette object to the sprite. By default it is assigned wit the Spriteset's own palette 2155 | 2156 | :param palette: Palette object to set 2157 | """ 2158 | ok = _tln.TLN_SetSpritePalette(self, palette) 2159 | _raise_exception(ok) 2160 | 2161 | def set_blend_mode(self, mode: int): 2162 | """ 2163 | Enables blending mode with background objects 2164 | 2165 | :param mode: One of the :class:`Blend` defined values 2166 | """ 2167 | ok = _tln.TLN_SetSpriteBlendMode(self, mode, 0) 2168 | _raise_exception(ok) 2169 | 2170 | def set_scaling(self, sx: float, sy: float): 2171 | """ 2172 | Enables sprite scaling 2173 | 2174 | :param sx: floating-point value with horizontal scaling factor 2175 | :param sy: floating-point value with vertical scaling factor 2176 | """ 2177 | ok = _tln.TLN_SetSpriteScaling(self, sx, sy) 2178 | _raise_exception(ok) 2179 | 2180 | def reset_mode(self): 2181 | """ 2182 | Disables scaling and returns to default render mode. 2183 | """ 2184 | ok = _tln.TLN_ResetSpriteScaling(self) 2185 | _raise_exception(ok) 2186 | 2187 | def get_picture(self) -> int: 2188 | """ 2189 | Returns the index of the assigned picture from the Spriteset 2190 | 2191 | :return: the graphic index 2192 | """ 2193 | return _tln.TLN_GetSpritePicture(self) 2194 | 2195 | def enable_collision(self, mode: bool=True): 2196 | """ 2197 | Enables pixel-accurate sprite collision detection with other sprites 2198 | 2199 | :param mode: True for enabling or False for disabling 2200 | """ 2201 | ok = _tln.TLN_EnableSpriteCollision(self, mode) 2202 | _raise_exception(ok) 2203 | 2204 | def check_collision(self) -> bool: 2205 | """ 2206 | Gets the collision status of the sprite. Requires :meth:`Sprite.enable_collision` set to True 2207 | 2208 | :return: True if collision with another sprite detected, or False if not 2209 | """ 2210 | return _tln.TLN_GetSpriteCollision(self) 2211 | 2212 | def disable(self): 2213 | """ 2214 | Disables the sprite so it is not drawn 2215 | """ 2216 | ok = _tln.TLN_DisableSprite(self) 2217 | _raise_exception(ok) 2218 | 2219 | def get_palette(self) -> Palette: 2220 | """ 2221 | Gets the sprite active palette 2222 | 2223 | :return: Palette object of the Sprite 2224 | """ 2225 | handle = _tln.TLN_GetSpritePalette(self) 2226 | if handle is not None: 2227 | return Palette(handle) 2228 | else: 2229 | _raise_exception() 2230 | 2231 | def set_animation(self, sequence: Sequence, loop: int=0): 2232 | """ 2233 | Starts a Sprite animation 2234 | 2235 | :param sequence: Sequence object to play on the sprite 2236 | :param loop: number of times to repeat, 0=infinite 2237 | """ 2238 | ok = _tln.TLN_SetSpriteAnimation(self, sequence, loop) 2239 | _raise_exception(ok) 2240 | 2241 | def get_animation_state(self) -> bool: 2242 | """ 2243 | Gets the state of the animation 2244 | 2245 | :return: True if still running, False if it has finished 2246 | """ 2247 | return _tln.TLN_GetAnimationState(self) 2248 | 2249 | def pause_animation(self): 2250 | """ 2251 | Paused sprite animation 2252 | """ 2253 | ok = _tln.TLN_PauseSpriteAnimation(self) 2254 | _raise_exception(ok) 2255 | 2256 | def resume_animation(self): 2257 | """ 2258 | Resumes paused animation 2259 | """ 2260 | ok = _tln.TLN_ResumeSpriteAnimation(self) 2261 | _raise_exception(ok) 2262 | 2263 | def disable_animation(self): 2264 | """ 2265 | Disables the animation so it doesn't run 2266 | """ 2267 | ok = _tln.TLN_DisableSpriteAnimation(self) 2268 | _raise_exception(ok) 2269 | 2270 | def get_state(self, state: POINTER(SpriteState)): 2271 | """ 2272 | Returns runtime info about the sprite 2273 | 2274 | :param state: user-allocated SpriteState structure 2275 | """ 2276 | ok = _tln.TLN_GetSpriteState(self, state) 2277 | _raise_exception(ok) 2278 | 2279 | def set_first(self): 2280 | """ 2281 | Sets this to be the first sprite drawn (beginning of list) 2282 | """ 2283 | ok = _tln.TLN_SetFirstSprite(self) 2284 | _raise_exception(ok) 2285 | 2286 | def set_next(self, next: int): 2287 | """ 2288 | Sets the next sprite to after this one, builds list 2289 | 2290 | :param next: sprite to draw after this one 2291 | """ 2292 | ok = _tln.TLN_SetNextSprite(self, next) 2293 | _raise_exception(ok) 2294 | 2295 | def enable_masking(self, enable: bool=True): 2296 | """ 2297 | Enables or disables masking for this sprite, if enabled it won't be drawn inside the region set up with Engine.set_sprite_mask_region() 2298 | """ 2299 | ok = _tln.TLN_EnableSpriteMasking(self, enable) 2300 | _raise_exception(ok) 2301 | 2302 | 2303 | # color cycle animation engine ------------------------------------------------------------ 2304 | _tln.TLN_SetPaletteAnimation.argtypes = [c_int, c_void_p, c_void_p, c_bool] 2305 | _tln.TLN_SetPaletteAnimation.restype = c_bool 2306 | _tln.TLN_SetPaletteAnimationSource.argtypes = [c_int, c_void_p] 2307 | _tln.TLN_SetPaletteAnimationSource = c_bool 2308 | _tln.TLN_GetAvailableAnimation.restype = c_int 2309 | _tln.TLN_DisablePaletteAnimation.argtypes = [c_int] 2310 | _tln.TLN_DisablePaletteAnimation.restype = c_bool 2311 | 2312 | 2313 | class Animation(object): 2314 | """ 2315 | The Animation object manages color-cycle (palette) animations 2316 | 2317 | :ivar index: animation index, from 0 to num_animations - 1 2318 | """ 2319 | def __init__(self, index: int): 2320 | self.index = index 2321 | self._as_parameter_ = index 2322 | 2323 | def set_palette_animation(self, palette: Palette, sequence: Sequence, blend: bool=True): 2324 | """ 2325 | Starts a color cycle animation for Palette object 2326 | 2327 | :param palette: Palette object to animate 2328 | :param sequence: Sequence object to play on the palette 2329 | :param blend: True for smooth frame interpolation, False for classic coarse mode 2330 | """ 2331 | ok = _tln.TLN_SetPaletteAnimation(self, palette, sequence, blend) 2332 | _raise_exception(ok) 2333 | 2334 | def set_palette_animation_source(self, palette: Palette): 2335 | """ 2336 | Sets the source palette of a color cycle animation already in motion. 2337 | Useful for combining color cycling and palette interpolation at the same time 2338 | 2339 | :param palette: Palette object to assign 2340 | """ 2341 | ok = _tln.TLN_SetPaletteAnimationSource(self, palette) 2342 | _raise_exception(ok) 2343 | 2344 | def disable(self): 2345 | """ 2346 | Disables the animation so it doesn't run 2347 | """ 2348 | ok = _tln.TLN_DisablePaletteAnimation(self) 2349 | _raise_exception(ok) 2350 | -------------------------------------------------------------------------------- /src/world.py: -------------------------------------------------------------------------------- 1 | """ world/play field game entity """ 2 | 3 | import xml.etree.ElementTree as ET 4 | from tilengine import Tilemap, Spriteset, Sequence 5 | from effect import Effect 6 | from score import Score 7 | from eagle import Eagle 8 | from opossum import Opossum 9 | import game 10 | 11 | class Tiles: 12 | """ types of tiles for sprite-terrain collision detection """ 13 | Empty, Floor, Gem, Wall, SlopeUp, SlopeDown, InnerSlopeUp, InnerSlopeDown = range(8) 14 | 15 | 16 | class Medium: 17 | """ types of environments """ 18 | Floor, Air, Ladder, Water = range(4) 19 | 20 | 21 | class Item(object): 22 | """ Generic item declared in tilemap object layer awaiting to spawn """ 23 | Opossum, Eagle, Frog = range(3) 24 | 25 | def __init__(self, item_type, x, y): 26 | self.type = item_type 27 | self.x = x 28 | self.y = y 29 | self.alive = False 30 | 31 | def try_spawn(self, x): 32 | """ Tries to spawn an active game object depending on screen position and item type """ 33 | if self.alive is False and x < self.x < x + game.WIDTH: 34 | self.alive = True 35 | if self.type is Item.Eagle: 36 | Eagle(self, self.x, self.y - Eagle.size[1]) 37 | elif self.type is Item.Opossum: 38 | Opossum(self, self.x, self.y - Opossum.size[1]) 39 | 40 | 41 | def load_objects(file_name, layer_name, first_gid): 42 | """ loads tiles in object layer from a tmx file. 43 | Returns list of Item objects """ 44 | tree = ET.parse(file_name) 45 | root = tree.getroot() 46 | for child in root.findall("objectgroup"): 47 | name = child.get("name") 48 | if name == layer_name: 49 | item_list = list() 50 | for item in child.findall("object"): 51 | gid = item.get("gid") 52 | if gid is not None: 53 | x = item.get("x") 54 | y = item.get("y") 55 | item_list.append( 56 | Item(int(gid) - first_gid, int(x), int(y))) 57 | return item_list 58 | return None 59 | 60 | class World(object): 61 | """ world/play field entity """ 62 | 63 | def __init__(self): 64 | self.foreground = game.engine.layers[1] 65 | self.background = game.engine.layers[2] 66 | self.clouds = 0.0 67 | self.foreground.setup(Tilemap.fromfile("layer_foreground.tmx")) 68 | self.background.setup(Tilemap.fromfile("layer_background.tmx")) 69 | self.spriteset_vanish = Spriteset.fromfile("effect_vanish") 70 | self.seq_vanish = Sequence.create_sprite_sequence(self.spriteset_vanish, "vanish", 4) 71 | self.x = 0 72 | self.x_max = self.foreground.width - game.WIDTH 73 | self.objects = load_objects(game.ASSETS_PATH + 74 | "/layer_foreground.tmx", "Capa de Objetos 1", 973) 75 | game.engine.set_background_color(self.background.tilemap) 76 | game.actors.append(self) 77 | 78 | def start(self): 79 | self.time = 30 80 | self.add_timer(0) 81 | game.ui.update_time(self.time) 82 | 83 | def pick_gem(self, tiles_list): 84 | """ updates tilemap when player picks a gem """ 85 | for tile_info in tiles_list: 86 | if tile_info.type is Tiles.Gem: 87 | self.foreground.tilemap.set_tile( 88 | tile_info.row, tile_info.col, None) 89 | Effect(tile_info.col*16, tile_info.row*16, 90 | self.spriteset_vanish, self.seq_vanish) 91 | game.sounds.play("pickup", 1) 92 | self.add_timer(1) 93 | Score(1, tile_info.col*16, tile_info.row*16) 94 | break 95 | 96 | def add_timer(self, amount): 97 | """ increases/decreases timer timer """ 98 | self.due_time = game.window.get_ticks() + 1000 99 | if amount >= 0: 100 | self.time += amount 101 | else: 102 | amount = -amount 103 | if self.time >= amount: 104 | self.time -= amount 105 | else: 106 | self.time = 0 107 | game.ui.update_time(self.time) 108 | 109 | def update(self): 110 | """ updates world state once per frame """ 111 | oldx = self.x 112 | 113 | if game.player.x < 240: 114 | self.x = 0 115 | else: 116 | self.x = int(game.player.x - 240) 117 | if self.x > self.x_max: 118 | self.x = self.x_max 119 | self.clouds += 0.1 120 | 121 | if self.x is not oldx: 122 | self.foreground.set_position(self.x, 0) 123 | self.background.set_position(self.x/8, 0) 124 | 125 | # spawn new entities from object list 126 | for item in self.objects: 127 | item.try_spawn(self.x) 128 | 129 | now = game.window.get_ticks() 130 | if now > self.due_time: 131 | self.add_timer(-1) 132 | 133 | return True 134 | --------------------------------------------------------------------------------