o hive.art transforma o tempo de espera passivo em uma experiência social ativa. a dinâmica do jogo começa com o sorteio de líderes, as 'Abelhas-Rainhas', que formam equipes (colmeias) com as pessoas ao redor usando códigos de emparelhamento exibidos na Apple TV.
o objetivo é puramente colaborativo: as colmeias recebem um tema surpresa — como 'Girafa' ou 'Espaço' — e cada jogador desenha uma parte da imagem em seu próprio iPhone. desse modo, múltiplas pessoas podem colaborar em uma produção artística coletiva, que ajuda a ressignificar o tempo de espera, fortalecer vínculos entre as pessoas em diferentes espaços e estimular a criatividade coletiva.
tecnicamente, o projeto viabiliza essa colaboração instantânea através de uma arquitetura modular compartilhada, representada por um target chamado HiveArtShared, e sincronização via Firebase Firestore. o uso do PencilKit permite a transmissão de vetores de desenho complexos em tempo real, garantindo que o traço feito no iPhone apareça instantaneamente em outros dispositivos.
o sistema gerencia estados de jogo concorrentes para manter a fluidez entre os múltiplos dispositivos da 'colmeia', assegurando que a experiência criativa seja contínua e imersiva para todos os participantes.
design choices
palette
honey#FF9500flora#9CD579bloom#A61825swarm#2E2E2E
typography
Monley/logo primário
Zebras jogam xadrez com o velho faquir
SF Pro Rounded/interface e texto
Zebras jogam xadrez com o velho faquir
some fonts used in this project are proprietary and may not display correctly if they are not installed on your system.
rationale
o design do hive.art foi cuidadosamente elaborado para refletir a temática de colaboração e criatividade do jogo aliada à ideia de abelhas em uma colmeia. a paleta de cores vibrantes, com tons de amarelo mel, verde, vermelho e preto, remete à natureza das abelhas e à energia dinâmica do processo criativo coletivo.
a tipografia escolhida, com fontes arredondadas e amigáveis, visa criar uma atmosfera acolhedora e acessível para os jogadores de todas as idades. a escolha pela SF Pro Rounded também tem um motivo técnico: considerando seu uso nativo nas plataformas Apple e sua legibilidade, a SF Pro facilita a navegação entre os dispositivos Apple TV e iPhone, garantindo uma experiência mais fluida.
o hive.art conta ainda com uma construção visual inspirada em hexágonos, a forma natural assumida pelas colmeias, além de detalhes gráficos que remetem ao universo das abelhas, como pequenas ilustrações e termos como abelha-rainha e colmeia. esses detalhes visuais e tipográficos reforçam a identidade do jogo e proporcionam uma experiência imersiva, conectando os jogadores à temática central enquanto colaboram na criação artística.
tech stack
SwiftUI
interface e lógica de jogo
tvOS
plataforma de exibição compartilhada
PencilKit
desenho em tempo real
Firebase
sincronização de dados
Combine
gerenciamento de estado reativo
code snippets
Forma Hexagonal
swift
struct Hexagon: Shape { func path(in rect: CGRect) -> Path { var path = Path() let center = CGPoint(x: rect.midX, y: rect.midY) let radius = min(rect.size.height, rect.size.width) / 2 let corners = corners(center: center, radius: radius) path.move(to: corners[0]) corners[1...5].forEach() { point in path.addLine(to: point) } path.closeSubpath() return path } func corners(center: CGPoint, radius: CGFloat) -> [CGPoint] { var points: [CGPoint] = [] for i in (0...5) { let angle = CGFloat.pi / 3 * CGFloat(i) let point = CGPoint( x: center.x + radius * cos(angle), y: center.y + radius * sin(angle) ) points.append(point) } return points }}
Vista do Coordenador
swift
struct CoordinatorView<C: CoordinatorViewable>: View { @ObservedObject var coordinator: C var body: some View { NavigationStack(path: $coordinator.navigationPath) { coordinator.getInitialPage().build(coordinator: coordinator) .navigationDestination(for: C.PageType.self) { page in page.build(coordinator: coordinator) } } .sheet(item: $coordinator.sheet) { item in item.page.build(coordinator: coordinator) } .fullScreenCover(item: $coordinator.fullScreenCover) { item in item.page.build(coordinator: coordinator) } .onAppear { coordinator.start() } }}
ViewModel Concorrente
swift
class DrawingSectionViewModel: ObservableObject { private(set) var coordinator: UIDrawingSectionViewCoordinator private var drawingService: any DrawingServiceProtocol init(drawing: UUID, drawingService: any DrawingServiceProtocol = RealtimeDBDrawingService()) { coordinator = .init() self.drawingService = drawingService Task { @MainActor in do { try await drawingService.subscribeToDrawing(id: drawing) } catch { print("Failed to subscribe to drawing \(drawing): \(error)") } } } private func onDrawingDidChange(_ drawing: PKDrawing) { Task { @MainActor in do { try await drawingService.updateDrawingData(newData: drawing) } catch { print("Error updating drawing: \(error)") } } }}
Serviço Firestore
swift
public final class FirestoreDatabaseService<T: Codable>: DatabaseServiceProtocol { public typealias Model = T private let db = Firestore.firestore() private var listeners: [UUID: ListenerRegistration] = [:] public func addListener(for collection: String, using closure: @escaping ([T], (any Error)?) -> Void) async -> UUID { let id = UUID() let listener = db.collection(collection).addSnapshotListener({ snapshot, error in guard let snapshot else { closure([], error) return } let documents = snapshot.documents.compactMap{ doc -> T? in try? doc.data(as: T.self) } closure(documents, error) }) listeners[id] = listener return id } public func addDocument(_ document: Model, to collection: String) async throws { do { try db.collection(collection).addDocument(from: document) } catch { NSLog("Error fetching documents from collection '(collection)': (error.localizedDescription)") throw error } }}
Processamento de Fila de Eventos
swift
final class RealtimeDBEventService: EventServiceProtocol { private let db: any DatabaseServiceProtocol<any Event> private let collectionPath: String func addEventListener(completion: @escaping (any Event) -> Void) async throws -> UUID { await db.addListener(for: collectionPath, using: { events, error in guard error == nil else { return } Task { @MainActor in for event in events { completion(event) try await self.db.deleteDocument(event.id.uuidString, from: self.collectionPath) } } }) }}
Orquestração Multiplayer
swift
class GameManager { func handle(event: any Event) { switch event { case let e as PlayerJoinRoomEvent: handlePlayerJoinRoomEvent(e) case let e as PlayerLeaveRoomEvent: handlePlayerLeaveRoomEvent(e) case let e as PlayerJoinHiveEvent: handlePlayerJoinHiveEvent(e) case let e as PlayerLeaveHiveEvent: handlePlayerLeaveHiveEvent(e) case let e as RoomEndStepEvent: handleRoomEndStepEvent(e) default: print("Unhandled event: \(event)") } } func handleRoomEndStepEvent(_ event: RoomEndStepEvent) { Task { @MainActor in switch event.step { case .joining: try await handleThemeReveal() case .themeReveal: try await handleLeaderReveal() case .leaderReveal: try await roomService.changeStep(to: .hivePairing) default: try await roomService.changeStep(to: .end) } } }}