- Introduced detailed analysis for the Minecart system, highlighting its state machine, track system, and areas for improvement. - Created an NPCs analysis document, summarizing various NPC sprites and their functionalities. - Added an Objects analysis document, covering interactive elements like collectibles, ice blocks, and minecarts. - Documented the Overlord sprite system, detailing its role in spawning other sprites and managing events. - Compiled a Dungeons & Indoor Areas document, outlining custom room tags, enhanced mechanics, and advanced collision systems. - Developed an Overworld Systems Analysis, focusing on the ZSCustomOverworld architecture and its core features. - Added a Time System document, explaining the in-game clock and day/night cycle management. - Documented the ZScream Custom Overworld, detailing its data-driven approach and key features.
7.6 KiB
Ice Block System (Sprites/Objects/ice_block.asm)
Overview
This file contains the logic for the pushable ice block sprite. It's a statue-like object that, when pushed by Link, slides in one direction until it collides with a wall, another object, or a switch.
Key Functionality
- Pushing Mechanics: When Link makes contact, the block's
SprMiscAregister stores Link's facing direction. A timer (SprTimerA) is used to confirm the push, after which the block is set in motion. - Sliding: Once pushed, the block moves at a constant velocity until a collision is detected.
- Collision: The block stops moving when it collides with a wall (detected via
SprCollision) or another sprite (handled inStatue_BlockSprites). It also stops when it hits a specific switch tile. - State Management: The sprite uses
SprMiscregisters to track its state, such as whether it's currently being pushed (SprMiscC) or is in motion (SprMiscB).
Analysis & Areas for Improvement
The ice block is a classic Zelda puzzle element, but its current implementation suffers from over-sensitivity and unpredictable behavior, leading to frustration for beta testers. The goal is to achieve a "Pokémon-style" grid-based sliding puzzle feel, requiring intentional pushes and predictable movement.
Current Problems:
- Over-sensitivity: The block initiates movement on simple hitbox overlap with Link, leading to accidental pushes.
- Unpredictable Direction: Link's slight movements or diagonal contact can result in the block sliding in unintended directions.
- Non-grid-aligned movement: The block does not snap to a grid, making its stopping positions feel imprecise.
Proposed Improvements:
1. Intent-Based Push Mechanics (Addressing Sensitivity & Direction)
The current JSL Sprite_CheckDamageToPlayerSameLayer is too broad. It will be replaced with a more robust system:
- Directional Alignment Check: A new subroutine (
IceBlock_CheckLinkPushAlignment) will be implemented. This routine will verify that Link is:- Directly adjacent to the ice block.
- Facing the ice block (e.g., if Link is facing right, the block must be to his right).
- Aligned within a small pixel tolerance (e.g., +/- 4 pixels) on the non-pushing axis (e.g., for a horizontal push, Link's Y-coordinate must be close to the block's Y-coordinate).
- Push Confirmation Timer: Instead of an immediate push, a short timer (e.g., 10-15 frames) will be introduced. Link must maintain the correct directional alignment and contact for this duration to confirm an intentional push. If contact or alignment is broken, the timer resets.
- Locked Direction: Once a push is confirmed, the block's movement direction will be locked to a single cardinal direction (horizontal or vertical) until it collides with an obstacle.
2. Predictable Movement & Stopping (Grid Alignment)
- Grid Snapping on Push: When a push is confirmed, the ice block's coordinates (
SprX, SprY) will be snapped to the nearest 8-pixel grid boundary before movement begins. This ensures that all slides start and end cleanly on the game's tile grid.
3. Code Refactoring & Readability (General Improvements)
While implementing the above, the following existing suggestions from the previous analysis will also be applied:
- Use
subroutinefor All Code Blocks: Convert all major logical blocks withinSprites/Objects/ice_block.asminto propersubroutines for better scope management and readability. - Replace Magic Numbers with Constants: Define named constants for all hardcoded values (speeds, timers, tile IDs, alignment tolerances) to improve code clarity and maintainability.
- Refactor
Sprite_ApplyPush: Convert theSprite_ApplyPushroutine to use a lookup table for settingSprXSpeedandSprYSpeedbased on the determined push direction. This will make the code more compact and easier to modify. - Clarify
Statue_BlockSprites: Rename this routine toIceBlock_HandleSpriteToSpriteCollisionand add detailed comments to explain its logic, especially concerning sprite-to-sprite collision and recoil calculations.
Deeper Analysis: Sprite Solidity and Link's Collision
To understand why the ice block is not solid, we need to look at both the sprite's properties and Link's collision detection code.
Sprite Property Analysis
Several flags in the sprite's RAM data structure control its physical properties. These are the most relevant for solidity:
-
!Statue = 01Property: In the sprite's header,!Statue = 01is set. This is a high-level property that should be translated into one or more of the low-level RAM flags by the%Set_Sprite_Propertiesmacro when the sprite is initialized. -
SprDefl($0CAA): This is the "Sprite Deflection" register and appears to be critical.- Bit 2 (
$04): TheCore/symbols.asmfile labels this the "pushable interaction flag". Although the comment says it's "Never queried," this is highly suspect and is the most likely candidate for enabling pushable-statue physics. - Our Bug: Our previous attempts to modify
Sprite_IceBlock_Prepeither cleared this register entirely (STZ.w SprDefl, X) or left it alone, both of which resulted in Link walking through the block. This indicates that this register's value is crucial and must be set correctly, likely by the engine's default property loading routines. Our code was interfering with this.
- Bit 2 (
-
SprHitbox($0F60): This register contains theI(Ignore Collisions) bit.- Bit 7 (
$80): If this bit is set, the sprite will ignore all collisions with Link. We must ensure our code does not accidentally set this bit.
- Bit 7 (
Link's Collision Logic (Hypothesis)
The core of Link's interaction with the world is handled in bank_07.asm.
Link_HandleCardinalCollision(JSRat#_0782C2): This is the key function that processes Link's movement against solid objects. A full analysis is pending a complete reading ofbank_07.asm, but we can hypothesize its behavior.- Hypothesis: This routine likely loops through all active sprites on screen. For each sprite, it checks a combination of flags (e.g.,
SprDefl,SprHitbox) to determine if the sprite is solid. If it is, it performs a bounding box check. If Link's next position would overlap with a solid sprite, his movement is halted. The fact that Link walks through the ice block proves that the block is not being flagged as solid correctly.
- Hypothesis: This routine likely loops through all active sprites on screen. For each sprite, it checks a combination of flags (e.g.,
Revised Troubleshooting Plan
Based on this deeper analysis, the plan is to work with the game engine's properties, not against them.
- Analyze
Link_HandleCardinalCollision: The top priority is to find and fully analyze this function inbank_07.asmto understand exactly which sprite flags it checks to identify a solid, pushable object. - Analyze
SpritePrep_LoadProperties: Understand how the%Set_Sprite_Propertiesmacro and the subsequentJSL SpritePrep_LoadPropertiesfunction translate the!Statue = 01property into RAM flags. This will reveal the correct default values for a statue. - Correct
Sprite_IceBlock_Prep: With the knowledge from the steps above, write a definitiveSprite_IceBlock_Preproutine that correctly initializes all necessary flags (SprDefl, etc.) for a pushable statue, without overriding engine defaults. - Verify Solidity: Build and test to confirm Link collides with the block.
- Re-evaluate Push Logic: Once the block is solid, re-evaluate the push initiation logic, which uses
JSL Sprite_CheckDamageToPlayerSameLayer. If it still fails, we will have a solid object to debug against, which is a much better state.