Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the library and exercised headless by ctest. The chapters quote them with `c excerpt=examples/...` blocks rather than restating the code, so a chapter cannot drift from a program that compiles -- the excerpt check fails the moment the source moves. 34 excerpts in one chapter, 21 in the other. The two are complementary. The sidescroller is the physics tutorial: gravity, a jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map, NPCs spawned from map objects, four-way per-facing animation, a text box, a follower. Both smoke tests drive real SDL_Events through akgl_controller_handle_event and step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run is deterministic and finishes in under five seconds. Writing them is what turned up most of the defects recorded in the next commit, because a game exercises paths a unit test does not. Each workaround says in the chapter which library gap forced it: - collision is written in a custom movementlogicfunc, because akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never calls collide at all; - the sidescroller cancels the step's own gravity when it blocks downward, because otherwise a quarter-pixel of penetration makes the *horizontal* sweep report blocked and the character walks backwards a tile at a time; - both clear movement_controls_face on every map-spawned actor, because the default facefunc leaves a stopped actor with no facing bit, no sprite, and no draw; - the JRPG's follower gets a renderfunc that nulls obj->parent for the duration of the draw, because a child's offset is counted twice. Assets are CC0 from three Kenney packs, vendored with per-pack licence text, per-file provenance, and the geometry contract in docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a reader who copies a tutorial into their own game inherits no obligation. scripts/fetch_tutorial_assets.sh refreshes them in the shape mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status, refuses a pack page that does not say CC0, verifies the archive and the staged dimensions, and leaves the tracked bytes untouched on any failure. Both failure paths were tested, and a no-op refresh is byte-identical. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
121
docs/tutorials/assets/LICENSE
Normal file
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
21
docs/tutorials/assets/LICENSE.kenney_music-jingles.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
Music Jingles
|
||||
|
||||
by Kenney Vleugels (Kenney.nl)
|
||||
|
||||
------------------------------
|
||||
|
||||
License (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
You may use these assets in personal and commercial projects.
|
||||
Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
Request: http://request.kenney.nl
|
||||
|
||||
Follow on Twitter for updates:
|
||||
@KenneyNL
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
Pixel Line Platformer (1.0)
|
||||
|
||||
Created/distributed by Kenney (www.kenney.nl)
|
||||
Creation date: 15-08-2021
|
||||
|
||||
------------------------------
|
||||
|
||||
License: (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
This content is free to use in personal, educational and commercial projects.
|
||||
|
||||
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
Patreon: http://patreon.com/kenney/
|
||||
|
||||
Follow on Twitter for updates:
|
||||
23
docs/tutorials/assets/LICENSE.kenney_rpg-urban-pack.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
RPG Urban Pack 1.0
|
||||
|
||||
Created/distributed by Kenney (www.kenney.nl)
|
||||
Creation date: 05-01-2019
|
||||
|
||||
------------------------------
|
||||
|
||||
License: (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
This content is free to use in personal, educational and commercial projects.
|
||||
|
||||
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
Request: http://request.kenney.nl
|
||||
Patreon: http://patreon.com/kenney/
|
||||
|
||||
Follow on Twitter for updates:
|
||||
151
docs/tutorials/assets/PROVENANCE.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Provenance of the tutorial assets
|
||||
|
||||
Everything under `docs/tutorials/assets/` is **CC0 1.0** or was written for this
|
||||
repository. Nothing here carries an attribution requirement, a share-alike
|
||||
clause, or a field-of-use restriction, which is the whole point: a reader who
|
||||
copies a tutorial into their own game inherits whatever obligation these assets
|
||||
carry, and CC0 carries none. "Free to download" is not the same thing and was
|
||||
not accepted -- each pack's licence was read on its Kenney page *and* in the
|
||||
`License.txt` it ships, and `scripts/fetch_tutorial_assets.sh` re-checks both on
|
||||
every refresh and refuses to vendor a pack that fails either.
|
||||
|
||||
`LICENSE` is the CC0 1.0 legal code. The three `LICENSE.kenney_*.txt` files are
|
||||
the licence statements the packs themselves ship, kept beside the art in the
|
||||
same way `tests/assets/akgl_test_mono.LICENSE.txt` sits beside its font.
|
||||
|
||||
The two tables below account for all 56 asset files: 14 carry upstream bytes and
|
||||
42 were written here. This file and `README.md` are the remaining two, and are
|
||||
documentation of the same kind as the rest of `docs/`.
|
||||
|
||||
## Upstream packs
|
||||
|
||||
| Pack | URL | Licence | Licence file in this tree |
|
||||
|-----------------------|------------------------------------------------|---------|--------------------------------------------|
|
||||
| Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | `LICENSE.kenney_pixel-line-platformer.txt` |
|
||||
| RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | `LICENSE.kenney_rpg-urban-pack.txt` |
|
||||
| Music Jingles | https://kenney.nl/assets/music-jingles | CC0 1.0 | `LICENSE.kenney_music-jingles.txt` |
|
||||
|
||||
Kenney asks for credit and does not require it. Credit is given here and in the
|
||||
tutorial chapters.
|
||||
|
||||
## Files carrying upstream bytes
|
||||
|
||||
| File | Pack | Source URL | Licence | Cropped / repacked into |
|
||||
|--------------------------------------------|-----------------------|-----------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------|
|
||||
| `LICENSE` | creativecommons.org | https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt | CC0 1.0 | The CC0 1.0 Universal legal code, fetched verbatim |
|
||||
| `LICENSE.kenney_pixel-line-platformer.txt` | Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | The pack's own `License.txt`, copied verbatim |
|
||||
| `LICENSE.kenney_rpg-urban-pack.txt` | RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | The pack's own `License.txt`, copied verbatim |
|
||||
| `LICENSE.kenney_music-jingles.txt` | Music Jingles | https://kenney.nl/assets/music-jingles | CC0 1.0 | The pack's own `License.txt`, copied verbatim |
|
||||
| `sidescroller/tiles.png` | Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | `Tilemap/tilemap_packed.png` copied verbatim: 160x96, 10x6 tiles of 16x16, no spacing, no margin |
|
||||
| `sidescroller/player.png` | Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | Tiles 40, 41, 42 and their horizontal mirrors, bottom-centred in six 32x32 cells |
|
||||
| `sidescroller/coin.png` | Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | Tile 44, bottom-centred in one 32x32 cell |
|
||||
| `sidescroller/hazard.png` | Pixel Line Platformer | https://kenney.nl/assets/pixel-line-platformer | CC0 1.0 | Tiles 55, 56, 51, 52, bottom-centred in four 32x32 cells |
|
||||
| `sidescroller/jingle_start.ogg` | Music Jingles | https://kenney.nl/assets/music-jingles | CC0 1.0 | `Audio/8-Bit jingles/jingles_NES00.ogg`, copied verbatim (1.8 s) |
|
||||
| `jrpg/tiles.png` | RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | `Tilemap/tilemap_packed.png` copied verbatim: 432x288, 27x18 tiles of 16x16, no spacing, no margin |
|
||||
| `jrpg/player.png` | RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | Character block 0 (tileset columns 23-26, rows 0-2) regrouped into twelve 32x32 cells |
|
||||
| `jrpg/npc_shopkeeper.png` | RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | Character block 3 (tileset columns 23-26, rows 9-11) regrouped into twelve 32x32 cells |
|
||||
| `jrpg/npc_elder.png` | RPG Urban Pack | https://kenney.nl/assets/rpg-urban-pack | CC0 1.0 | Character block 2 (tileset columns 23-26, rows 6-8) regrouped into twelve 32x32 cells |
|
||||
| `jrpg/jingle_start.ogg` | Music Jingles | https://kenney.nl/assets/music-jingles | CC0 1.0 | `Audio/Pizzicato jingles/jingles_PIZZI07.ogg`, copied verbatim (1.3 s) |
|
||||
|
||||
## Files written for this repository
|
||||
|
||||
These contain no upstream content. They are libakgl source in JSON form: they
|
||||
name the art above, they do not embed it.
|
||||
|
||||
| File | Kind | What it is |
|
||||
|-------------------------------------------------|-----------|---------------------------------------------------------------------------------|
|
||||
| `jrpg/character_jrpg_elder.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `jrpg/character_jrpg_player.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `jrpg/character_jrpg_shopkeeper.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `jrpg/sprite_jrpg_elder_idle_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_idle_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_idle_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_idle_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_walk_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_walk_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_walk_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_elder_walk_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_idle_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_idle_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_idle_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_idle_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_walk_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_walk_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_walk_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_player_walk_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_idle_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_idle_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_idle_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_idle_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_walk_down.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_walk_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_walk_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/sprite_jrpg_shopkeeper_walk_up.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `jrpg/town.tmj` | map | Tiled 1.8 TMJ, tileset **embedded**. 30x20 cells, ground + decoration + actors |
|
||||
| `sidescroller/character_ss_coin.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `sidescroller/character_ss_hazard_blob.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `sidescroller/character_ss_hazard_moth.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `sidescroller/character_ss_player.json` | character | State-name to sprite-name bindings; contains no upstream content |
|
||||
| `sidescroller/level1.tmj` | map | Tiled 1.8 TMJ, tileset **embedded**. 40x15 cells, background + terrain + actors |
|
||||
| `sidescroller/sprite_ss_coin.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_hazard_blob.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_hazard_moth.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_idle_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_idle_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_jump_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_jump_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_run_left.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
| `sidescroller/sprite_ss_player_run_right.json` | sprite | Frame list into the sheet named by its own `spritesheet.filename` |
|
||||
|
||||
## How the repacking works, and why
|
||||
|
||||
**Tilesets are copied verbatim.** Both packs already ship a 16x16 grid with zero
|
||||
spacing and zero margin, which is the only geometry
|
||||
`akgl_tilemap_compute_tileset_offsets` gets right -- it adds `spacing` to the
|
||||
tile pitch but sets the *first* row's y offset to `spacing` rather than zero,
|
||||
and it ignores `margin` completely. A packed sheet with a 1 px gutter, which is
|
||||
what most of Kenney's older packs ship, would render every tile in row 0 one
|
||||
pixel low. That ruled several otherwise-suitable packs out.
|
||||
|
||||
**Character sheets are repacked into one row of 32x32 cells.** The asset
|
||||
contract for these tutorials is 16x16 tiles and 32x32 character frames, and
|
||||
neither pack ships 32x32 art, so each 16x16 source tile is composited at (8, 16)
|
||||
inside its cell -- horizontally centred, sitting on the cell's bottom edge. The
|
||||
art is not resampled: a 2x upscale would put 2x2 pixel blocks next to 1x1 tiles,
|
||||
and every sprite in the repository would stop matching the source pixel for
|
||||
pixel. The cost is that three quarters of each cell is empty, and the benefit is
|
||||
one rule the tutorials can rely on: **an actor drawn into a 32x32 destination
|
||||
rectangle has its feet on the bottom edge of that rectangle and its body
|
||||
centred**, whichever sheet it came from.
|
||||
|
||||
**Left-facing frames are stored, not mirrored at draw time.**
|
||||
`akgl_actor_render` calls `draw_texture` with `SDL_FLIP_NONE` hard-coded
|
||||
(`src/actor.c`), so there is no way to ask for a mirrored blit. The sidescroller
|
||||
sheet therefore carries frames 0-2 facing right and frames 3-5 as their
|
||||
horizontal mirrors. The JRPG sheets need no mirroring: the RPG Urban Pack draws
|
||||
both side views.
|
||||
|
||||
**One frame is used twice.** Pixel Line Platformer has no dedicated jump pose.
|
||||
Frame 1 is the passing position of the run cycle, which is the airborne one, and
|
||||
`ss_player_jump_right` reuses it. Said here rather than left for a reader to
|
||||
notice.
|
||||
|
||||
**The coin does not animate.** Pixel Line Platformer ships one gold pickup tile
|
||||
and no rotation frames, so `sprite_ss_coin.json` is a single frame. Nothing was
|
||||
invented to pad it out.
|
||||
|
||||
## Reproducing this
|
||||
|
||||
```sh
|
||||
scripts/fetch_tutorial_assets.sh
|
||||
```
|
||||
|
||||
Fetches all three packs, checks each page and each shipped `License.txt` says
|
||||
CC0, verifies the source images are the size the frame arithmetic assumes,
|
||||
repacks into a temporary directory, checks every staged file's dimensions, and
|
||||
only then moves anything into place. A failure at any step leaves the tracked
|
||||
copies untouched and exits non-zero. The output is byte-for-byte reproducible --
|
||||
ImageMagick's wall-clock `date:create` and `date:modify` chunks are stripped, so
|
||||
a refresh that changes nothing produces no diff.
|
||||
|
||||
The script does not touch the JSON or the TMJ maps. Those are hand-maintained.
|
||||
162
docs/tutorials/assets/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# The tutorial asset contract
|
||||
|
||||
What `examples/sidescroller/` and `examples/jrpg/` are drawing, and the exact
|
||||
numbers they have to use. Licensing is in `PROVENANCE.md`; refreshing the art
|
||||
from upstream is `scripts/fetch_tutorial_assets.sh`.
|
||||
|
||||
Every claim below was checked against `src/`, not against header prose, and the
|
||||
whole set was loaded through the real library -- `akgl_sprite_load_json`,
|
||||
`akgl_character_load_json`, `akgl_tilemap_load` -- before being committed.
|
||||
|
||||
## Geometry
|
||||
|
||||
| Thing | Value | Why it is that value |
|
||||
|-------------------------|--------|---------------------------------------------------------------------------|
|
||||
| Tile | 16x16 | Both tilesets ship on a 16x16 grid |
|
||||
| Tileset spacing, margin | 0, 0 | `akgl_tilemap_compute_tileset_offsets` gets nothing else right; see below |
|
||||
| Character frame | 32x32 | The fixed contract for these tutorials |
|
||||
| Art inside a frame | 16x16 | Composited at (8, 16): centred, sitting on the cell's bottom edge |
|
||||
| Frames per sheet row | all | Every sheet is a single row, so `coords_for_frame` never has to wrap |
|
||||
| Frames per animation | max 16 | `AKGL_SPRITE_MAX_FRAMES`; a 17th is `AKERR_OUTOFBOUNDS` at load |
|
||||
| Frame id | 0..255 | `uint8_t`; a larger id is `AKERR_OUTOFBOUNDS` at load |
|
||||
|
||||
**The one alignment rule.** `akgl_actor_render` draws into a rectangle of
|
||||
`sprite->width` by `sprite->height` with its top-left at the actor's `x`, `y`.
|
||||
Because every frame's art is bottom-centred in its cell, an actor at `(x, y)`
|
||||
has its feet at `y + 32` and its 16 px body spanning `x + 8` to `x + 23`. Ground
|
||||
contact, pickup tests and hitboxes should use that inner rectangle, not the
|
||||
32x32 cell.
|
||||
|
||||
## Sidescroller sheets
|
||||
|
||||
`sidescroller/player.png` -- 192x32, six frames.
|
||||
|
||||
| Frame | Facing | Pose | Used by |
|
||||
|-------|--------|---------------------------------|-----------------------------------------------|
|
||||
| 0 | right | contact | `ss_player_idle_right`, `ss_player_run_right` |
|
||||
| 1 | right | passing, airborne | `ss_player_run_right`, `ss_player_jump_right` |
|
||||
| 2 | right | opposite contact | `ss_player_run_right` |
|
||||
| 3 | left | contact (mirror of 0) | `ss_player_idle_left`, `ss_player_run_left` |
|
||||
| 4 | left | passing, airborne (mirror of 1) | `ss_player_run_left`, `ss_player_jump_left` |
|
||||
| 5 | left | opposite contact (mirror of 2) | `ss_player_run_left` |
|
||||
|
||||
The left-facing frames exist as their own art because `akgl_actor_render` passes
|
||||
`SDL_FLIP_NONE` to `draw_texture` unconditionally (`src/actor.c`). There is no
|
||||
way to ask the library for a mirrored blit.
|
||||
|
||||
`sidescroller/coin.png` -- 32x32, one frame. The pack ships no rotation frames.
|
||||
|
||||
`sidescroller/hazard.png` -- 128x32, four frames.
|
||||
|
||||
| Frame | What | Used by |
|
||||
|-------|--------------------------|------------------|
|
||||
| 0 | red blob, ground, pose A | `ss_hazard_blob` |
|
||||
| 1 | red blob, ground, pose B | `ss_hazard_blob` |
|
||||
| 2 | moth, flying, wings up | `ss_hazard_moth` |
|
||||
| 3 | moth, flying, wings down | `ss_hazard_moth` |
|
||||
|
||||
## JRPG sheets
|
||||
|
||||
`jrpg/player.png`, `jrpg/npc_shopkeeper.png` and `jrpg/npc_elder.png` are
|
||||
384x32, twelve frames, and share one layout.
|
||||
|
||||
| Frames | Facing | Poses | Idle sprite uses | Walk sprite uses |
|
||||
|--------|--------|-----------------------|------------------|------------------|
|
||||
| 0-2 | down | stand, step A, step B | 0 | 1, 0, 2, 0 |
|
||||
| 3-5 | left | stand, step A, step B | 3 | 4, 3, 5, 3 |
|
||||
| 6-8 | right | stand, step A, step B | 6 | 7, 6, 8, 6 |
|
||||
| 9-11 | up | stand, step A, step B | 9 | 10, 9, 11, 9 |
|
||||
|
||||
The stand frame between the two steps is what makes it read as a walk rather
|
||||
than a shuffle; it is the same three-frame cycle the source art was drawn for.
|
||||
|
||||
## States
|
||||
|
||||
A character's sprite map is keyed by `SDL_itoa(state)` and looked up with
|
||||
`SDL_GetPointerProperty` (`akgl_character_sprite_get`), so **the match is on the
|
||||
whole integer, not on a mask test**. A state the character has no exact entry
|
||||
for makes the actor invisible for that frame -- `actor_visible` treats
|
||||
`AKERR_KEY` as "nothing to draw", which is an answer, not an error.
|
||||
|
||||
These are the values the library's own input handlers produce.
|
||||
`akgl_actor_cmhf_<dir>_on` clears every `FACE_*` and `MOVING_*` bit before
|
||||
setting its own pair, and `_off` clears only its `MOVING_*` bit, so the facing
|
||||
survives the key release and no two `MOVING_*` bits are ever set at once.
|
||||
|
||||
| State | Bits | Sidescroller sprite | JRPG sprite |
|
||||
|-------|-------------------------------------|---------------------|------------------|
|
||||
| 16 | ALIVE | (coin, hazards) | -- |
|
||||
| 17 | ALIVE, FACE_DOWN | `..._idle_right` | `..._idle_down` |
|
||||
| 18 | ALIVE, FACE_LEFT | `..._idle_left` | `..._idle_left` |
|
||||
| 20 | ALIVE, FACE_RIGHT | `..._idle_right` | `..._idle_right` |
|
||||
| 24 | ALIVE, FACE_UP | -- | `..._idle_up` |
|
||||
| 146 | ALIVE, FACE_LEFT, MOVING_LEFT | `..._run_left` | `..._walk_left` |
|
||||
| 276 | ALIVE, FACE_RIGHT, MOVING_RIGHT | `..._run_right` | `..._walk_right` |
|
||||
| 530 | ALIVE, FACE_LEFT, MOVING_UP | `..._jump_left` | -- |
|
||||
| 532 | ALIVE, FACE_RIGHT, MOVING_UP | `..._jump_right` | -- |
|
||||
| 536 | ALIVE, FACE_UP, MOVING_UP | `..._jump_right` | `..._walk_up` |
|
||||
| 658 | ALIVE, FACE_LEFT, MOVING_LEFT, UP | `..._jump_left` | -- |
|
||||
| 788 | ALIVE, FACE_RIGHT, MOVING_RIGHT, UP | `..._jump_right` | -- |
|
||||
| 1041 | ALIVE, FACE_DOWN, MOVING_DOWN | -- | `..._walk_down` |
|
||||
|
||||
The four jump states are there because a sidescroller that routes its jump
|
||||
through `akgl_actor_cmhf_up_on` lands on 536 -- that handler clears `FACE_RIGHT`
|
||||
and sets `FACE_UP`, which in a side view is not a facing at all. A game that
|
||||
sets `ey` directly instead never reaches those states, and nothing breaks
|
||||
either way.
|
||||
|
||||
State names in character JSON are the **prefixed** spellings from
|
||||
`src/actor_state_string_names.c`: `AKGL_ACTOR_STATE_ALIVE`, not
|
||||
`ACTOR_STATE_ALIVE`. `util/assets/littleguy.json` uses the old unprefixed names
|
||||
and `velocity_x`, and does not load against the current library. Do not copy it.
|
||||
|
||||
## Maps
|
||||
|
||||
Both maps are Tiled 1.8 TMJ with the tileset **embedded**.
|
||||
|
||||
`akgl_tilemap_load` reads `columns`, `firstgid`, `tilecount`, `image`,
|
||||
`imagewidth`, `imageheight`, `margin`, `spacing`, `tilewidth`, `tileheight` and
|
||||
`name` straight out of each element of the map's `tilesets` array
|
||||
(`akgl_tilemap_load_tilesets_each`). The string `source` appears nowhere in
|
||||
`src/tilemap.c` and nothing in the library ever opens a `.tsj`, so an external
|
||||
tileset reference fails with `AKERR_KEY` on the missing `columns`. `README.md`
|
||||
in the repository root says the opposite; it is wrong.
|
||||
|
||||
| Map | Cells | Layers | Objects |
|
||||
|---------------------------|-------|-------------------------------------------|---------|
|
||||
| `sidescroller/level1.tmj` | 40x15 | background (tile), terrain (tile), actors | 7 |
|
||||
| `jrpg/town.tmj` | 30x20 | ground (tile), decoration (tile), actors | 3 |
|
||||
|
||||
Global tile ids are `(row * columns) + column + 1`; `0` means an empty cell and
|
||||
is skipped. `sidescroller/tiles.png` has ten columns and 60 tiles;
|
||||
`jrpg/tiles.png` has twenty-seven columns and 486 tiles, the last four columns
|
||||
of which are the character art the sheets above were cut from. Those cells are
|
||||
never referenced by `town.tmj`.
|
||||
|
||||
Other things the loader insists on, all of them checked:
|
||||
|
||||
- **Every object needs a `type` string**, including ones that are not actors. A
|
||||
missing `type` fails the whole load, not just that object.
|
||||
- An actor object needs a non-empty `name`, a `character` **string** property
|
||||
and a `state` **int** property. The string-array form of `state` works in
|
||||
character JSON and is not accepted here.
|
||||
- The `character` named must already be in `AKGL_REGISTRY_CHARACTER`, which
|
||||
means every sprite and character JSON has to be loaded before the map.
|
||||
- A map's `properties` are optional; if present, `physics.model` must name a
|
||||
backend that exists (`null` or `arcade`) or the load fails. Gravity and drag
|
||||
keys are `float`.
|
||||
- Tileset image paths resolve through `akgl_path_relative` against the map's own
|
||||
directory. Image-layer paths do not -- those go through a plain `"%s/%s"`
|
||||
join, so an absolute path there does not work. Keep every path relative.
|
||||
|
||||
## Physics
|
||||
|
||||
The sidescroller map asks for the `arcade` backend with `physics.gravity.y`
|
||||
900.0 and `physics.drag.y` 1.5. There is no terminal velocity in the backend;
|
||||
`ey` approaches `gravity_y / drag_y`, so those two numbers set it to 600 px/s.
|
||||
The JRPG map asks for `arcade` with both gravity components at 0.0.
|
||||
|
||||
`ss_player` has `speed_y` and `acceleration_y` of 0.0 on purpose: a zero top
|
||||
speed on an axis means `akgl_physics_simulate` zeroes that axis's thrust
|
||||
outright, so holding a vertical direction cannot make the player fly. A jump
|
||||
belongs in `ey`.
|
||||
70
docs/tutorials/assets/jrpg/character_jrpg_elder.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "jrpg_elder",
|
||||
"speedtime": 150,
|
||||
"speed_x": 60.0,
|
||||
"speed_y": 60.0,
|
||||
"acceleration_x": 400.0,
|
||||
"acceleration_y": 400.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_elder_idle_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN",
|
||||
"AKGL_ACTOR_STATE_MOVING_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_elder_walk_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_elder_idle_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_elder_walk_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_elder_idle_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_elder_walk_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP"
|
||||
],
|
||||
"sprite": "jrpg_elder_idle_up"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "jrpg_elder_walk_up"
|
||||
}
|
||||
]
|
||||
}
|
||||
70
docs/tutorials/assets/jrpg/character_jrpg_player.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "jrpg_player",
|
||||
"speedtime": 150,
|
||||
"speed_x": 60.0,
|
||||
"speed_y": 60.0,
|
||||
"acceleration_x": 400.0,
|
||||
"acceleration_y": 400.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_player_idle_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN",
|
||||
"AKGL_ACTOR_STATE_MOVING_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_player_walk_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_player_idle_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_player_walk_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_player_idle_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_player_walk_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP"
|
||||
],
|
||||
"sprite": "jrpg_player_idle_up"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "jrpg_player_walk_up"
|
||||
}
|
||||
]
|
||||
}
|
||||
70
docs/tutorials/assets/jrpg/character_jrpg_shopkeeper.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "jrpg_shopkeeper",
|
||||
"speedtime": 150,
|
||||
"speed_x": 60.0,
|
||||
"speed_y": 60.0,
|
||||
"acceleration_x": 400.0,
|
||||
"acceleration_y": 400.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_idle_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN",
|
||||
"AKGL_ACTOR_STATE_MOVING_DOWN"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_walk_down"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_idle_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_walk_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_idle_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_walk_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_idle_up"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "jrpg_shopkeeper_walk_up"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
docs/tutorials/assets/jrpg/jingle_start.ogg
Normal file
BIN
docs/tutorials/assets/jrpg/npc_elder.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
docs/tutorials/assets/jrpg/npc_shopkeeper.png
Normal file
|
After Width: | Height: | Size: 783 B |
BIN
docs/tutorials/assets/jrpg/player.png
Normal file
|
After Width: | Height: | Size: 640 B |
16
docs/tutorials/assets/jrpg/sprite_jrpg_elder_idle_down.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_idle_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_elder_idle_left.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_idle_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
3
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_elder_idle_right.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_idle_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
6
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_elder_idle_up.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_idle_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
9
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_elder_walk_down.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_walk_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_elder_walk_left.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_walk_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
4,
|
||||
3,
|
||||
5,
|
||||
3
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_elder_walk_right.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_walk_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
7,
|
||||
6,
|
||||
8,
|
||||
6
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_elder_walk_up.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_elder.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_elder_walk_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
10,
|
||||
9,
|
||||
11,
|
||||
9
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_player_idle_down.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_idle_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_player_idle_left.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_idle_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_idle_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
6
|
||||
]
|
||||
}
|
||||
16
docs/tutorials/assets/jrpg/sprite_jrpg_player_idle_up.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_idle_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
9
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_player_walk_down.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_walk_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_player_walk_left.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_walk_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
4,
|
||||
3,
|
||||
5,
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_walk_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
7,
|
||||
6,
|
||||
8,
|
||||
6
|
||||
]
|
||||
}
|
||||
19
docs/tutorials/assets/jrpg/sprite_jrpg_player_walk_up.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_player_walk_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
10,
|
||||
9,
|
||||
11,
|
||||
9
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_idle_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_idle_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_idle_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
6
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_idle_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
9
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_walk_down",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_walk_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
4,
|
||||
3,
|
||||
5,
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_walk_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
7,
|
||||
6,
|
||||
8,
|
||||
6
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "npc_shopkeeper.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "jrpg_shopkeeper_walk_up",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 150,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
10,
|
||||
9,
|
||||
11,
|
||||
9
|
||||
]
|
||||
}
|
||||
BIN
docs/tutorials/assets/jrpg/tiles.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
1356
docs/tutorials/assets/jrpg/town.tmj
Normal file
16
docs/tutorials/assets/sidescroller/character_ss_coin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "ss_coin",
|
||||
"speedtime": 200,
|
||||
"speed_x": 0.0,
|
||||
"speed_y": 0.0,
|
||||
"acceleration_x": 0.0,
|
||||
"acceleration_y": 0.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE"
|
||||
],
|
||||
"sprite": "ss_coin"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "ss_hazard_blob",
|
||||
"speedtime": 180,
|
||||
"speed_x": 24.0,
|
||||
"speed_y": 0.0,
|
||||
"acceleration_x": 200.0,
|
||||
"acceleration_y": 0.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE"
|
||||
],
|
||||
"sprite": "ss_hazard_blob"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "ss_hazard_blob"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "ss_hazard_blob"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "ss_hazard_blob"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "ss_hazard_blob"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "ss_hazard_moth",
|
||||
"speedtime": 120,
|
||||
"speed_x": 40.0,
|
||||
"speed_y": 40.0,
|
||||
"acceleration_x": 300.0,
|
||||
"acceleration_y": 300.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE"
|
||||
],
|
||||
"sprite": "ss_hazard_moth"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "ss_hazard_moth"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "ss_hazard_moth"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "ss_hazard_moth"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "ss_hazard_moth"
|
||||
}
|
||||
]
|
||||
}
|
||||
89
docs/tutorials/assets/sidescroller/character_ss_player.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "ss_player",
|
||||
"speedtime": 120,
|
||||
"speed_x": 90.0,
|
||||
"speed_y": 0.0,
|
||||
"acceleration_x": 600.0,
|
||||
"acceleration_y": 0.0,
|
||||
"sprite_mappings": [
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT"
|
||||
],
|
||||
"sprite": "ss_player_idle_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT"
|
||||
],
|
||||
"sprite": "ss_player_run_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT"
|
||||
],
|
||||
"sprite": "ss_player_idle_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT"
|
||||
],
|
||||
"sprite": "ss_player_run_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_DOWN"
|
||||
],
|
||||
"sprite": "ss_player_idle_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_UP",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "ss_player_jump_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "ss_player_jump_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "ss_player_jump_left"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_RIGHT",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "ss_player_jump_right"
|
||||
},
|
||||
{
|
||||
"state": [
|
||||
"AKGL_ACTOR_STATE_ALIVE",
|
||||
"AKGL_ACTOR_STATE_FACE_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_LEFT",
|
||||
"AKGL_ACTOR_STATE_MOVING_UP"
|
||||
],
|
||||
"sprite": "ss_player_jump_left"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
docs/tutorials/assets/sidescroller/coin.png
Normal file
|
After Width: | Height: | Size: 122 B |
BIN
docs/tutorials/assets/sidescroller/hazard.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
docs/tutorials/assets/sidescroller/jingle_start.ogg
Normal file
1448
docs/tutorials/assets/sidescroller/level1.tmj
Normal file
BIN
docs/tutorials/assets/sidescroller/player.png
Normal file
|
After Width: | Height: | Size: 383 B |
16
docs/tutorials/assets/sidescroller/sprite_ss_coin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "coin.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_coin",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 200,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "hazard.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_hazard_blob",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 180,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "hazard.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_hazard_moth",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 120,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_idle_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
3
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_idle_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_jump_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
4
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_jump_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 250,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
1
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_run_left",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 90,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
4
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "player.png",
|
||||
"frame_width": 32,
|
||||
"frame_height": 32
|
||||
},
|
||||
"name": "ss_player_run_right",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"speed": 90,
|
||||
"loop": true,
|
||||
"loopReverse": false,
|
||||
"frames": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
1
|
||||
]
|
||||
}
|
||||
BIN
docs/tutorials/assets/sidescroller/tiles.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |