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
Why do I need to modify the Unity plugin? Using the Decentraland SDK to place entities "manually" in the typescript code may be a slow and tedious process, especially if you have to put a lot of them and you have to look one by one if they are in the right place. That's why you have tools to help you build a scene, like the Decentraland builder or the Unity to DCL export plugin. However there are a limited set of things you can do with them. In this guide we'll see how to make custom modifications to the Unity plugin for DCL to expand the number of things you can export to code from the Unity scene editor. Before starting with this guide you should have a minimun knowledge of the Decentraland SDK, the basics of how to export a scene from unity to DCL and some programing basics (we'll use typescript and C#).
Prepare your Typescript project
It isn't necessary to make a custom component to place an NFTShape (which is already a component), but for the sake of the guide we'll do it anyway, besides it will be useful for you as a base to make your own more complex components. Before start writing code in your game.ts file, be aware that the Unity export plugin will ovewrite the entire game.ts file and your code will be lost, because of this we need to work in a separate file. Create a new file src/imports/components/NFT.ts with the following code: //Creates an NFTShape component with the given info export function createNFTComponent(entity: IEntity, smartContract: string, tokenId: string){ entity.addComponent( new NFTShape('ethereum://'+smartContract+'/'+tokenId,Color3.Blue()) ) } //Add a NFTdata component to the entity, creates an NFT component with the given info @Component('NFTdata') export class NFTdata{ entity: IEntity //entity of the NFT smartContract: string //Smart contract of the NFT tokenId: string //Token ID of the NFT constructor(entity: IEntity, smartContract: string, tokenId: string){ this.entity = entity this.smartContract = smartContract this.tokenId = tokenId createNFTComponent(entity, smartContract, tokenId) } } And another one src/imports/index.ts with: export { NFTdata, createNFTComponent } from "./components/NFT" Now we have our code ready for when our modified plugin exports the game.ts Make a Unity script to hold the data to export Create a empty script in Unity and open it in visual studio (or your favorite code editor). You only need to store here the NFT address info for this guide, but fell free to add anything you need for your project. public class nft_script : MonoBehaviour { public string smartContract; public string tokenId; } Now we have the data stored in our entity, the only step left is to modify the plugin to translate this data to our project in typescript. Modify the Unity plugin in C# Modifing the plugin may seem like an overwhelming task, but keep in mind we only need to add our little pieces of code in it and expand its functionalities, simple if you know where to do it. Open SceneTraverser.cs in the Decentraland plugin folder: Assets/Decentraland/SceneTraverser.cs Find the public static ResourceRecorder TraverseAllScene function and add the following code after the comment //====== Start Traversing ====== //====== Start Traversing ====== if (exportStr != null) { exportStr.AppendLine("import { NFTdata } from \"./imports/index\"\n\n"); } This will import our NFTdata class at the start of game.ts Next find the public static void RecursivelyTraverseTransform function and after exportStr.AppendFormat(NewEntityWithName, entityName, tra.name); add this code: nft_script nftObject = (tra.gameObject.GetComponent("nft_script") as nft_script); if (nftObject) { exportStr.AppendFormat(SetNFT, entityName, nftObject.smartContract, nftObject.tokenId); } Last step, find the place where the export strings are declared and add the SetNFT string at the end. private const string SetNFT = "{0}.addComponent(new NFTdata({0}, \"{1}\", \"{2}\")) \n"; This code will check if the exported entity has an nft_script and will add the NFT data component to the entity in our game.ts file. With all done your scene is ready to be exported to a typescript project and your resulting game.ts should look like this: 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) Final tips You can access from anywhere in the project to an array of entities with your custom components, this can be usefull to control where and how some components or behaviours start. engine.getEntitiesWithComponent(NFTdata) You don't need to make complex new components if you don't have to, for example you can export from unity only the info to put an entity in an array and apply to them a custom behaviour. Before start making your componets, take a look at the decentraland sdk and the decentraland utils library, they may have what you are looking for. ECS API Reference: decentraland-ecs-utils: npm install decentraland-ecs-utils Conclusion If you have understood the steps done in this guide, you are ready now to make your own components for decentraland and place them using the Unity editor, this will be very usefull to fill your scenes with gameplay and interaction. I hope it makes your development process for decentraland easier. Alex Picazo PROGRAMMER Videogame programmer, love developing cool stuff. Always searching for new interesting stories.
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.
Decentraland official documentation Libraries and imports You need to install in your typescript project the eth-connect library, to interface with Ethereum contracts and call their functions. npm install eth-connect Also, you will need to import the following functions in your file. getUserAccount to obtain the user's ethereum address. getProvider to create an instance of the web3 provider to interface with Metamask. getUserAccount and getProvider are provided by the decentraland SDK. import { getProvider } from '@decentraland/web3-provider' import { getUserAccount } from '@decentraland/EthereumController' import * as EthConnect from '../node_modules/eth-connect/esm' For this libraries to work in the DCL localhost preview, you'll need to paste &ENABLE_WEB3 to the url, example: http://192.168.0.112:8000/?SCENE_DEBUG_PANEL&position=12%2C44&ENABLE_WEB3 Important information needed before start To perform a MANA payment you need to gather some info. Ethereum address to pay the mana, this will be the address that will recive the MANA from the user's payments. example: const PAYMENT_ADDRESS ="0x4tU......8dY" MANA units you want the user to pay example: const MANA_PAYMENT = 10 The MANA ethereum contract address const MANA_ADDRESS = "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942" Fake MANA contract address (optional), only if you want to do test transations with Fake MANA const FAKE_MANA_ADDRESS = "0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb" The MANA contract ABI Create a empty file inside your project, "/contracts/mana.ts" and paste the MANA ABI in export const abi, or paste the contents of this file.
<
>
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"} Fake MANA contract ABI (optional)
<
>
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' } ] Code Now that we have all the info and libraries we need it's time to develop or code. Import the functions you'll need at the begining of the file. import { getProvider } from '@decentraland/web3-provider' import { getUserAccount } from '@decentraland/EthereumController' import * as EthConnect from '../node_modules/eth-connect/esm' Import the MANA ABI (or the fake MANA ABI for test only) import { abi } from './contracts/mana' Now you can create a simple payment function, to call when you want the user to pay. 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()) } }) When this function is called, the user's Metamask extension will popup a comfirmation for the payment, if there is a problem with the payment or the user rejects it, the payment() function will throw an error. If oyu want you can add your own callback(error, result) function to manage the success or fail of the payment request. payment(function(error, result){ if (!error) { //Payment success } else{ //Payment fail } }) Test with fake MANA payments
For the last step, you need to change the MANA contract address and the MANA ABI in your code for the fake ones. import { abi } from './contracts/fakemana' const MANA_ADDRESS = "0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb" With this your test MANA payments will be done with the fake in the Ropsten Test Network, and will be sent to the PAYMENT_ADDRESS in the Ropsten network as well. Alex Picazo PROGRAMMER 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. With the release to the public of Decentraland, I was given a scene block out and I was tasked with the rearranging the elements around and making textures based on a moodboard for Sometimes a Thousand Twangling instruments (SaTTi, from now on). The Decentraland part of SaTTi consists in a series of banners where you can change what sounds are playing on them (which are generated from drawings) and that change the "music" depending on where you stop the banners and where you stand in the floor, making the sounds overlap in different ways and making the sounds come from different directions. After seeing the moodboard and knowing what the project was about, and because I was in a tight deadline, I decided to take the same approach for textures: layer generators, alphas, and other elements that already existed in Painter to create the needed textures.
The Floors This is the stack of fills, filters and generators I used to create the desired look. I won't share the exact settings of the effects because the point is to get straight the reason of why I used them
The Hexagons The polished ones I decided to approach these ones as a stack of different layers instead of using multiple fills and filters in a single layer, since it gave me more flexibility to create variations, specially controlling the different opacities of channels in each layer. So these are the layers and blending options I used for this:
The organic ones Again, I approached these as a stack of layers These are the layers and blending options I used for this:
Conclusion Not every texture made was used in the end, but it served to explore the aesthetic of the parcel, and we can always use them in case we want to update the scene or keep developing the visual aspect of it further. The fact that we used Painter to make the textures means that we can export the textures in 4k and change the size after to meet the limitations of Decentraland (max resolution 512x512 per texture). Each of the textures here had 3 more variations made to them. Changing the opacity, blending options, seed of the generators and effects lets you generate variants very easily when you're in a rush. Álex 3D GENERALIST 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
March 2022
|