treballant en les naus de title
This commit is contained in:
278
CLAUDE.md
278
CLAUDE.md
@@ -505,6 +505,284 @@ void dibuixar() {
|
||||
}
|
||||
```
|
||||
|
||||
## Title Screen Ship System (BETA 3.0)
|
||||
|
||||
The title screen features two 3D ships floating on the starfield with perspective rendering, entry/exit animations, and subtle floating motion.
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
**Files:**
|
||||
- `source/game/title/ship_animator.hpp/cpp` - Ship animation state machine
|
||||
- `source/core/rendering/shape_renderer.hpp/cpp` - 3D rotation + perspective projection
|
||||
- `source/core/defaults.hpp` - Title::Ships namespace with all constants
|
||||
- `source/game/escenes/escena_titol.hpp/cpp` - Integration with title scene
|
||||
|
||||
**Design Philosophy:**
|
||||
- **Static 3D rotation**: Ships have fixed pitch/yaw/roll angles (not recalculated per frame)
|
||||
- **Simple Z-axis simulation**: Scale changes simulate depth, not full perspective recalculation
|
||||
- **State machine**: ENTERING → FLOATING → EXITING states
|
||||
- **Easing functions**: Smooth transitions with ease_out_quad (entry) and ease_in_quad (exit)
|
||||
- **Sinusoidal floating**: Organic motion using X/Y oscillation with phase offset
|
||||
|
||||
### 3D Rendering System
|
||||
|
||||
#### Rotation3D Struct (shape_renderer.hpp)
|
||||
|
||||
```cpp
|
||||
struct Rotation3D {
|
||||
float pitch; // X-axis rotation (nose up/down)
|
||||
float yaw; // Y-axis rotation (turn left/right)
|
||||
float roll; // Z-axis rotation (bank left/right)
|
||||
|
||||
Rotation3D() : pitch(0.0f), yaw(0.0f), roll(0.0f) {}
|
||||
Rotation3D(float p, float y, float r) : pitch(p), yaw(y), roll(r) {}
|
||||
|
||||
bool has_rotation() const {
|
||||
return pitch != 0.0f || yaw != 0.0f || roll != 0.0f;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3D Transformation Pipeline (shape_renderer.cpp)
|
||||
|
||||
```cpp
|
||||
static Punt apply_3d_rotation(float x, float y, const Rotation3D& rot) {
|
||||
float z = 0.0f; // All 2D points start at Z=0
|
||||
|
||||
// 1. Pitch (X-axis): Rotate around horizontal axis
|
||||
float cos_pitch = std::cos(rot.pitch);
|
||||
float sin_pitch = std::sin(rot.pitch);
|
||||
float y1 = y * cos_pitch - z * sin_pitch;
|
||||
float z1 = y * sin_pitch + z * cos_pitch;
|
||||
|
||||
// 2. Yaw (Y-axis): Rotate around vertical axis
|
||||
float cos_yaw = std::cos(rot.yaw);
|
||||
float sin_yaw = std::sin(rot.yaw);
|
||||
float x2 = x * cos_yaw + z1 * sin_yaw;
|
||||
float z2 = -x * sin_yaw + z1 * cos_yaw;
|
||||
|
||||
// 3. Roll (Z-axis): Rotate around depth axis
|
||||
float cos_roll = std::cos(rot.roll);
|
||||
float sin_roll = std::sin(rot.roll);
|
||||
float x3 = x2 * cos_roll - y1 * sin_roll;
|
||||
float y3 = x2 * sin_roll + y1 * cos_roll;
|
||||
|
||||
// 4. Perspective projection (Z-divide)
|
||||
constexpr float perspective_factor = 500.0f;
|
||||
float scale_factor = perspective_factor / (perspective_factor + z2);
|
||||
|
||||
return {x3 * scale_factor, y3 * scale_factor};
|
||||
}
|
||||
```
|
||||
|
||||
**Rendering order**: 3D rotation → perspective → 2D scale → 2D rotation → translation
|
||||
|
||||
**Backward compatibility**: Optional `rotation_3d` parameter (default nullptr) - existing code unaffected
|
||||
|
||||
### Ship Animation State Machine
|
||||
|
||||
#### States (ship_animator.hpp)
|
||||
|
||||
```cpp
|
||||
enum class EstatNau {
|
||||
ENTERING, // Entering from off-screen
|
||||
FLOATING, // Floating at target position
|
||||
EXITING // Flying towards vanishing point
|
||||
};
|
||||
|
||||
struct NauTitol {
|
||||
int jugador_id; // 1 or 2
|
||||
EstatNau estat; // Current state
|
||||
float temps_estat; // Time in current state
|
||||
|
||||
Punt posicio_inicial; // Start position
|
||||
Punt posicio_objectiu; // Target position
|
||||
Punt posicio_actual; // Current interpolated position
|
||||
|
||||
float escala_inicial; // Start scale
|
||||
float escala_objectiu; // Target scale
|
||||
float escala_actual; // Current interpolated scale
|
||||
|
||||
Rotation3D rotacio_3d; // STATIC 3D rotation (never changes)
|
||||
float fase_oscilacio; // Oscillation phase accumulator
|
||||
|
||||
std::shared_ptr<Graphics::Shape> forma;
|
||||
bool visible;
|
||||
};
|
||||
```
|
||||
|
||||
#### State Transitions
|
||||
|
||||
**ENTERING** (2.0s):
|
||||
- Ships appear from beyond screen edges (calculated radially from clock positions)
|
||||
- Lerp position: off-screen → target (clock 8 / clock 4)
|
||||
- Lerp scale: 1.0 → 0.6 (perspective effect)
|
||||
- Easing: `ease_out_quad` (smooth deceleration)
|
||||
- Transition: → FLOATING when complete
|
||||
|
||||
**FLOATING** (indefinite):
|
||||
- Sinusoidal oscillation on X/Y axes
|
||||
- Different frequencies (0.5 Hz / 0.7 Hz) with phase offset (π/2)
|
||||
- Creates organic circular/elliptical motion
|
||||
- Scale constant at 0.6
|
||||
- Transition: → EXITING when START pressed
|
||||
|
||||
**EXITING** (1.0s):
|
||||
- Ships fly towards vanishing point (center: 320, 240)
|
||||
- Lerp position: current → vanishing point
|
||||
- Lerp scale: current → 0.0 (simulates Z → infinity)
|
||||
- Easing: `ease_in_quad` (acceleration)
|
||||
- Edge case: If START pressed during ENTERING, ships fly from mid-animation position
|
||||
- Marks invisible when complete
|
||||
|
||||
### Configuration (defaults.hpp)
|
||||
|
||||
```cpp
|
||||
namespace Title {
|
||||
namespace Ships {
|
||||
// Clock positions (polar coordinates from center 320, 240)
|
||||
constexpr float CLOCK_8_ANGLE = 150.0f * Math::PI / 180.0f; // Bottom-left
|
||||
constexpr float CLOCK_4_ANGLE = 30.0f * Math::PI / 180.0f; // Bottom-right
|
||||
constexpr float CLOCK_RADIUS = 150.0f;
|
||||
|
||||
// Target positions (pre-calculated)
|
||||
constexpr float P1_TARGET_X = 190.0f; // Clock 8
|
||||
constexpr float P1_TARGET_Y = 315.0f;
|
||||
constexpr float P2_TARGET_X = 450.0f; // Clock 4
|
||||
constexpr float P2_TARGET_Y = 315.0f;
|
||||
|
||||
// 3D rotations (STATIC - tuned for subtle effect)
|
||||
constexpr float P1_PITCH = 0.1f; // ~6° nose-up
|
||||
constexpr float P1_YAW = -0.15f; // ~9° turn left
|
||||
constexpr float P1_ROLL = -0.05f; // ~3° bank left
|
||||
|
||||
constexpr float P2_PITCH = 0.1f; // ~6° nose-up
|
||||
constexpr float P2_YAW = 0.15f; // ~9° turn right
|
||||
constexpr float P2_ROLL = 0.05f; // ~3° bank right
|
||||
|
||||
// Scales
|
||||
constexpr float ENTRY_SCALE_START = 1.0f;
|
||||
constexpr float FLOATING_SCALE = 0.6f;
|
||||
|
||||
// Animation durations
|
||||
constexpr float ENTRY_DURATION = 2.0f;
|
||||
constexpr float EXIT_DURATION = 1.0f;
|
||||
constexpr float ENTRY_OFFSET = 200.0f; // Distance beyond screen edge
|
||||
|
||||
// Floating oscillation
|
||||
constexpr float FLOAT_AMPLITUDE_X = 6.0f;
|
||||
constexpr float FLOAT_AMPLITUDE_Y = 4.0f;
|
||||
constexpr float FLOAT_FREQUENCY_X = 0.5f;
|
||||
constexpr float FLOAT_FREQUENCY_Y = 0.7f;
|
||||
constexpr float FLOAT_PHASE_OFFSET = 1.57f; // π/2 (90°)
|
||||
|
||||
// Vanishing point
|
||||
constexpr float VANISHING_POINT_X = 320.0f;
|
||||
constexpr float VANISHING_POINT_Y = 240.0f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration with EscenaTitol
|
||||
|
||||
#### Constructor
|
||||
|
||||
```cpp
|
||||
// Initialize ships after starfield
|
||||
ship_animator_ = std::make_unique<Title::ShipAnimator>(sdl_.obte_renderer());
|
||||
ship_animator_->inicialitzar();
|
||||
|
||||
if (estat_actual_ == EstatTitol::MAIN) {
|
||||
// Jump to MAIN: ships already in position (no entry animation)
|
||||
ship_animator_->set_visible(true);
|
||||
} else {
|
||||
// Normal flow: ships enter during STARFIELD_FADE_IN
|
||||
ship_animator_->set_visible(true);
|
||||
ship_animator_->start_entry_animation();
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Loop
|
||||
|
||||
```cpp
|
||||
// Update ships in visible states
|
||||
if (ship_animator_ &&
|
||||
(estat_actual_ == EstatTitol::STARFIELD_FADE_IN ||
|
||||
estat_actual_ == EstatTitol::STARFIELD ||
|
||||
estat_actual_ == EstatTitol::MAIN ||
|
||||
estat_actual_ == EstatTitol::PLAYER_JOIN_PHASE)) {
|
||||
ship_animator_->actualitzar(delta_time);
|
||||
}
|
||||
|
||||
// Trigger exit when START pressed
|
||||
if (checkStartGameButtonPressed()) {
|
||||
estat_actual_ = EstatTitol::PLAYER_JOIN_PHASE;
|
||||
ship_animator_->trigger_exit_animation(); // Edge case: handles mid-ENTERING
|
||||
Audio::get()->fadeOutMusic(MUSIC_FADE);
|
||||
}
|
||||
```
|
||||
|
||||
#### Draw Loop
|
||||
|
||||
```cpp
|
||||
// Draw order: starfield → ships → logo → text
|
||||
if (starfield_) starfield_->dibuixar();
|
||||
|
||||
if (ship_animator_ &&
|
||||
(estat_actual_ == EstatTitol::STARFIELD_FADE_IN ||
|
||||
estat_actual_ == EstatTitol::STARFIELD ||
|
||||
estat_actual_ == EstatTitol::MAIN ||
|
||||
estat_actual_ == EstatTitol::PLAYER_JOIN_PHASE)) {
|
||||
ship_animator_->dibuixar();
|
||||
}
|
||||
|
||||
// Logo and text drawn after ships (foreground)
|
||||
```
|
||||
|
||||
### Timing & Visibility
|
||||
|
||||
**Timeline:**
|
||||
1. **STARFIELD_FADE_IN** (3.0s): Ships enter from off-screen
|
||||
2. **STARFIELD** (4.0s): Ships floating
|
||||
3. **MAIN** (indefinite): Ships floating + logo + text visible
|
||||
4. **PLAYER_JOIN_PHASE** (2.5s): Ships exit (1.0s) + text blink
|
||||
5. **BLACK_SCREEN** (2.0s): Ships already invisible (exit completed at 1.0s)
|
||||
|
||||
**Automatic visibility management:**
|
||||
- Ships marked `visible = false` when exit animation completes (actualitzar_exiting)
|
||||
- No manual hiding needed - state machine handles it
|
||||
|
||||
### Tuning Notes
|
||||
|
||||
**If ships look distorted:**
|
||||
- Reduce rotation angles (P1_PITCH, P1_YAW, P1_ROLL, P2_*)
|
||||
- Current values (0.1, 0.15, 0.05) are tuned for subtle 3D effect
|
||||
- Angles in radians: 0.1 rad ≈ 6°, 0.15 rad ≈ 9°
|
||||
|
||||
**If ships are too large/small:**
|
||||
- Adjust FLOATING_SCALE (currently 0.6)
|
||||
- Adjust ENTRY_SCALE_START (currently 1.0)
|
||||
|
||||
**If floating motion is too jerky/smooth:**
|
||||
- Adjust FLOAT_AMPLITUDE_X/Y (currently 6.0/4.0 pixels)
|
||||
- Adjust FLOAT_FREQUENCY_X/Y (currently 0.5/0.7 Hz)
|
||||
|
||||
**If entry/exit animations are too fast/slow:**
|
||||
- Adjust ENTRY_DURATION (currently 2.0s)
|
||||
- Adjust EXIT_DURATION (currently 1.0s)
|
||||
|
||||
### Implementation Phases (Completed)
|
||||
|
||||
✅ **Phase 1**: 3D infrastructure (Rotation3D, render_shape extension)
|
||||
✅ **Phase 2**: Foundation (ship_animator files, constants)
|
||||
✅ **Phase 3**: Configuration & loading (shape loading, initialization)
|
||||
✅ **Phase 4**: Floating animation (sinusoidal oscillation)
|
||||
✅ **Phase 5**: Entry animation (off-screen → position with easing)
|
||||
✅ **Phase 6**: Exit animation (position → vanishing point)
|
||||
✅ **Phase 7**: EscenaTitol integration (constructor, update, draw)
|
||||
✅ **Phase 8**: Polish & tuning (angles, scales, edge cases)
|
||||
✅ **Phase 9**: Documentation (CLAUDE.md, code comments)
|
||||
|
||||
## Migration Progress
|
||||
|
||||
### ✅ Phase 0: Project Setup
|
||||
|
||||
Reference in New Issue
Block a user