Add screen fade-in and card deal-in animations
Cards slide in one after another when a game starts and can't be picked until they have landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83ff9511ec
commit
3794a9e2e9
4 files changed
+1751
-1159
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -13,6 +13,8 @@ import screens "src/screens"
|
||||
import themes "src/themes"
|
||||
import ui "src/ui"
|
||||
|
||||
TRANSITION_SPEED :: 4 // screen fade-ins per second
|
||||
|
||||
// Where Escape takes you from each screen (nil = quit)
|
||||
parent_screen :: proc(screen: themes.Screen) -> Maybe(themes.Screen) {
|
||||
switch screen {
|
||||
@@ -53,6 +55,7 @@ main :: proc() {
|
||||
if saved, ok := reflect.enum_from_name(memory.Difficulty, profile.data.difficulty); ok {
|
||||
difficulty = saved
|
||||
}
|
||||
transition_t: f32 = 1 // fades the screen in from the background color
|
||||
running := true
|
||||
for running && !raylib.WindowShouldClose() {
|
||||
dt := raylib.GetFrameTime()
|
||||
@@ -60,6 +63,7 @@ main :: proc() {
|
||||
if raylib.IsKeyPressed(.ESCAPE) {
|
||||
if parent, ok := parent_screen(screen).?; ok {
|
||||
screen = parent
|
||||
transition_t = 1
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
@@ -90,6 +94,11 @@ main :: proc() {
|
||||
case .MEMORY:
|
||||
next = memory.draw()
|
||||
}
|
||||
if transition_t > 0 {
|
||||
alpha := transition_t * transition_t
|
||||
raylib.DrawRectangle(0, 0, themes.SCREEN_WIDTH, themes.SCREEN_HEIGHT, raylib.Fade(themes.MENU_BACKGROUND, alpha))
|
||||
transition_t = max(transition_t - dt * TRANSITION_SPEED, 0)
|
||||
}
|
||||
ui.end_frame()
|
||||
raylib.EndDrawing()
|
||||
free_all(context.temp_allocator)
|
||||
@@ -99,6 +108,7 @@ main :: proc() {
|
||||
if deck, ok := start_deck.?; ok {
|
||||
memory.start(deck, difficulty)
|
||||
}
|
||||
if next != screen { transition_t = 1 }
|
||||
screen = next
|
||||
}
|
||||
}
|
||||
+21
-5
@@ -15,6 +15,8 @@ ROUNDNESS :: 0.16
|
||||
SEGMENTS :: 16
|
||||
|
||||
FLIP_SPEED :: 2.6 // flips per second
|
||||
DEAL_SPEED :: 3.5 // how fast a dealt card settles
|
||||
DEAL_STAGGER :: 0.12 // delay between consecutive cards, in deal_t units
|
||||
PULSE_DURATION :: 0.35 // seconds
|
||||
HOVER_LIFT :: 4
|
||||
|
||||
@@ -28,6 +30,7 @@ Card :: struct {
|
||||
matched: bool,
|
||||
flip_t: f32, // 0 = back showing, 1 = face showing
|
||||
pulse_t: f32, // counts down after a match
|
||||
deal_t: f32, // > 0 while being dealt onto the table (0 = resting)
|
||||
face_art: raylib.RenderTexture2D,
|
||||
}
|
||||
|
||||
@@ -214,6 +217,14 @@ update :: proc(card: ^Card, dt: f32) {
|
||||
if card.pulse_t > 0 {
|
||||
card.pulse_t = max(card.pulse_t - dt, 0)
|
||||
}
|
||||
if card.deal_t > 0 {
|
||||
card.deal_t = max(card.deal_t - dt * DEAL_SPEED, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Starts the deal-in animation; later cards in the deal arrive later
|
||||
start_deal :: proc(card: ^Card, order: int) {
|
||||
card.deal_t = 1 + f32(order) * DEAL_STAGGER
|
||||
}
|
||||
|
||||
start_pulse :: proc(card: ^Card) {
|
||||
@@ -241,7 +252,12 @@ draw :: proc(card: ^Card, theme: themes.Theme, clickable: bool, focused := false
|
||||
hovered := mouse_over || (clickable && focused)
|
||||
if hovered { lift += HOVER_LIFT }
|
||||
|
||||
scale: f32 = 1
|
||||
// Dealing: slide up from below and grow in
|
||||
dealt := smoothstep(1 - clamp(card.deal_t, 0, 1))
|
||||
if dealt == 0 { return }
|
||||
deal_offset := (1 - dealt) * 60
|
||||
|
||||
scale: f32 = 0.85 + 0.15 * dealt
|
||||
if card.pulse_t > 0 {
|
||||
progress := 1 - card.pulse_t / PULSE_DURATION
|
||||
scale += 0.09 * math.sin(progress * math.PI)
|
||||
@@ -249,20 +265,20 @@ draw :: proc(card: ^Card, theme: themes.Theme, clickable: bool, focused := false
|
||||
|
||||
width := card.rect.width * scale
|
||||
height := card.rect.height * scale
|
||||
center := raylib.Vector2{card.rect.x + card.rect.width / 2, card.rect.y + card.rect.height / 2 - lift}
|
||||
center := raylib.Vector2{card.rect.x + card.rect.width / 2, card.rect.y + card.rect.height / 2 - lift + deal_offset}
|
||||
|
||||
// Shadow stays on the table and spreads as the card lifts
|
||||
shadow := raylib.Rectangle{
|
||||
center.x - width * squash / 2 + 2,
|
||||
card.rect.y + card.rect.height * (1 - scale) / 2 + 4 + lift * 0.15,
|
||||
card.rect.y + card.rect.height * (1 - scale) / 2 + 4 + lift * 0.15 + deal_offset,
|
||||
max(width * squash, 2),
|
||||
height,
|
||||
}
|
||||
raylib.DrawRectangleRounded(shadow, ROUNDNESS, SEGMENTS, raylib.Fade(themes.BLACK, 0.12 - lift * 0.004))
|
||||
raylib.DrawRectangleRounded(shadow, ROUNDNESS, SEGMENTS, raylib.Fade(themes.BLACK, (0.12 - lift * 0.004) * dealt))
|
||||
|
||||
// Darken the card while it is edge-on, to fake lighting
|
||||
shade := u8(255 - (1 - squash) * 70)
|
||||
tint := raylib.Color{shade, shade, shade, 255}
|
||||
tint := raylib.Color{shade, shade, shade, u8(255 * dealt)}
|
||||
if hovered && !showing_face {
|
||||
tint = raylib.ColorBrightness(tint, 0.08)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ deal :: proc(deck_id: decks.Deck_Id, game_difficulty: Difficulty) {
|
||||
start_x := (themes.SCREEN_WIDTH - total_w) / 2
|
||||
start_y := themes.GRID_TOP + (themes.SCREEN_HEIGHT - 16 - themes.GRID_TOP - total_h) / 2
|
||||
for &c, i in active_cards() {
|
||||
card.start_deal(&c, i)
|
||||
c.rect = {
|
||||
start_x + f32(i % layout.cols) * step_x,
|
||||
start_y + f32(i / layout.cols) * step_y,
|
||||
@@ -134,6 +135,11 @@ unload :: proc() {
|
||||
for &c in cards { card.unload_face(&c) }
|
||||
}
|
||||
|
||||
// Whether the player can click or press on card i right now (it must have finished being dealt)
|
||||
can_interact :: proc(i: int) -> bool {
|
||||
return can_pick(i) && cards[i].deal_t == 0
|
||||
}
|
||||
|
||||
can_pick :: proc(i: int) -> bool {
|
||||
if i < 0 || i >= card_count { return false }
|
||||
c := &cards[i]
|
||||
@@ -190,7 +196,7 @@ handle_keyboard :: proc() {
|
||||
}
|
||||
if raylib.IsKeyPressed(.ENTER) || raylib.IsKeyPressed(.SPACE) {
|
||||
keyboard_active = true
|
||||
select_card(focus)
|
||||
if can_interact(focus) { select_card(focus) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +242,7 @@ update :: proc(dt: f32) {
|
||||
elapsed += dt
|
||||
if raylib.IsMouseButtonPressed(.LEFT) {
|
||||
for &c, i in active_cards() {
|
||||
if card.is_hovered(&c) && select_card(i) {
|
||||
if card.is_hovered(&c) && can_interact(i) && select_card(i) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -271,7 +277,7 @@ draw :: proc() -> themes.Screen {
|
||||
}
|
||||
|
||||
for &c, i in active_cards() {
|
||||
card.draw(&c, theme, can_pick(i), focused = keyboard_active && i == focus && state != .OVER)
|
||||
card.draw(&c, theme, can_interact(i), focused = keyboard_active && i == focus && state != .OVER && c.deal_t == 0)
|
||||
}
|
||||
|
||||
if state == .OVER {
|
||||
|
||||
Reference in new issue
Block a user