mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
Add bubble patches and bubbles (no collision or drowning yet)
This commit is contained in:
parent
888b363aa2
commit
69c885beb1
11 changed files with 240 additions and 13 deletions
Binary file not shown.
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 11 KiB |
|
@ -426,6 +426,60 @@ frames = [
|
|||
]
|
||||
loopback = 0
|
||||
|
||||
|
||||
# ============
|
||||
|
||||
[bubble_patch]
|
||||
|
||||
[[bubble_patch.animations]]
|
||||
id = 0
|
||||
name = "default"
|
||||
duration = 16
|
||||
frames = [
|
||||
[16, 206, 16, 16],
|
||||
[32, 206, 16, 16],
|
||||
]
|
||||
loopback = 0
|
||||
|
||||
# ============
|
||||
|
||||
[bubble]
|
||||
|
||||
[[bubble.animations]]
|
||||
id = 0
|
||||
name = "small"
|
||||
duration = 8
|
||||
frames = [
|
||||
[ 2, 194, 4, 4],
|
||||
[ 8, 192, 8, 8],
|
||||
]
|
||||
loopback = 1
|
||||
|
||||
[[bubble.animations]]
|
||||
id = 1
|
||||
name = "medium"
|
||||
duration = 8
|
||||
frames = [
|
||||
[ 2, 194, 4, 4],
|
||||
[ 8, 192, 8, 8],
|
||||
[18, 194, 12, 12],
|
||||
]
|
||||
loopback = 2
|
||||
|
||||
[[bubble.animations]]
|
||||
id = 2
|
||||
name = "big"
|
||||
duration = 8
|
||||
frames = [
|
||||
[ 2, 194, 4, 4],
|
||||
[ 8, 192, 8, 8],
|
||||
[18, 194, 12, 12],
|
||||
[ 0, 200, 16, 16],
|
||||
[ 0, 222, 23, 23],
|
||||
[48, 172, 32, 32],
|
||||
]
|
||||
loopback = 5
|
||||
|
||||
# ============
|
||||
# Dummy objects
|
||||
# ============
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.11.0" name="objects_common" tilewidth="64" tileheight="64" tilecount="16" columns="4">
|
||||
<image source="obj_common_designtiles.png" width="256" height="256"/>
|
||||
<tileset version="1.10" tiledversion="1.11.0" name="objects_common" tilewidth="64" tileheight="64" tilecount="20" columns="4">
|
||||
<image source="obj_common_designtiles.png" width="256" height="320"/>
|
||||
<tile id="0" type="ring"/>
|
||||
<tile id="1" type="monitor">
|
||||
<properties>
|
||||
|
@ -52,4 +52,10 @@
|
|||
<tile id="13" type="explosion"/>
|
||||
<tile id="14" type="monitor_image"/>
|
||||
<tile id="15" type="shield"/>
|
||||
<tile id="16" type="bubble_patch">
|
||||
<properties>
|
||||
<property name="frequency" type="int" value="0"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="17" type="bubble"/>
|
||||
</tileset>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="255" height="31" tilewidth="128" tileheight="128" infinite="0" nextlayerid="4" nextobjectid="109">
|
||||
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="255" height="31" tilewidth="128" tileheight="128" infinite="0" nextlayerid="4" nextobjectid="112">
|
||||
<tileset firstgid="1" source="128x128.tsx"/>
|
||||
<tileset firstgid="57" source="../COMMON/objects_common.tsx"/>
|
||||
<layer id="1" name="LAYER0" width="255" height="31">
|
||||
|
@ -115,6 +115,16 @@
|
|||
<object id="105" gid="65" x="2961.5" y="834" width="64" height="64"/>
|
||||
<object id="106" gid="61" x="3888" y="1104" width="64" height="64"/>
|
||||
<object id="108" gid="61" x="1024" y="848" width="64" height="64" rotation="90"/>
|
||||
<object id="109" gid="73" x="1767" y="960" width="64" height="64">
|
||||
<properties>
|
||||
<property name="frequency" type="int" value="0"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="111" gid="73" x="1097" y="896" width="64" height="64">
|
||||
<properties>
|
||||
<property name="frequency" type="int" value="1"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</group>
|
||||
</map>
|
||||
|
|
|
@ -27,6 +27,8 @@ typedef enum {
|
|||
OBJ_EXPLOSION = 0x0a,
|
||||
OBJ_MONITOR_IMAGE = 0x0b,
|
||||
OBJ_SHIELD = 0x0c,
|
||||
OBJ_BUBBLE_PATCH = 0x0d,
|
||||
OBJ_BUBBLE = 0x0e,
|
||||
} ObjectType;
|
||||
|
||||
#define MASK_FLIP_FLIPX 0x1 // Flip on X axis
|
||||
|
|
|
@ -36,6 +36,20 @@ typedef struct {
|
|||
uint8_t kind;
|
||||
} MonitorExtra;
|
||||
|
||||
typedef struct {
|
||||
// Defined during design
|
||||
uint8_t frequency;
|
||||
|
||||
// Runtime variables
|
||||
uint8_t timer; // Decrementing random timer. idle=[128, 255]; producing=[0, 31]
|
||||
uint8_t state; // 0 = idle, 1 = producing
|
||||
uint8_t num_bubbles; // num=[1, 6] at every producing delay end
|
||||
uint8_t bubble_set; // bubble set picked at random=[0, 3]
|
||||
uint8_t cycle; // large bubble cycle counter
|
||||
uint8_t bubble_idx;
|
||||
uint8_t produced_big; // Whether a big bubble was produced this cycle
|
||||
} BubblePatchExtra;
|
||||
|
||||
typedef struct {
|
||||
uint16_t animation;
|
||||
uint8_t frame;
|
||||
|
|
|
@ -99,6 +99,13 @@ load_object_placement(const char *filename, void *lvl_data)
|
|||
extra = alloc_arena_malloc(&_level_arena, sizeof(MonitorExtra));
|
||||
((MonitorExtra *)extra)->kind = get_byte(bytes, &b);
|
||||
break;
|
||||
case OBJ_BUBBLE_PATCH:
|
||||
extra = alloc_arena_malloc(&_level_arena, sizeof(BubblePatchExtra));
|
||||
((BubblePatchExtra *)extra)->frequency = get_byte(bytes, &b);
|
||||
|
||||
// Start timer at a low timer so we start with idle instead of producing
|
||||
((BubblePatchExtra *)extra)->timer = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get chunk at position
|
||||
|
@ -289,7 +296,8 @@ begin_render_routine:
|
|||
sort_prim(poly,
|
||||
((state->id == OBJ_RING)
|
||||
|| (state->id == OBJ_SHIELD)
|
||||
|| (state->id == OBJ_EXPLOSION))
|
||||
|| (state->id == OBJ_EXPLOSION)
|
||||
|| (state->id == OBJ_BUBBLE))
|
||||
? OTZ_LAYER_OBJECTS
|
||||
: OTZ_LAYER_PLAYER);
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ extern int debug_mode;
|
|||
extern uint8_t level_ring_count;
|
||||
extern uint32_t level_score_count;
|
||||
extern uint8_t level_finished;
|
||||
extern int32_t level_water_y;
|
||||
|
||||
|
||||
// Object-specific definitions
|
||||
|
@ -53,6 +54,8 @@ static void _explosion_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
|||
static void _monitor_image_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
||||
static void _shield_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
||||
static void _switch_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
||||
static void _bubble_patch_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
||||
static void _bubble_update(ObjectState *state, ObjectTableEntry *, VECTOR *);
|
||||
|
||||
// Player hitbox information. Calculated once per frame.
|
||||
static int32_t player_vx, player_vy; // Top left corner of player hitbox
|
||||
|
@ -115,6 +118,8 @@ object_update(ObjectState *state, ObjectTableEntry *typedata, VECTOR *pos)
|
|||
case OBJ_MONITOR_IMAGE: _monitor_image_update(state, typedata, pos); break;
|
||||
case OBJ_SHIELD: _shield_update(state, typedata, pos); break;
|
||||
case OBJ_SWITCH: _switch_update(state, typedata, pos); break;
|
||||
case OBJ_BUBBLE_PATCH: _bubble_patch_update(state, typedata, pos); break;
|
||||
case OBJ_BUBBLE: _bubble_update(state, typedata, pos); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -653,3 +658,115 @@ _switch_update(ObjectState *state, ObjectTableEntry *, VECTOR *pos)
|
|||
state->props &= ~OBJ_FLAG_SWITCH_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_bubble_patch_update(ObjectState *state, ObjectTableEntry *, VECTOR *pos)
|
||||
{
|
||||
// Fixed bubble patch sets
|
||||
static const uint8_t bubble_size_sets[4][6] = {
|
||||
{0, 0, 0, 0, 1, 0},
|
||||
{0, 0, 0, 1, 0, 0},
|
||||
{1, 0, 1, 0, 0, 0},
|
||||
{0, 1, 0, 0, 1, 0},
|
||||
};
|
||||
|
||||
BubblePatchExtra *extra = state->extra;
|
||||
|
||||
extra->timer--;
|
||||
|
||||
if(extra->timer == 0) {
|
||||
// Flip production state between idle/producing.
|
||||
// Since num_bubbles is always 0 when idle, we use this as shortcut
|
||||
if(extra->num_bubbles == 0) {
|
||||
extra->state = (extra->state + 1) % 2;
|
||||
if(extra->state == 0) {
|
||||
// Flipped from producing to idle. Pick a random delay [128; 255]
|
||||
extra->timer = 128 + (rand() % 128);
|
||||
extra->cycle = (extra->cycle + 1) % (extra->frequency + 1);
|
||||
extra->produced_big = 0;
|
||||
} else {
|
||||
// Flipped from idle to producing
|
||||
extra->num_bubbles = 1 + (rand() % 6); // Random [1; 6] bubbles
|
||||
extra->bubble_set = rand() % 4; // Random bubble set [0; 4]
|
||||
extra->bubble_idx = 0;
|
||||
extra->timer = rand() % 32; // Random [0; 31] frames
|
||||
}
|
||||
} else {
|
||||
// Produce a bubble according to constants
|
||||
PoolObject *bubble = object_pool_create(OBJ_BUBBLE);
|
||||
if(bubble) {
|
||||
bubble->state.anim_state.animation =
|
||||
bubble_size_sets[extra->bubble_set][extra->bubble_idx++];
|
||||
bubble->freepos.vx = (pos->vx << 12) - (8 << 12) + ((rand() % 16) << 12);
|
||||
bubble->freepos.vy = (pos->vy << 12);
|
||||
|
||||
// If this is a big bubble cycle, however, we may want to turn
|
||||
// the current bubble into a big one
|
||||
if((extra->cycle == extra->frequency) && !extra->produced_big) {
|
||||
// Roll a dice with 1/4 of chance, but if this is a big
|
||||
// bubble cycle and we're at the last bubble, make it big
|
||||
// regardless!
|
||||
if((extra->num_bubbles == 1) || !(rand() % 4)) {
|
||||
extra->produced_big = 1;
|
||||
bubble->state.anim_state.animation = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extra->timer = rand() % 32; // Random [0; 31] frames
|
||||
extra->num_bubbles--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
_bubble_update(ObjectState *state, ObjectTableEntry *, VECTOR *)
|
||||
{
|
||||
// NOTE: this object can only exist as a free object. Do not insist.
|
||||
|
||||
// FIRST OFF: If way too far from camera, destroy it
|
||||
if((state->freepos->vx < camera.pos.vx - (SCREEN_XRES << 13))
|
||||
|| (state->freepos->vx > camera.pos.vx + (SCREEN_XRES << 13))
|
||||
|| (state->freepos->vy < camera.pos.vy - (SCREEN_YRES << 13))
|
||||
|| (state->freepos->vy > camera.pos.vy + (SCREEN_YRES << 13))
|
||||
|| (state->timer >= 256)) {
|
||||
state->props |= OBJ_FLAG_DESTROYED;
|
||||
return;
|
||||
}
|
||||
|
||||
// A bubble can be of three diameters: small (8), medium (12) or big (32).
|
||||
// Animation dictates object diameter.
|
||||
int32_t diameter = 0;
|
||||
switch(state->anim_state.animation) {
|
||||
default:
|
||||
case 0: diameter = 8; break; // small (breath)
|
||||
case 1: diameter = 12; break; // medium
|
||||
case 2: diameter = 32; break; // big
|
||||
}
|
||||
|
||||
// Bubbles should always be ascending with a 0.5 speed (-0x800).
|
||||
state->freepos->vy -= 0x800;
|
||||
|
||||
// Bubbles also sway back-and-forth in a sine-like movement.
|
||||
// x = initial_x + 8 * sin(timer / 128.0)
|
||||
|
||||
// When the bubble's top interact with water surface, destroy it
|
||||
if(state->freepos->vy - (diameter << 12) <= level_water_y) {
|
||||
state->props |= OBJ_FLAG_DESTROYED;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Bubbles can also only be interacted when big and on last animation frame.
|
||||
// There is no destruction animation because of VRAM constraints; we still
|
||||
// technically have a whole area available to add it, but I felt like I
|
||||
// shouldn't create a whole new texture this time just because of a single
|
||||
// bubble frame.
|
||||
if(state->anim_state.animation == 2 && state->anim_state.frame == 5) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,9 @@ screen_level_load()
|
|||
|
||||
demo_init();
|
||||
|
||||
// init proper RNG per level
|
||||
srand(get_global_frames());
|
||||
|
||||
// If it is a demo or we're recording, skip title card
|
||||
if(level_mode == LEVEL_MODE_DEMO
|
||||
|| level_mode == LEVEL_MODE_RECORD) {
|
||||
|
|
|
@ -36,13 +36,11 @@ class ObjectId(Enum):
|
|||
SPRING_RED_DIAGONAL = 0x07
|
||||
SWITCH = 0x08
|
||||
GOAL_SIGN = 0x09
|
||||
|
||||
# Certain objects simply cannot be placed.
|
||||
# This happens when the object in question is a particle
|
||||
# or effect.
|
||||
EXPLOSION = 0x0a
|
||||
MONITOR_IMAGE = 0x0b
|
||||
SHIELD = 0x0c
|
||||
EXPLOSION = 0x0A
|
||||
MONITOR_IMAGE = 0x0B
|
||||
SHIELD = 0x0C
|
||||
BUBBLE_PATCH = 0x0D
|
||||
BUBBLE = 0x0E
|
||||
|
||||
@staticmethod
|
||||
def get(name):
|
||||
|
@ -60,6 +58,8 @@ class ObjectId(Enum):
|
|||
"explosion": ObjectId.EXPLOSION,
|
||||
"monitor_image": ObjectId.MONITOR_IMAGE,
|
||||
"shield": ObjectId.SHIELD,
|
||||
"bubble_patch": ObjectId.BUBBLE_PATCH,
|
||||
"bubble": ObjectId.BUBBLE,
|
||||
}
|
||||
result = switch.get(name.lower())
|
||||
assert result is not None, f"Unknown common object {name}"
|
||||
|
@ -230,7 +230,15 @@ class MonitorProperties:
|
|||
f.write(c_ubyte(self.kind))
|
||||
|
||||
|
||||
ObjectProperties = MonitorProperties | None
|
||||
@dataclass
|
||||
class BubblePatchProperties:
|
||||
frequency: int = 0
|
||||
|
||||
def write_to(self, f):
|
||||
f.write(c_ubyte(self.frequency))
|
||||
|
||||
|
||||
ObjectProperties = MonitorProperties | BubblePatchProperties | None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -170,7 +170,12 @@ def parse_object_group(
|
|||
m = MonitorProperties()
|
||||
m.kind = MonitorKind.get(prop.get("value")).value
|
||||
p.properties = m
|
||||
pass
|
||||
elif p.otype == ObjectId.BUBBLE_PATCH.value:
|
||||
prop = props.find("property")
|
||||
bp = BubblePatchProperties()
|
||||
# Get first available value
|
||||
bp.frequency = int(prop.get("value"))
|
||||
p.properties = bp
|
||||
# print(
|
||||
# f"Object type {current_ts.object_types[p.otype + current_ts.firstgid].name if p.otype >= 0 else 'DUMMY'}"
|
||||
# )
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue