Improve player sprite export instructions, add some animations

This commit is contained in:
Lucas S. Vieira 2024-11-03 01:46:09 -03:00
parent ded76efbcf
commit 8b03030ba8
19 changed files with 139 additions and 104 deletions

View file

@ -10,7 +10,7 @@ File_7=Z:\home\alchemist\git\engine-psx\assets\levels\R2\tiles.TIM
File_8=Z:\home\alchemist\git\engine-psx\assets\levels\R3\BG0.TIM File_8=Z:\home\alchemist\git\engine-psx\assets\levels\R3\BG0.TIM
File_9=Z:\home\alchemist\git\engine-psx\assets\levels\R3\TILES.TIM File_9=Z:\home\alchemist\git\engine-psx\assets\levels\R3\TILES.TIM
File_10=Z:\home\alchemist\git\engine-psx\assets\sprites\BASEFONT.TIM File_10=Z:\home\alchemist\git\engine-psx\assets\sprites\BASEFONT.TIM
File_11=Z:\home\alchemist\git\engine-psx\assets\sprites\SONIC.TIM File_11=Z:\home\alchemist\git\engine-psx\assets\sprites\CHARA\SONIC.TIM
[Graphics Mode] [Graphics Mode]
Width=320 Width=320
Height=240 Height=240

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -44,7 +44,8 @@ typedef enum {
ACTION_JUMPING, ACTION_JUMPING,
ACTION_ROLLING, ACTION_ROLLING,
ACTION_SPINDASH, ACTION_SPINDASH,
ACTION_DROPDASH ACTION_DROPDASH,
ACTION_SPRING,
} PlayerAction; } PlayerAction;
typedef struct { typedef struct {
@ -64,6 +65,7 @@ typedef struct {
uint8_t push; uint8_t push;
uint32_t spinrev; uint32_t spinrev;
uint8_t ctrllock; uint8_t ctrllock;
uint8_t airdirlock;
uint8_t framecount; uint8_t framecount;
uint8_t holding_jump; uint8_t holding_jump;

View file

@ -17,10 +17,10 @@
<dir name="SPRITES"> <dir name="SPRITES">
<file name="SONIC.TIM" <file name="SONIC.TIM"
type="data" type="data"
source="${PROJECT_SOURCE_DIR}/assets/sprites/SONIC.TIM" /> source="${PROJECT_SOURCE_DIR}/assets/sprites/CHARA/SONIC.TIM" />
<file name="SONIC.CHARA" <file name="SONIC.CHARA"
type="data" type="data"
source="${PROJECT_SOURCE_DIR}/assets/sprites/SONIC.CHARA" /> source="${PROJECT_SOURCE_DIR}/assets/sprites/CHARA/SONIC.CHARA" />
<file name="BASEFONT.TIM" <file name="BASEFONT.TIM"
type="data" type="data"
source="${PROJECT_SOURCE_DIR}/assets/sprites/BASEFONT.TIM" /> source="${PROJECT_SOURCE_DIR}/assets/sprites/BASEFONT.TIM" />

View file

@ -143,7 +143,7 @@ chara_render_frame(Chara *chara, int16_t framenum, int16_t vx, int16_t vy, uint8
increment_prim(sizeof(POLY_FT4)); increment_prim(sizeof(POLY_FT4));
setPolyFT4(poly); setPolyFT4(poly);
setRGB0(poly, level_fade, level_fade, level_fade); setRGB0(poly, level_fade, level_fade, level_fade);
setTPage(poly, 0, 0, chara->prectx, chara->precty); setTPage(poly, 1, 1, chara->prectx, chara->precty); // 8-bit CLUT
setClut(poly, chara->crectx, chara->crecty); setClut(poly, chara->crectx, chara->crecty);
setXYWH(poly, xy0.vx, xy0.vy, 8, 8); setXYWH(poly, xy0.vx, xy0.vy, 8, 8);

View file

@ -13,9 +13,6 @@
#include "screen.h" #include "screen.h"
#include "screens/level.h" #include "screens/level.h"
// Adler32 sums of player animation names
#define ANIM_WALKING 0x0854020e
// Extern elements // Extern elements
extern Player player; extern Player player;
extern Camera camera; extern Camera camera;
@ -277,95 +274,19 @@ _spring_update(ObjectState *state, ObjectTableEntry *, VECTOR *pos, uint8_t is_r
player.pos.vy = (solidity_vy - (player_height >> 1)) << 12; player.pos.vy = (solidity_vy - (player_height >> 1)) << 12;
player.grnd = 0; player.grnd = 0;
player.vel.vy = is_red ? -0x10000 : -0xa000; player.vel.vy = is_red ? -0x10000 : -0xa000;
player.vel.vz = 0;
player.angle = 0; player.angle = 0;
player.action = 0; player.action = ACTION_SPRING;
state->anim_state.animation = 1; state->anim_state.animation = 1;
player_set_animation_direct(&player, ANIM_WALKING);
sound_play_vag(sfx_sprn, 0); sound_play_vag(sfx_sprn, 0);
} else if(state->flipmask & MASK_FLIP_FLIPY) { // Bottom-pointing spring } else if(state->flipmask & MASK_FLIP_FLIPY) { // Bottom-pointing spring
player.pos.vy = (solidity_vy + solidity_h + (player_height >> 1)) << 12; player.pos.vy = (solidity_vy + solidity_h + (player_height >> 1)) << 12;
player.grnd = 0; player.grnd = 0;
player.vel.vy = is_red ? 0x10000 : 0xa000; player.vel.vy = is_red ? 0x10000 : 0xa000;
player.vel.vz = 0;
player.angle = 0; player.angle = 0;
player.action = 0; player.action = ACTION_SPRING;
state->anim_state.animation = 1; state->anim_state.animation = 1;
player_set_animation_direct(&player, ANIM_WALKING);
sound_play_vag(sfx_sprn, 0); sound_play_vag(sfx_sprn, 0);
} }
// Complex spring collision. Commented out. Doesn't work properly.
/* switch(collision_side) { */
/* default: return; */
/* case OBJ_SIDE_LEFT: */
/* if(state->flipmask & MASK_FLIP_ROTCT) { */
/* if(player.grnd) player.vel.vz = is_red ? -0x10000 : -0xa000; */
/* else player.vel.vx = is_red ? -0x10000 : -0xa000; */
/* player.ctrllock = 16; */
/* player.anim_dir = -1; */
/* state->anim_state.animation = 1; */
/* sound_play_vag(sfx_sprn, 0); */
/* } else { */
/* player.ev_right = (CollisionEvent) { */
/* .collided = 1, */
/* .coord = solidity_vx + 1, */
/* .angle = 0 */
/* }; */
/* } */
/* break; */
/* case OBJ_SIDE_RIGHT: */
/* if(state->flipmask & MASK_FLIP_ROTCW) { */
/* if(player.grnd) player.vel.vz = is_red ? 0x10000 : 0xa000; */
/* else player.vel.vx = is_red ? 0x10000 : 0xa000; */
/* player.ctrllock = 16; */
/* player.anim_dir = 1; */
/* state->anim_state.animation = 1; */
/* sound_play_vag(sfx_sprn, 0); */
/* } else { */
/* player.ev_left = (CollisionEvent) { */
/* .collided = 1, */
/* .coord = (solidity_vx + 15), */
/* .angle = 0 */
/* }; */
/* } */
/* break; */
/* case OBJ_SIDE_TOP: */
/* if(state->flipmask == 0) { */
/* player.grnd = 0; */
/* player.vel.vy = is_red ? -0x10000 : -0xa000; */
/* player.angle = 0; */
/* player.action = 0; */
/* state->anim_state.animation = 1; */
/* player_set_animation_direct(&player, ANIM_WALKING); */
/* sound_play_vag(sfx_sprn, 0); */
/* } else { */
/* player.ev_grnd1 = player.ev_grnd2 = (CollisionEvent) { */
/* .collided = 1, */
/* .coord = solidity_vy + 8, */
/* .angle = 0 */
/* }; */
/* } */
/* break; */
/* case OBJ_SIDE_BOTTOM: */
/* if(state->flipmask & MASK_FLIP_FLIPY) { */
/* player.grnd = 0; */
/* player.vel.vy = is_red ? 0x10000 : 0xa000; */
/* player.angle = 0; */
/* player.action = 0; */
/* state->anim_state.animation = 1; */
/* player_set_animation_direct(&player, ANIM_WALKING); */
/* sound_play_vag(sfx_sprn, 0); */
/* } else { */
/* player.ev_ceil1 = player.ev_ceil2 = (CollisionEvent) { */
/* .collided = 1, */
/* .coord = solidity_vy + solidity_h, */
/* .angle = 0 */
/* }; */
/* } */
/* break; */
/* } */
} else if(state->anim_state.animation == OBJ_ANIMATION_NO_ANIMATION) { } else if(state->anim_state.animation == OBJ_ANIMATION_NO_ANIMATION) {
state->anim_state.animation = 0; state->anim_state.animation = 0;
state->anim_state.frame = 0; state->anim_state.frame = 0;
@ -440,16 +361,15 @@ _spring_diagonal_update(ObjectState *state, ObjectTableEntry *, VECTOR *pos, uin
player.grnd = 0; player.grnd = 0;
player.vel.vx = is_red ? SPRND_ST_R : SPRND_ST_Y; player.vel.vx = is_red ? SPRND_ST_R : SPRND_ST_Y;
player.vel.vy = is_red ? SPRND_ST_R : SPRND_ST_Y; player.vel.vy = is_red ? SPRND_ST_R : SPRND_ST_Y;
player.vel.vz = 0;
if(!(state->flipmask & MASK_FLIP_FLIPY)) player.vel.vy *= -1; if(!(state->flipmask & MASK_FLIP_FLIPY)) player.vel.vy *= -1;
if(state->flipmask & MASK_FLIP_FLIPX) { if(state->flipmask & MASK_FLIP_FLIPX) {
player.vel.vx *= -1; player.vel.vx *= -1;
player.anim_dir = -1; // Flip on X: point player left player.anim_dir = -1; // Flip on X: point player left
} else player.anim_dir = 1; // No flip on X: point player right } else player.anim_dir = 1; // No flip on X: point player right
player.angle = 0; player.angle = 0;
player.action = 0; player.airdirlock = 1;
player.action = ACTION_SPRING;
state->anim_state.animation = 1; state->anim_state.animation = 1;
player_set_animation_direct(&player, ANIM_WALKING);
sound_play_vag(sfx_sprn, 0); sound_play_vag(sfx_sprn, 0);
} else if(state->anim_state.animation == OBJ_ANIMATION_NO_ANIMATION) { } else if(state->anim_state.animation == OBJ_ANIMATION_NO_ANIMATION) {

View file

@ -14,16 +14,19 @@
#define ANIM_IDLE_TIMER_MAX 180 #define ANIM_IDLE_TIMER_MAX 180
// Adler32 sums of animation names for ease of use // Adler32 sums of animation names for ease of use
#define ANIM_STOPPED 0x08cd0220 #define ANIM_STOPPED 0x08cd0220
#define ANIM_IDLE 0x02d1011f #define ANIM_IDLE 0x02d1011f
#define ANIM_WALKING 0x0854020e #define ANIM_WALKING 0x0854020e
#define ANIM_RUNNING 0x08bf0222 #define ANIM_RUNNING 0x08bf0222
#define ANIM_ROLLING 0x08890218 #define ANIM_ROLLING 0x08890218
#define ANIM_SKIDDING 0x0a85024e #define ANIM_SKIDDING 0x0a85024e
#define ANIM_PEELOUT 0x0849021f #define ANIM_PEELOUT 0x0849021f
#define ANIM_PUSHING 0x08b2021f #define ANIM_PUSHING 0x08b2021f
#define ANIM_CROUCHDOWN 0x104802fd #define ANIM_CROUCHDOWN 0x104802fd
#define ANIM_LOOKUP 0x067001db #define ANIM_LOOKUP 0x067001db
#define ANIM_SPRING 0x068e01d4
#define ANIM_HURT 0x031b0144
#define ANIM_DEATH 0x04200167
extern int debug_mode; extern int debug_mode;
@ -56,6 +59,7 @@ load_player(Player *player,
player->angle = 0; player->angle = 0;
player->spinrev = 0; player->spinrev = 0;
player->ctrllock = 0; player->ctrllock = 0;
player->airdirlock = 0;
player->framecount = 0; player->framecount = 0;
player_set_animation_direct(player, ANIM_STOPPED); player_set_animation_direct(player, ANIM_STOPPED);
@ -367,8 +371,12 @@ _player_handle_collision(Player *player)
player->pos.vy = ((new_coord - 16) << 12); player->pos.vy = ((new_coord - 16) << 12);
player->grnd = 1; player->grnd = 1;
if(player->action == ACTION_JUMPING || player->action == ACTION_ROLLING) if(player->action == ACTION_JUMPING
|| player->action == ACTION_ROLLING
|| player->action == ACTION_SPRING) {
player->action = ACTION_NONE; player->action = ACTION_NONE;
player->airdirlock = 0;
}
else if(player->action == ACTION_DROPDASH) { else if(player->action == ACTION_DROPDASH) {
// Perform drop dash // Perform drop dash
player->framecount = 0; player->framecount = 0;
@ -555,11 +563,13 @@ player_update(Player *player)
if(pad_pressing(PAD_RIGHT)) { if(pad_pressing(PAD_RIGHT)) {
if(player->vel.vx < X_TOP_SPD) if(player->vel.vx < X_TOP_SPD)
player->vel.vx += X_AIR_ACCEL; player->vel.vx += X_AIR_ACCEL;
player->anim_dir = 1; if(!player->airdirlock)
player->anim_dir = 1;
} else if(pad_pressing(PAD_LEFT)) { } else if(pad_pressing(PAD_LEFT)) {
if(player->vel.vx > -X_TOP_SPD) if(player->vel.vx > -X_TOP_SPD)
player->vel.vx -= X_AIR_ACCEL; player->vel.vx -= X_AIR_ACCEL;
player->anim_dir = -1; if(!player->airdirlock)
player->anim_dir = -1;
} }
// Air drag. Calculated before applying gravity. // Air drag. Calculated before applying gravity.
@ -654,7 +664,21 @@ player_update(Player *player)
player_set_animation_direct(player, ANIM_RUNNING); player_set_animation_direct(player, ANIM_RUNNING);
} else player_set_animation_direct(player, ANIM_WALKING); } else player_set_animation_direct(player, ANIM_WALKING);
} }
} else player->idle_timer = ANIM_IDLE_TIMER_MAX; } else {
player->idle_timer = ANIM_IDLE_TIMER_MAX;
if(player->action == ACTION_SPRING) {
if(player->vel.vy < 0) {
player_set_animation_direct(player, ANIM_SPRING);
} else {
player->airdirlock = 0;
if(abs(player->vel.vz) >= (10 << 12)) {
player_set_animation_direct(player, ANIM_PEELOUT);
} else if(abs(player->vel.vz) >= (6 << 12)) {
player_set_animation_direct(player, ANIM_RUNNING);
} else player_set_animation_direct(player, ANIM_WALKING);
}
}
}
// Animation speed correction // Animation speed correction
if(player->anim_timer == 0) { if(player->anim_timer == 0) {

View file

@ -28,6 +28,95 @@ python -m venv ./venv
To run any scripts with this venv, run ~./tools/venv/bin/python To run any scripts with this venv, run ~./tools/venv/bin/python
./tools/script.py~. ./tools/script.py~.
* Generating a character sprite 8x8 tileset and mappings
For characters, we need to use the Aseprite tool for building sprites.
Import the character's sprites as a sprite sheet and into an .aseprite file (see
'assets/sprites/CHARA/SONIC.aseprite').
The character's sprites must be manipulated into individual frames on a tileset
layer, with 8x8 tiles. Plus, you'll have to organize the frames in such a way
that frames of the same animation are coupled together.
Finally, group frames into animations by creating tags with animation names
(again, see 'SONIC.aseprite' for valid animation names).
Finally, export the animation names and mappings using 'export_tilemap_psx.lua',
then export the player tileset using 'export_tileset_psx.lua'.
This will create a 'SONIC.png' file and a 'SONIC.json' file.
Now, just pack the frame and tile data into a CHARA file, and generate a TIM
image with correct CLUT and TPAGE info:
#+begin_src bash :eval never
framepacker.py SONIC.json SONIC.CHARA
img2tim -usealpha -org 320 0 -plt 0 480 -bpp 8 -o SONIC.TIM SONIC.png
#+end_src
Notice that, different than other image tile data, character sprites rely on a
PNG's alpha channel to generate transparency bits, instead of using the full
black color as mask.
NOTE: If you need to refer to an animation directly by name, the animations are
referred to by the engine by their Adler32 hash, so you might want to add a new
definition for that on ~player.c~.
The names used on tags are always converted to uppercase, with no spaces.
To calculate the Adler32 hash for a string, one may use Zlib through Python.
Here's a tool to generate hash definitions for some animations.
#+begin_src python :results output
import zlib
names = [
"STOPPED",
"IDLE",
"WALKING",
"RUNNING",
"ROLLING",
"SKIDDING",
"PEELOUT",
"PUSHING",
"CROUCHDOWN",
"LOOKUP",
"SPRING",
"HURT",
"DEATH",
]
def get_hash(name):
hash = zlib.adler32(str.encode(name))
return f"0x{hash:08x}"
def print_hashes(names):
for name in names:
hash = get_hash(name)
print(f"#define ANIM_{name:16} {hash}")
print_hashes(names)
#+end_src
#+RESULTS:
#+begin_example
#define ANIM_STOPPED 0x08cd0220
#define ANIM_IDLE 0x02d1011f
#define ANIM_WALKING 0x0854020e
#define ANIM_RUNNING 0x08bf0222
#define ANIM_ROLLING 0x08890218
#define ANIM_SKIDDING 0x0a85024e
#define ANIM_PEELOUT 0x0849021f
#define ANIM_PUSHING 0x08b2021f
#define ANIM_CROUCHDOWN 0x104802fd
#define ANIM_LOOKUP 0x067001db
#define ANIM_SPRING 0x068e01d4
#define ANIM_HURT 0x031b0144
#define ANIM_DEATH 0x04200167
#+end_example
* Generating 8x8 tiles and their 16x16 mappings * Generating 8x8 tiles and their 16x16 mappings
The following steps will allow you to create intermediate files 'tiles.png', The following steps will allow you to create intermediate files 'tiles.png',