Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Introducción a JavaFX para el desarrollo de juegos

Scroll to top
Read Time: 18 min

() translation by (you can also view the original English article)

JavaFX es un conjunto de herramientas GUI de plataforma cruzada para Java y es el sucesor de las bibliotecas Java Swing. En este tutorial, exploraremos las características de JavaFX que lo hacen fácil de usar para comenzar a programar juegos en Java.

Este tutorial asume que ya sabes cómo codificar en Java. Si no es así, echa un vistazo a Aprender Java para Android, Introducción a la programación de computadoras con Java: 101 y 201, Head First Java, Greenfoot, o Learn Java the Hard Way para empezar.

Instalación

Si ya desarrolla aplicaciones con Java, probablemente no necesite descargar nada en absoluto: JavaFX se ha incluido con el paquete JDK (Java Development Kit) estándar desde JDK versión 7u6 (agosto de 2012). Si no ha actualizado su instalación de Java en un tiempo, diríjase al sitio web de descarga de Java para obtener la versión más reciente.

Clases básicas del marco

La creación de un programa JavaFX comienza con la clase Application, desde la que se extienden todas las aplicaciones JavaFX. Su clase principal debe llamar al método launch(), que entonces llamará al método init() ya continuación al método start(), esperará a que finalice la aplicación y, a continuación, llame al método stop(). De estos métodos, sólo el método start() es abstracto y debe anularse.

La clase Stage es el contenedor JavaFX de nivel superior. Cuando se inicia una aplicación, se crea una fase inicial y se pasa al método de inicio de la aplicación. Las etapas controlan las propiedades básicas de la ventana, como título, icono, visibilidad, resizable, modo de pantalla completa y decoraciones; El último se configura usando StageStyle. Pueden construirse Stages adicionales según sea necesario. Después de que se ha configurado una etapa y se agrega el contenido, se llama al método show().

Sabiendo todo esto, podemos escribir un ejemplo mínimo que lanza una ventana en JavaFX:

1
import javafx.application.Application;
2
import javafx.stage.Stage;
3
4
public class Example1 extends Application 
5
{
6
    public static void main(String[] args) 
7
    {
8
        launch(args);
9
    }
10
11
    public void start(Stage theStage) 
12
    {
13
        theStage.setTitle("Hello, World!");
14
        theStage.show();
15
    }
16
}

Estructuración del Contenido

El contenido en JavaFX (como el texto, las imágenes y los controles de interfaz de usuario) se organiza utilizando una estructura de datos similar a un árbol, conocida como una gráfico de escena, que agrupa y organiza los elementos de una gráfico de escena .

JavaFX Scene GraphJavaFX Scene GraphJavaFX Scene Graph
Representación de un gráfico de escena JavaFX.

Un elemento general de un gráfico de escena en JavaFX se denomina Nodo. Cada Nodo en un árbol tiene un único nodo "padre", con la excepción de un nodo especial designado como "raíz". Un Group es un nodo que puede tener muchos elementos en los nodo "secundario". Las transformaciones gráficas (traducción, rotación y escala) y los efectos aplicados a un grupo también se aplican a sus hijos. Los nodos se pueden diseñar utilizando JavaFX Cascading Style Sheets (CSS), muy similar al CSS utilizado para formatear documentos HTML.

La clase Scene contiene todo el contenido de un gráfico de escena y requiere que se establezca un nodo raíz (en la práctica, éste suele ser un grupo). Usted puede fijar el tamaño de una escena específicamente; De lo contrario, el tamaño de una escena se calculará automáticamente en función de su contenido. Un objeto de escena se debe pasar al escenario (por el método setScene()) para que se muestre.

Rendimiento de gráficos

¡La renderización de gráficos es particularmente importante para los programadores de juegos! En JavaFX, el objeto Canvas es una imagen en la que podemos dibujar texto, formas e imágenes, utilizando su objeto GraphicsContext asociado. (Para los desarrolladores familiarizados con el conjunto de herramientas Java Swing, esto es similar al objeto Graphics pasado al método paint() en la clase JFrame).

El objeto GraphicsContext contiene una gran cantidad de poderosas capacidades de personalización. Para elegir colores para dibujar texto y formas, puede establecer los colores de relleno (interior) y de trazo (borde), que son objetos Paint: estos pueden ser un solo color sólido, un degradado definido por el usuario (LinearGradient o RadialGradient) o Incluso un ImagePattern. También puede aplicar uno o más objetos Effect de estilo, como Lighting, Shadow o GaussianBlur y cambiar las fuentes del valor predeterminado utilizando la clase Font.

La clase Image hace que sea fácil cargar imágenes de una variedad de formatos de archivos y dibujarlos a través de la clase GraphicsContext. Es fácil construir imágenes generadas mediante procedimientos usando la clase WritableImage junto con las clases PixelReader y PixelWriter.

Usando estas clases, podemos escribir un ejemplo de  estilo "Hola, Mundo" mucho más digno con lo siguiente. Para la brevedad, sólo vamos a incluir el método start() aquí (saltaremos las declaraciones de importación y el método main()); Sin embargo, el código fuente de trabajo completo se puede encontrar en el repositorio de GitHub que acompaña a este tutorial.

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Canvas Example" );
4
        
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
        
9
    Canvas canvas = new Canvas( 400, 200 );
10
    root.getChildren().add( canvas );
11
        
12
    GraphicsContext gc = canvas.getGraphicsContext2D();
13
        
14
    gc.setFill( Color.RED );
15
    gc.setStroke( Color.BLACK );
16
    gc.setLineWidth(2);
17
    Font theFont = Font.font( "Times New Roman", FontWeight.BOLD, 48 );
18
    gc.setFont( theFont );
19
    gc.fillText( "Hello, World!", 60, 50 );
20
    gc.strokeText( "Hello, World!", 60, 50 );
21
    
22
    Image earth = new Image( "earth.png" );
23
    gc.drawImage( earth, 180, 100 );
24
        
25
    theStage.show();
26
}

El bucle del juego

A continuación, necesitamos hacer que nuestros programas sean dinámicos, lo que significa que el estado del juego cambia con el tiempo. Vamos a implementar un bucle de juego: un bucle infinito que actualiza los objetos del juego y hace que la escena a la pantalla, idealmente a una tasa de 60 veces por segundo.

La forma más fácil de lograr esto en JavaFX es usar la clase AnimationTimer, donde se puede escribir un método llamado handle() que se llamará a una velocidad de 60 veces por segundo, o tan cerca de esa tasa como sea posible. (Esta clase no tiene que ser usada solamente para propósitos de animación, sino que es capaz de mucho más.)

Usar la clase AnimationTimer es un poco complicado: ya que es una clase abstracta, no se puede crear directamente—la clase debe ampliarse antes de que se pueda crear una instancia. Sin embargo, para nuestros ejemplos simples, extenderemos la clase escribiendo una clase interna anónima. Esta clase interna debe definir el método abstracto handle(), que se pasará un argumento único: el tiempo del sistema actual en nanosegundos. Después de definir la clase interna, inmediatamente invocamos el método start(), que inicia el bucle. (El bucle se puede detener llamando al método stop().)

Con estas clases, podemos modificar nuestro ejemplo "Hello, World", creando una animación que consiste en la Tierra orbitando alrededor del Sol contra una imagen de fondo estrellada.

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Timeline Example" );
4
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
9
    Canvas canvas = new Canvas( 512, 512 );
10
    root.getChildren().add( canvas );
11
12
    GraphicsContext gc = canvas.getGraphicsContext2D();
13
14
    Image earth = new Image( "earth.png" );
15
    Image sun   = new Image( "sun.png" );
16
    Image space = new Image( "space.png" );
17
18
    final long startNanoTime = System.nanoTime();
19
20
    new AnimationTimer()
21
    {
22
        public void handle(long currentNanoTime)
23
        {
24
            double t = (currentNanoTime - startNanoTime) / 1000000000.0; 
25
26
            double x = 232 + 128 * Math.cos(t);
27
            double y = 232 + 128 * Math.sin(t);
28
29
            // background image clears canvas

30
            gc.drawImage( space, 0, 0 );
31
            gc.drawImage( earth, x, y );
32
            gc.drawImage( sun, 196, 196 );
33
        }
34
    }.start();
35
36
    theStage.show();
37
}

Hay formas alternativas de implementar un bucle de juego en JavaFX. Un enfoque ligeramente más largo (pero más flexible) implica la clase Timeline, que es una secuencia de animación que consiste en un conjunto de objetos KeyFrame. Para crear un bucle de juego, la Línea de tiempo debe establecerse para repetirse indefinidamente y solo se requiere un único KeyFrame, con su Duration establecida en 0.016 segundos (para alcanzar 60 ciclos por segundo). Esta implementación se puede encontrar en el archivo Example3T.java en el repo de GitHub.

Animación basada en cuadros

Otro componente de programación de juego comúnmente necesario es la animación basada en cuadros: mostrar una secuencia de imágenes en rápida sucesión para crear la ilusión de movimiento.

Suponiendo que todas las animaciones de bucle y todos los cuadros se muestran para el mismo número de segundos, una implementación básica podría ser tan simple como sigue:

1
public class AnimatedImage
2
{
3
    public Image[] frames;
4
    public double duration;
5
    
6
    public Image getFrame(double time)
7
    {
8
        int index = (int)((time % (frames.length * duration)) / duration);
9
        return frames[index];
10
    }
11
}

Para integrar esta clase en el ejemplo anterior, podríamos crear un OVNI animado, inicializando el objeto utilizando el código:

1
AnimatedImage ufo = new AnimatedImage();
2
Image[] imageArray = new Image[6];
3
for (int i = 0; i < 6; i++)
4
    imageArray[i] = new Image( "ufo_" + i + ".png" );
5
ufo.frames = imageArray;
6
ufo.duration = 0.100;

... y, dentro de AnimationTimer, añadiendo la única línea de código:

1
gc.drawImage( ufo.getFrame(t), 450, 25 ); 

... en el lugar apropiado. Para obtener un ejemplo completo de código de trabajo, vea el archivo Example3AI.java en el repo de GitHub.

Manejo de la entrada de usuario

Detectar y procesar la entrada de usuario en JavaFX es sencillo. Las acciones del usuario que pueden ser detectadas por el sistema, como pulsaciones de teclas y clics del ratón, se denominan eventos. En JavaFX, estas acciones provocan automáticamente la generación de objetos (como KeyEvent y MouseEvent) que almacenan los datos asociados (como la tecla real presionada o la ubicación del puntero del ratón). Cualquier clase JavaFX que implementa la clase EventTarget, como una Scene, puede "escuchar" eventos y manejarlos; En los ejemplos que siguen, mostraremos cómo configurar una escena para procesar varios eventos.

Mirando a través de la documentación de la clase Scene, hay muchos métodos que escuchan para manejar diferentes tipos de entrada de diferentes fuentes. Por ejemplo, el método setOnKeyPressed() puede asignar un EventHandler que se activará cuando se presione una tecla, el método setOnMouseClicked () puede asignar un EventHandler que se active cuando se presiona un botón del ratón, etc. La clase EventHandler tiene un propósito: encapsular un método (llamado handle()) que se llama cuando se produce el evento correspondiente.

Al crear un EventHandler, debe especificar el tipo de Evento que maneja: puede declarar un EventHandler<KeyEvent> o un EventHandler<MouseEvent>, por ejemplo. se vuelve verde. Además, EventHandlers a menudo se crean como clases internas anónimas, ya que normalmente sólo se utilizan una vez (cuando se pasan como argumento a uno de los métodos enumerados anteriormente).

Manejo de eventos de teclado

La entrada de usuario se procesa a menudo dentro del bucle del juego principal y, por lo tanto, se debe mantener un registro de las teclas que están actualmente activas. Una forma de lograr esto es creando un ArrayList de objetos String. Cuando una tecla es presionada inicialmente, agregamos la representación String del KeyCode de KeyEvent a la lista; Cuando se suelta la llave, la eliminamos de la lista.

En el ejemplo siguiente, el lienzo contiene dos imágenes de teclas de flecha; Cuando se pulsa una tecla, la imagen correspondiente


El código fuente está contenido en el archivo Example4K.java en el repo de GitHub.

1
public void start(Stage theStage) 
2
    {
3
        theStage.setTitle( "Keyboard Example" );
4
5
        Group root = new Group();
6
        Scene theScene = new Scene( root );
7
        theStage.setScene( theScene );
8
9
        Canvas canvas = new Canvas( 512 - 64, 256 );
10
        root.getChildren().add( canvas );
11
12
        ArrayList<String> input = new ArrayList<String>();
13
14
        theScene.setOnKeyPressed(
15
            new EventHandler<KeyEvent>()
16
            {
17
                public void handle(KeyEvent e)
18
                {
19
                    String code = e.getCode().toString();
20
21
                    // only add once... prevent duplicates

22
                    if ( !input.contains(code) )
23
                        input.add( code );
24
                }
25
            });
26
27
        theScene.setOnKeyReleased(
28
            new EventHandler<KeyEvent>()
29
            {
30
                public void handle(KeyEvent e)
31
                {
32
                    String code = e.getCode().toString();
33
                    input.remove( code );
34
                }
35
            });
36
37
        GraphicsContext gc = canvas.getGraphicsContext2D();
38
39
        Image left = new Image( "left.png" );
40
        Image leftG = new Image( "leftG.png" );
41
42
        Image right = new Image( "right.png" );
43
        Image rightG = new Image( "rightG.png" );
44
45
        new AnimationTimer()
46
        {
47
            public void handle(long currentNanoTime)
48
            {
49
                // Clear the canvas

50
                gc.clearRect(0, 0, 512,512);
51
52
                if (input.contains("LEFT"))
53
                    gc.drawImage( leftG, 64, 64 );
54
                else
55
                    gc.drawImage( left, 64, 64 );
56
57
                if (input.contains("RIGHT"))
58
                    gc.drawImage( rightG, 256, 64 );
59
                else
60
                    gc.drawImage( right, 256, 64 );
61
            }
62
        }.start();
63
64
        theStage.show();
65
    }

Manejo de eventos del mouse.

Ahora veamos un ejemplo que se centra en la clase MouseEvent en lugar de la clase KeyEvent. En este mini-juego, el jugador gana un punto cada vez que se hace clic en el objetivo


Dado que los EventHandlers son clases internas, cualquier variable que utilice debe ser final o "efectivamente final", lo que significa que las variables no pueden ser reinicializadas. En el ejemplo anterior, los datos fueron pasados al EventHandler por medio de un ArrayList, cuyos valores pueden ser cambiados sin reinicializar (a través de los métodos add() y remove()).

Sin embargo, en el caso de tipos de datos básicos, los valores no se pueden cambiar una vez inicializados. Si desea que el Administrador de eventos tenga acceso a tipos de datos básicos que se cambian en otro lugar del programa, puede crear una clase de contenedor que contenga variables públicas o métodos getter / setter. (En el ejemplo siguiente, IntValue es una clase que contiene una variable public int denominada value.)

1
public void start(Stage theStage) 
2
{
3
    theStage.setTitle( "Click the Target!" );
4
5
    Group root = new Group();
6
    Scene theScene = new Scene( root );
7
    theStage.setScene( theScene );
8
9
    Canvas canvas = new Canvas( 500, 500 );
10
11
    root.getChildren().add( canvas );
12
13
    Circle targetData = new Circle(100,100,32);
14
    IntValue points = new IntValue(0);
15
16
    theScene.setOnMouseClicked(
17
        new EventHandler<MouseEvent>()
18
        {
19
            public void handle(MouseEvent e)
20
            {
21
                if ( targetData.containsPoint( e.getX(), e.getY() ) )
22
                {
23
                    double x = 50 + 400 * Math.random(); 
24
                    double y = 50 + 400 * Math.random();
25
                    targetData.setCenter(x,y);
26
                    points.value++;
27
                }
28
                else
29
                    points.value = 0;
30
            }
31
        });
32
33
    GraphicsContext gc = canvas.getGraphicsContext2D();
34
35
    Font theFont = Font.font( "Helvetica", FontWeight.BOLD, 24 );
36
    gc.setFont( theFont );
37
    gc.setStroke( Color.BLACK );
38
    gc.setLineWidth(1);
39
40
    Image bullseye = new Image( "bullseye.png" );
41
42
    new AnimationTimer()
43
    {
44
        public void handle(long currentNanoTime)
45
        {
46
            // Clear the canvas

47
            gc.setFill( new Color(0.85, 0.85, 1.0, 1.0) );
48
            gc.fillRect(0,0, 512,512);
49
50
            gc.drawImage( bullseye, 
51
                targetData.getX() - targetData.getRadius(),
52
                targetData.getY() - targetData.getRadius() );
53
54
            gc.setFill( Color.BLUE );
55
56
            String pointsText = "Points: " + points.value;
57
            gc.fillText( pointsText, 360, 36 );
58
            gc.strokeText( pointsText, 360, 36 );
59
        }
60
    }.start();
61
62
    theStage.show();
63
}

El código fuente completo está contenido en el repo de GitHub; La clase principal es Example4M.java.

Creación de una clase básica Sprite con JavaFX

En los videojuegos, un sprite es el término para una sola entidad visual. A continuación se muestra un ejemplo de una clase Sprite que almacena una imagen y posición, así como información de velocidad (para entidades móviles) e información de ancho / altura que se usan al calcular cuadros delimitadores para propósitos de detección de colisiones. También tenemos los métodos getter / setter estándar para la mayoría de estos datos (omitidos por brevedad), y algunos métodos estándar necesarios en el desarrollo de juegos:

  • update(): calcula la nueva posición basada en la velocidad del Sprite.
  • render(): dibuja la imagen asociada al lienzo (a través de la clase GraphicsContext) utilizando la posición como coordenadas.
  • getBoundary(): devuelve un objeto JavaFX Rectangle2D, útil en la detección de colisiones debido a su método de intersección.
  • intersects(): determina si el cuadro delimitador de este Sprite intersecta con el de otro Sprite.
1
public class Sprite
2
{
3
    private Image image;
4
    private double positionX;
5
    private double positionY;    
6
    private double velocityX;
7
    private double velocityY;
8
    private double width;
9
    private double height;
10
11
    // ...

12
    // methods omitted for brevity

13
    // ...

14
15
    public void update(double time)
16
    {
17
        positionX += velocityX * time;
18
        positionY += velocityY * time;
19
    }
20
21
    public void render(GraphicsContext gc)
22
    {
23
        gc.drawImage( image, positionX, positionY );
24
    }
25
26
    public Rectangle2D getBoundary()
27
    {
28
        return new Rectangle2D(positionX,positionY,width,height);
29
    }
30
31
    public boolean intersects(Sprite s)
32
    {
33
        return s.getBoundary().intersects( this.getBoundary() );
34
    }
35
}

El código fuente completo se incluye en Sprite.java en el repo de GitHub.

Uso de la clase Sprite

Con la ayuda de la clase Sprite, podemos crear fácilmente un simple juego de recolección en JavaFX. En este juego, usted asume el papel de un maletín sensitivo cuyo objetivo es recoger los muchos bolsos del dinero que se han dejado el mentir alrededor por un dueño anterior descuidado. Las teclas de flecha mueven el jugador en la pantalla.

Este código toma mucho de los ejemplos anteriores: configurar fuentes para mostrar la partitura, almacenar la entrada de teclado con una ArrayList, implementar el bucle de juego con un AnimationTimer y crear clases de contenedor para valores simples que deben modificarse durante el bucle del juego.

Un segmento de código de particular interés involucra la creación de un objeto Sprite para el jugador (maletín) y un ArrayList de objetos Sprite para los coleccionables (bolsas de dinero):

1
Sprite briefcase = new Sprite();
2
briefcase.setImage("briefcase.png");
3
briefcase.setPosition(200, 0);
4
5
ArrayList<Sprite> moneybagList = new ArrayList<Sprite>();
6
7
for (int i = 0; i < 15; i++)
8
{
9
    Sprite moneybag = new Sprite();
10
  moneybag.setImage("moneybag.png");
11
	double px = 350 * Math.random() + 50;
12
	double py = 350 * Math.random() + 50;          
13
	moneybag.setPosition(px,py);
14
	moneybagList.add( moneybag );
15
}

Otro segmento de código de interés es la creación de AnimationTimer, que está encargado de:

  • Calcular el tiempo transcurrido desde la última actualización
  • Ajustando la velocidad del jugador dependiendo de las teclas presionadas
  • Realizar la detección de colisiones entre el jugador y coleccionables y actualizar los puntos y la lista de objetos coleccionables cuando esto ocurre (se utiliza un Iterador en lugar de ArrayList directamente para evitar una excepción de modificación simultánea al eliminar objetos de la lista)    
  • Procesando los sprites y el texto en el lienzo
1
new AnimationTimer()
2
{
3
    public void handle(long currentNanoTime)
4
	{
5
		// calculate time since last update.

6
		double elapsedTime = (currentNanoTime - lastNanoTime.value) / 1000000000.0;
7
		lastNanoTime.value = currentNanoTime;
8
		
9
		// game logic

10
		
11
		briefcase.setVelocity(0,0);
12
		if (input.contains("LEFT"))
13
			briefcase.addVelocity(-50,0);
14
		if (input.contains("RIGHT"))
15
			briefcase.addVelocity(50,0);
16
		if (input.contains("UP"))
17
			briefcase.addVelocity(0,-50);
18
		if (input.contains("DOWN"))
19
			briefcase.addVelocity(0,50);
20
			
21
		briefcase.update(elapsedTime);
22
		
23
		// collision detection

24
		
25
		Iterator<Sprite> moneybagIter = moneybagList.iterator();
26
		while ( moneybagIter.hasNext() )
27
		{
28
			Sprite moneybag = moneybagIter.next();
29
			if ( briefcase.intersects(moneybag) )
30
			{
31
				moneybagIter.remove();
32
				score.value++;
33
			}
34
		}
35
		
36
		// render

37
		
38
		gc.clearRect(0, 0, 512,512);
39
		briefcase.render( gc );
40
		
41
		for (Sprite moneybag : moneybagList )
42
			moneybag.render( gc );
43
44
		String pointsText = "Cash: $" + (100 * score.value);
45
		gc.fillText( pointsText, 360, 36 );
46
		gc.strokeText( pointsText, 360, 36 );
47
	}
48
}.start();

Como de costumbre, el código completo se puede encontrar en el archivo de código adjunto (Example5.java) en el repo de GitHub.

Próximos pasos

  • Hay una colección de tutoriales introductorios en el sitio web de Oracle, que le ayudará a aprender tareas comunes de JavaFX: Primeros pasos con aplicaciones JavaFX de ejemplo .
  • Puede estar interesado en aprender cómo usar Scene Builder, un entorno de diseño visual para diseñar interfaces de usuario. Este programa genera FXML, que es un lenguaje basado en XML que se puede utilizar para definir una interfaz de usuario para un programa JavaFX. Para esto, vea JavaFX Scene Builder: Getting Started.
  • FX Experience es un excelente blog, actualizado con regularidad, que contiene información y ejemplos de proyectos de interés para los desarrolladores de JavaFX. ¡Muchas de las demostraciones enumeradas son absolutamente inspiradoras!
  • José Pereda tiene excelentes ejemplos de juegos más avanzados construidos con JavaFX en su repositorio GitHub.
  • El proyecto JFxtras consiste en un grupo de desarrolladores que han creado componentes JavaFX adicionales que proporcionan la funcionalidad comúnmente necesaria que falta actualmente en JavaFX.
  • El proyecto JavaFXPorts le permite empaquetar su aplicación JavaFX para su implementación en iOS y Android.
  • Debe marcar las referencias oficiales de JavaFX, en particular, la guía JavaFX de Oracle y la documentación de la API.
  • Algunos libros bien revisados ​​en JavaFX incluyen Pro JavaFX 8, JavaFX 8 - Introducción por ejemplo, y, de particular interés para los desarrolladores de juegos, Beginning Java 8 Games Development.

Conclusión

En este tutorial, te he presentado a las clases JavaFX que son útiles en la programación de juegos. Trabajamos a través de una serie de ejemplos de creciente complejidad, culminando en un juego basado en sprite de estilo de colección. Ahora ya está listo para investigar algunos de los recursos enumerados anteriormente, o para sumergirse y comenzar a crear su propio juego. ¡La mejor de las suertes a ti en tus esfuerzos!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.