Desarrollando un RPG simple, con QUARK

Dr. GoDKeR

El Rey y el As
Miembro del equipo
Administrador
Developer
Moderador de RRPP
Moderador de AO
Moderador de Tecnología
Moderador de Entretenimiento
Moderador de Diseño
Especialista de RRPP
Especialista de Entretenimiento
Especialista de Tecnología
Especialista de Argentum
Especialista de Diseño
Voy a explicarles como pueden armar un rpg super basico, con el objetivo de que puedan ver Quark funcionando (y entenderlo un poco).

Primero, lo que necesitan es:

  • IntellJ IDEA (es el IDE a usar, pueden usar otros, pero van a tener que ver por ustedes como configurar el proyecto de Gradle)
  • Java Development Kit 8
    Si tienen Windows 10 y alguna placa de video que ya no sea soportada (el unico caso que he visto, es el de la mia, Intel HD Graphics 3000) deberan bajar el JDK 8u45 y no actualizarlo nunca.
  • Tiled (editor de mapas generico, muy facil de usar)
  • Darle una Star al proyecto en GitHub (enserio)
Capas tengan que establecer en el IDE la ubicación de la JVM (pero si mal no recuerdo, la encuentra automáticamente)

Creando el gradle proyect
En la ventana de bienvenida, el ide les da la opcion de crear un nuevo proyecto, asi que le dan ahí, pero no van a crear un proyecto de Java, sino un proyecto de Gradle, y marcar que vamos a usar java.


y3Na0K7.png


La proxima ventana nos muestra unos campos que requiere el proyecto de gradle, simplemente agregamos "rpg" en ArtifactId y damos a siguiente.

En este paso no deberian tocar nada, pero les muestro como deberia estar.
5H58shi.png

(Tengan en cuenta que la version de java va a variar de la mia)

El proximo paso es darle un nombre al proyecto, y luego darle Finish.

Configurando las dependecias del proyecto
Ahora vamos a ir a nuestro proyecto, y abrir el archivo build.gradle, ahi dentro de dependecies van a ir las librerias (externas) que el proyecto va a utilizar.


El archivo gradle les deberia quedar asi:
Código:
apply plugin: 'java'[/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4]
sourceCompatibility = 1.8

repositories {
    mavenCentral()

    maven { url "https://jitpack.io" }

    jcenter()

    maven {
        url "http://oss.sonatype.org/content/groups/public/"
    }

    maven {
        url "http://oss.sonatype.org/content/repositories/snapshots/"
    }

}

dependencies {
    compile 'com.github.Wolftein:Quark-Engine:1.x-SNAPSHOT'
}

Nota: en dependecies yo he agregado el repositorio de Quark, y le he dicho que me de la version snapshot (mas reciente) del branch 1.x, este branch usa librerias que hacen que no se pueda compilar el proyecto a web haciendo que solo pueda ser compilado a un jar.
Este problema esta dado por un bug del "traductor" a JS, TeaVM.

Si desean utilizar el branch master en cambio, solo quiten el 1.x de la linea compile (dejando el -SNAPSHOT)

En otro momento puedo mostrar como seria el archivo de gradle para usar el proyecto para web (ya que hay que agregar las dependecias de TeaVM)

Luego vamos a la ventana de gradle:
mhoMywp.png


Se va a abrir la ventana de Gradle, y ahi le damos al boton de Refresh. Va a tardar un rato en descargar el codigo de los repositorios, compilarlos, etc.

Creando los directorios
Una vez que termine todo el trabajo vamos a crear la estructura del proyecto para poder empezar a codear.


Agregamos en la raiz del proyecto (siempre desde el IDE) una carpeta llamada "src". (Sin las comillas obviamente)
Dentro de esa, creamos una carpeta llamada "main" (apenas la creemos, el icono va a ser diferente a la carpeta src, esta nueva carpeta va a tener un cuadrito azul en su dibujo)

Y dentro de la carpeta main creamos las carpetas "java" (esta carpeta deberia ser azul) y "resources" (tendra un pequeño icono amarillo)

Algo de código
Ahora les voy a empezar por dejar el código para que puedan cargar un mapa ".tmx" de tiled (en formato csv, sin base64).


Agregan (ahora, siempre dentro de la carpeta java) un package llamado "util" y un package llamado "world".
Dentro de util, crean la clase XmlUtil y dentro ponen esto:
Código:
/**[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]* http://stackoverflow.com/a/19591302/5513642
*/
package util;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;

public final class XmlUtil {
    private XmlUtil(){}

    public static List<Node> asList(NodeList n) {
        return n.getLength()==0?
                Collections.<Node>emptyList(): new NodeListWrapper(n);
    }
    static final class NodeListWrapper extends AbstractList<Node>
            implements RandomAccess {
        private final NodeList list;
        NodeListWrapper(NodeList l) {
            list=l;
        }
        public Node get(int index) {
            return list.item(index);
        }
        public int size() {
            return list.getLength();
        }
    }
}

Esto es simplemente para comodidad.

Dentro del package world, crean la clase WorldLoader (que mas abajo les voy a dejar el código explicado) y dentro del mismo package, crean otro llamado tiled.

Dentro de este package vamos a crear las siguientes clases (respetando siempre las mayusculas)
Data, Layer, Map, TileSet, TileSetImage

Código:
Código:
package world.tiled;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
/**
* Created by Facu on 19/7/2016.
*/
public class Data {

    /**
     * The encode used by Tiled
     */
    private String encoding;

    /**
     * The grid with the gid's of the map
     */
    private String[] grid;

    public Data(String encoding, String[] grid) {
        this.encoding = encoding;
        this.grid = grid;

    }

    public String getEncoding() {
        return encoding;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public String[] getGrid() {
        return grid;
    }

    public void setGrid(String[] grid) {
        this.grid = grid;
    }
}

Código:
package world.tiled;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
/**
* Created by Facu on 19/7/2016.
*/
public class Layer {

    /**
     * The name of the layer.
     */
    private String layerName;

    /**
     * The size of the layer.
     */
    private int layerWidth, layerHeight;

    /**
     * The data grid.
     */
    private Data layerData;

    /**
     * Layer matrix with the map gid's
     */
    private int[][] tileCoordinates;

    public Layer(String layerName, int layerWidth, int layerHeight, Data layerData) {
        this.layerName = layerName;
        this.layerWidth = layerWidth;
        this.layerHeight = layerHeight;
        this.layerData = layerData;

        tileCoordinates = new int[layerWidth][layerHeight];

        /**
         * Fill the layer coords with the gid
         */
        for (int x = 0; x < layerWidth ; x++) {
            for (int y = 0; y < layerHeight; y++) {
                tileCoordinates[x][y] = Integer.parseInt(layerData.getGrid()[(x + (y * layerWidth))]);
            }
        }
    }

    public int[][] getTileCoordinates() {
        return tileCoordinates;
    }

    public void setTileCoordinates(int[][] tileCoordinates) {
        this.tileCoordinates = tileCoordinates;
    }

    public String getLayerName() {
        return layerName;
    }

    public void setLayerName(String layerName) {
        this.layerName = layerName;
    }

    public int getLayerWidth() {
        return layerWidth;
    }

    public void setLayerWidth(int layerWidth) {
        this.layerWidth = layerWidth;
    }

    public int getLayerHeight() {
        return layerHeight;
    }

    public void setLayerHeight(int layerHeight) {
        this.layerHeight = layerHeight;
    }

    public Data getLayerData() {
        return layerData;
    }

    public void setLayerData(Data layerData) {
        this.layerData = layerData;
    }
}

Código:
package world.tiled;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.impl.list.mutable.FastList;

/**
* Created by Facu on 15/7/2016.
*/
public class Map {

    /**
     * The map size (in tiles)
     */
    private int mapWidth, mapHeight;

    /**
     * The tiles size (in pixels)
     */
    private int tileWidth, tileHeight;

    private int nextObjectId;

    private String version;

    private MutableList<TileSet> tileSets;

    private MutableList<Layer> mapLayers;

    public Map(){
        tileSets = new FastList<>();
        mapLayers = new FastList<>();
    }

    public Map(int mapWidth, int mapHeight, int tileWidth, int tileHeight, MutableList<TileSet> tileSets, MutableList<Layer> mapLayers) {
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
        this.tileWidth = tileWidth;
        this.tileHeight = tileHeight;
        this.tileSets = tileSets;
        this.mapLayers = mapLayers;
    }

    public MutableList<Layer> getMapLayers() {
        return mapLayers;
    }

    public void addLayer(Layer l){
        mapLayers.add(l);

    }

    public void setMapLayers(MutableList<Layer> mapLayers) {
        this.mapLayers = mapLayers;
    }

    public int getMapWidth() {
        return mapWidth;
    }

    public void setMapWidth(int mapWidth) {
        this.mapWidth = mapWidth;
    }

    public int getMapHeight() {
        return mapHeight;
    }

    public void setMapHeight(int mapHeight) {
        this.mapHeight = mapHeight;
    }

    public int getTileWidth() {
        return tileWidth;
    }

    public void setTileWidth(int tileWidth) {
        this.tileWidth = tileWidth;
    }

    public int getTileHeight() {
        return tileHeight;
    }

    public void setTileHeight(int tileHeight) {
        this.tileHeight = tileHeight;
    }

    public int getNextObjectId() {
        return nextObjectId;
    }

    public void setNextObjectId(int nextObjectId) {
        this.nextObjectId = nextObjectId;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public MutableList<TileSet> getTileSets() {
        return tileSets;
    }

    public void addTileSet(TileSet ts){
        tileSets.add(ts);
    }

    public void setTileSets(MutableList<TileSet> tileSets) {
        this.tileSets = tileSets;
    }

    /**
     * <p>Gives the TileSet corresponding the gid</p>
     * @param gid
     * @return A TileSet if contains the given gid, <code>null</code> otherwise
     */
    public TileSet getImageByGID(int gid){

        for(TileSet ts : tileSets){
            if (gid >= ts.getFirstGid() && gid <= ts.getLastGid())
                return ts;
        }
        return null;
    }
}

Código:
package world.tiled;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
/**
* Created by Facu on 17/7/2016.
*/
public class TileSet {

    /**
     * First and last gid
     */
    int firstGid, lastGid;

    String name;

    TileSetImage image;

    int tileWidth, tileHeight, tileCount;

    int columns;

    int tileAmountWidth;

    public TileSet(int firstGid, String name, TileSetImage image, int tileWidth, int tileHeight, int tileCount, int columns) {
        this.firstGid = firstGid;
        this.name = name;
        this.image = image;
        this.tileWidth = tileWidth;
        this.tileHeight = tileHeight;
        this.tileCount = tileCount;
        this.columns = columns;
        this.tileAmountWidth = (int) Math.floor(image.getImageWidth() / tileWidth);
        this.lastGid = tileAmountWidth * (int) Math.floor(image.getImageHeight() / tileHeight) + firstGid;
    }

    public int getTileAmountWidth() {
        return tileAmountWidth;
    }

    public void setTileAmountWidth(int tileAmountWidth) {
        this.tileAmountWidth = tileAmountWidth;
    }

    public int getLastGid() {
        return lastGid;
    }

    public void setLastGid(int lastGid) {
        this.lastGid = lastGid;
    }

    public int getFirstGid() {
        return firstGid;
    }

    public void setFirstGid(int firstGid) {
        this.firstGid = firstGid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public TileSetImage getImage() {
        return image;
    }

    public void setImage(TileSetImage image) {
        this.image = image;
    }

    public int getTileWidth() {
        return tileWidth;
    }

    public void setTileWidth(int tileWidth) {
        this.tileWidth = tileWidth;
    }

    public int getTileHeight() {
        return tileHeight;
    }

    public void setTileHeight(int tileHeight) {
        this.tileHeight = tileHeight;
    }

    public int getTileCount() {
        return tileCount;
    }

    public void setTileCount(int tileCount) {
        this.tileCount = tileCount;
    }

    public int getColumns() {
        return columns;
    }

    public void setColumns(int columns) {
        this.columns = columns;
    }
}


Código:
package world.tiled;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
/**
* Created by Facu on 19/7/2016.
*/
public class TileSetImage {
    private int imageWidth, imageHeight;

    private String imageSource;

    public TileSetImage(String imageSource, int imageWidth, int imageHeight) {
        this.imageSource = imageSource.trim().substring(3); //replace by getFileName - todo

        this.imageWidth = imageWidth;
        this.imageHeight = imageHeight;
    }

    public int getImageWidth() {
        return imageWidth;
    }

    public void setImageWidth(int imageWidth) {
        this.imageWidth = imageWidth;
    }

    public int getImageHeight() {
        return imageHeight;
    }

    public void setImageHeight(int imageHeight) {
        this.imageHeight = imageHeight;
    }

    public String getImageSource() {
        return imageSource;
    }

    public void setImageSource(String imageSource) {
        this.imageSource = imageSource;
    }
}


Agregamos tambien un package (dentro del package actual, tiled) llamado "io" y dentro de este creamos la clase TmxMapReader:
Código:
package world.tiled.io;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
import org.w3c.dom.*;
import world.tiled.*;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;

import static util.XmlUtil.asList;


/**
* Created by Facu on 15/7/2016.
*/
public class TmxMapReader {


    private static String getAttributeValue(Node node, String attrName) {
        final NamedNodeMap attributes = node.getAttributes();
        String value = null;
        if (attributes != null) {
            Node attribute = attributes.getNamedItem(attrName);
            if (attribute != null) {
                value = attribute.getNodeValue();
            }
        }
        return value;
    }

    private static int getAttribute(Node node, String attrName, int def) {
        final String attr = getAttributeValue(node, attrName);
        if (attr != null) {
            return Integer.parseInt(attr);
        } else {
            return def;
        }
    }

    /**
     * Returns the first valid Node (ELEMENT_NODE) in the node childs.
     * @param node
     * @return the first ELEMENT_NODE founded, null otherwise.
     */
    private static Node getFirstValidChildNode(Node node){
        NodeList nl = node.getChildNodes();

        for(Node n : asList(nl)){
            if (n.getNodeType() == Node.ELEMENT_NODE)
                return n;
        }

        return null;
    }

    private static String[] getData(Node node){
        String value = node.getTextContent();

        return value.trim().split("[\\s]*,[\\s]*");
    }

    public Map load(InputStream in) throws Exception {
        Map map = new Map();
        Node mapNode;

        //Dom parsing
        Document doc = DocumentBuilderFactory.newInstance()
                .newDocumentBuilder()
                .parse(in);

        mapNode = doc.getDocumentElement();
        //mapNode.normalize();

        if (!"map".equals(mapNode.getNodeName()))
            throw new Exception("Not a valid tmx map.");

        /**
         * Loads the map attributes
         */
        map.setVersion(getAttributeValue(mapNode, "version"));
        map.setMapWidth(getAttribute(mapNode, "width", 0));
        map.setMapHeight(getAttribute(mapNode, "height", 0));
        map.setTileWidth(getAttribute(mapNode, "tilewidth", 0));
        map.setTileHeight(getAttribute(mapNode, "tileheight", 0));
        map.setNextObjectId(getAttribute(mapNode, "nextobjectid", 0));

        NodeList nl = doc.getElementsByTagName("tileset");


        /**
         * Loads the tilesets
         */
        for (Node n : asList(nl)) {
            map.addTileSet(new TileSet(getAttribute(n, "firstgid", 0),
                    getAttributeValue(n, "name"),
                    new TileSetImage(getAttributeValue(getFirstValidChildNode(n), "source"),
                            getAttribute(getFirstValidChildNode(n), "width", 0),
                            getAttribute(getFirstValidChildNode(n), "height", 0)),
                    getAttribute(n, "tilewidth", 0),
                    getAttribute(n, "tileheight", 0),
                    getAttribute(n, "tilecount", 0),
                    getAttribute(n, "columns", 0)));
        }

        /**
         * Loads the layers
         */
        nl = doc.getElementsByTagName("layer");

        for (Node n : asList(nl)) {
            map.addLayer(new Layer(getAttributeValue(n, "name"),
                    getAttribute(n, "width", 0),
                    getAttribute(n, "height", 0),
                    new Data(getAttributeValue(getFirstValidChildNode(n), "encoding"),
                            getData(getFirstValidChildNode(n)))));
        }

        return map;
    }
}

Ahora volvemos a la clase que habiamos creado anteriormente, WorldLoader, y la llenamos con este codigo que voy a explicar mas detalladamente para que puedan entender como funciona la carga de assets en quark.

Código:
package world;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]
import ar.com.quark.asset.AssetDescriptor;
import ar.com.quark.asset.AssetKey;
import ar.com.quark.asset.AssetLoader;
import ar.com.quark.asset.AssetManager;

import java.io.IOException;
import java.io.InputStream;

import world.tiled.Map;
import world.tiled.io.TmxMapReader;
/**
* Created by Facu on 4/7/2016.
*/
public class WorldLoader implements AssetLoader<Map, AssetDescriptor> {

    /**
     * <p>Load an asset</p>
     *
     * @param manager    the asset manager
     * @param descriptor the asset descriptor
     * @param input      the asset input
     * @return the key that represent the asset
     * @throws IOException indicates failure loading the asset
     */
    @Override
    public AssetKey<Map, AssetDescriptor> load(AssetManager manager, AssetDescriptor descriptor, InputStream input) throws IOException {
        TmxMapReader mr = new TmxMapReader();

        try {
            return new AssetKey<>(mr.load(input), descriptor);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

El truco esta en esta linea:
Código:
public class WorldLoader implements AssetLoader<Map, AssetDescriptor> {

Cuando nosotros hacemos implements AssetLoader<ClaseDelAssset, AssetDescriptor> el ide nos va a pedir que implementemos sus metodos (load), y dentro de ese método nosotros vamos a hacer toda la logica para cargar el recurso, en este caso, solo decimos que retorne un new AssetKey<>(mr.load(input), descriptor)

mr.load(input) devuelve un nuevo Map, que es el que el parser se encarga de cargar y crear.

Ahora dentro de java, tienen que crear 2 clases: Renderer y Game.

Renderer
Código:
import ar.com.quark.graphic.font.FontGlyph;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]import ar.com.quark.graphic.font.FontRenderer;
import ar.com.quark.graphic.storage.*;
import ar.com.quark.graphic.storage.factory.FactoryArrayStorage;
import ar.com.quark.graphic.storage.factory.FactoryElementStorage;
import ar.com.quark.graphic.texture.Image;
import ar.com.quark.graphic.texture.Texture;
import ar.com.quark.mathematic.Colour;
import ar.com.quark.utility.Manageable;
import ar.com.quark.utility.buffer.Float32Buffer;
import ar.com.quark.utility.buffer.UnsignedInt16Buffer;
import org.eclipse.collections.impl.factory.Lists;

import static ar.com.quark.Quark.QKGraphic;

/**
* !! DESCRIPTION !!
*/
public final class Renderer implements FontRenderer {
    /**
     * Hold all descriptor for performing rendering.
     */
    private final VertexDescriptor[] mDescriptor = new VertexDescriptor[3 * 4];

    /**
     * Hold the current buffer on which is working.
     */
    private Float32Buffer mBuffer;

    /**
     * Hold the current position of the descriptor.
     */
    private int mPosition = 0;
    private int mCount = 0;

    /**
     * <p>Initialise renderer</p>
     */
    public void initialise() {
        final FactoryElementStorage.UInt16 indices = buildIndices(500);

        for (int i = 0; i < mDescriptor.length; ++i) {
            mDescriptor[i] = new VertexDescriptor(Lists.immutable.of(buildVertices(500)), indices);
            mDescriptor[i].create();
        }
    }

    /**
     * <p>Render a texture</p>
     */
    public void render(Texture texture, float x1, float y1) {
        if (texture == null) {
            return;
        }
        final Image image = texture.getImage();

        render(texture, x1, y1, image.getWidth(), image.getHeight(), 0.0f, 0.0f,
                image.getWidth(), image.getHeight(), Colour.WHITE, Colour.WHITE, Colour.WHITE, Colour.WHITE);
    }

    /**
     * <p>Render a texture</p>
     */
    public void render(Texture texture, float x1, float y1, float x2, float y2) {
        if (texture == null) {
            return;
        }
        final Image image = texture.getImage();

        render(texture, x1, y1, x2, y2, 0.0f, 0.0f, image.getWidth(), image.getHeight(),
                Colour.WHITE, Colour.WHITE, Colour.WHITE, Colour.WHITE);
    }

    /**
     * <p>Render a texture</p>
     */
    public void render(Texture texture, float x1, float y1, float x2, float y2,
                       float tx1, float ty1,
                       float tx2, float ty2) {
        render(texture, x1, y1, x2, y2, tx1, ty1, tx2, ty2, Colour.WHITE, Colour.WHITE, Colour.WHITE, Colour.WHITE);
    }

    /**
     * <p>Render a texture</p>
     */
    public void render(Texture texture, float x1, float y1, float x2, float y2,
                       float tx1, float ty1,
                       float tx2, float ty2, Colour c0, Colour c1, Colour c2, Colour c3) {
        if (texture == null) {
            return;
        }

        flushIfAllowed(texture);

        final float x3 = x1 + x2;
        final float y3 = y1 + y2;

        final Image image = texture.getImage();
        final float u = tx1 / image.getWidth();
        final float v = ty1 / image.getHeight();
        final float u2 = (tx1 + tx2) / image.getWidth();
        final float v2 = (ty1 + ty2) / image.getHeight();


        mBuffer.write(x1).write(y1).write(c0.toPackedFloat32FormatABGR()).write(u).write(v);
        mBuffer.write(x1).write(y3).write(c1.toPackedFloat32FormatABGR()).write(u).write(v2);
        mBuffer.write(x3).write(y3).write(c2.toPackedFloat32FormatABGR()).write(u2).write(v2);
        mBuffer.write(x3).write(y1).write(c3.toPackedFloat32FormatABGR()).write(u2).write(v);
        mCount++;
    }

    /**
     * <p>Begin the execution of batch</p>
     */
    public void begin() {
        //!
        //! Acquire the descriptor
        //!
        mDescriptor[mPosition].acquire();
        mDescriptor[mPosition].getVertices().get(0).acquire();

        //!
        //! Map the buffer0
        //!
        mBuffer = (Float32Buffer) mDescriptor[mPosition].getVertices().get(0).map(Storage.ACCESS_UNSYNCHRONIZED);
    }

    /**
     * <p>End the execution of batch</p>
     */
    public void end() {
        flush(false);

    }

    /**
     * <p>Perform the flush operation</p>
     */
    public void flush(boolean restore) {
        if (mCount > 0) {
            //!
            //! Unmap the buffer
            //!
            mDescriptor[mPosition].getVertices().get(0).unmap();
            mDescriptor[mPosition].getVertices().get(0).update();

            //!
            //! Render the buffer
            //!
            QKGraphic.draw(Primitive.TRIANGLES, 0, mCount * 6, VertexFormat.UNSIGNED_SHORT);
        }

        if (restore) {
            //!
            //! Check whenever we can reuse the same descriptor.
            //!
            if (mCount > 0) {
                mPosition = (++mPosition % mDescriptor.length);

                mDescriptor[mPosition].acquire();

                mDescriptor[mPosition].getVertices().get(0).acquire();
                mBuffer = (Float32Buffer) mDescriptor[mPosition].getVertices().get(0).map(Storage.ACCESS_UNSYNCHRONIZED);
            }

        } else {
            //!
            //! Unmap the buffer
            //!
            if (mCount == 0) {
                mDescriptor[mPosition].getVertices().get(0).unmap();
            }

            //!
            //! Release the descriptor
            //!
            mDescriptor[mPosition].release();

            if (mCount > 0) {
                mPosition = (++mPosition % mDescriptor.length);
            }
        }
        mCount = 0;
    }

    /**
     * <p>Check if should flush the storage(s)</p>
     */
    private void flushIfAllowed(Texture texture) {
        if (texture.getHandle() == Manageable.INVALID_HANDLE) {
            flush(true);

            texture.create();
            texture.acquire();
            texture.update();
        } else if (!QKGraphic.isActive(texture)) {
            flush(true);

            texture.acquire();
            texture.update();
        } else if (mCount == 500) {
            flush(true);
        }
    }

    private FactoryArrayStorage.Float32 buildVertices(int count) {
        return new FactoryArrayStorage.Float32(
                StorageType.SERVER_MAPPED,
                StorageMode.STREAM_DRAW, 5 * 4 * count,
                new Vertex.Builder()
                        .add(0, 2, VertexFormat.FLOAT)
                        .add(1, 4, VertexFormat.UNSIGNED_BYTE, true)
                        .add(2, 2, VertexFormat.FLOAT).build());
    }

    private FactoryElementStorage.UInt16 buildIndices(int count) {
        final FactoryElementStorage.UInt16 indices = new FactoryElementStorage.UInt16(
                StorageType.CLIENT,
                StorageMode.STATIC_DRAW,
                6 * count);

        //!
        //! Create the static indices for the renderer.
        //!
        final UnsignedInt16Buffer data = indices.map();

        for (short i = 0, j = 0; i < data.capacity(); i += 6, j += 4) {
            data.write(j);
            data.write((short) (j + 1));
            data.write((short) (j + 2));
            data.write((short) (j + 2));
            data.write((short) (j + 3));
            data.write(j);
        }
        indices.unmap();
        return indices;
    }

    /**
     * <p>Render a {@link FontGlyph}</p>
     *
     * @param texture the` texture
     * @param x       the x1 coordinate (in screen coordinates) of the glyph
     * @param y       the y1 coordinate (in screen coordinates) of the glyph
     * @param x2      the x2 coordinate (in screen coordinates) of the glyph
     * @param y2      the y2 coordinate (in screen coordinates) of the glyph
     * @param tx1     the x1 texture coordinate (normalised) of the glyph
     * @param ty1     the y1 texture coordinate (normalised) of the glyph
     * @param tx2     the x2 texture coordinate (normalised) of the glyph
     * @param ty2     the y2 texture coordinate (normalised) of the glyph
     * @param colour  the colour
     */
    @Override
    public void drawFontGlyph(Texture texture, float x, float y, float x2, float y2, float tx1, float ty1, float tx2, float ty2, Colour colour) {
        render(texture, x, y, x2, y2, tx1, tx2, ty1, ty2, colour, colour, colour, colour);
    }
}


Game
Código:
import ar.com.quark.graphic.GraphicState;[/SIZE][/SIZE][/SIZE][/SIZE][/SIZE][/SIZE]
[SIZE=6][SIZE=4][SIZE=6][SIZE=4][SIZE=6][SIZE=4]import ar.com.quark.graphic.font.Font;
import ar.com.quark.graphic.shader.*;
import ar.com.quark.graphic.shader.data.UniformMatrix4;
import ar.com.quark.graphic.texture.Texture;
import ar.com.quark.graphic.texture.TextureFilter;
import ar.com.quark.graphic.texture.TextureFormat;
import ar.com.quark.input.InputListener;
import ar.com.quark.input.device.InputKey;
import ar.com.quark.input.device.InputMouseButton;
import ar.com.quark.mathematic.Camera;
import ar.com.quark.mathematic.ImmutableMatrix4f;

import ar.com.quark.mathematic.Vector3f;
import ar.com.quark.system.Display;
import ar.com.quark.system.DisplayLifecycle;
import ar.com.quark.system.DisplayMode;
import ar.com.quark.backend.lwjgl.system.Desktop;

import world.WorldLoader;
import world.tiled.Layer;
import world.tiled.Map;
import world.tiled.TileSet;

import static ar.com.quark.Quark.*;


public class Game implements DisplayLifecycle, InputListener {
    /**
     * Camera of the game.
     */
    private final Camera mCamera = new Camera(ImmutableMatrix4f.createOrthographic(0, 800, 600, 0, -1, 1));

    /**
     * Renderer of the game.
     */
    private Renderer mRenderer;
    private GraphicState mState = new GraphicState().setDepth(GraphicState.Flag.DISABLE);

    /**
     * Default shader
     */
    private Shader defaultShader;

    private Map mMap;

    private int xStart, yStart, xEnd, yEnd;

    public static void main(String[] args) {
        Desktop.create(new Game(),
                new Display.Preference(
                        new DisplayMode(800, 600, 32), "RPGWorld", 0, Display.Preference.FLAG_DECORATED));

    }

    /**
     * <p>Called when the display is created</p>
     *
     * @see #onDispose()
     */
    @Override
    public void onCreate() {
        QKGraphic.colour(0.0f, 0.0f, 0.0f, 1.0f);
        QKDisplay.setSynchronised(true);

        QKResources.registerAssetLoader(new WorldLoader(), "tmx");

        mMap = QKResources.load("world/1.tmx");

        QKInput.addInputListener(this);

        mRenderer = new Renderer();
        mRenderer.initialise();

        defaultShader = QKResources.load(new Shader.Descriptor("Resources/Shader/Simple2D.shader"));
        defaultShader.create();

        mCamera.setPosition(0.0f, 0.0f, 1.0f);
    }

    /**
     * <p>Called when the display has resize</p>
     *
     * @param width  the new width (in pixel) of the display
     * @param height the new height (in pixel) of the display
     */
    @Override
    public void onResize(int width, int height) {
        QKGraphic.viewport(0, 0, width, height);

        //!
        //! Update camera.
        //!
        defaultShader.<UniformMatrix4>getUniform("uProjectionView").setValue(mCamera.getViewProjection());
    }

    /**
     * <p>Called when the display require to render</p>
     *
     * @param time the time since the last render
     */
    @Override
    public void onRender(float time) {
        QKGraphic.clear(true, false, false);

        final Vector3f position = mCamera.getPosition();
        xStart = (int) (position.getX() / 32);
        yStart = (int) (position.getY() / 32);
        xEnd = (int)Math.ceil(QKDisplay.getWidth() / 32.0f);
        yEnd = (int)Math.ceil(QKDisplay.getHeight() / 32.0f);

        QKGraphic.apply(mState.setBlend(GraphicState.Blend.ALPHA));

        defaultShader.acquire();
        defaultShader.update();
        {
            mRenderer.begin();
            for (Layer l : mMap.getMapLayers()) {
                for (int y = yStart; y < yStart + yEnd; y++) {
                    for (int x = xStart; x < xStart + xEnd; x++) {
                        if (x >= 0 && x < mMap.getMapWidth() && y >= 0 && y < mMap.getMapHeight()) {
                            int gid = l.getTileCoordinates()[x][y];

                            if (gid != 0) {
                                TileSet ts = mMap.getImageByGID(gid);
                                if (ts != null) {
                                    gid -= ts.getFirstGid();

                                    int sX, sY;
                                    int w, h;

                                    sY = (int) Math.ceil(gid / ts.getTileAmountWidth());
                                    sX = gid - (ts.getTileAmountWidth() * sY);
                                    w = ts.getTileWidth();
                                    h = ts.getTileHeight();

                                    mRenderer.render(getTexture(ts.getImage().getImageSource()), x * w, y * h,
                                            w, h,
                                            sX * w, sY * h,
                                            w, h);
                                }
                            }
                        }
                    }
                }
            }
            mRenderer.end();
        }
    }

    /**
     * <p>Called when the display has been pause from a running state</p>
     *
     * @see #onResume()
     */
    @Override
    public void onPause() {

    }

    /**
     * <p>Called when the display has been resume from a pause state</p>
     *
     * @see #onPause()
     */
    @Override
    public void onResume() {

    }

    /**
     * <p>Called when the display is disposed</p>
     *
     * @see #onCreate()
     */
    @Override
    public void onDispose() {

    }

    /**
     * <p>Called when a {@link InputKey} is being held down</p>
     *
     * @param key the input key that trigger the event
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onKeyboardKeyDown(InputKey key) {
        if (key == InputKey.KEY_DOWN) {
            mCamera.translate(0.0f, 32.0f, 0.0f);
            defaultShader.<UniformMatrix4>getUniform("uProjectionView").setValue(mCamera.getViewProjection());
        } else if (key == InputKey.KEY_UP) {
            mCamera.translate(0.0f, -32.0f, 0.0f);
            defaultShader.<UniformMatrix4>getUniform("uProjectionView").setValue(mCamera.getViewProjection());
        } else if (key == InputKey.KEY_RIGHT) {
            mCamera.translate(32.0f, 0.0f, 0.0f);
            defaultShader.<UniformMatrix4>getUniform("uProjectionView").setValue(mCamera.getViewProjection());
        } else if (key == InputKey.KEY_LEFT) {
            mCamera.translate(-32.0f, 0.0f, 0.0f);
            defaultShader.<UniformMatrix4>getUniform("uProjectionView").setValue(mCamera.getViewProjection());
        }
        return false;
    }

    /**
     * <p>Called when a {@link InputKey} stop being held down</p>
     *
     * @param key the input key that trigger the event
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onKeyboardKeyUp(InputKey key) {
        return false;
    }

    /**
     * <p>Called when a {@link InputKey} typed a key</p>
     *
     * @param key the input key that trigger the event
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onKeyboardKeyType(char key) {
        return false;
    }

    /**
     * <p>Called when a {@link InputMouseButton} is being held down</p>
     *
     * @param x      the x coordinate (in screen coordinate) of the cursor
     * @param y      the y coordinate (in screen coordinate) of the cursor
     * @param button the input button that trigger the event
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onMouseButtonDown(int x, int y, InputMouseButton button) {
        return false;
    }

    /**
     * <p>Called when a {@link InputMouseButton} stop being held down</p>
     *
     * @param x      the x coordinate (in screen coordinate) of the cursor
     * @param y      the y coordinate (in screen coordinate) of the cursor
     * @param button the input button that trigger the event
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onMouseButtonUp(int x, int y, InputMouseButton button) {
        return false;
    }

    /**
     * <p>Called when the cursor has been move</p>
     *
     * @param x  the new x coordinate (in screen coordinate) of the cursor
     * @param y  the new y coordinate (in screen coordinate) of the cursor
     * @param dx the delta x coordinate (in screen coordinate) of the cursor
     * @param dy the delta y coordinate (in screen coordinate) of the cursor
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onMouseMove(int x, int y, int dx, int dy) {
        return false;
    }

    /**
     * <p>Called when the wheel has been move</p>
     *
     * @param x     the x coordinate (in screen coordinate) of the cursor
     * @param y     the y coordinate (in screen coordinate) of the cursor
     * @param delta the delta value of the wheel movement (positive means up, negative means down)
     * @return <code>true</code> if the event has been consume, <code>false</code> otherwise
     */
    @Override
    public boolean onMouseWheel(int x, int y, int delta) {
        return false;
    }

    private Texture getTexture(String filename) {
        Texture texture = QKResources.get(filename);

        if (texture == null) {
            texture = QKResources.load(
                    new Texture.Descriptor(filename,TextureFormat.COMPRESSED_RGBA, TextureFilter.POINT));
        }
        return texture;
    }
}

En la clase Game, tenemos que implementar DisplayLifecycle (y si quieren input, tambien lo implementan) luego les va a pedir que implementen todos sus metodos.

Ahora les explico para que sirven cada uno de los métodos (por si la doc no es suficientemente clara)

onCreate():
Preparamos todo lo que vayamos a necesitar en el proyecto, se llama cuando se crea el display.
Acá tenemos que registrar nuestros assetsloaders que tengamos, en este caso el del mapa:

Código:
QKResources.registerAssetLoader(new WorldLoader(), "tmx");

Ahi especificamos que registre un nuevo assetloader, le pasamos una nueva instancia de WorldLoader (que implementa assetmanager) y en el otro parametro le pasamos el formato de los archivos.

Y para cargarlos:
Código:
mMap = QKResources.load("world/1.tmx");
Tambien añadimos un inputlistener para poder trabajar con inputs:
Código:
QKInput.addInputListener(this);

Despues lo que hace es crear el render, preparar el shader y la cámara.

onResize()
Se ejecuta cuando la ventana cambia de tamaño. En el ejemplo solo se ajusta el viewport y la camara.

onRender()
Aca es donde vamos a dibujar todo, las primeras lineas son para ajustar los bucles de dibujo a la camara, luego se aplica un renderState(alpha) y le decimos al motor que vamos a usar el shader por defecto que instanciamos arriba para dibujar. dentro de las llaves debajo de update(); va a ir lo que queramos dibujar con ese shader.

Preparamos la escena con .begin(), dibujamos todo y terminamos la escena con .end();

onPause()
Es llamado cuando el motor sale del estado de ejecución a pausa.

onResume()
Inverso a arriba.

onDispose()
Cuando el motor es cerrado.

Descargan estos recursos, y lo descomprimen dentro de resources.
https://mega.nz/#!ZhQWwBxb!RJIVl-ADgAXy24idh82lAY_DypyWew02A3bE50o1a0s

Les debe quedar algo asi al final:
KTcvqAm.png


Resultado
Al final de todo, les deberia quedar asi:

3AwLSgP.png

Enlaces de interés
Si quieren entender como se parsea un mapa de tiled, entender ciertos conceptos como que es el gid, etc, pueden ver esto (usa una version vieja, cambia poco)


Saludos
 
Última edición:

Shak

Evolution
Miembro del equipo
Developer
Especialista de Argentum
Buena explicación! Wolftein me habia dado una mini explicacion para comenzar a probar Quark, pero con esto me quedo mucho más claro el manejo del mismo

////HABLEMOS SIN SABER//

Que bueno que finalmente, esté evolucionando el desarrollo del AO (Digo AO, sin discriminar a cualquier otro rpg que se quiera crear)

Lo ilógico de esto, es que la iniciativa no fue por parte del staff actual de Argentum Online, sino por gente del foro como Wolftein.

//FIN HABLEMOS SIN SABER//

bqNCF69.jpg
 

Dr. Wolftein

Computer's Scientist
Miembro del equipo
Administrador
Especialista de Tecnología
Buena explicación! Wolftein me habia dado una mini explicacion para comenzar a probar Quark, pero con esto me quedo mucho más claro el manejo del mismo

////HABLEMOS SIN SABER//

Que bueno que finalmente, esté evolucionando el desarrollo del AO (Digo AO, sin discriminar a cualquier otro rpg que se quiera crear)

Lo ilógico de esto, es que la iniciativa no fue por parte del staff actual de Argentum Online, sino por gente del foro como Wolftein.

//FIN HABLEMOS SIN SABER//

bqNCF69.jpg

Estas en lo correcto, como veras el foro/comunidad de AO se mantiene por los usuarios y no por el staff anymore.
 

Franco77

THUSING
Lei Quark y dije.. woow, q raro q haya investigado tanto y nunca me tope con esto... lo busco en google pensando q era una libreria de afuera... resulta q es de wolftein jajajajaj
 

Dunkan

Veterano
Muchísimas gracias por esta info Fer, me resultó muy útil :)

edit: le re choreaste la firma al rubio no? jajajajajaj
 
Última edición:
Arriba