mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
Improve player sprite export instructions, add some animations
This commit is contained in:
parent
ded76efbcf
commit
8b03030ba8
19 changed files with 139 additions and 104 deletions
|
@ -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_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_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]
|
||||
Width=320
|
||||
Height=240
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
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.
BIN
assets/sprites/CHARA/SONIC.TIM
Normal file
BIN
assets/sprites/CHARA/SONIC.TIM
Normal file
Binary file not shown.
BIN
assets/sprites/CHARA/SONIC.aseprite
Normal file
BIN
assets/sprites/CHARA/SONIC.aseprite
Normal file
Binary file not shown.
1
assets/sprites/CHARA/SONIC.json
Normal file
1
assets/sprites/CHARA/SONIC.json
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/sprites/CHARA/SONIC.png
Normal file
BIN
assets/sprites/CHARA/SONIC.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
assets/sprites/CHARA/sonic-sheet.png
Normal file
BIN
assets/sprites/CHARA/sonic-sheet.png
Normal file
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 |
|
@ -44,7 +44,8 @@ typedef enum {
|
|||
ACTION_JUMPING,
|
||||
ACTION_ROLLING,
|
||||
ACTION_SPINDASH,
|
||||
ACTION_DROPDASH
|
||||
ACTION_DROPDASH,
|
||||
ACTION_SPRING,
|
||||
} PlayerAction;
|
||||
|
||||
typedef struct {
|
||||
|
@ -64,6 +65,7 @@ typedef struct {
|
|||
uint8_t push;
|
||||
uint32_t spinrev;
|
||||
uint8_t ctrllock;
|
||||
uint8_t airdirlock;
|
||||
uint8_t framecount;
|
||||
uint8_t holding_jump;
|
||||
|
||||
|
|
4
iso.xml
4
iso.xml
|
@ -17,10 +17,10 @@
|
|||
<dir name="SPRITES">
|
||||
<file name="SONIC.TIM"
|
||||
type="data"
|
||||
source="${PROJECT_SOURCE_DIR}/assets/sprites/SONIC.TIM" />
|
||||
source="${PROJECT_SOURCE_DIR}/assets/sprites/CHARA/SONIC.TIM" />
|
||||
<file name="SONIC.CHARA"
|
||||
type="data"
|
||||
source="${PROJECT_SOURCE_DIR}/assets/sprites/SONIC.CHARA" />
|
||||
source="${PROJECT_SOURCE_DIR}/assets/sprites/CHARA/SONIC.CHARA" />
|
||||
<file name="BASEFONT.TIM"
|
||||
type="data"
|
||||
source="${PROJECT_SOURCE_DIR}/assets/sprites/BASEFONT.TIM" />
|
||||
|
|
|
@ -143,7 +143,7 @@ chara_render_frame(Chara *chara, int16_t framenum, int16_t vx, int16_t vy, uint8
|
|||
increment_prim(sizeof(POLY_FT4));
|
||||
setPolyFT4(poly);
|
||||
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);
|
||||
setXYWH(poly, xy0.vx, xy0.vy, 8, 8);
|
||||
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
#include "screen.h"
|
||||
#include "screens/level.h"
|
||||
|
||||
// Adler32 sums of player animation names
|
||||
#define ANIM_WALKING 0x0854020e
|
||||
|
||||
// Extern elements
|
||||
extern Player player;
|
||||
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.grnd = 0;
|
||||
player.vel.vy = is_red ? -0x10000 : -0xa000;
|
||||
player.vel.vz = 0;
|
||||
player.angle = 0;
|
||||
player.action = 0;
|
||||
player.action = ACTION_SPRING;
|
||||
state->anim_state.animation = 1;
|
||||
player_set_animation_direct(&player, ANIM_WALKING);
|
||||
sound_play_vag(sfx_sprn, 0);
|
||||
} else if(state->flipmask & MASK_FLIP_FLIPY) { // Bottom-pointing spring
|
||||
player.pos.vy = (solidity_vy + solidity_h + (player_height >> 1)) << 12;
|
||||
player.grnd = 0;
|
||||
player.vel.vy = is_red ? 0x10000 : 0xa000;
|
||||
player.vel.vz = 0;
|
||||
player.angle = 0;
|
||||
player.action = 0;
|
||||
player.action = ACTION_SPRING;
|
||||
state->anim_state.animation = 1;
|
||||
player_set_animation_direct(&player, ANIM_WALKING);
|
||||
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) {
|
||||
state->anim_state.animation = 0;
|
||||
state->anim_state.frame = 0;
|
||||
|
@ -440,16 +361,15 @@ _spring_diagonal_update(ObjectState *state, ObjectTableEntry *, VECTOR *pos, uin
|
|||
player.grnd = 0;
|
||||
player.vel.vx = 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_FLIPX) {
|
||||
player.vel.vx *= -1;
|
||||
player.anim_dir = -1; // Flip on X: point player left
|
||||
} else player.anim_dir = 1; // No flip on X: point player right
|
||||
player.angle = 0;
|
||||
player.action = 0;
|
||||
player.airdirlock = 1;
|
||||
player.action = ACTION_SPRING;
|
||||
state->anim_state.animation = 1;
|
||||
player_set_animation_direct(&player, ANIM_WALKING);
|
||||
sound_play_vag(sfx_sprn, 0);
|
||||
|
||||
} else if(state->anim_state.animation == OBJ_ANIMATION_NO_ANIMATION) {
|
||||
|
|
28
src/player.c
28
src/player.c
|
@ -24,6 +24,9 @@
|
|||
#define ANIM_PUSHING 0x08b2021f
|
||||
#define ANIM_CROUCHDOWN 0x104802fd
|
||||
#define ANIM_LOOKUP 0x067001db
|
||||
#define ANIM_SPRING 0x068e01d4
|
||||
#define ANIM_HURT 0x031b0144
|
||||
#define ANIM_DEATH 0x04200167
|
||||
|
||||
extern int debug_mode;
|
||||
|
||||
|
@ -56,6 +59,7 @@ load_player(Player *player,
|
|||
player->angle = 0;
|
||||
player->spinrev = 0;
|
||||
player->ctrllock = 0;
|
||||
player->airdirlock = 0;
|
||||
player->framecount = 0;
|
||||
|
||||
player_set_animation_direct(player, ANIM_STOPPED);
|
||||
|
@ -367,8 +371,12 @@ _player_handle_collision(Player *player)
|
|||
player->pos.vy = ((new_coord - 16) << 12);
|
||||
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->airdirlock = 0;
|
||||
}
|
||||
else if(player->action == ACTION_DROPDASH) {
|
||||
// Perform drop dash
|
||||
player->framecount = 0;
|
||||
|
@ -555,10 +563,12 @@ player_update(Player *player)
|
|||
if(pad_pressing(PAD_RIGHT)) {
|
||||
if(player->vel.vx < X_TOP_SPD)
|
||||
player->vel.vx += X_AIR_ACCEL;
|
||||
if(!player->airdirlock)
|
||||
player->anim_dir = 1;
|
||||
} else if(pad_pressing(PAD_LEFT)) {
|
||||
if(player->vel.vx > -X_TOP_SPD)
|
||||
player->vel.vx -= X_AIR_ACCEL;
|
||||
if(!player->airdirlock)
|
||||
player->anim_dir = -1;
|
||||
}
|
||||
|
||||
|
@ -654,7 +664,21 @@ player_update(Player *player)
|
|||
player_set_animation_direct(player, ANIM_RUNNING);
|
||||
} 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
|
||||
if(player->anim_timer == 0) {
|
||||
|
|
|
@ -28,6 +28,95 @@ python -m venv ./venv
|
|||
To run any scripts with this venv, run ~./tools/venv/bin/python
|
||||
./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
|
||||
|
||||
The following steps will allow you to create intermediate files 'tiles.png',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue