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 themes "src/themes"
|
||||||
import ui "src/ui"
|
import ui "src/ui"
|
||||||
|
|
||||||
|
TRANSITION_SPEED :: 4 // screen fade-ins per second
|
||||||
|
|
||||||
// Where Escape takes you from each screen (nil = quit)
|
// Where Escape takes you from each screen (nil = quit)
|
||||||
parent_screen :: proc(screen: themes.Screen) -> Maybe(themes.Screen) {
|
parent_screen :: proc(screen: themes.Screen) -> Maybe(themes.Screen) {
|
||||||
switch screen {
|
switch screen {
|
||||||
@@ -53,6 +55,7 @@ main :: proc() {
|
|||||||
if saved, ok := reflect.enum_from_name(memory.Difficulty, profile.data.difficulty); ok {
|
if saved, ok := reflect.enum_from_name(memory.Difficulty, profile.data.difficulty); ok {
|
||||||
difficulty = saved
|
difficulty = saved
|
||||||
}
|
}
|
||||||
|
transition_t: f32 = 1 // fades the screen in from the background color
|
||||||
running := true
|
running := true
|
||||||
for running && !raylib.WindowShouldClose() {
|
for running && !raylib.WindowShouldClose() {
|
||||||
dt := raylib.GetFrameTime()
|
dt := raylib.GetFrameTime()
|
||||||
@@ -60,6 +63,7 @@ main :: proc() {
|
|||||||
if raylib.IsKeyPressed(.ESCAPE) {
|
if raylib.IsKeyPressed(.ESCAPE) {
|
||||||
if parent, ok := parent_screen(screen).?; ok {
|
if parent, ok := parent_screen(screen).?; ok {
|
||||||
screen = parent
|
screen = parent
|
||||||
|
transition_t = 1
|
||||||
} else {
|
} else {
|
||||||
running = false
|
running = false
|
||||||
}
|
}
|
||||||
@@ -90,6 +94,11 @@ main :: proc() {
|
|||||||
case .MEMORY:
|
case .MEMORY:
|
||||||
next = memory.draw()
|
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()
|
ui.end_frame()
|
||||||
raylib.EndDrawing()
|
raylib.EndDrawing()
|
||||||
free_all(context.temp_allocator)
|
free_all(context.temp_allocator)
|
||||||
@@ -99,6 +108,7 @@ main :: proc() {
|
|||||||
if deck, ok := start_deck.?; ok {
|
if deck, ok := start_deck.?; ok {
|
||||||
memory.start(deck, difficulty)
|
memory.start(deck, difficulty)
|
||||||
}
|
}
|
||||||
|
if next != screen { transition_t = 1 }
|
||||||
screen = next
|
screen = next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-5
@@ -15,6 +15,8 @@ ROUNDNESS :: 0.16
|
|||||||
SEGMENTS :: 16
|
SEGMENTS :: 16
|
||||||
|
|
||||||
FLIP_SPEED :: 2.6 // flips per second
|
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
|
PULSE_DURATION :: 0.35 // seconds
|
||||||
HOVER_LIFT :: 4
|
HOVER_LIFT :: 4
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ Card :: struct {
|
|||||||
matched: bool,
|
matched: bool,
|
||||||
flip_t: f32, // 0 = back showing, 1 = face showing
|
flip_t: f32, // 0 = back showing, 1 = face showing
|
||||||
pulse_t: f32, // counts down after a match
|
pulse_t: f32, // counts down after a match
|
||||||
|
deal_t: f32, // > 0 while being dealt onto the table (0 = resting)
|
||||||
face_art: raylib.RenderTexture2D,
|
face_art: raylib.RenderTexture2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +217,14 @@ update :: proc(card: ^Card, dt: f32) {
|
|||||||
if card.pulse_t > 0 {
|
if card.pulse_t > 0 {
|
||||||
card.pulse_t = max(card.pulse_t - dt, 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) {
|
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)
|
hovered := mouse_over || (clickable && focused)
|
||||||
if hovered { lift += HOVER_LIFT }
|
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 {
|
if card.pulse_t > 0 {
|
||||||
progress := 1 - card.pulse_t / PULSE_DURATION
|
progress := 1 - card.pulse_t / PULSE_DURATION
|
||||||
scale += 0.09 * math.sin(progress * math.PI)
|
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
|
width := card.rect.width * scale
|
||||||
height := card.rect.height * 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 stays on the table and spreads as the card lifts
|
||||||
shadow := raylib.Rectangle{
|
shadow := raylib.Rectangle{
|
||||||
center.x - width * squash / 2 + 2,
|
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),
|
max(width * squash, 2),
|
||||||
height,
|
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
|
// Darken the card while it is edge-on, to fake lighting
|
||||||
shade := u8(255 - (1 - squash) * 70)
|
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 {
|
if hovered && !showing_face {
|
||||||
tint = raylib.ColorBrightness(tint, 0.08)
|
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_x := (themes.SCREEN_WIDTH - total_w) / 2
|
||||||
start_y := themes.GRID_TOP + (themes.SCREEN_HEIGHT - 16 - themes.GRID_TOP - total_h) / 2
|
start_y := themes.GRID_TOP + (themes.SCREEN_HEIGHT - 16 - themes.GRID_TOP - total_h) / 2
|
||||||
for &c, i in active_cards() {
|
for &c, i in active_cards() {
|
||||||
|
card.start_deal(&c, i)
|
||||||
c.rect = {
|
c.rect = {
|
||||||
start_x + f32(i % layout.cols) * step_x,
|
start_x + f32(i % layout.cols) * step_x,
|
||||||
start_y + f32(i / layout.cols) * step_y,
|
start_y + f32(i / layout.cols) * step_y,
|
||||||
@@ -134,6 +135,11 @@ unload :: proc() {
|
|||||||
for &c in cards { card.unload_face(&c) }
|
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 {
|
can_pick :: proc(i: int) -> bool {
|
||||||
if i < 0 || i >= card_count { return false }
|
if i < 0 || i >= card_count { return false }
|
||||||
c := &cards[i]
|
c := &cards[i]
|
||||||
@@ -190,7 +196,7 @@ handle_keyboard :: proc() {
|
|||||||
}
|
}
|
||||||
if raylib.IsKeyPressed(.ENTER) || raylib.IsKeyPressed(.SPACE) {
|
if raylib.IsKeyPressed(.ENTER) || raylib.IsKeyPressed(.SPACE) {
|
||||||
keyboard_active = true
|
keyboard_active = true
|
||||||
select_card(focus)
|
if can_interact(focus) { select_card(focus) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +242,7 @@ update :: proc(dt: f32) {
|
|||||||
elapsed += dt
|
elapsed += dt
|
||||||
if raylib.IsMouseButtonPressed(.LEFT) {
|
if raylib.IsMouseButtonPressed(.LEFT) {
|
||||||
for &c, i in active_cards() {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +277,7 @@ draw :: proc() -> themes.Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for &c, i in active_cards() {
|
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 {
|
if state == .OVER {
|
||||||
|
|||||||
Reference in new issue
Block a user