Bienvenido, les saluda Miguel y para hoy les traigo un tutorial.
Índice
Aprenda los conceptos básicos de la programación de su propio protector de pantalla de Mac OS X
Los protectores de pantalla predeterminados de Apple para Mac OS X son agradables, pero se vuelven aburridos después de un tiempo. ¡Cualquier verdadero nerd tomaría el asunto en sus propias manos cuando eso suceda!
Si desea crear su propio protector de pantalla personalizado para Mac OS X, este artículo es para usted. Veremos un tutorial para hacer un protector de pantalla que emule el juego Pong . Va a ser muy simple, esto es solo para comenzar con cómo hacer un protector de pantalla. Al final, se verá así:
Paso 1: configuración del proyecto
Los protectores de pantalla para Mac OS X se desarrollan con Xcode. Lo primero que deberá hacer es crear un nuevo proyecto de Xcode con la categoría «Protector de pantalla».
Nombré a mi proyecto Pong, pero puedes nombrar el tuyo como quieras. A partir de Xcode 10.2.1, Xcode generará algunos archivos: PongView.h
,PongView.m
yInfo.plist
.
Dado que Objective-C no es el lenguaje más bonito y Swift se está volviendo más común, vamos a eliminar estos archivos Objective-C generados automáticamente y crear un nuevo archivo, PongView.swift
.
La estructura de este archivo Swift es bastante simple. Puede copiar y pegar la plantilla desde abajo:
import ScreenSaver class PongView: ScreenSaverView { // MARK: - Initialization override init?(frame: NSRect, isPreview: Bool) { super.init(frame: frame, isPreview: isPreview) } @available(*, unavailable) required init?(coder decoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Lifecycle override func draw(_ rect: NSRect) { // Draw a single frame in this function } override func animateOneFrame() { super.animateOneFrame() // Update the "state" of the screensaver in this function } }
Usaremos la función draw()
para representar el contenido de cada fotograma de la animación del protector de pantalla.
Usaremos la función animateOneFrame()
para actualizar el «estado» del protector de pantalla cada vez que se dispara el temporizador de animación (este temporizador es creado y manejado automáticamente por el sistema operativo). Es importante llamar setNeedsDisplay(bounds)
al final de esta función para que el sistema operativo sepa volver a dibujar la pantalla.
Importante
Dado que nos deshicimos de las cosas de Objective-C y usamos Swift, el proyecto Info.plist
archivo necesita ser actualizado. Prefije el valor de la clave «Clase principal» con el nombre del proyecto Xcode. Por ejemplo, mi proyecto de Xcode se llama Pong, así que cambié el valor de PongView
a Pong.PongView
.
Paso 2: implementar la lógica
El estado de nuestro protector de pantalla pong se rastreará con solo unas pocas variables simples:
private var ballPosition: CGPoint = .zero private var ballVelocity: CGVector = .zero private var paddlePosition: CGFloat = 0 private let ballRadius: CGFloat = 15 private let paddleBottomOffset: CGFloat = 100 private let paddleSize = NSSize(width: 60, height: 20)
En la función init()
, vamos a configurar la posición inicial y la velocidad de la bola. Establecemos la posición inicial en el centro de la pantalla y establecemos la velocidad inicial en un vector aleatorio con magnitud 10
.
override init?(frame: NSRect, isPreview: Bool) { super.init(frame: frame, isPreview: isPreview) ballPosition = CGPoint(x: frame.width / 2, y: frame.height / 2) ballVelocity = initialVelocity() } private func initialVelocity() -> CGVector { let desiredVelocityMagnitude: CGFloat = 10 let xVelocity = CGFloat.random(in: 2.5...7.5) let xSign: CGFloat = Bool.random() ? 1 : -1 let yVelocity = sqrt(pow(desiredVelocityMagnitude, 2) - pow(xVelocity, 2)) let ySign: CGFloat = Bool.random() ? 1 : -1 return CGVector(dx: xVelocity * xSign, dy: yVelocity * ySign) }
Ahora necesitamos algunas funciones de ayuda para determinar cuándo la pelota hace contacto con los bordes de la pantalla y / o con la paleta. Las siguientes dos funciones harán el truco:
private func ballIsOOB() -> (xAxis: Bool, yAxis: Bool) { let xAxisOOB = ballPosition.x - ballRadius <= 0 || ballPosition.x + ballRadius >= bounds.width let yAxisOOB = ballPosition.y - ballRadius <= 0 || ballPosition.y + ballRadius >= bounds.height return (xAxisOOB, yAxisOOB) } private func ballHitPaddle() -> Bool { let xBounds = (lower: paddlePosition - paddleSize.width / 2, upper: paddlePosition + paddleSize.width / 2) let yBounds = (lower: paddleBottomOffset - paddleSize.height / 2, upper: paddleBottomOffset + paddleSize.height / 2) return ballPosition.x >= xBounds.lower && ballPosition.x <= xBounds.upper && ballPosition.y - ballRadius >= yBounds.lower && ballPosition.y - ballRadius <= yBounds.upper }
La función ballHitPaddle()
no es 100% robusto para verificar el contacto entre la pelota y la paleta, lo sé. Pero para nuestros propósitos, es lo suficientemente bueno.
Ahora, usaremos estas funciones auxiliares para actualizar el «estado» de nuestro protector de pantalla en animateOneFrame()
.
override func animateOneFrame() { super.animateOneFrame() let oobAxes = ballIsOOB() if oobAxes.xAxis { ballVelocity.dx *= -1 } if oobAxes.yAxis { ballVelocity.dy *= -1 } let paddleContact = ballHitPaddle() if paddleContact { ballVelocity.dy *= -1 } ballPosition.x += ballVelocity.dx ballPosition.y += ballVelocity.dy paddlePosition = ballPosition.x setNeedsDisplay(bounds) }
Se garantiza que la paleta rastreará la posición x de la pelota. Por lo tanto, siempre hará contacto con la pelota antes de que rebote en la parte inferior de la pantalla (a menos que establezca la velocidad de la pelota muy alta, en cuyo caso podría “saltar” a través de la paleta).
Paso 3: Dibujar animaciones
Lo único que queda por hacer es sacar la pelota y remar en la draw()
función. Tenga en cuenta que primero volvemos a dibujar el fondo antes de dibujar cualquier otra cosa, de lo contrario, la bola dejaría un «rastro» al rebotar en la pantalla.
También definimos más funciones auxiliares para compartimentar la lógica del dibujo:
override func draw(_ rect: NSRect) { drawBackground(.white) drawBall() drawPaddle() } private func drawBackground(_ color: NSColor) { let background = NSBezierPath(rect: bounds) color.setFill() background.fill() } private func drawBall() { let ballRect = NSRect(x: ballPosition.x - ballRadius, y: ballPosition.y - ballRadius, width: ballRadius * 2, height: ballRadius * 2) let ball = NSBezierPath(roundedRect: ballRect, xRadius: ballRadius, yRadius: ballRadius) NSColor.black.setFill() ball.fill() } private func drawPaddle() { let paddleRect = NSRect(x: paddlePosition - paddleSize.width / 2, y: paddleBottomOffset - paddleSize.height / 2, width: paddleSize.width, height: paddleSize.height) let paddle = NSBezierPath(rect: paddleRect) NSColor.black.setFill() paddle.fill() }
Paso 4: instalar y depurar
En este punto, el protector de pantalla está listo para funcionar. Puedes encontrar todo el código aquí.
Para instalar el protector de pantalla, presione el botón «Ejecutar» en Xcode, abra el producto de compilación Pong.saver
en Finder y haga doble clic en él. La aplicación Configuración debería abrirse y se le pedirá que instale el protector de pantalla.
Si realiza cambios en el protector de pantalla y desea volver a instalarlo, es importante cerrar la aplicación Configuración antes de intentar reinstalar su protector de pantalla.
Depuración adicional
No es ideal probar su protector de pantalla mediante el tedioso proceso de abrir su producto de compilación en el Finder, cerrar la aplicación Configuración, reinstalar el protector de pantalla, etc.
Además, este método no le permite establecer puntos de interrupción, interactuar lldb
o ver la salida de la consola. ¡Pero hay una manera!
Su clase de protector de pantalla hereda de ScreenSaverView
, que a su vez hereda NSView
. Esto significa que se puede agregar a una subvista de una aplicación Mac OS X normal.
Simplemente cree un nuevo proyecto de Xcode del tipo «Cocoa App». Luego, agregue su protector de pantalla como una subvista de la NSViewController
vista principal generada por Xcode. Necesitamos crear artificialmente un temporizador para el ciclo de animación, pero es una solución bastante simple.
import Cocoa import ScreenSaver class ViewController: NSViewController { // MARK: - Properties private var saver: ScreenSaverView? private var timer: Timer? // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() addScreensaver() timer = Timer.scheduledTimer(withTimeInterval: 1.0/30, repeats: true) { [weak self] _ in self?.saver?.animateOneFrame() } } deinit { timer?.invalidate() } // MARK: - Helper Functions private func addScreensaver() { if let saver = PongView(frame: view.frame, isPreview: false) { view.addSubview(saver) self.saver = saver } } }
Cuando ejecute esta aplicación Cocoa desde Xcode, todas las herramientas normales, como los puntos de interrupción y los registros de la consola, estarán disponibles para su placer de depuración, ¡y desarrollar su protector de pantalla será mucho más fácil!
Recursos
Esta fue solo una introducción al desarrollo de un protector de pantalla para Mac OS X. Tus posibilidades solo están limitadas por la API de Apple, y se puede hacer mucho más.
- El código completo para
PongView.swift
está disponible aquí.
Añadir comentario