Blog |
Por qué necesito modificar el Plugin de Unity? Usar el SDK de Decentraland para poner entidades "manualmente" en el código de typescript puede ser un proceso lento y tedioso, especialmente si tienes que colocar muchos de ellos y tienes que comprobar que estén todos en el sitio correcto. Por eso necesitas herramientas que ayuden a construir una escena, como el Decentraland builder o el Unity to DCL export plugin. Aun así, hay ciertos límites sobre lo que puedes hacer con ellos. En esta guía vamos a ver cómo hacer modificaciones al Unity plugin para DCL para expandir el número de cosas que puedes exportar al código de Decentraland desde el editor de Unity. Antes de empezar con la guía deberías tener un mínimo de conocimientos del SDK de Decentraland, cómo exportar una escena desde Unity para DCL y algo de programación (usaremos typescript y C#).
Preparar el proyecto de Typescript
No es necesario hacer un componente propio para poner un NFTShape (que ya es un componente de por sí), pero para esta guía lo haremos igualmente, además será últil para usarlo como base para tus propios componentes más complejos. Antes de empezar a escribir el código en tu archivo game.ts, ten en cuenta que el plugin de Unity sobreescribe el archivo entero y todo tu código se perderá. Por ello debemos trabajar en un archivo separado. Crea un nuevo archivo en src/imports/components/NFT.ts con el siguiente código: //Crea un componente NFTShape con la información dada export function createNFTComponent(entity: IEntity, smartContract: string, tokenId: string){ entity.addComponent( new NFTShape('ethereum://'+smartContract+'/'+tokenId,Color3.Blue()) ) } //Añade un componente NFTdata a la entidad, crea un componente NFTcon la información dada @Component('NFTdata') export class NFTdata{ entity: IEntity //entidad del NFT smartContract: string //Smart contract del NFT tokenId: string //Token ID del NFT constructor(entity: IEntity, smartContract: string, tokenId: string){ this.entity = entity this.smartContract = smartContract this.tokenId = tokenId createNFTComponent(entity, smartContract, tokenId) } } Crea otro script en src/imports/index.ts con: export { NFTdata, createNFTComponent } from "./components/NFT" Ahora tenemos nuestro código listo para cuando el plugin exporte el game.ts Haz un script de Unity que contenga la información a exportar Crea un script vacío en Unity y ábrelo con Visual Studio (o tu editor de código favorito). En este ejemplo, solo necesitamos guardar la información del NFT, pero siéntente libre de añadir lo que necesitas para tu propio proyecto. public class nft_script : MonoBehaviour { public string smartContract; public string tokenId; } Ahora tenemos la información guardada en nuestra entidad, el último paso que falta es traducir estos datos a nuestro proyecto en typsecript. Modificando el plugin de Unity en C# Modificar el plugin puede parecer una tarea muy complicada, pero ten en cuenta que solo necesitamos añadir nuestras pequeñas piezas de código para expandir sus funcionalidades; muy sencillo de hacer si sabes dónde hacerlo. Abre SceneTraverser.cs en la carpeta del plugin de Decentraland: Assets/Decentraland/SceneTraverser.cs Encuentra la función public static ResourceRecorder TraverseAllScene y añade el siguiente código después del comentario //====== Start Traversing ====== //====== Start Traversing ====== if (exportStr != null) { exportStr.AppendLine("import { NFTdata } from \"./imports/index\"\n\n"); } Esto importará nuestra clase NFTdata al inicio de game.ts Después encuentra la función public static void RecursivelyTraverseTransform y después de exportStr.AppendFormat(NewEntityWithName, entityName, tra.name); añade: nft_script nftObject = (tra.gameObject.GetComponent("nft_script") as nft_script); if (nftObject) { exportStr.AppendFormat(SetNFT, entityName, nftObject.smartContract, nftObject.tokenId); } Último paso, encuentra dónde están declaradas las strings de exportación y añade la string del SetNFT al final. private const string SetNFT = "{0}.addComponent(new NFTdata({0}, \"{1}\", \"{2}\")) \n"; Este código comprobará si la entidad exportada tiene un nft_script y le añadirá el componente NFT data a la entidad dentro de nuestro archivo game.ts. Con todo esto terminado, tu escena está lista para ser exportada a un proyecto de typescript y tu game.ts resultante debería parecerse a esto: import { NFTdata } from "./imports/index" var entity1372n = new Entity("NFTentity") entity1372n.addComponent(new NFTdata(entity1372n, "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d", "558536")) engine.addEntity(entity1372n) entity1372n.addComponent(new Transform({ position: new Vector3(6, 1.5, 6) })) entity1372n.getComponent(Transform).rotation.set(0, 0, 0, 1) entity1372n.getComponent(Transform).scale.set(1, 1, 1) Consejos finales Puedes acceder desde cualquier parte en el proyecto a cualquier array de entidades con tus componentes personalizados, y esto puede ser útil para controlar dónde y cómo algunos componentes o comportamientos empiezan. engine.getEntitiesWithComponent(NFTdata) No necesitas hacer nuevos componentes muy complejos si no lo necesitas, por ejemplo puedes exportar desde Unity solamente la información para añadirle a una entidad en un array y aplicarle un comportamiento personalizado. Antes de empezar a hacer tus propios compontentes, echa un vistazo al sdk de Decentraland y a la librería de utilidades de Decentraland, puede que ya tengan hecho lo que estás buscando. Referencia a la API de ECS: decentraland-ecs-utils: npm install decentraland-ecs-utils Conclusión Si has entendido los pasos de ésta guía, estás list@ para hacer tus propios componentes para Decentraland y poder utilizarlos en el Editor de Unity, y será de gran ayuda para llenar tus escenas con gameplay e interacciones. Espero que te haga el proceso de desarrollo para Decentraland un poquito más fácil. Alex Picazo PROGRAMADOR Videogame programmer, love developing cool stuff. Always searching for new interesting stories.
0 Comments
Documentación oficial: Librerías e imports al proyecto: Para esto necesitarás instalar en tu proyecto de Typescript la libreria eth-connect para hacer de interfaz con los contratos de Ethereum y poder llamar a sus funciones. npm install eth-connect Además, necesitarás importar las siguientes funciones al archivo: getUserAccount para obtener la dirección de Ethereum del usuario. getProvider para crear una instancia del proveedor web3 a la interfaz de Metamask. Ambos, getUserAccount y getProvider, son proporcionados por el SDK de Decentraland. import { getProvider } from '@decentraland/web3-provider' import { getUserAccount } from '@decentraland/EthereumController' import * as EthConnect from '../node_modules/eth-connect/esm' Para que estas librerías funcionen en la previsualización de DCL en modo localhost, necesitarás pegar &ENABLE_WEB3 a la URL. Ejemplo: http://192.168.0.112:8000/?SCENE_DEBUG_PANEL&position=12%2C44&ENABLE_WEB3 Antes de empezar: Para poder realizar un pago mediante MANA necesitas varias cosas... La dirección de Ethereum a pagar el MANA, esta es la dirección que recibirá el MANA de los pagos de los usuarios: Ejemplo: const PAYMENT_ADDRESS ="0x4tU......8dY" La cantidad de MANA que el usuario pagará. Ejemplo: const MANA_PAYMENT = 10 La dirección del contrato de MANA: const MANA_ADDRESS = "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942" La dirección del contrato de MANA falso (opcional), solo si quieres primero probar transacciones con dinero falso (MANA): const FAKE_MANA_ADDRESS = "0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb" MANA contract ABI: Crea un archivo vacío en tu proyecto, "/contracts/mana.ts" y pega el MANA ABI en export const abi, o pega directamente el contenido del siguiente archivo.
<
>
export const abi = [{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"burner","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"} Contrato de MANA ABI falso (opcional)
<
>
export const abi = [ { constant: true, inputs: [], name: 'mintingFinished', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'name', outputs: [ { name: '', type: 'string' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: '_spender', type: 'address' }, { name: '_value', type: 'uint256' } ], name: 'approve', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'totalSupply', outputs: [ { name: '', type: 'uint256' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: '_from', type: 'address' }, { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' } ], name: 'transferFrom', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'decimals', outputs: [ { name: '', type: 'uint8' } ], payable: false, type: 'function' }, { constant: false, inputs: [], name: 'unpause', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_amount', type: 'uint256' } ], name: 'mint', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: '_value', type: 'uint256' } ], name: 'burn', outputs: [], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'paused', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [ { name: '_owner', type: 'address' } ], name: 'balanceOf', outputs: [ { name: 'balance', type: 'uint256' } ], payable: false, type: 'function' }, { constant: false, inputs: [], name: 'finishMinting', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: false, inputs: [], name: 'pause', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'owner', outputs: [ { name: '', type: 'address' } ], payable: false, type: 'function' }, { constant: true, inputs: [], name: 'symbol', outputs: [ { name: '', type: 'string' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' } ], name: 'transfer', outputs: [ { name: '', type: 'bool' } ], payable: false, type: 'function' }, { constant: true, inputs: [ { name: '_owner', type: 'address' }, { name: '_spender', type: 'address' } ], name: 'allowance', outputs: [ { name: 'remaining', type: 'uint256' } ], payable: false, type: 'function' }, { constant: false, inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' } ], name: 'setBalance', outputs: [], payable: false, type: 'function' }, { constant: false, inputs: [ { name: 'newOwner', type: 'address' } ], name: 'transferOwnership', outputs: [], payable: false, type: 'function' }, { anonymous: false, inputs: [ { indexed: true, name: 'to', type: 'address' }, { indexed: false, name: 'amount', type: 'uint256' } ], name: 'Mint', type: 'event' }, { anonymous: false, inputs: [], name: 'MintFinished', type: 'event' }, { anonymous: false, inputs: [], name: 'Pause', type: 'event' }, { anonymous: false, inputs: [], name: 'Unpause', type: 'event' }, { anonymous: false, inputs: [ { indexed: true, name: 'burner', type: 'address' }, { indexed: false, name: 'value', type: 'uint256' } ], name: 'Burn', type: 'event' }, { anonymous: false, inputs: [ { indexed: true, name: 'owner', type: 'address' }, { indexed: true, name: 'spender', type: 'address' }, { indexed: false, name: 'value', type: 'uint256' } ], name: 'Approval', type: 'event' }, { anonymous: false, inputs: [ { indexed: true, name: 'from', type: 'address' }, { indexed: true, name: 'to', type: 'address' }, { indexed: false, name: 'value', type: 'uint256' } ], name: 'Transfer', type: 'event' } ] Código Ahora que ya tenemos recopilada toda la información y las librerías instaladas es hora de desarrollar el código. Importa las funciones que necesites al principio del archivo. import { getProvider } from '@decentraland/web3-provider' import { getUserAccount } from '@decentraland/EthereumController' import * as EthConnect from '../node_modules/eth-connect/esm' Importa el MANA ABI (o el falso MANA ABI para hacer pruebas) import { abi } from './contracts/mana' Ahora ya puedes crear una función de pago y llamarla a uso cuando quieras hacer que el usuario realice la transacción. const PAYMENT_ADDRESS ="0x4tU......8dY" const MANA_ADDRESS = "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942" const MANA_PAYMENT = 10 function payment(callback = function(error?, result?){}) { executeTask(async () => { try { const provider = await getProvider() const requestManager = new EthConnect.RequestManager(provider) const factory = new EthConnect.ContractFactory(requestManager, abi) const contract = (await factory.at( MANA_ADDRESS )) as any const address = await getUserAccount() const res = await contract.transfer( PAYMENT_ADDRESS, MANA_PAYMENT*1000000000000000000, { from: address } ) } catch (error) { callback(error) log(error.toString()) } }) Cuando esta función es llamada, Metamask abrirá una ventana emergente pidiendo la confirmación del pago, si ocurriere algun problema con el pago, durante el pago, o el usuario rechazara este pago, la función payment() capturara un error. Si lo deseas puedes poner tu propia función para gestionar el error. payment(function(error, result){ if (!error) { //Payment success } else{ //Payment fail } }) Pruebas con pagos mediante MANA falso
Por último, necesitas cambiar la dirección de contrato de MANA colocada en el código al igual que cambiar el MANA ABI por los falsos. import { abi } from './contracts/fakemana' const MANA_ADDRESS = "0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb" Con estos cambios las pruebas en los pagos de MANA se harán bajo la Ropsten Test Network, y se enviarán a la PAYMENT_ADDRESS de la misma red también. Alex Picaxo PROGRAMADOR Videogame programmer, love developing cool stuff. Always searching for new interesting stories. Con el lanzamiento al público de Decentraland, se me dio el block out de la escena, en la cual estaba encargado de reorganizar y texturizar basandome en el moodboard para Sometimes a Thousand Twangling instruments (SaTTi, a partir de ahora). En la parte de Decentraland de SaTTi consiste en una serie de banners donde puedes cambiar los sonidos que se reproducen (los cuales se generan a partir de los dibujos), y eso cambiara la "música" dependiendo de donde pares los banners y donde estés, haciendo que el sonido se superponga de diferentes maneras y haciendo que los sonido provengan desde direcciones diferentes. Tras ver el moodboard y sabiendo de que trataba el proyecto, y porque la fecha de entrega estaba muy cerca, decidí tomar el mismo enfoque que con las texturas: generadores de capas, alfas, y otros elementos que ya existían en Painter, para crear las texturas necesarias.
Los suelos Este es el grupo de rellenos, filtros y generadores que use para crear el aspecto deseado. No compartiré los ajustes exactos porque el punto es llegar hasta la razón de porque los he usado.
Los Hexágonos Las pulidas Decide trabajar estas texturas como como un grupo de diferentes capas, en lugar de usar múltiples filtros en una sola capa, así contaba con más flexibilidad para crear variaciones, y especialmente para controlar las diferentes opacidades de los canales en cada capa. Así estas son las capas con las opciones que use para esto:
Los orgánicos De nuevo, ataje usando un grupo de capas. Estas son las capas y la opciones que use para esto:
Conclusión No todas las texturas hechas no fueron utilizadas al final, pero sirvieron para explorar la estética de la parcela y siempre podemos usarla en caso de querer actualizar la escena o desarrollar su aspecto visual más allá. El hecho de que usáramos Painter para hacer las texturas significa que podemos exportar las texturas en 4K y cambiar el tamaño para que se ajuste a las limitaciones de Decentraland ( max Resolución 512x512 por textura). Cada una de las texturas tenia más de tres variaciones hechas, cambiando la opacidad, opciones de blending, generadores y efectos te permite generar variantes muy fácilmente cuando tienes prisa. Álex GENERALISTA 3D Alejandro Bielsa is a junior 3D artist working at Polygonal Mind's in-house team. Passionate about videogames, vivid tutorial drinker and cat lover. Premisa El 20 de febrero Decentraland fue abierta al público. Para aquellos que no lo sepan, Decentraland es un mundo virtual que se apoya en el blockchain de Ethereum donde su comunidad define la utilidad y la estética de sus parcelas, creando una relación innovadora y dinámica entre propietarios y visitantes. Bajo esta premisa, el único límite es la imaginación. Tuvimos la oportunidad de formar parte del desarrollo del contenido de varias parcelas antes del lanzamiento, desde aventuras laberínticas y arte experimental sonora hasta galerías de arte, jardines e incluso un sistema de transporte. Darle forma al futuro del transporte virtual, hoy os enseñamos como creamos el Trail Transit System en el sur de Decentraland de inicio a fin, el día anterior al lanzamiento.
Antes de empezar Para este workflow, en Polygonal Mind hemos usado un plugin para Unity que puedes encontrar en GitHub [https://github.com/fairwood/DecentralandUnityPlugin/]. Este plugin nos ha permitido trabajar de forma nativa en Unity, componer y crear los escenarios sin tocar demasiado código. Es importante recalcar que no hemos usado exactamente la misma versión que puedes descargar en el link de arriba, el plugin ha sido modificado para sacarle todo el partido en nuestro caso particular y para nuestras necesidades, mejorando su rendimiento y dejándolo listo para el motor gráfico de DCL. Cada proyecto requiere un acercamiento distinto y una pipeline de producción diferente, es por eso que el plugin fue modificado conforme nos fuimos encontrando con distintos desafíos y problemas técnicos, principalmente relacionados con el rendimiento una vez deployeados en Decentraland. #00 - Conceptos básicos de un sistema de transporte
Tan pronto como empezamos a definir sobre papel todo el trabajo que teníamos que hacer decidimos inclinarnos por hacer que todo el entorno fuera una estructura megalítica con un ascensor al que se podía acceder desde el suelo. El problema del tren girando a la hora de volver fue solucionado teniendo dos vagones yendo en direcciones opuestas para evitar tener que esperar demasiado tiempo a un tren. Esto creaba un problema visual y de diseño: necesitábamos 4 plataformas por nivel y estos niveles debían estar conectados, para que el usuario pudiera subir libremente por toda la estructura. Hice una mood board visual y la propuse para basar todo el aspecto visual para poder empezar con los primeros diseños. Para el estilo artístico me inspiré mucho en los entornos y diseño artístico de Bioshock, y en el sistema de transporte de Fallout 4. Ambos juegos tienen una influencia muy grande en el retrofuturismo, pero apoyándose en distintos estilos artísticos. Bioshock* tiene unos preciosos entornos, muy influenciados por el art-deco, mezclados con entornos monumentalisticos de hierro forjado. Respecto a Fallout 4**, tiene un mundo muy bien construido basado en un futurismo desde los ojos de la gente en los 50 y los 60, lleno de detalles. Bioshock hace un uso magistral del canal metálico en sus entornos y esto fue algo decisivo como inspiración para mí cuando decidimos tomar nuestra propia aproximación al entorno art-deco. Una vez los límites del arte fueron establecidos, pudimos empezar a trabajar. Vamos a hacer el desglose de cómo se crearon los assets, empezando por la primera versión del tren, el que va encima del rail; seguido del disñeo de la estación y su integración con su entorno. Y por último pero no menos importante enseñaremos la iteración con el segundo tren, el situado bajo el rail. *Bioshock, desarrollado por 2K Boston / Irrational Games y publicado por Take Two. **Fallout 4, desaroolado por Bethesda Game Studios y publicado por Bethesda Softworks. #01 El tren: dándole forma al vagón virtual Para un terreno de 55 parcelas the Scene Limitations were pretty high for a environment that was basically a train system so we decided to go for a high-polygon mesh level of detail instead of a more blocky low-res shape resolution. The limits were a maximum of 550k tris, 116 materials and 58 textures (512 square max) to be implemented along the scene. El diseño resultante que salió basado en una sola imagen encajaba perfectamente en la idea que llevábamos para el "feeling" del entorno, pero nos salió el tiro por la culata durnate el desarrollo por ser una imagen dibujada a mano, sin referencia real y con perspectiva forzada de una vista del futuro durante los años 50. Así como el diseño iba avanzando, los problemas empezaron a manifestarse en la zona de la rueda (porque deformaba la estructura principal) y cómo el tren se iba a mover en el raíl. Esto llevó a cambiar el diseño, favoreciendo una cubierta para la rueda más gande y más suavizada, y un raíl también más ancho que se adaptaba a una rueda nueva, también más ancha. Si vamos al interior, se puede observar que el diseño del vagón está basado en concepts de los años 50 y el uso del contraste entre madera y hierro. Lo más complicado fue darle "controles" al tren, lo que queire decir que hacía falta incluir un pane lde mandos o algo similar, con palancas y botones. El diseño de materiales consiste en utilizar principalmente dos materiales, uno para el exterior y otro para los props del interior, y uno extra para el emisivo que tenía el panel de control. De nuevo la falta de contenido dado que las parcelas iban a funcionar como un sistema de transporte significaba que podíamos dedicarle más mimo, detalle y recursos, con el límite de las texturas de 512x512 px. Tras unas cuantas iteraciones teníamos nuestro primer modelo del tren viajando por el rail y listo para hacer las primeras pruebas. #02 La estación: un balance entre composición y utilidad Como hemos dicho antes, la estación tenía que tener dos niveles de plataformas: una a 16 metros con conexiones a ambos lados de la parcela y otra a la altura de 32 metros. Todas las plataformas tenían que estar conectadas de uan forma u otra para ser jugable para los jugadores y debía ser sencillo girar hacia el lado contrario en caso de que te hubieras pasado una parada, como en un sistema de metro real. Además, desde un punto de vista artístico, la idea era evitar en la medidad de lo posible el uso de escaleras o caminos largos para poder encajarlo con la visión más mecánica y futurista de lo que un transporte debería ser que teníamos. Tomando los entornos de Bioshock y concept art como ejemplo en materiales y composición, empecé a hacer los primeros esbozos de la forma que tendría la estación. Empecé a definir el diseño del ascensor, basado en cómo Bioshock hace uso del art-deco mezclado con steampunk para sus ascensores. Decidí seguir un diseño similar desde 0 hasta el look final del modelo. El diseño terminó siendo de concepto abierto y sin apredes, para evitar la claustrofobia y que sirviera como un punto de referencia en detalle de modelo y material. Desde el principio quise hacer un sistema de apertura y cierre mecánico para el asvensor así que empecé a planificar cómo iba a ser la animación. Tuve que tener en cuenta dónde tenían que estar los pivotes y como tenía que ser la jerarquía para que la animación se realice correctamente. Una vez el ascensor estaba terminado y situado en su posición final, se colocó la carcasa alrededor para permitir el acceso al ascensor. Los andenes empezaban a tomar forma. Empezamos el desarrollo de los andenes con idea de cómo iban a funcionar desde un punto de vista jugable, así que era crucial diseñar cómo la estación iba a ser jugada y experienciada por el jugador primero. Así que hice un level design o graybox definiendo los límites jugables y anticipando la parte visual que vendría después y todos los problemas que podrían venir después, y así es como encontramos y lidiamos con los problemas de conexión que había entre el suelo y el andén de 16 metros, así como otros problemas que complicaban la integración visual de todo el set. Siguiendo la idea original, el concepto era subir al usuario del andén de 16 metros al de 32 con un ascensor situado encima del andén superior. Esto nos hizo darnos cuenta de todos los problemas de integración visual y de gameplay y decidimos descartarlo rápidamente porque sobrecargaba la escena de polígonos. En su lugar, encontramos la solución en una plataforma entre los dos andenes principales. Con los últimos retoques era hora de pintar y diseñar sus materiales. Me decidí por usar texturas tileables seamless para los objetos más grandes, porque me daría más control de los detalles. Para los andenes y el ascensor, hice sus UVs y lo empaqueté en un atlas, maximizando el control sobre el detalle y mejorando los resultados del apartado gráfico. También nos aprovechamos del canal de metalness/smoothness para que el entorno brillase de forma natural. #03 El segundo tren: Reinventando el mismo concepto Cuando las estaciones estaban colocadas y funcionando, era hora de incorporar un segundo tren al rail. Este diseño estaba planeado para estar colgado del mismo rail que el vagón original pero con modificaciones claramente aparentes. La principal limitación para remodelar el exterior fue mantener las UVs para no tener que rehacer el texturizado, porque ambos trenes usan los mismos materiales. #04 Límites en los materiales y optimización de assets Los trenes estaban acabados y colocados en su sitio, las estaciones estaban en su posición final y los primeros tests daban buenos resultados, porque para este proyecto decidimos usar una aproximación por prefabs, y A. Picazo, nuestro programador, sugirió e implementó algunas modificaciones al plugin para reconocer las mismas meshes e instanciarlos en lugar de exportar cada uno de ellos individualmente como GLTFs diferentes. El rendimiento en general mejoró muchísimo y era hora entonces de recortar un poco en el campo de las texturas, no porque realmente fuera necesario, sino porque siempre es buena idea revisitar el trabajo realizado y mejorar o borrar archivos irrelevantes. Conforme fui adentrándome más en la relación entre uso de textura y resultado final concluí que el Normal map no era realmente necesario para darle el look que buscábamos. Es porque el ambient light de Decentraland MATA este tipo de microdetalle y la luz direccional no ayuda realmente en estas situaciones. Así que los normal maps fueron eliminados de todos los materiales que no necesitasen simular información de bump. I must say, that one thing that really helped the graphical level we accomplished in this scene was the use of the metallic/smoothness channel, as it meant a discrete type of shinning without being visually invasive as it is the glow channel. Packing ORM de Texturas y Decentraland Decentraland recomienda la optimización de texturas usando texutras RGB empaquetadas como ORM (Occlusion, Roughness, Metalness). Es una buena idea cuando trabajas con un workflow PBR pero para este proyecto evitamos usar Ambient Occlusion y en cambio lo bakeamos en el albedo, con un modo de fusión Multiplicar. Más info de este tipo de texturas se puede encontrar en la documentación de Khronos Group: Creando Colliders Los colliders en Decentraland se crean superponiendo una malla en el modelo que quieras que tenga colisión y añadiéndole el sufijo "_collider" (en minúscula, MUY IMPORTANTE) al nombre de la mesh que quieras usar de collider. Es importante no tener el mismo nombre dos veces en la misma jerarquía o que el padre e hijo con el mismo nombre haría que el GLTF export crashee. La jerarquía y las convenciones de nombramiento (naming conventions) son muy importantes. En cuanto a rendimiento se refiere, recomiendo usar una versión low-res de la malla cuando se cree el collider porque Decentraland crea Mesh Colliders basados en la malla con el sufijo que comento antes. Si eres familiar con Unity, sabrás que los Mesh Colliders no son lo más adecuado para escenas complejas y lo mejor es evitar colliders con formas muy complejas e intentar hacer sus boundaries. Problemas básicos al usar el wokflow de Unity Decentraland corre en una versión modificada del motor de Unity con modificaciones centradas en sus actuales y futuras características de la plataforma. Esto es algo bueno porque puedes ver cómo está quedando tu proyecto en tiempo real dentro de Unity, y lo parecido que quedará una vez deployeado. Aunque hay diferencias visuales significativas por el Ambient Light y Directional Light dentro de Decentraland. A pesar de la mejora de eficiencia, hacerlo significa que trabajar con el editor de Unity haya dos limitaciones a tener en cuenta. La mayor parte de los componentes no se exportarán por el formato GLTF y por ello no se verán tras el deploy: sistemas de partículas, luces, cámaras, componentes 2d, colliders y vfx no funcionarán. Los GTLFs solo te dejan tener los mesh renderer, materiales y un Animator con funcionalidad limitada. A tener en cuenta es que el eje X en Unity está flippeado y puede dar lugar a colocar cosas fuera de lugar, porque DCL n otiene el eje invertido. X = -X Además, el exporter no se lleva bien con escenas muy grandes y puede hacer que el editor se ralentice. Lo recomendable con escenas grandes es tener plugin de DCL escondido mientras se edita y abrirlo para evaluar las entidades y tener en mente si te estás saliendo de los límites de la parcela. #05 Fondos Tan pronto como las estaciones principales estuvieron colocadas en su sitio y el transporte era funcional, era hora de desarollar los alrededores del suelo. Me decidí por algo casi desértico, sin plantas, porque quería evitar a toda costa pasarme con los polígonos y quería mejorar el detalle de la experiencia del tren, y no empeorarla. Esto fue lo que decidimos hacer, el entorno rocoso fue detallado con algunas áreas de descanso y tuberías decorando los caminos. Las áreas de descanso reusan las texturas de los props de la estación. Se crearon 3 materiales más para la suciedad, las rocas y las estructuras de cristal. #06 Arreglos finales Durante la parte final del desarrollo y con el lanzamiento de Decentraland a la vuelta de la esquina, KnownOrigin decidió ser el anfitrión del art que se expondría en las estructuras para ser visto por el público. Un numero limitado de piezas fueron seleccionadas para ponerlas a lo largo del rail, permitiendo que fueran vistas mientras se viaja en el tren de una estación a otra y mientras se espera al tren. #07 Conclusión Este entorno fue un desafío enorme para el equipo, porque necesitábamos desarollar un sistema personalizado para que le tren se moviera y se parara en todas las paradas. Todas mis fleicitaciones a A. Picazo por conseguir que quede así de bonito. Para mí, que fui quien estuvo a cargo de todo el entorno, significó mucho porque pude darle vida a un diseño genial basado en dos juegos y un estilo artístico que me encantan. Además hubo bajas limitaciones en cuanto al apartado gráfico porque acabamos reusando muchos assets, eso sí, sin hacer la composición aburrida o repetitiva. Esto me dio la libertad de mejorar la apareciencia añadiendo geometría y acercándome a un diseño de materiales realistas, dándole un toque extra de detalles a una plataforma en la que reina el flat-shading y el low-poly en los entornos como es Decentraland. Kourtin ARTISTA DE ENTORNOS I purr when you're not looking. I'm passionate about environments and all the techie stuff to make them look rad. Learning and improving everyday to be a better hooman.
La idea
Cuando empezamos a esbozar el proyecto ya teníamos claras algunas las bases: sensación de juego arcade, fácil de jugar pero difícil de dominar... varios juegos que encajaban ese espíritu eran los clásicos de Nintendo como Zelda y Super Mario.
Nos decantamos por un estilo de jugabilidad en concreto que se encontraba en The Legend of Zelda: Majora's Mask, basándonos en un nivel en concreto anotamos varias ideas que podríamos simular en Decentraland.
Limitaciones en Decentraland
En otros artículos hemos hablado de las limitaciones que nos encontramos en Decentraland, normalmente cuanto más simple sea la forma menor la probabilidad será de encontrarnos defectos en su render.
De todas formas, en Tomb Chaser queríamos dar un aspecto megalítico, de forma que podíamos jugar mejor enrevesando la forma y la distribución de los espacios. Esta principal característica la tuvimos en cuenta a la hora de determinar el flujo de trabajo del proyecto. Desarrollamos para la plataforma usando Unity, de esta forma las composiciones pueden ser fácilmente diseñadas y tener una aproximación bastante certera de como se verá en futuras pruebas y demostraciones previas a su forma final. El plugin DCL Export que usamos nos ayuda a transformar toda la información y metadata de Unity al formato GTLF listo para Decentrlaand. En busca de acelerar el proceso de creación hacemos uso de los Unity Prefabs, ya preparados para tener colisiones en la plataforma. Durante el proceso mediante prueba-error pudimos aprender con más profundidad cómo interactuaba el motor con nuestros assets. Por ejemplo, los GTLFs están preparados para pesar poco y suponer poca carga de procesado en comparación a otros formatos estándar como .fbx/.obj/.dae ... pero por otra parte suponen otras restricciones como su imposibilidad para transportar información de luces u otros componentes más allá de forma de malla, asignación de materiales y animación de transformaciones. Una vez dentro de Decentraland, tenemos otros impedimentos visuales como una acusada intensidad de luz ambiental añadida a la luz direccional que, obviamente, funciona one sided con las mallas en escena. En Decentraland lo más difícil es que algo quede oscuro.
El diseño del suelo
Para diseñar un laberinto has de pensar en su principio y su final, el camino que los conecta y luego añadir todo lo demás. De los puntos positivos de trabajar en una LAND tan grande para este proyecto fue su gran extendida altura máxima.
Decentraland hace cálculos para determinar tus limitaciones según el número de LANDs que estés haciendo uso en el proyecto, la fórmula es la siguiente: log2(n+1) x 20
Donde n representa al número de parcelas.
Puedes encontrar más información siguiendo los siguiente enlaces:
Una tierra como esta de 25 parcelas suponía tener a nuestra disposición 94 metros de altura, esto nos daba amplia libertad para crear diferentes alturas con diferentes recorridos y diseños.
Con estas amplias restricciones decidimos dedicar cada planta a un estilo de jugabilidad, algunas dedicadas a eventos de habilidad, correr o agilidad visual. Por ejemplo, la primera zona se basaba en la velocidad y la segunda en los reflejos que tenia el jugador para poder avanzar sin perder al fantasma.
Creación de las texturas
Cuando ya tuvimos el diseño general, era hora de pensar en su aspecto visual y la creación de las consecuentes texturas. Decentraland tiene una limitación de 512px por textura creada. El número que puedes hacer uso también esta delimitado según la cantidad de parcelas que forman parte de tu estado.
Para ponerlo en perspectiva, un material PBR básico hace uso de 3 texturas RGB+A mínimo: Albedo/Transparency (RGBA) map, Normal (RGB) map and Metallic/Smoothness/Occlusion map (RGBA). Para su desarrollo hicimos uso de dos técnicas de optimización de texturas: el uso de Trim Textures y Tileable Textures, ambas desgranamos a continuación.
Mediante el uso de este tipo de texturas no deberías encontrar ningún problema visual más allá de los mencionados. El principal enemigo de las tileable textures son los patrones de formas y colores, que pueden suponer una desmejora visual. Cuanto más neutra sea la composición más fácil será de mimetizarse en grandes superficies.
Trim Textures (Trim, eng. : Recortar) son similares a las Tileable Textures pero, mientras que las Tileable Textures hacen uso de su repetición extensiva mediante la ampliación de las coordenadas UVs, las Trim Textures hacen gala de "doblarse" sobre si mismas o repetirse la malla en pequeños trozos para crear el efecto de repetición. Esta situación supone un aumento de polígonos para en las UVs doblarse sobre si mismo pero su rendimiento no se ve necesariamente afectado.
Iluminando sin luz: Planos transparentes
Si decides juntar las texturas de las llamas u otros elementos semitransparentes con la información de bits de texturas opacas como la de las columnas, lo más probable es que al marcar el tipo de shader como "Fade" en Unity se generen una serie de defectos visuales no paliables sin acceder al render queue. Como este error es común debido a que las transparencias son lo último en renderizar en un espacio 3D, deberíamos siempre separar los objetos por materiales según si van a hacer uso de transparencias o no.
Ambiente oscuro a plena luz
El nulo control sobre la iluminación en las parcelas de Decentraland no te permite disponer del abanico de herramientas que Unity tiene para crear un ambiente correctamente iluminado, pero siempre hay formas de doblegar el sistema a tu voluntad, o al menos acercarnos lo máximo a nuestra visión de como queremos que sea la mazmorra.
En este proyecto toda la acción se desarrolla en un ambiente cerrado y sin apenas iluminación del exterior, pero la presencia de una luz ambiental tan fuerte lo complica bastante. Primero de todo oscurecemos las texturas, para falsificar esa falta de luz hacemos gala de una capa oscura adicional en modo multiply desde Photoshop. Mediante el uso de materiales emisivos de forma puntual podemos crear esa sensación de iluminación que realmente no existe. Además de las ya mencionadas limitaciones técnicas tenemos que poner en relieve un hecho básico en el desarrollo de entornos 3D: la luz atraviesa normales invertidas (en valor 0). Esto significa que para evitar que la luz entrara en la mazmorra había que colocar un plano oscuro mirando hacia el otro lado para cubrir cualquier posibilidad de que el valor del bias en la luz se colara en los pasillos.
Llegados al punto de tener el ambiente oscuro que deseábamos pasamos a añadir los puntos de luz que harán de contraste. Para ello hicimos uso de una forma inteligente y ligera del mapa de emisivos en diferentes assets en escena. De esta forma creábamos una imposición de color sin sacrificios visuales provocados por el post-effect automático de bloom de Decentraland.
Para los detalles finales sobre la luz, en este caso proviniente de una antorcha, deberíamos hacer uso de un mapa definido de emisivo, con zonas delimitadas en negro total para conseguir un mayor control sobre este efecto. Para añadir mayor credibilidad, una animación simple de escala y ligera rotación mediante el motor interno de Unity puede dar el toque realista que eleve el resultado a otro nivel. El uso del motor interno de edición y animación de assets de Unity es la mejor forma para así previsualizar el resultado más aproximado que se verá en Decentraland, pero recuerda que un GTLF solo aceptará la animación de sus transformaciones y nada más.
Conclusión
Considerando todo lo mencionado anteriormente, este artículo es un gran resumen de cosas que aprendimos y aplicamos durante el desarrollo del entorno. Ha sido una experiencia interesante porque las limitaciones nos llevaron a pensar de forma básica e inteligente formas de sortear todas las limitaciones sin llegar a sacrificar el resultado visual que buscábamos.
¡Una experiencia que me gustaría repetir!
Laura Usón
3D ARTIST
Passionate about videogames, movies and creatures. artist by day and superhero at night.
Se me encargó el modelado y texturizado del exterior de nuestro juego para Decentraland 'Tomb Chaser', así que pensé que este sería un buen ejemplo para probar en producción y, aprovechando para matar dos pájaros de un tiro, mostrar una de las técnicas que aprendí hace unos meses.
Planeando Lo primero que tenemos que hacer es planear cómo vamos a hacer nuestra Trim Texture. Porque una textura solo puede ser de proporciones 1:1 (en realidad no, pero para este ejemplo lo haremos así), así que vamos a crear un plano 1:1. Vamos a dividirlo en diferentes secciones. Es más una guía sobre cómo creemos que podremos aprovechar cada pixel de la textura, pero como esto se puede cambiar después, vamos a hacer algo que funcione por ahora. Después separamos cada sección para que sea su propia malla. Esculpiendo Vamos a importar nuestro plano a Zbrush y empezaremos a separar todas las secciones que tendremos. Ahora separamos los diferentes ladrillos que queremos. Debería quedar algo similar a esto una vez que hayamos hecho los detalles. Puedes aprovecharte del alpha, pinceles y booleanos y otros trucos para hacer este proceso más rápidamente. Una vez terminado, ¡podemos pasar al bake! Baking Puedes usar xNormal, Marmoset o incluso Painter para bakear tu modelo high-poly a un modelo low-poly. Asegúrate que usas un plano que tenga todo el espacio de las UVs desde U(0,1)-V(0,1) en uso para una máxima eficiencia. Texturizar Si bakeaste en Painter, podrás empezar a pintar directamente. Si has usado otro software puedes importar tus bakes en Painter y después texturizar a tu gusto! Se consciente del estilo y tipo de Trim que estás haciendo. No es lo mismo hacer algo estilizado que algo realista (aunque esto no debería notarse en el proceso de esculpido), pero cosas como la suciedad, las grietas y los detalles de la superficie dependen mucho del estilo que estés buscando. Es un proceso de capas e intentar obtener la cantidad de detalle donde importa. Así es como queda para mí después de texturizar. Cabe señalar que después de la implementación decidimos que era muy soso y realista, así que mi compañera Laura lo cambió un poco para hacerlo más saturado y colorido. Después de acabar esto, lo exportamos y comenzamos a hacer el exterior y algunos props con esta textura. Conclusión Crear Trim Textures es un proceso simple y permite un nivel de detalle que es muy difícil adquirir con otras técnicas de texturizado como desenvolver cada isla de UV por separado. Espero que hayas aprendido algo nuevo hoy y puedas implementarlo en tu método de trabajo. Nos vemos por aquí! :) Álex GENERALISTA 3D Alejandro Bielsa is a junior 3D artist working at Polygonal Mind's in-house team. Passionate about videogames, vivid tutorial drinker and cat lover. |
Categories
All
Archives
February 2021
|